import { describe, it, expect, beforeEach, vi } from "vitest";
import { waitFor } from "@testing-library/react";
import { useConfigStore } from "./configStore";
import type { Agent, AgentPoliciesByAgent, Team, Config } from "@/types/config";

// Mock fetch globally
global.fetch = vi.fn();

function makeAgentPolicy(
  agentName: string,
  overrides: Partial<AgentPoliciesByAgent[string]> = {},
): AgentPoliciesByAgent[string] {
  return {
    agent_name: agentName,
    is_private: false,
    effective_execution_scope: null,
    scope_label: "unscoped",
    scope_source: "unscoped",
    dashboard_credentials_supported: true,
    team_eligibility_reason: null,
    private_knowledge_base_id: null,
    private_workspace_enabled: false,
    private_agent_knowledge_enabled: false,
    ...overrides,
  };
}

function deferred<T>() {
  let resolve!: (value: T | PromiseLike<T>) => void;
  let reject!: (reason?: unknown) => void;
  const promise = new Promise<T>((resolvePromise, rejectPromise) => {
    resolve = resolvePromise;
    reject = rejectPromise;
  });
  return { promise, resolve, reject };
}

describe("configStore", () => {
  beforeEach(() => {
    // Reset store state
    useConfigStore.setState({
      committedGeneration: 0,
      loadedConfig: null,
      config: null,
      recoveryConfigSource: null,
      recoveryConfigSourceOriginal: null,
      draftVersion: 0,
      agents: [],
      teams: [],
      cultures: [],
      rooms: [],
      agentPoliciesByAgent: {},
      agentPoliciesStale: false,
      agentPoliciesRequestId: 0,
      loadConfigRequestId: 0,
      saveConfigRequestId: 0,
      selectedAgentId: null,
      selectedTeamId: null,
      selectedCultureId: null,
      selectedRoomId: null,
      isDirty: false,
      dirtyRoots: [],
      isLoading: false,
      diagnostics: [],
      syncStatus: "disconnected",
      configUsesIncludes: false,
      privateWorkerScopeBackups: {},
    });

    // Clear all mocks
    vi.clearAllMocks();
  });

  describe("loadConfig", () => {
    it("should load configuration successfully", async () => {
      const mockConfig = {
        agents: {
          test: {
            display_name: "Test Agent",
            role: "Test role",
            tools: ["calculator"],
            skills: [],
            instructions: ["Test instruction"],
            rooms: ["lobby"],
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { test: makeAgentPolicy("test") },
        }),
      });

      const { loadConfig } = useConfigStore.getState();
      await loadConfig();

      const state = useConfigStore.getState();
      expect(state.config).toEqual({
        ...mockConfig,
        knowledge_bases: {},
        cultures: {},
      });
      expect(state.agents).toHaveLength(1);
      expect(state.agents[0].id).toBe("test");
      expect(state.agents[0].display_name).toBe("Test Agent");
      expect(state.agents[0].learning).toBe(true);
      expect(state.agents[0].learning_mode).toBe("always");
      expect(state.agentPoliciesByAgent).toEqual({
        test: makeAgentPolicy("test"),
      });
      expect(state.syncStatus).toBe("synced");
    });

    it("records when the loaded config is composed from include files", async () => {
      const mockConfig = {
        agents: {},
        models: { default: { provider: "ollama", id: "test-model" } },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
        headers: {
          get: (name: string) =>
            name === "x-mindroom-config-uses-includes" ? "true" : null,
        },
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ agent_policies: {} }),
      });

      await useConfigStore.getState().loadConfig();

      expect(useConfigStore.getState().configUsesIncludes).toBe(true);
    });

    it("uses the room id fallback for blank authored room display names", async () => {
      const mockConfig = {
        agents: {},
        rooms: {
          project_room: {
            display_name: "   ",
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: {},
        }),
      });

      await useConfigStore.getState().loadConfig();

      const state = useConfigStore.getState();
      expect(state.rooms).toEqual([
        {
          id: "project_room",
          display_name: "Project Room",
          description: "",
          agents: [],
          model: undefined,
        },
      ]);
      expect(state.config?.rooms?.project_room).not.toHaveProperty(
        "display_name",
      );
    });

    it("shows room-specific model overrides even without room metadata or memberships", async () => {
      const mockConfig = {
        agents: {},
        room_models: {
          project_room: "claude",
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
          claude: {
            provider: "anthropic",
            id: "claude-sonnet-4-6",
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: {},
        }),
      });

      await useConfigStore.getState().loadConfig();

      expect(useConfigStore.getState().rooms).toEqual([
        {
          id: "project_room",
          display_name: "Project Room",
          description: "",
          agents: [],
          model: "claude",
        },
      ]);
    });

    it("normalizes mixed tool entries when loading configuration", async () => {
      const mockConfig = {
        agents: {
          test: {
            display_name: "Test Agent",
            role: "Test role",
            tools: ["calculator", { shell: { sandbox: "tight" } }],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        defaults: {
          markdown: true,
          tools: [{ gmail: { label: "support" } }, "file"],
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { test: makeAgentPolicy("test") },
        }),
      });

      await useConfigStore.getState().loadConfig();

      const state = useConfigStore.getState();
      expect(state.agents[0].tools).toEqual(["calculator", "shell"]);
      expect(state.config?.agents.test.tools).toEqual(["calculator", "shell"]);
      expect(state.config?.defaults.tools).toEqual(["gmail", "file"]);
    });

    it("should apply global learning defaults when agent settings are omitted", async () => {
      const mockConfig = {
        agents: {
          test: {
            display_name: "Test Agent",
            role: "Test role",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        defaults: {
          learning: false,
          learning_mode: "agentic",
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { test: makeAgentPolicy("test") },
        }),
      });

      const { loadConfig } = useConfigStore.getState();
      await loadConfig();

      const state = useConfigStore.getState();
      expect(state.agents[0].learning).toBe(false);
      expect(state.agents[0].learning_mode).toBe("agentic");
    });

    it("normalizes teams without authored rooms when loading configuration", async () => {
      const mockConfig = {
        agents: {
          test: {
            display_name: "Test Agent",
            role: "Test role",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        teams: {
          ops: {
            display_name: "Ops Team",
            role: "Coordinates operations",
            agents: ["test"],
            mode: "coordinate",
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { test: makeAgentPolicy("test") },
        }),
      });

      await useConfigStore.getState().loadConfig();

      const state = useConfigStore.getState();
      expect(state.syncStatus).toBe("synced");
      expect(state.teams).toEqual([
        {
          id: "ops",
          display_name: "Ops Team",
          role: "Coordinates operations",
          agents: ["test"],
          rooms: [],
          mode: "coordinate",
        },
      ]);
    });

    it("should preserve explicit learning=false from configuration", async () => {
      const mockConfig = {
        agents: {
          test: {
            display_name: "Test Agent",
            role: "Test role",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            learning: false,
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { test: makeAgentPolicy("test") },
        }),
      });

      const { loadConfig } = useConfigStore.getState();
      await loadConfig();

      const state = useConfigStore.getState();
      expect(state.agents[0].learning).toBe(false);
      expect(state.agents[0].learning_mode).toBe("always");
    });

    it("should preserve explicit learning_mode from configuration", async () => {
      const mockConfig = {
        agents: {
          test: {
            display_name: "Test Agent",
            role: "Test role",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            learning: true,
            learning_mode: "agentic",
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { test: makeAgentPolicy("test") },
        }),
      });

      const { loadConfig } = useConfigStore.getState();
      await loadConfig();

      const state = useConfigStore.getState();
      expect(state.agents[0].learning_mode).toBe("agentic");
    });

    it("should preserve private agent configuration from the backend", async () => {
      const mockConfig = {
        agents: {
          mind: {
            display_name: "Mind",
            role: "Private assistant",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            private: {
              per: "user",
              root: "mind_data",
              context_files: ["SOUL.md"],
              knowledge: {
                enabled: true,
                path: "memory",
                watch: true,
              },
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: {
            mind: makeAgentPolicy("mind", {
              is_private: true,
              effective_execution_scope: "user",
              scope_label: "private.per=user",
              scope_source: "private.per",
              dashboard_credentials_supported: false,
              team_eligibility_reason:
                "Private agents cannot be configured as team members.",
              private_workspace_enabled: true,
            }),
          },
        }),
      });

      const { loadConfig } = useConfigStore.getState();
      await loadConfig();

      const state = useConfigStore.getState();
      expect(state.agents[0].private).toEqual({
        per: "user",
        root: "mind_data",
        context_files: ["SOUL.md"],
        knowledge: {
          enabled: true,
          path: "memory",
          watch: true,
        },
      });
    });

    it("should handle load errors", async () => {
      const existingConfig: Config = {
        memory: {
          backend: "mem0",
          embedder: {
            provider: "openai",
            config: { model: "text-embedding-3-small" },
          },
        },
        models: {
          default: { provider: "ollama", id: "test-model" },
        },
        agents: {
          assistant: {
            display_name: "Assistant",
            role: "Helpful",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        },
        defaults: { markdown: true },
        router: { model: "default" },
      };
      const existingAgent: Agent = {
        id: "assistant",
        display_name: "Assistant",
        role: "Helpful",
        tools: [],
        skills: [],
        instructions: [],
        rooms: ["lobby"],
      };
      useConfigStore.setState({
        config: existingConfig,
        agents: [existingAgent],
        rooms: [
          {
            id: "lobby",
            display_name: "Lobby",
            description: "",
            agents: ["assistant"],
          },
        ],
        selectedAgentId: "assistant",
        isDirty: true,
        privateWorkerScopeBackups: {
          assistant: "shared",
        },
        agentPoliciesByAgent: {
          assistant: makeAgentPolicy("assistant"),
        },
      });
      (global.fetch as any).mockRejectedValueOnce(new Error("Network error"));

      const { loadConfig } = useConfigStore.getState();
      await loadConfig();

      const state = useConfigStore.getState();
      expect(state.syncStatus).toBe("error");
      expect(state.config).toEqual(existingConfig);
      expect(state.agents).toEqual([existingAgent]);
      expect(state.rooms).toEqual([
        {
          id: "lobby",
          display_name: "Lobby",
          description: "",
          agents: ["assistant"],
        },
      ]);
      expect(state.selectedAgentId).toBe("assistant");
      expect(state.isDirty).toBe(true);
      expect(state.privateWorkerScopeBackups).toEqual({
        assistant: "shared",
      });
      expect(state.agentPoliciesByAgent).toEqual({
        assistant: makeAgentPolicy("assistant"),
      });
      expect(state.diagnostics).toEqual([
        {
          kind: "global",
          message: "Network error",
          blocking: true,
        },
      ]);
    });

    it("stores backend validation issues and loads raw recovery source when load returns 422", async () => {
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agents: {
            assistant: {
              display_name: "Assistant",
              role: "Helpful",
              tools: [],
              skills: [],
              instructions: [],
              rooms: ["lobby"],
            },
          },
          models: {
            default: {
              provider: "ollama",
              id: "test-model",
            },
          },
        }),
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { assistant: makeAgentPolicy("assistant") },
        }),
      });
      await useConfigStore.getState().loadConfig();

      (global.fetch as any).mockResolvedValueOnce({
        ok: false,
        status: 422,
        json: async () => ({
          detail: [
            {
              loc: ["plugins", 0],
              msg: "Plugin tools_module must be a string",
              type: "value_error",
            },
          ],
        }),
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          source: "agents:\n  assistant:\n    role: broken\n",
        }),
      });

      await useConfigStore.getState().loadConfig();

      const state = useConfigStore.getState();
      expect(state.syncStatus).toBe("error");
      expect(state.config).toBeNull();
      expect(state.recoveryConfigSource).toBe(
        "agents:\n  assistant:\n    role: broken\n",
      );
      expect(state.recoveryConfigSourceOriginal).toBe(
        "agents:\n  assistant:\n    role: broken\n",
      );
      expect(state.agents).toEqual([]);
      expect(state.rooms).toEqual([]);
      expect(state.agentPoliciesByAgent).toEqual({});
      expect(state.diagnostics).toEqual([
        {
          kind: "global",
          message: "Configuration validation failed",
          blocking: true,
        },
        {
          kind: "validation",
          issue: {
            loc: ["plugins", 0],
            msg: "Plugin tools_module must be a string",
            type: "value_error",
          },
        },
      ]);
    });

    it("surfaces raw recovery fetch failures instead of masking them as validation blockers", async () => {
      (global.fetch as any).mockResolvedValueOnce({
        ok: false,
        status: 422,
        json: async () => ({
          detail: [
            {
              loc: ["plugins", 0],
              msg: "Plugin tools_module must be a string",
              type: "value_error",
            },
          ],
        }),
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: false,
        status: 401,
        json: async () => ({
          detail:
            "Authentication required. Please log in to access this instance.",
        }),
      });

      await useConfigStore.getState().loadConfig();

      expect(useConfigStore.getState()).toMatchObject({
        config: null,
        recoveryConfigSource: null,
        syncStatus: "error",
        diagnostics: [
          {
            kind: "global",
            message:
              "Authentication required. Please log in to access this instance.",
            blocking: true,
          },
        ],
      });
    });

    it("ignores stale successful load results after a newer 422 failure", async () => {
      const pendingConfigResponse = deferred<{
        ok: boolean;
        json: () => Promise<{
          agents: Record<
            string,
            {
              display_name: string;
              role: string;
              tools: string[];
              skills: string[];
              instructions: string[];
              rooms: string[];
            }
          >;
          models: { default: { provider: string; id: string } };
        }>;
      }>();

      (global.fetch as any)
        .mockReturnValueOnce(pendingConfigResponse.promise)
        .mockResolvedValueOnce({
          ok: false,
          status: 422,
          json: async () => ({
            detail: [
              {
                loc: ["plugins", 0],
                msg: "Plugin tools_module must be a string",
                type: "value_error",
              },
            ],
          }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({
            source: "plugins:\n  - bad\n",
          }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({
            agent_policies: { assistant: makeAgentPolicy("assistant") },
          }),
        });

      const firstLoadPromise = useConfigStore.getState().loadConfig();
      const secondLoadPromise = useConfigStore.getState().loadConfig();

      await secondLoadPromise;

      let state = useConfigStore.getState();
      expect(state.loadConfigRequestId).toBe(2);
      expect(state.syncStatus).toBe("error");
      expect(state.config).toBeNull();
      expect(state.recoveryConfigSource).toBe("plugins:\n  - bad\n");

      pendingConfigResponse.resolve({
        ok: true,
        json: async () => ({
          agents: {
            assistant: {
              display_name: "Assistant",
              role: "Helpful",
              tools: [],
              skills: [],
              instructions: [],
              rooms: ["lobby"],
            },
          },
          models: {
            default: {
              provider: "ollama",
              id: "test-model",
            },
          },
        }),
      });

      await firstLoadPromise;

      state = useConfigStore.getState();
      expect(state.loadConfigRequestId).toBe(2);
      expect(state.syncStatus).toBe("error");
      expect(state.config).toBeNull();
      expect(state.agents).toEqual([]);
      expect(state.rooms).toEqual([]);
      expect(state.agentPoliciesByAgent).toEqual({});
      expect(state.diagnostics).toEqual([
        {
          kind: "global",
          message: "Configuration validation failed",
          blocking: true,
        },
        {
          kind: "validation",
          issue: {
            loc: ["plugins", 0],
            msg: "Plugin tools_module must be a string",
            type: "value_error",
          },
        },
      ]);
    });

    it("invalidates in-flight policy refreshes when load returns 422", async () => {
      const pendingPolicyResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ agent_policies: AgentPoliciesByAgent }>;
      }>();

      useConfigStore.setState({
        config: {
          memory: {
            backend: "mem0",
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          models: {
            default: { provider: "ollama", id: "test-model" },
          },
          agents: {},
          defaults: { markdown: true },
          router: { model: "default" },
        },
        agentPoliciesByAgent: {
          assistant: makeAgentPolicy("assistant"),
        },
      });

      (global.fetch as any).mockReturnValueOnce(pendingPolicyResponse.promise);
      const refreshPromise = useConfigStore.getState().refreshAgentPolicies([
        {
          id: "assistant",
          display_name: "Assistant",
          role: "Helpful",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
        },
      ]);

      expect(useConfigStore.getState().agentPoliciesRequestId).toBe(1);

      (global.fetch as any).mockResolvedValueOnce({
        ok: false,
        status: 422,
        json: async () => ({
          detail: [
            {
              loc: ["plugins", 0],
              msg: "Plugin tools_module must be a string",
              type: "value_error",
            },
          ],
        }),
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          source: "plugins:\n  - bad\n",
        }),
      });

      await useConfigStore.getState().loadConfig();

      let state = useConfigStore.getState();
      expect(state.agentPoliciesRequestId).toBe(2);
      expect(state.config).toBeNull();
      expect(state.recoveryConfigSource).toBe("plugins:\n  - bad\n");
      expect(state.agentPoliciesByAgent).toEqual({});

      pendingPolicyResponse.resolve({
        ok: true,
        json: async () => ({
          agent_policies: {
            assistant: makeAgentPolicy("assistant"),
          },
        }),
      });

      await refreshPromise;

      state = useConfigStore.getState();
      expect(state.agentPoliciesRequestId).toBe(2);
      expect(state.config).toBeNull();
      expect(state.agentPoliciesByAgent).toEqual({});
      expect(state.diagnostics).toEqual([
        {
          kind: "global",
          message: "Configuration validation failed",
          blocking: true,
        },
        {
          kind: "validation",
          issue: {
            loc: ["plugins", 0],
            msg: "Plugin tools_module must be a string",
            type: "value_error",
          },
        },
      ]);
    });

    it("preserves a newer dirty structured draft when a late load returns 422", async () => {
      const existingConfig = {
        memory: {
          backend: "mem0",
          embedder: {
            provider: "openai",
            config: { model: "text-embedding-3-small" },
          },
        },
        knowledge_bases: {},
        cultures: {},
        agents: {
          assistant: {
            display_name: "Assistant",
            role: "Original role",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        defaults: {
          markdown: true,
        },
        models: {
          default: {
            provider: "ollama",
            id: "existing-model",
          },
        },
        router: {
          model: "default",
        },
      } satisfies Config;
      const pendingRawSourceResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ source: string }>;
      }>();

      useConfigStore.setState({
        loadedConfig: existingConfig,
        config: existingConfig,
        agents: [
          {
            id: "assistant",
            display_name: "Assistant",
            role: "Original role",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            learning: true,
            learning_mode: "always",
          },
        ],
      });

      (global.fetch as any)
        .mockResolvedValueOnce({
          ok: false,
          status: 422,
          json: async () => ({
            detail: [
              {
                loc: ["plugins", 0],
                msg: "Plugin tools_module must be a string",
                type: "value_error",
              },
            ],
          }),
        })
        .mockReturnValueOnce(pendingRawSourceResponse.promise);

      const loadPromise = useConfigStore.getState().loadConfig();
      await waitFor(() =>
        expect((global.fetch as any).mock.calls).toHaveLength(2),
      );

      useConfigStore
        .getState()
        .updateAgent("assistant", { role: "Edited role" });

      pendingRawSourceResponse.resolve({
        ok: true,
        json: async () => ({
          source: "plugins:\n  - bad\n",
        }),
      });

      await loadPromise;

      const state = useConfigStore.getState();
      expect(state.agents[0].role).toBe("Edited role");
      expect(state.recoveryConfigSource).toBeNull();
      expect(state.isDirty).toBe(true);
      expect(state.syncStatus).toBe("error");
      expect(state.diagnostics).toEqual([
        {
          kind: "global",
          message: "Configuration validation failed",
          blocking: false,
        },
        {
          kind: "validation",
          issue: {
            loc: ["plugins", 0],
            msg: "Plugin tools_module must be a string",
            type: "value_error",
          },
        },
      ]);
    });

    it("preserves newer recovery edits when a retry load returns 422", async () => {
      const pendingRawSourceResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ source: string }>;
      }>();

      useConfigStore.setState({
        diagnostics: [
          {
            kind: "global",
            message: "Configuration validation failed",
            blocking: true,
          },
        ],
        syncStatus: "error",
        recoveryConfigSource: "plugins:\n  - bad\n",
        recoveryConfigSourceOriginal: "plugins:\n  - bad\n",
      });

      (global.fetch as any)
        .mockResolvedValueOnce({
          ok: false,
          status: 422,
          json: async () => ({
            detail: [
              {
                loc: ["plugins", 0],
                msg: "Plugin tools_module must be a string",
                type: "value_error",
              },
            ],
          }),
        })
        .mockReturnValueOnce(pendingRawSourceResponse.promise);

      const loadPromise = useConfigStore.getState().loadConfig();
      await waitFor(() =>
        expect((global.fetch as any).mock.calls).toHaveLength(2),
      );

      useConfigStore
        .getState()
        .updateRecoveryConfigSource("plugins:\n  - fixed\n");

      pendingRawSourceResponse.resolve({
        ok: true,
        json: async () => ({
          source: "plugins:\n  - bad\n",
        }),
      });

      await loadPromise;

      const state = useConfigStore.getState();
      expect(state.recoveryConfigSource).toBe("plugins:\n  - fixed\n");
      expect(state.recoveryConfigSourceOriginal).toBe("plugins:\n  - bad\n");
      expect(state.isDirty).toBe(true);
      expect(state.syncStatus).toBe("error");
      expect(state.diagnostics).toEqual([
        {
          kind: "global",
          message: "Configuration validation failed",
          blocking: true,
        },
        {
          kind: "validation",
          issue: {
            loc: ["plugins", 0],
            msg: "Plugin tools_module must be a string",
            type: "value_error",
          },
        },
      ]);
    });

    it("exits recovery mode when a retry load succeeds after local recovery edits", async () => {
      useConfigStore.setState({
        diagnostics: [
          {
            kind: "global",
            message: "Configuration validation failed",
            blocking: true,
          },
        ],
        syncStatus: "error",
        recoveryConfigSource: "agents:\n  helper:\n    role: Fixed locally\n",
        recoveryConfigSourceOriginal: "agents:\n  helper:\n    role: Broken\n",
        isDirty: true,
        draftVersion: 1,
      });

      (global.fetch as any)
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({
            agents: {
              helper: {
                display_name: "Helper",
                role: "Fixed on disk",
                tools: [],
                skills: [],
                instructions: [],
                rooms: [],
              },
            },
            models: {
              default: {
                provider: "ollama",
                id: "test-model",
              },
            },
          }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({
            agent_policies: { helper: makeAgentPolicy("helper") },
          }),
        });

      await useConfigStore.getState().loadConfig();

      expect(useConfigStore.getState()).toMatchObject({
        recoveryConfigSource: null,
        recoveryConfigSourceOriginal: null,
        isDirty: false,
        syncStatus: "synced",
        diagnostics: [],
      });
      expect(useConfigStore.getState().config?.agents.helper.role).toBe(
        "Fixed on disk",
      );
    });

    it("saves raw recovery source and reloads the structured config", async () => {
      useConfigStore.setState({
        diagnostics: [
          {
            kind: "global",
            message: "Configuration validation failed",
            blocking: true,
          },
        ],
        syncStatus: "error",
        recoveryConfigSource: "agents:\n  helper:\n    role: Fixed\n",
        recoveryConfigSourceOriginal: "agents:\n  helper:\n    role: Broken\n",
        isDirty: true,
      });

      (global.fetch as any)
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ success: true }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({
            agents: {
              helper: {
                display_name: "Helper",
                role: "Fixed",
                tools: [],
                skills: [],
                instructions: [],
                rooms: [],
              },
            },
            models: {
              default: {
                provider: "ollama",
                id: "test-model",
              },
            },
          }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({
            agent_policies: { helper: makeAgentPolicy("helper") },
          }),
        });

      const result = await useConfigStore.getState().saveRecoveryConfigSource();

      expect(result).toEqual({ status: "saved" });
      expect((global.fetch as any).mock.calls[0][0]).toBe("/api/config/raw");
      expect((global.fetch as any).mock.calls[1][0]).toBe("/api/config/load");
      expect(useConfigStore.getState()).toMatchObject({
        recoveryConfigSource: null,
        recoveryConfigSourceOriginal: null,
        syncStatus: "synced",
        isDirty: false,
        diagnostics: [],
      });
      expect(useConfigStore.getState().config?.agents.helper.role).toBe(
        "Fixed",
      );
    });

    it("returns an error when the post-save recovery reload fails", async () => {
      useConfigStore.setState({
        diagnostics: [
          {
            kind: "global",
            message: "Configuration validation failed",
            blocking: true,
          },
        ],
        syncStatus: "error",
        recoveryConfigSource: "agents:\n  helper:\n    role: Fixed\n",
        recoveryConfigSourceOriginal: "agents:\n  helper:\n    role: Broken\n",
        isDirty: true,
      });

      (global.fetch as any)
        .mockResolvedValueOnce({
          ok: true,
          headers: {
            get: (name: string) =>
              name === "x-mindroom-config-generation" ? "7" : null,
          },
          json: async () => ({ success: true }),
        })
        .mockResolvedValueOnce({
          ok: false,
          status: 401,
          json: async () => ({
            detail:
              "Authentication required. Please log in to access this instance.",
          }),
        });

      const result = await useConfigStore.getState().saveRecoveryConfigSource();

      expect(result).toEqual({
        status: "error",
        message:
          "Authentication required. Please log in to access this instance.",
        diagnostics: [
          {
            kind: "global",
            message:
              "Authentication required. Please log in to access this instance.",
            blocking: true,
          },
        ],
      });
      expect(useConfigStore.getState()).toMatchObject({
        committedGeneration: 7,
        recoveryConfigSource: "agents:\n  helper:\n    role: Fixed\n",
        recoveryConfigSourceOriginal: "agents:\n  helper:\n    role: Broken\n",
        isDirty: true,
        syncStatus: "error",
      });
    });

    it("preserves newer recovery edits when an older recovery save finishes later", async () => {
      const pendingSaveResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ success: boolean }>;
      }>();

      useConfigStore.setState({
        diagnostics: [
          {
            kind: "global",
            message: "Configuration validation failed",
            blocking: true,
          },
        ],
        syncStatus: "error",
        recoveryConfigSource: "agents:\n  helper:\n    role: Fixed\n",
        recoveryConfigSourceOriginal: "agents:\n  helper:\n    role: Broken\n",
        draftVersion: 1,
        isDirty: true,
      });

      (global.fetch as any).mockReturnValueOnce(pendingSaveResponse.promise);

      const savePromise = useConfigStore.getState().saveRecoveryConfigSource();
      useConfigStore
        .getState()
        .updateRecoveryConfigSource(
          "agents:\n  helper:\n    role: Newer fix\n",
        );

      pendingSaveResponse.resolve({
        ok: true,
        json: async () => ({ success: true }),
      });

      const result = await savePromise;

      expect(result).toEqual({ status: "stale" });
      expect((global.fetch as any).mock.calls).toHaveLength(1);
      expect(useConfigStore.getState()).toMatchObject({
        recoveryConfigSource: "agents:\n  helper:\n    role: Newer fix\n",
        recoveryConfigSourceOriginal: "agents:\n  helper:\n    role: Broken\n",
        syncStatus: "error",
        isDirty: true,
      });
    });

    it("loads config and records a non-blocking diagnostic when agent policy derivation fails during reload", async () => {
      const existingConfig = {
        memory: {
          backend: "mem0",
          embedder: {
            provider: "openai",
            config: { model: "text-embedding-3-small" },
          },
        },
        knowledge_bases: {},
        cultures: {},
        agents: {
          existing: {
            display_name: "Existing Agent",
            role: "Existing role",
            tools: ["calculator"],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        },
        defaults: {
          markdown: true,
        },
        models: {
          default: {
            provider: "ollama",
            id: "existing-model",
          },
        },
        router: {
          model: "default",
        },
      } satisfies Config;

      useConfigStore.setState({
        config: existingConfig,
        agents: [
          {
            id: "existing",
            display_name: "Existing Agent",
            role: "Existing role",
            tools: ["calculator"],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
            learning: true,
            learning_mode: "always",
          },
        ],
        agentPoliciesByAgent: {
          existing: makeAgentPolicy("existing"),
        },
        syncStatus: "synced",
      });

      const replacementConfig = {
        agents: {
          replacement: {
            display_name: "Replacement Agent",
            role: "Replacement role",
            tools: ["weather"],
            skills: [],
            instructions: [],
            rooms: ["desk"],
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "replacement-model",
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => replacementConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: false,
        status: 500,
        json: async () => ({ detail: "boom" }),
      });

      await useConfigStore.getState().loadConfig();

      const state = useConfigStore.getState();
      expect(state.config).toEqual({
        ...replacementConfig,
        knowledge_bases: {},
        cultures: {},
      });
      expect(state.agents).toEqual([
        {
          id: "replacement",
          display_name: "Replacement Agent",
          role: "Replacement role",
          tools: ["weather"],
          skills: [],
          instructions: [],
          rooms: ["desk"],
          knowledge_bases: [],
          delegate_to: [],
          context_files: [],
          learning: true,
          learning_mode: "always",
        },
      ]);
      expect(state.agentPoliciesByAgent).toEqual({});
      expect(state.diagnostics).toEqual([
        {
          kind: "global",
          message: "Failed to derive agent policies",
          blocking: false,
        },
      ]);
      expect(state.syncStatus).toBe("synced");
    });
  });

  describe("refreshAgentPolicies", () => {
    it("stores backend-derived agent policies", async () => {
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: {
            helper: makeAgentPolicy("helper"),
            mind: makeAgentPolicy("mind", {
              is_private: true,
              effective_execution_scope: "user",
              scope_label: "private.per=user",
              scope_source: "private.per",
              dashboard_credentials_supported: false,
              team_eligibility_reason:
                "Private agents cannot be configured as team members.",
              private_workspace_enabled: true,
            }),
          },
        }),
      });

      useConfigStore.setState({
        config: {
          memory: {
            backend: "mem0",
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          models: {
            default: { provider: "ollama", id: "test-model" },
          },
          agents: {},
          defaults: { markdown: true },
          router: { model: "default" },
        },
      });

      await useConfigStore.getState().refreshAgentPolicies([
        {
          id: "helper",
          display_name: "Helper",
          role: "Helps",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
        },
        {
          id: "mind",
          display_name: "Mind",
          role: "Private",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
          private: { per: "user" },
        },
      ]);

      expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({
        helper: makeAgentPolicy("helper"),
        mind: makeAgentPolicy("mind", {
          is_private: true,
          effective_execution_scope: "user",
          scope_label: "private.per=user",
          scope_source: "private.per",
          dashboard_credentials_supported: false,
          team_eligibility_reason:
            "Private agents cannot be configured as team members.",
          private_workspace_enabled: true,
        }),
      });
    });

    it("invalidates the current preview while a refresh is in flight", async () => {
      const pendingResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ agent_policies: AgentPoliciesByAgent }>;
      }>();

      (global.fetch as any).mockReturnValueOnce(pendingResponse.promise);

      useConfigStore.setState({
        config: {
          memory: {
            backend: "mem0",
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          models: {
            default: { provider: "ollama", id: "test-model" },
          },
          agents: {},
          defaults: { markdown: true },
          router: { model: "default" },
        },
        agentPoliciesByAgent: {
          helper: makeAgentPolicy("helper"),
        },
      });

      const refreshPromise = useConfigStore.getState().refreshAgentPolicies([
        {
          id: "helper",
          display_name: "Helper",
          role: "Helps",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
        },
      ]);

      expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({});
      expect(useConfigStore.getState().agentPoliciesStale).toBe(true);

      pendingResponse.resolve({
        ok: true,
        json: async () => ({
          agent_policies: {
            helper: makeAgentPolicy("helper"),
          },
        }),
      });

      await refreshPromise;

      expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({
        helper: makeAgentPolicy("helper"),
      });
      expect(useConfigStore.getState().agentPoliciesStale).toBe(false);
    });

    it("clears policies and records a non-blocking diagnostic when refresh fails", async () => {
      (global.fetch as any).mockResolvedValueOnce({
        ok: false,
        status: 500,
        json: async () => ({ detail: "boom" }),
      });

      useConfigStore.setState({
        config: {
          memory: {
            backend: "mem0",
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          models: {
            default: { provider: "ollama", id: "test-model" },
          },
          agents: {},
          defaults: { markdown: true },
          router: { model: "default" },
        },
        agentPoliciesByAgent: {
          helper: makeAgentPolicy("helper"),
        },
      });

      await useConfigStore.getState().refreshAgentPolicies([
        {
          id: "helper",
          display_name: "Helper",
          role: "Helps",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
        },
      ]);

      expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({});
      expect(useConfigStore.getState().diagnostics).toEqual([
        {
          kind: "global",
          message: "Failed to derive agent policies",
          blocking: false,
        },
      ]);
    });
  });

  describe("saveConfig", () => {
    it("surfaces the includes rejection message instead of a stale retry", async () => {
      const includesMessage =
        "configuration is composed from multiple files via !include; edit the source files instead";
      useConfigStore.setState({
        config: {
          agents: {},
          models: { default: { provider: "ollama", id: "test-model" } },
          memory: {
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-ada-002" },
            },
          },
          defaults: { markdown: true },
          router: { model: "default" },
        } as Config,
        isDirty: true,
        syncStatus: "synced",
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: false,
        status: 409,
        json: async () => ({
          detail: {
            code: "config_composed_from_includes",
            message: includesMessage,
          },
        }),
      });

      const result = await useConfigStore.getState().saveConfig();

      expect(result).toEqual({
        status: "error",
        message: includesMessage,
        diagnostics: [
          { kind: "global", message: includesMessage, blocking: false },
        ],
      });
      const state = useConfigStore.getState();
      expect(state.syncStatus).toBe("error");
      expect(state.configUsesIncludes).toBe(true);
    });

    it("should save configuration successfully", async () => {
      // Set up initial state with agents array
      const mockConfig: Config = {
        agents: {
          test: {
            display_name: "Test",
            role: "Test role",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        models: {},
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      };
      const mockAgents = [
        {
          id: "test",
          display_name: "Test",
          role: "Test role",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
        },
      ];
      useConfigStore.setState({
        config: mockConfig,
        agents: mockAgents,
        isDirty: true,
        syncStatus: "synced",
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ success: true }),
      });

      const { saveConfig } = useConfigStore.getState();
      await saveConfig();

      // The saveConfig removes the id field when saving
      const { id: _id, ...agentWithoutId } = mockAgents[0];
      expect(global.fetch).toHaveBeenCalledTimes(1);
      expect(global.fetch).toHaveBeenNthCalledWith(1, "/api/config/save", {
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          "x-mindroom-config-generation": "0",
        },
        body: JSON.stringify({
          ...mockConfig,
          agents: { test: agentWithoutId },
        }),
      });

      const state = useConfigStore.getState();
      expect(state.isDirty).toBe(false);
      expect(state.syncStatus).toBe("synced");
      expect(state.config?.agents).toEqual({ test: agentWithoutId });
    });

    it("rebuilds mixed tool entries on save after config clones", async () => {
      const mockConfig = {
        agents: {
          test: {
            display_name: "Test Agent",
            role: "Test role",
            tools: ["calculator", { shell: { sandbox: "tight" } }],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        defaults: {
          markdown: true,
          tools: [{ gmail: { label: "support" } }, "file"],
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { test: makeAgentPolicy("test") },
        }),
      });

      await useConfigStore.getState().loadConfig();
      useConfigStore.getState().updateToolConfig("gmail", { enabled: true });
      useConfigStore
        .getState()
        .updateAgent("test", { tools: ["shell", "browser"] });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ success: true }),
      });

      await useConfigStore.getState().saveConfig();

      const saveCall = (global.fetch as any).mock.calls[2];
      expect(saveCall[0]).toBe("/api/config/save");
      expect(saveCall[1]).toMatchObject({
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          "x-mindroom-config-generation": "0",
        },
      });
      expect(JSON.parse(saveCall[1].body)).toMatchObject({
        defaults: {
          markdown: true,
          tools: [{ gmail: { label: "support" } }, "file"],
        },
        agents: {
          test: {
            tools: [{ shell: { sandbox: "tight" } }, "browser"],
          },
        },
        tools: {
          gmail: { enabled: true },
        },
      });
      expect(useConfigStore.getState().config?.agents.test.tools).toEqual([
        "shell",
        "browser",
      ]);
      expect(useConfigStore.getState().config?.defaults.tools).toEqual([
        "gmail",
        "file",
      ]);
    });

    it("preserves structured tool entries when a room creation clone is followed by agent edits", async () => {
      const mockConfig = {
        agents: {
          test: {
            display_name: "Test Agent",
            role: "Test role",
            tools: ["calculator", { shell: { sandbox: "tight" } }],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        defaults: {
          markdown: true,
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { test: makeAgentPolicy("test") },
        }),
      });

      await useConfigStore.getState().loadConfig();
      useConfigStore.getState().createRoom({
        display_name: "Project Room",
        description: "Planning space",
        agents: [],
      });
      useConfigStore
        .getState()
        .updateAgent("test", { tools: ["shell", "browser"] });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ success: true }),
      });

      await useConfigStore.getState().saveConfig();

      const saveCall = (global.fetch as any).mock.calls[2];
      expect(JSON.parse(saveCall[1].body)).toMatchObject({
        agents: {
          test: {
            tools: [{ shell: { sandbox: "tight" } }, "browser"],
          },
        },
        rooms: {
          project_room: {
            display_name: "Project Room",
            description: "Planning space",
          },
        },
      });
    });

    it("stores backend validation issues without poisoning the global load error", async () => {
      const mockConfig: Config = {
        models: {
          default: { provider: "test", id: "test-model" },
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        agents: {},
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      };
      const mockAgents = [
        {
          id: "mind",
          display_name: "Mind",
          role: "Assistant",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
          private: {
            per: "user" as const,
            root: "../outside",
          },
        },
      ];
      useConfigStore.setState({
        config: mockConfig,
        agents: mockAgents,
        isDirty: true,
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: false,
        status: 422,
        json: async () => ({
          detail: [
            {
              loc: ["agents", "mind", "private", "root"],
              msg: "private.root must stay within the private instance root",
              type: "value_error",
            },
          ],
        }),
      });

      await useConfigStore.getState().saveConfig();

      const state = useConfigStore.getState();
      expect(state.diagnostics).toEqual([
        {
          kind: "global",
          message: "Configuration validation failed",
          blocking: false,
        },
        {
          kind: "validation",
          issue: {
            loc: ["agents", "mind", "private", "root"],
            msg: "private.root must stay within the private instance root",
            type: "value_error",
          },
        },
      ]);
    });

    it("ignores stale successful save results after a newer validation failure", async () => {
      const pendingSaveResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ success: true }>;
      }>();
      const config: Config = {
        models: {
          default: { provider: "test", id: "test-model" },
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        agents: {
          helper: {
            display_name: "Helper",
            role: "Helpful",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      };
      const agents = [
        {
          id: "helper",
          display_name: "Helper",
          role: "Helpful",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
        },
      ];
      useConfigStore.setState({
        config,
        agents,
        isDirty: true,
      });

      (global.fetch as any)
        .mockReturnValueOnce(pendingSaveResponse.promise)
        .mockResolvedValueOnce({
          ok: false,
          status: 422,
          json: async () => ({
            detail: [
              {
                loc: ["agents", "helper", "role"],
                msg: "role is required",
                type: "value_error",
              },
            ],
          }),
        });

      const firstSavePromise = useConfigStore.getState().saveConfig();
      const secondSavePromise = useConfigStore.getState().saveConfig();

      await secondSavePromise;

      let state = useConfigStore.getState();
      expect(state.saveConfigRequestId).toBe(2);
      expect(state.syncStatus).toBe("error");
      expect(state.isDirty).toBe(true);
      expect(state.diagnostics).toEqual([
        {
          kind: "global",
          message: "Configuration validation failed",
          blocking: false,
        },
        {
          kind: "validation",
          issue: {
            loc: ["agents", "helper", "role"],
            msg: "role is required",
            type: "value_error",
          },
        },
      ]);

      pendingSaveResponse.resolve({
        ok: true,
        json: async () => ({ success: true }),
      });
      await firstSavePromise;

      state = useConfigStore.getState();
      expect(state.saveConfigRequestId).toBe(2);
      expect(state.syncStatus).toBe("error");
      expect(state.isDirty).toBe(true);
      expect(state.diagnostics).toEqual([
        {
          kind: "global",
          message: "Configuration validation failed",
          blocking: false,
        },
        {
          kind: "validation",
          issue: {
            loc: ["agents", "helper", "role"],
            msg: "role is required",
            type: "value_error",
          },
        },
      ]);
    });

    it("preserves newer draft edits when an older save finishes later", async () => {
      const pendingSaveResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ success: true }>;
      }>();
      const config: Config = {
        models: {
          default: { provider: "test", id: "test-model" },
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        agents: {
          helper: {
            display_name: "Helper",
            role: "Helpful",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      };
      const agents = [
        {
          id: "helper",
          display_name: "Helper",
          role: "Helpful",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
        },
      ];
      useConfigStore.setState({
        config,
        agents,
        isDirty: true,
        privateWorkerScopeBackups: {
          helper: "shared",
        },
      });

      (global.fetch as any).mockReturnValueOnce(pendingSaveResponse.promise);

      const savePromise = useConfigStore.getState().saveConfig();
      useConfigStore
        .getState()
        .updateAgent("helper", { role: "Still editing" });

      pendingSaveResponse.resolve({
        ok: true,
        json: async () => ({ success: true }),
      });
      await savePromise;

      const state = useConfigStore.getState();
      expect(state.agents[0].role).toBe("Still editing");
      expect(state.isDirty).toBe(true);
      expect(state.privateWorkerScopeBackups).toEqual({
        helper: "shared",
      });
      expect(state.syncStatus).toBe("error");
    });

    it("retains unrelated validation diagnostics when a stale save is superseded by newer edits", async () => {
      const pendingSaveResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ success: true }>;
      }>();
      const config: Config = {
        agents: {
          helper: {
            display_name: "Helper",
            role: "",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        teams: {
          ops: {
            display_name: "",
            role: "Runs ops",
            agents: ["helper"],
            rooms: [],
            mode: "coordinate",
          },
        },
        defaults: { markdown: true },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        models: {
          default: { provider: "test", id: "test-model" },
        },
        router: { model: "default" },
      };
      useConfigStore.setState({
        config,
        loadedConfig: config,
        agents: [
          {
            id: "helper",
            display_name: "Helper",
            role: "",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
        teams: [
          {
            id: "ops",
            display_name: "",
            role: "Runs ops",
            agents: ["helper"],
            rooms: [],
            mode: "coordinate",
          },
        ],
        diagnostics: [
          {
            kind: "global",
            message: "Configuration validation failed",
            blocking: false,
          },
          {
            kind: "validation",
            issue: {
              loc: ["agents", "helper", "role"],
              msg: "role is required",
              type: "value_error",
            },
          },
          {
            kind: "validation",
            issue: {
              loc: ["teams", "ops", "display_name"],
              msg: "display_name is required",
              type: "value_error",
            },
          },
        ],
        isDirty: true,
      });

      (global.fetch as any).mockReturnValueOnce(pendingSaveResponse.promise);

      const savePromise = useConfigStore.getState().saveConfig();
      useConfigStore.getState().updateAgent("helper", { role: "Now valid" });

      pendingSaveResponse.resolve({
        ok: true,
        json: async () => ({ success: true }),
      });

      expect(await savePromise).toEqual({ status: "stale" });
      expect(useConfigStore.getState().diagnostics).toEqual([
        {
          kind: "global",
          message: "Configuration validation failed",
          blocking: false,
        },
        {
          kind: "validation",
          issue: {
            loc: ["teams", "ops", "display_name"],
            msg: "display_name is required",
            type: "value_error",
          },
        },
      ]);
    });

    it("preserves newer voice edits when an older save finishes later", async () => {
      const pendingSaveResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ success: true }>;
      }>();
      const config: Config = {
        models: {
          default: { provider: "test", id: "test-model" },
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        agents: {},
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
        voice: {
          enabled: false,
          visible_router_echo: true,
          stt: {
            provider: "openai",
            model: "whisper-1",
          },
          intelligence: {
            model: "default",
          },
        },
      };
      useConfigStore.setState({
        config,
        agents: [],
        isDirty: true,
      });

      (global.fetch as any).mockReturnValueOnce(pendingSaveResponse.promise);

      const savePromise = useConfigStore.getState().saveConfig();
      useConfigStore.getState().updateVoiceConfig({
        enabled: true,
        visible_router_echo: true,
        stt: {
          provider: "openai",
          model: "whisper-1",
          host: "http://localhost:8080",
        },
        intelligence: {
          model: "default",
        },
      });

      pendingSaveResponse.resolve({
        ok: true,
        json: async () => ({ success: true }),
      });
      await savePromise;

      const state = useConfigStore.getState();
      expect(state.config?.voice).toEqual({
        enabled: true,
        visible_router_echo: true,
        stt: {
          provider: "openai",
          model: "whisper-1",
          host: "http://localhost:8080",
        },
        intelligence: {
          model: "default",
        },
      });
      expect(state.isDirty).toBe(true);
      expect(state.syncStatus).toBe("error");
    });

    it("ignores successful load results while a dirty draft save is still in flight", async () => {
      const pendingSaveResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ success: true }>;
      }>();
      const loadedConfig: Config = {
        models: {
          default: { provider: "test", id: "old-model" },
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        agents: {
          helper: {
            display_name: "Helper",
            role: "Helpful",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      };
      const draftConfig: Config = {
        ...loadedConfig,
        models: {
          default: { provider: "test", id: "new-model" },
        },
      };
      const agents = [
        {
          id: "helper",
          display_name: "Helper",
          role: "Helpful",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
        },
      ];
      useConfigStore.setState({
        loadedConfig,
        config: draftConfig,
        agents,
        isDirty: true,
        dirtyRoots: ["models"],
      });

      (global.fetch as any)
        .mockReturnValueOnce(pendingSaveResponse.promise)
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({
            ...loadedConfig,
          }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({
            agent_policies: { helper: makeAgentPolicy("helper") },
          }),
        });

      const savePromise = useConfigStore.getState().saveConfig();
      const loadPromise = useConfigStore.getState().loadConfig();

      await loadPromise;

      let state = useConfigStore.getState();
      expect(state.config?.models.default.id).toBe("new-model");
      expect(state.loadedConfig?.models.default.id).toBe("old-model");
      expect(state.isDirty).toBe(true);
      expect(state.syncStatus).toBe("syncing");

      pendingSaveResponse.resolve({
        ok: true,
        json: async () => ({ success: true }),
      });
      await savePromise;

      state = useConfigStore.getState();
      expect(state.config?.models.default.id).toBe("new-model");
      expect(state.loadedConfig?.models.default.id).toBe("new-model");
      expect(state.isDirty).toBe(false);
      expect(state.syncStatus).toBe("synced");
    });

    it("saves against the latest loaded config for untouched sections", async () => {
      const loadedConfig: Config = {
        agents: {
          assistant: {
            display_name: "Assistant",
            role: "Original role",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        models: {
          default: { provider: "test", id: "test-model" },
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
        voice: {
          enabled: false,
          visible_router_echo: true,
          stt: {
            provider: "openai",
            model: "whisper-1",
          },
          intelligence: {
            model: "default",
          },
        },
      };
      const staleDraftConfig: Config = {
        ...loadedConfig,
        voice: {
          ...loadedConfig.voice!,
          visible_router_echo: false,
        },
      };
      useConfigStore.setState({
        loadedConfig,
        config: staleDraftConfig,
        agents: [
          {
            id: "assistant",
            display_name: "Assistant",
            role: "Edited role",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
        isDirty: true,
        dirtyRoots: ["agents"],
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ success: true }),
      });

      await useConfigStore.getState().saveConfig();

      const saveBody = JSON.parse((global.fetch as any).mock.calls[0][1].body);
      expect(saveBody.voice.visible_router_echo).toBe(true);
      expect(saveBody.agents.assistant.role).toBe("Edited role");
      expect(useConfigStore.getState().config?.voice?.visible_router_echo).toBe(
        true,
      );
    });

    it("clears validation issues for fields edited after a failed save while keeping unrelated ones", async () => {
      const config: Config = {
        agents: {
          helper: {
            display_name: "Helper",
            role: "",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        teams: {
          ops: {
            display_name: "",
            role: "Runs ops",
            agents: ["helper"],
            rooms: [],
            mode: "coordinate",
          },
        },
        defaults: { markdown: true },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        models: {
          default: { provider: "test", id: "test-model" },
        },
        router: { model: "default" },
      };
      const diagnostics = [
        {
          kind: "global" as const,
          message: "Configuration validation failed",
          blocking: false,
        },
        {
          kind: "validation" as const,
          issue: {
            loc: ["agents", "helper", "role"],
            msg: "role is required",
            type: "value_error",
          },
        },
        {
          kind: "validation" as const,
          issue: {
            loc: ["teams", "ops", "display_name"],
            msg: "display_name is required",
            type: "value_error",
          },
        },
      ];
      useConfigStore.setState({
        loadedConfig: config,
        config,
        agents: [
          {
            id: "helper",
            display_name: "Helper",
            role: "",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
        teams: [
          {
            id: "ops",
            display_name: "",
            role: "Runs ops",
            agents: ["helper"],
            rooms: [],
            mode: "coordinate",
          },
        ],
        diagnostics,
        syncStatus: "error",
      });

      useConfigStore.getState().updateAgent("helper", { role: "Now valid" });

      expect(useConfigStore.getState().diagnostics).toEqual([
        {
          kind: "global",
          message: "Configuration validation failed",
          blocking: false,
        },
        {
          kind: "validation",
          issue: {
            loc: ["teams", "ops", "display_name"],
            msg: "display_name is required",
            type: "value_error",
          },
        },
      ]);
    });

    it("refreshes agent policies after a successful save when preview state is stale", async () => {
      const mockConfig: Config = {
        agents: {
          helper: {
            display_name: "Helper",
            role: "Helps",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        models: {},
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      };
      const mockAgents = [
        {
          id: "helper",
          display_name: "Helper",
          role: "Helps",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
        },
      ];
      useConfigStore.setState({
        config: mockConfig,
        agents: mockAgents,
        agentPoliciesByAgent: {},
        agentPoliciesStale: true,
        diagnostics: [],
        isDirty: true,
      });
      (global.fetch as any)
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ success: true }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({
            agent_policies: {
              helper: makeAgentPolicy("helper"),
            },
          }),
        });

      await useConfigStore.getState().saveConfig();

      await waitFor(() => {
        expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({
          helper: makeAgentPolicy("helper"),
        });
      });

      expect(useConfigStore.getState().agentPoliciesStale).toBe(false);
      expect(global.fetch).toHaveBeenNthCalledWith(
        1,
        "/api/config/save",
        expect.any(Object),
      );
      expect(global.fetch).toHaveBeenNthCalledWith(
        2,
        "/api/config/agent-policies",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            defaults: mockConfig.defaults,
            agents: {
              helper: {
                display_name: "Helper",
                role: "Helps",
                tools: [],
                skills: [],
                instructions: [],
                rooms: [],
                knowledge_bases: [],
                delegate_to: [],
                context_files: [],
                learning: true,
                learning_mode: "always",
              },
            },
          }),
        },
      );
    });
  });

  describe("updateAgent", () => {
    it("clears legacy worker_scope when private state is enabled", () => {
      useConfigStore.setState({
        agents: [
          {
            id: "mind",
            display_name: "Mind",
            role: "Assistant",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            worker_scope: "user_agent",
          },
        ],
        isDirty: false,
      });

      useConfigStore.getState().updateAgent("mind", {
        private: { per: "user_agent" },
      });

      const state = useConfigStore.getState();
      expect(state.agents[0].worker_scope).toBeUndefined();
      expect(state.agents[0].private).toEqual({ per: "user_agent" });
    });

    it("defaults private knowledge path when enabling it from an empty state", () => {
      useConfigStore.setState({
        agents: [
          {
            id: "mind",
            display_name: "Mind",
            role: "Assistant",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
        isDirty: false,
      });

      useConfigStore.getState().updateAgent("mind", {
        private: {
          per: "user",
          knowledge: { enabled: true, watch: true },
        },
      });

      const state = useConfigStore.getState();
      expect(state.agents[0].private?.knowledge).toEqual({
        enabled: true,
        path: "memory",
        watch: true,
      });
    });

    it("restores the backed-up worker_scope when private mode is disabled", () => {
      useConfigStore.setState({
        agents: [
          {
            id: "mind",
            display_name: "Mind",
            role: "Assistant",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            worker_scope: "user_agent",
          },
        ],
        isDirty: false,
      });

      useConfigStore.getState().setAgentPrivateEnabled("mind", true);
      useConfigStore.getState().setAgentPrivateEnabled("mind", false);

      const state = useConfigStore.getState();
      expect(state.agents[0].private).toBeUndefined();
      expect(state.agents[0].worker_scope).toBe("user_agent");
      expect(state.privateWorkerScopeBackups).toEqual({});
    });

    it("preserves the private-toggle worker_scope backup across failed saves", async () => {
      const mockConfig: Config = {
        agents: {
          mind: {
            display_name: "Mind",
            role: "Assistant",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            worker_scope: "user_agent",
          },
        },
        models: {},
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      };

      useConfigStore.setState({
        config: mockConfig,
        agents: [
          {
            id: "mind",
            display_name: "Mind",
            role: "Assistant",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            worker_scope: "user_agent",
          },
        ],
        isDirty: false,
      });

      useConfigStore.getState().setAgentPrivateEnabled("mind", true);
      (global.fetch as any).mockResolvedValueOnce({
        ok: false,
        status: 500,
        statusText: "Internal Server Error",
        json: async () => ({ detail: "save failed" }),
      });

      await useConfigStore.getState().saveConfig();
      useConfigStore.getState().setAgentPrivateEnabled("mind", false);

      const state = useConfigStore.getState();
      expect(state.syncStatus).toBe("error");
      expect(state.agents[0].private).toBeUndefined();
      expect(state.agents[0].worker_scope).toBe("user_agent");
    });

    it("clears the private-toggle worker_scope backup after a successful save", async () => {
      const mockConfig: Config = {
        agents: {
          mind: {
            display_name: "Mind",
            role: "Assistant",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            worker_scope: "user_agent",
          },
        },
        models: {},
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-ada-002",
            },
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      };

      useConfigStore.setState({
        config: mockConfig,
        agents: [
          {
            id: "mind",
            display_name: "Mind",
            role: "Assistant",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            worker_scope: "user_agent",
          },
        ],
        isDirty: false,
      });

      useConfigStore.getState().setAgentPrivateEnabled("mind", true);
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ success: true }),
      });

      await useConfigStore.getState().saveConfig();
      useConfigStore.getState().setAgentPrivateEnabled("mind", false);

      const state = useConfigStore.getState();
      expect(state.syncStatus).toBe("synced");
      expect(state.agents[0].private).toBeUndefined();
      expect(state.agents[0].worker_scope).toBeUndefined();
    });

    it("keeps draft team membership when backend eligibility marks an edited agent unsupported", async () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          agents: {},
          defaults: { markdown: true },
          models: {},
          router: { model: "default" },
        },
        agents: [
          {
            id: "leader",
            display_name: "Leader",
            role: "Lead",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
          {
            id: "helper",
            display_name: "Helper",
            role: "Help",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
        teams: [
          {
            id: "duo",
            display_name: "Duo",
            role: "Two agents",
            agents: ["leader", "helper"],
            rooms: [],
            mode: "coordinate",
          },
        ],
        agentPoliciesByAgent: {
          leader: makeAgentPolicy("leader"),
          helper: makeAgentPolicy("helper"),
        },
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: {
            leader: makeAgentPolicy("leader", {
              is_private: true,
              effective_execution_scope: "user",
              scope_label: "private.per=user",
              scope_source: "private.per",
              dashboard_credentials_supported: false,
              team_eligibility_reason:
                "Private agents cannot be configured as team members.",
              private_workspace_enabled: true,
            }),
            helper: makeAgentPolicy("helper"),
          },
        }),
      });

      useConfigStore.getState().updateAgent("leader", {
        private: { per: "user" },
      });

      await waitFor(() => {
        expect(useConfigStore.getState().teams[0].agents).toEqual([
          "leader",
          "helper",
        ]);
        expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({
          leader: makeAgentPolicy("leader", {
            is_private: true,
            effective_execution_scope: "user",
            scope_label: "private.per=user",
            scope_source: "private.per",
            dashboard_credentials_supported: false,
            team_eligibility_reason:
              "Private agents cannot be configured as team members.",
            private_workspace_enabled: true,
          }),
          helper: makeAgentPolicy("helper"),
        });
      });
    });

    it("does not refresh agent policies for non-policy agent edits", async () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          agents: {},
          defaults: { markdown: true },
          models: {},
          router: { model: "default" },
        },
        agents: [
          {
            id: "leader",
            display_name: "Leader",
            role: "Lead",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            delegate_to: [],
          },
        ],
      });

      useConfigStore.getState().updateAgent("leader", {
        display_name: "Updated Leader",
      });

      await Promise.resolve();

      expect(global.fetch).not.toHaveBeenCalled();
      expect(useConfigStore.getState().agents[0].display_name).toBe(
        "Updated Leader",
      );
    });

    it("does not refresh agent policies for private workspace edits that keep private mode enabled", async () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          agents: {},
          defaults: { markdown: true },
          models: {},
          router: { model: "default" },
        },
        agents: [
          {
            id: "mind",
            display_name: "Mind",
            role: "Private",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            private: {
              per: "user",
              root: "mind_data",
            },
          },
        ],
      });

      useConfigStore.getState().updateAgent("mind", {
        private: {
          per: "user",
          root: "updated_root",
        },
      });

      await Promise.resolve();

      expect(global.fetch).not.toHaveBeenCalled();
      expect(useConfigStore.getState().agents[0].private?.root).toBe(
        "updated_root",
      );
    });

    it("keeps draft team membership when delegation now reaches a private agent", async () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          agents: {},
          defaults: { markdown: true },
          models: {},
          router: { model: "default" },
        },
        agents: [
          {
            id: "leader",
            display_name: "Leader",
            role: "Lead",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            delegate_to: [],
          },
          {
            id: "helper",
            display_name: "Helper",
            role: "Help",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
          {
            id: "mind",
            display_name: "Mind",
            role: "Private",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            private: { per: "user" },
          },
        ],
        teams: [
          {
            id: "duo",
            display_name: "Duo",
            role: "Two agents",
            agents: ["leader", "helper"],
            rooms: [],
            mode: "coordinate",
          },
        ],
        agentPoliciesByAgent: {
          leader: makeAgentPolicy("leader"),
          helper: makeAgentPolicy("helper"),
          mind: makeAgentPolicy("mind", {
            is_private: true,
            effective_execution_scope: "user",
            scope_label: "private.per=user",
            scope_source: "private.per",
            dashboard_credentials_supported: false,
            team_eligibility_reason:
              "Private agents cannot be configured as team members.",
            private_workspace_enabled: true,
          }),
        },
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: {
            leader: makeAgentPolicy("leader", {
              team_eligibility_reason:
                "Delegates to private agent 'mind', so it cannot participate in teams.",
            }),
            helper: makeAgentPolicy("helper"),
            mind: makeAgentPolicy("mind", {
              is_private: true,
              effective_execution_scope: "user",
              scope_label: "private.per=user",
              scope_source: "private.per",
              dashboard_credentials_supported: false,
              team_eligibility_reason:
                "Private agents cannot be configured as team members.",
              private_workspace_enabled: true,
            }),
          },
        }),
      });

      useConfigStore.getState().updateAgent("leader", {
        delegate_to: ["mind"],
      });

      await waitFor(() => {
        expect(useConfigStore.getState().teams[0].agents).toEqual([
          "leader",
          "helper",
        ]);
        expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({
          leader: makeAgentPolicy("leader", {
            team_eligibility_reason:
              "Delegates to private agent 'mind', so it cannot participate in teams.",
          }),
          helper: makeAgentPolicy("helper"),
          mind: makeAgentPolicy("mind", {
            is_private: true,
            effective_execution_scope: "user",
            scope_label: "private.per=user",
            scope_source: "private.per",
            dashboard_credentials_supported: false,
            team_eligibility_reason:
              "Private agents cannot be configured as team members.",
            private_workspace_enabled: true,
          }),
        });
      });
    });

    it("drops stale policy immediately when a policy-affecting edit triggers refresh", async () => {
      const pendingResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ agent_policies: AgentPoliciesByAgent }>;
      }>();

      (global.fetch as any).mockReturnValueOnce(pendingResponse.promise);

      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          agents: {},
          defaults: { markdown: true },
          models: {},
          router: { model: "default" },
        },
        agents: [
          {
            id: "leader",
            display_name: "Leader",
            role: "Lead",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            delegate_to: [],
          },
          {
            id: "mind",
            display_name: "Mind",
            role: "Private",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            private: { per: "user" },
          },
        ],
        agentPoliciesByAgent: {
          leader: makeAgentPolicy("leader"),
          mind: makeAgentPolicy("mind", {
            is_private: true,
            effective_execution_scope: "user",
            scope_label: "private.per=user",
            scope_source: "private.per",
            dashboard_credentials_supported: false,
            team_eligibility_reason:
              "Private agents cannot be configured as team members.",
            private_workspace_enabled: true,
          }),
        },
      });

      useConfigStore.getState().updateAgent("leader", {
        delegate_to: ["mind"],
      });

      expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({});
      expect(useConfigStore.getState().agentPoliciesStale).toBe(true);

      pendingResponse.resolve({
        ok: true,
        json: async () => ({
          agent_policies: {
            leader: makeAgentPolicy("leader", {
              team_eligibility_reason:
                "Delegates to private agent 'mind', so it cannot participate in teams.",
            }),
            mind: makeAgentPolicy("mind", {
              is_private: true,
              effective_execution_scope: "user",
              scope_label: "private.per=user",
              scope_source: "private.per",
              dashboard_credentials_supported: false,
              team_eligibility_reason:
                "Private agents cannot be configured as team members.",
              private_workspace_enabled: true,
            }),
          },
        }),
      });

      await waitFor(() => {
        expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({
          leader: makeAgentPolicy("leader", {
            team_eligibility_reason:
              "Delegates to private agent 'mind', so it cannot participate in teams.",
          }),
          mind: makeAgentPolicy("mind", {
            is_private: true,
            effective_execution_scope: "user",
            scope_label: "private.per=user",
            scope_source: "private.per",
            dashboard_credentials_supported: false,
            team_eligibility_reason:
              "Private agents cannot be configured as team members.",
            private_workspace_enabled: true,
          }),
        });
      });
      expect(useConfigStore.getState().agentPoliciesStale).toBe(false);
    });
  });

  describe("agent operations", () => {
    beforeEach(() => {
      // Set up agents
      const agents: Agent[] = [
        {
          id: "agent1",
          display_name: "Agent 1",
          role: "Role 1",
          tools: [],
          skills: [],
          instructions: [],
          rooms: [],
        },
        {
          id: "agent2",
          display_name: "Agent 2",
          role: "Role 2",
          tools: ["calculator"],
          skills: [],
          instructions: ["Test"],
          rooms: ["lobby"],
        },
      ];
      useConfigStore.setState({ agents });
    });

    it("should select agent", () => {
      const { selectAgent } = useConfigStore.getState();
      selectAgent("agent2");

      const state = useConfigStore.getState();
      expect(state.selectedAgentId).toBe("agent2");
    });

    it("should update agent", () => {
      const { updateAgent } = useConfigStore.getState();
      updateAgent("agent1", { display_name: "Updated Agent" });

      const state = useConfigStore.getState();
      const updatedAgent = state.agents.find((a) => a.id === "agent1");
      expect(updatedAgent?.display_name).toBe("Updated Agent");
      expect(state.isDirty).toBe(true);
    });

    it("should create new agent", () => {
      const newAgentData = {
        display_name: "New Agent",
        role: "New role",
        tools: [],
        skills: [],
        instructions: [],
        rooms: [],
      };

      const { createAgent } = useConfigStore.getState();
      createAgent(newAgentData);

      const state = useConfigStore.getState();
      expect(state.agents).toHaveLength(3);
      const newAgent = state.agents[2];
      expect(newAgent.display_name).toBe("New Agent");
      expect(newAgent.learning).toBe(true);
      expect(newAgent.learning_mode).toBe("always");
      expect(state.selectedAgentId).toBe(newAgent.id);
      expect(state.isDirty).toBe(true);
    });

    it("should create new agent with learning values from global defaults", () => {
      const newAgentData = {
        display_name: "New Agent",
        role: "New role",
        tools: [],
        skills: [],
        instructions: [],
        rooms: [],
      };
      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          models: {},
          agents: {},
          defaults: {
            markdown: true,
            learning: false,
            learning_mode: "agentic",
          },
          router: { model: "default" },
        },
      });

      const { createAgent } = useConfigStore.getState();
      createAgent(newAgentData);

      const state = useConfigStore.getState();
      const newAgent = state.agents[state.agents.length - 1];
      expect(newAgent?.learning).toBe(false);
      expect(newAgent?.learning_mode).toBe("agentic");
    });

    it("refreshes agent policies when creating a standard shared agent", async () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          models: {},
          agents: {},
          defaults: {
            markdown: true,
          },
          router: { model: "default" },
        },
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: {
            new_agent: makeAgentPolicy("new_agent"),
          },
        }),
      });

      useConfigStore.getState().createAgent({
        display_name: "New Agent",
        role: "New role",
        tools: [],
        skills: [],
        instructions: [],
        rooms: [],
      });

      await Promise.resolve();

      await waitFor(() => {
        expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({
          new_agent: makeAgentPolicy("new_agent"),
        });
      });
    });

    it("refreshes agent policies when creating a private agent draft", async () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          models: {},
          agents: {},
          defaults: {
            markdown: true,
          },
          router: { model: "default" },
        },
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: {
            new_agent: makeAgentPolicy("new_agent", {
              is_private: true,
              effective_execution_scope: "user",
              scope_label: "private.per=user",
              scope_source: "private.per",
              dashboard_credentials_supported: false,
              team_eligibility_reason:
                "Private agents cannot be configured as team members.",
              private_workspace_enabled: true,
            }),
          },
        }),
      });

      useConfigStore.getState().createAgent({
        display_name: "New Agent",
        role: "New role",
        tools: [],
        skills: [],
        instructions: [],
        rooms: [],
        private: { per: "user" },
      });

      await waitFor(() => {
        expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({
          new_agent: makeAgentPolicy("new_agent", {
            is_private: true,
            effective_execution_scope: "user",
            scope_label: "private.per=user",
            scope_source: "private.per",
            dashboard_credentials_supported: false,
            team_eligibility_reason:
              "Private agents cannot be configured as team members.",
            private_workspace_enabled: true,
          }),
        });
      });
    });

    it("should delete agent", () => {
      useConfigStore.setState({
        cultures: [
          {
            id: "engineering",
            description: "Engineering standards",
            agents: ["agent1", "agent2"],
            mode: "automatic",
          },
        ],
        teams: [
          {
            id: "team1",
            display_name: "Team 1",
            role: "Test team",
            agents: ["agent1", "agent2"],
            rooms: [],
            mode: "coordinate",
          },
        ],
      });
      const { deleteAgent } = useConfigStore.getState();
      deleteAgent("agent1");

      const state = useConfigStore.getState();
      expect(state.agents).toHaveLength(1);
      expect(state.agents[0].id).toBe("agent2");
      expect(state.cultures[0].agents).toEqual(["agent2"]);
      expect(state.teams[0].agents).toEqual(["agent2"]);
      expect(state.isDirty).toBe(true);
      expect(state.dirtyRoots).toEqual(
        expect.arrayContaining(["agents", "teams", "cultures"]),
      );
    });

    it("serializes dependent team and culture removals after deleteAgent", async () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Role 1",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
          agent2: {
            display_name: "Agent 2",
            role: "Role 2",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        teams: {
          team1: {
            display_name: "Team 1",
            role: "Test team",
            agents: ["agent1", "agent2"],
            rooms: [],
            mode: "coordinate",
          },
        },
        cultures: {
          engineering: {
            description: "Engineering standards",
            agents: ["agent1", "agent2"],
            mode: "automatic",
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "test",
            },
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      };

      useConfigStore.setState({
        loadedConfig: mockConfig as Config,
        config: mockConfig as Config,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Role 1",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
          {
            id: "agent2",
            display_name: "Agent 2",
            role: "Role 2",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
        teams: [
          {
            id: "team1",
            display_name: "Team 1",
            role: "Test team",
            agents: ["agent1", "agent2"],
            rooms: [],
            mode: "coordinate",
          },
        ],
        cultures: [
          {
            id: "engineering",
            description: "Engineering standards",
            agents: ["agent1", "agent2"],
            mode: "automatic",
          },
        ],
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { agent2: makeAgentPolicy("agent2") },
        }),
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        headers: { get: () => "1" },
        json: async () => ({}),
      });

      useConfigStore.getState().deleteAgent("agent1");
      await waitFor(() => {
        expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({
          agent2: makeAgentPolicy("agent2"),
        });
      });
      await useConfigStore.getState().saveConfig();

      const saveCall = (global.fetch as any).mock.calls[1];
      expect(saveCall[0]).toBe("/api/config/save");
      expect(JSON.parse(saveCall[1].body)).toMatchObject({
        agents: {
          agent2: mockConfig.agents.agent2,
        },
        teams: {
          team1: {
            ...mockConfig.teams.team1,
            agents: ["agent2"],
          },
        },
        cultures: {
          engineering: {
            ...mockConfig.cultures.engineering,
            agents: ["agent2"],
          },
        },
      });
    });

    it("refreshes agent policies when deleting an unrelated shared agent", async () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          models: {},
          agents: {},
          defaults: {
            markdown: true,
          },
          router: { model: "default" },
        },
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Role 1",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
          {
            id: "agent2",
            display_name: "Agent 2",
            role: "Role 2",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: {
            agent2: makeAgentPolicy("agent2"),
          },
        }),
      });

      useConfigStore.getState().deleteAgent("agent1");

      await waitFor(() => {
        expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({
          agent2: makeAgentPolicy("agent2"),
        });
      });
    });

    it("refreshes agent policies when deleting an agent referenced by delegation", async () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: { model: "text-embedding-3-small" },
            },
          },
          models: {},
          agents: {},
          defaults: {
            markdown: true,
          },
          router: { model: "default" },
        },
        agents: [
          {
            id: "leader",
            display_name: "Leader",
            role: "Lead",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            delegate_to: ["mind"],
          },
          {
            id: "mind",
            display_name: "Mind",
            role: "Private",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            private: { per: "user" },
          },
        ],
        agentPoliciesByAgent: {
          leader: makeAgentPolicy("leader", {
            team_eligibility_reason:
              "Delegates to private agent 'mind', so it cannot participate in teams.",
          }),
          mind: makeAgentPolicy("mind", {
            is_private: true,
            effective_execution_scope: "user",
            scope_label: "private.per=user",
            scope_source: "private.per",
            dashboard_credentials_supported: false,
            team_eligibility_reason:
              "Private agents cannot be configured as team members.",
            private_workspace_enabled: true,
          }),
        },
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: {
            leader: makeAgentPolicy("leader"),
          },
        }),
      });

      useConfigStore.getState().deleteAgent("mind");

      await waitFor(() => {
        expect(useConfigStore.getState().agentPoliciesByAgent).toEqual({
          leader: makeAgentPolicy("leader"),
        });
      });
    });
  });

  describe("dirty state", () => {
    it("should mark state as dirty", () => {
      const { markDirty } = useConfigStore.getState();
      markDirty();

      const state = useConfigStore.getState();
      expect(state.isDirty).toBe(true);
    });
  });

  describe("teams", () => {
    beforeEach(() => {
      const mockTeams: Team[] = [
        {
          id: "team1",
          display_name: "Team 1",
          role: "Test team 1",
          agents: ["agent1", "agent2"],
          rooms: ["room1"],
          mode: "coordinate",
        },
        {
          id: "team2",
          display_name: "Team 2",
          role: "Test team 2",
          agents: ["agent3"],
          rooms: ["room2"],
          mode: "collaborate",
          model: "gpt4",
        },
      ];

      useConfigStore.setState({
        teams: mockTeams,
        selectedTeamId: "team1",
      });
    });

    it("should select team", () => {
      const { selectTeam } = useConfigStore.getState();
      selectTeam("team2");

      const state = useConfigStore.getState();
      expect(state.selectedTeamId).toBe("team2");
    });

    it("should update team", () => {
      const { updateTeam } = useConfigStore.getState();
      updateTeam("team1", { display_name: "Updated Team" });

      const state = useConfigStore.getState();
      const updatedTeam = state.teams.find((t) => t.id === "team1");
      expect(updatedTeam?.display_name).toBe("Updated Team");
      expect(state.isDirty).toBe(true);
    });

    it("normalizes empty team compaction overrides away", () => {
      const { updateTeam } = useConfigStore.getState();
      updateTeam("team1", { compaction: {} });

      const state = useConfigStore.getState();
      const updatedTeam = state.teams.find((team) => team.id === "team1");
      expect(updatedTeam?.compaction).toBeUndefined();
    });

    it("should create new team", () => {
      const { createTeam } = useConfigStore.getState();
      const newTeamData = {
        display_name: "New Team",
        role: "New team role",
        agents: ["agent1"],
        rooms: ["lobby"],
        mode: "coordinate" as const,
      };

      createTeam(newTeamData);

      const state = useConfigStore.getState();
      expect(state.teams).toHaveLength(3);
      const newTeam = state.teams[2];
      expect(newTeam.display_name).toBe("New Team");
      expect(newTeam.id).toBe("new_team");
      expect(state.selectedTeamId).toBe("new_team");
      expect(state.isDirty).toBe(true);
    });

    it("should delete team", () => {
      const { deleteTeam } = useConfigStore.getState();
      deleteTeam("team1");

      const state = useConfigStore.getState();
      expect(state.teams).toHaveLength(1);
      expect(state.teams[0].id).toBe("team2");
      expect(state.selectedTeamId).toBe(null);
      expect(state.isDirty).toBe(true);
    });
  });

  describe("cultures", () => {
    beforeEach(() => {
      useConfigStore.setState({
        cultures: [
          {
            id: "engineering",
            description: "Engineering standards",
            agents: ["agent1"],
            mode: "automatic",
          },
          {
            id: "support",
            description: "Support playbooks",
            agents: ["agent2"],
            mode: "manual",
          },
        ],
        selectedCultureId: "engineering",
      });
    });

    it("should select culture", () => {
      const { selectCulture } = useConfigStore.getState();
      selectCulture("support");

      const state = useConfigStore.getState();
      expect(state.selectedCultureId).toBe("support");
    });

    it("should update culture and enforce unique agent assignment", () => {
      const { updateCulture } = useConfigStore.getState();
      updateCulture("support", {
        agents: ["agent1", "agent2"],
        mode: "agentic",
      });

      const state = useConfigStore.getState();
      expect(
        state.cultures.find((culture) => culture.id === "support")?.mode,
      ).toBe("agentic");
      expect(
        state.cultures.find((culture) => culture.id === "support")?.agents,
      ).toEqual(["agent1", "agent2"]);
      expect(
        state.cultures.find((culture) => culture.id === "engineering")?.agents,
      ).toEqual([]);
      expect(state.isDirty).toBe(true);
    });

    it("should create new culture", () => {
      const { createCulture } = useConfigStore.getState();
      createCulture({
        description: "Product knowledge",
        agents: ["agent3"],
        mode: "automatic",
      });

      const state = useConfigStore.getState();
      expect(state.cultures).toHaveLength(3);
      const newCulture = state.cultures.find(
        (culture) => culture.id === "product_knowledge",
      );
      expect(newCulture?.description).toBe("Product knowledge");
      expect(state.selectedCultureId).toBe("product_knowledge");
      expect(state.isDirty).toBe(true);
    });

    it("should delete culture", () => {
      const { deleteCulture } = useConfigStore.getState();
      deleteCulture("engineering");

      const state = useConfigStore.getState();
      expect(state.cultures).toHaveLength(1);
      expect(state.cultures[0].id).toBe("support");
      expect(state.selectedCultureId).toBe(null);
      expect(state.isDirty).toBe(true);
    });
  });

  describe("room models", () => {
    it("should update room models", () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: { provider: "openai", config: { model: "test" } },
          },
          models: {},
          agents: {},
          defaults: {
            markdown: true,
          },
          router: { model: "default" },
        },
      });

      const { updateRoomModels } = useConfigStore.getState();
      const roomModels = {
        lobby: "gpt4",
        dev: "claude",
      };

      updateRoomModels(roomModels);

      const state = useConfigStore.getState();
      expect(state.config?.room_models).toEqual(roomModels);
      expect(state.isDirty).toBe(true);
    });
  });

  describe("memory config", () => {
    it("should update memory configuration", () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: {
                model: "text-embedding-ada-002",
              },
            },
          },
          models: {},
          agents: {},
          defaults: {
            markdown: true,
          },
          router: { model: "default" },
        },
      });

      const { updateMemoryConfig } = useConfigStore.getState();
      const newMemoryConfig = {
        provider: "ollama",
        model: "nomic-embed-text",
        host: "http://localhost:11434",
      };

      updateMemoryConfig(newMemoryConfig);

      const state = useConfigStore.getState();
      expect(state.config?.memory.embedder.provider).toBe("ollama");
      expect(state.config?.memory.embedder.config.model).toBe(
        "nomic-embed-text",
      );
      expect(state.config?.memory.embedder.config.host).toBe(
        "http://localhost:11434",
      );
      expect(state.isDirty).toBe(true);
    });

    it("should handle memory config without host", () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: {
              provider: "openai",
              config: {
                model: "text-embedding-ada-002",
              },
            },
          },
          models: {},
          agents: {},
          defaults: {
            markdown: true,
          },
          router: { model: "default" },
        },
      });

      const { updateMemoryConfig } = useConfigStore.getState();
      const newMemoryConfig = {
        provider: "openai",
        model: "text-embedding-3-small",
      };

      updateMemoryConfig(newMemoryConfig);

      const state = useConfigStore.getState();
      expect(state.config?.memory.embedder.provider).toBe("openai");
      expect(state.config?.memory.embedder.config.model).toBe(
        "text-embedding-3-small",
      );
      expect(state.config?.memory.embedder.config.host).toBeUndefined();
    });
  });

  describe("knowledge bases", () => {
    it("should preserve git settings when updating base path or watch", () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: { provider: "openai", config: { model: "test" } },
          },
          knowledge_bases: {
            docs: {
              path: "./docs",
              watch: true,
              chunk_size: 5000,
              chunk_overlap: 0,
              git: {
                repo_url: "https://github.com/pipefunc/pipefunc",
                branch: "main",
                include_patterns: ["docs/**"],
              },
            },
          },
          models: {},
          agents: {},
          defaults: {
            markdown: true,
          },
          router: { model: "default" },
        } as Config,
      });

      const { updateKnowledgeBase } = useConfigStore.getState();
      updateKnowledgeBase("docs", { path: "./docs-sync", watch: false });

      const state = useConfigStore.getState();
      expect(state.config?.knowledge_bases?.docs).toEqual({
        path: "./docs-sync",
        watch: false,
        chunk_size: 5000,
        chunk_overlap: 0,
        git: {
          repo_url: "https://github.com/pipefunc/pipefunc",
          branch: "main",
          include_patterns: ["docs/**"],
        },
      });
      expect(state.isDirty).toBe(true);
    });

    it("should remove deleted knowledge base from all agent assignments", () => {
      useConfigStore.setState({
        config: {
          memory: {
            embedder: { provider: "openai", config: { model: "test" } },
          },
          knowledge_bases: {
            legal: { path: "./legal", watch: true },
            research: { path: "./research", watch: true },
          },
          models: {},
          agents: {
            agent1: {
              display_name: "Agent 1",
              role: "Test agent",
              tools: [],
              skills: [],
              instructions: [],
              rooms: [],
              knowledge_bases: ["research", "legal"],
            },
            agent2: {
              display_name: "Agent 2",
              role: "Test agent 2",
              tools: [],
              skills: [],
              instructions: [],
              rooms: [],
              knowledge_bases: ["research"],
            },
          },
          defaults: {
            markdown: true,
          },
          router: { model: "default" },
        },
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            knowledge_bases: ["research", "legal"],
          },
          {
            id: "agent2",
            display_name: "Agent 2",
            role: "Test agent 2",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
            knowledge_bases: ["research"],
          },
        ],
      });

      const { deleteKnowledgeBase } = useConfigStore.getState();
      deleteKnowledgeBase("research");

      const state = useConfigStore.getState();
      expect(state.config?.knowledge_bases).toEqual({
        legal: { path: "./legal", watch: true },
      });
      expect(
        state.agents.find((agent) => agent.id === "agent1")?.knowledge_bases,
      ).toEqual(["legal"]);
      expect(
        state.agents.find((agent) => agent.id === "agent2")?.knowledge_bases,
      ).toEqual([]);
      expect(state.config?.agents.agent1.knowledge_bases).toEqual(["legal"]);
      expect(state.config?.agents.agent2.knowledge_bases).toEqual([]);
      expect(state.isDirty).toBe(true);
    });
  });

  describe("rooms", () => {
    beforeEach(() => {
      const mockRooms = [
        {
          id: "lobby",
          display_name: "Lobby",
          description: "Main room",
          agents: ["agent1"],
          model: "default",
        },
        {
          id: "dev",
          display_name: "Dev Room",
          description: "Development room",
          agents: ["agent2"],
        },
      ];

      const mockAgents = [
        {
          id: "agent1",
          display_name: "Agent 1",
          role: "Test agent",
          tools: [],
          skills: [],
          instructions: [],
          rooms: ["lobby"],
        },
        {
          id: "agent2",
          display_name: "Agent 2",
          role: "Test agent 2",
          tools: [],
          skills: [],
          instructions: [],
          rooms: ["dev"],
        },
      ];

      useConfigStore.setState({
        rooms: mockRooms,
        agents: mockAgents,
        selectedRoomId: "lobby",
      });
    });

    it("should select room", () => {
      const { selectRoom } = useConfigStore.getState();
      selectRoom("dev");

      const state = useConfigStore.getState();
      expect(state.selectedRoomId).toBe("dev");
    });

    it("should update room", () => {
      const { updateRoom } = useConfigStore.getState();
      updateRoom("lobby", { display_name: "Updated Lobby" });

      const state = useConfigStore.getState();
      const updatedRoom = state.rooms.find((r) => r.id === "lobby");
      expect(updatedRoom?.display_name).toBe("Updated Lobby");
      expect(state.isDirty).toBe(true);
    });

    it("should ignore unchanged room updates while a save is in progress", async () => {
      const pendingSaveResponse = deferred<{
        ok: boolean;
        json: () => Promise<{ success: true }>;
      }>();
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
          agent2: {
            display_name: "Agent 2",
            role: "Test agent 2",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        },
        rooms: {
          lobby: {
            display_name: "Lobby",
            description: "Main room",
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
          {
            id: "agent2",
            display_name: "Agent 2",
            role: "Test agent 2",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        ],
        rooms: [
          {
            id: "lobby",
            display_name: "Lobby",
            description: "Main room",
            agents: ["agent1", "agent2"],
          },
        ],
        isDirty: true,
        dirtyRoots: ["rooms"],
      });

      (global.fetch as any).mockReturnValueOnce(pendingSaveResponse.promise);

      const savePromise = useConfigStore.getState().saveConfig();
      useConfigStore.getState().updateRoom("lobby", {
        display_name: "Lobby",
        agents: ["agent2", "agent1"],
      });

      pendingSaveResponse.resolve({
        ok: true,
        json: async () => ({ success: true }),
      });

      await expect(savePromise).resolves.toEqual({ status: "saved" });
    });

    it("should update agents when room agents change", () => {
      const { updateRoom } = useConfigStore.getState();
      updateRoom("lobby", { agents: ["agent1", "agent2"] });

      const state = useConfigStore.getState();
      const agent2 = state.agents.find((a) => a.id === "agent2");
      expect(agent2?.rooms).toContain("lobby");
      expect(state.isDirty).toBe(true);
    });

    it("writes room metadata edits to draft config rooms", () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        rooms: [
          {
            id: "lobby",
            display_name: "Lobby",
            description: "",
            agents: ["agent1"],
          },
        ],
      });

      useConfigStore.getState().updateRoom("lobby", {
        display_name: "Project Lobby",
      });

      const state = useConfigStore.getState();
      expect(state.config?.rooms).toEqual({
        lobby: {
          display_name: "Project Lobby",
        },
      });
      expect(
        state.rooms.find((room) => room.id === "lobby")?.display_name,
      ).toBe("Project Lobby");
      expect(state.dirtyRoots).toContain("rooms");
    });

    it("normalizes blank room display-name edits to omitted metadata", async () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["project_room"],
          },
        },
        rooms: {
          project_room: {
            display_name: "Project Alpha",
            description: "Planning space",
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["project_room"],
          },
        ],
        rooms: [
          {
            id: "project_room",
            display_name: "Project Alpha",
            description: "Planning space",
            agents: ["agent1"],
          },
        ],
      });

      useConfigStore.getState().updateRoom("project_room", {
        display_name: "   ",
      });

      const state = useConfigStore.getState();
      expect(state.config?.rooms?.project_room).toEqual({
        description: "Planning space",
      });
      expect(
        state.rooms.find((room) => room.id === "project_room")?.display_name,
      ).toBe("Project Room");

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const payload = JSON.parse((global.fetch as any).mock.calls[0][1].body);
      expect(payload.rooms).toEqual({
        project_room: {
          description: "Planning space",
        },
      });
    });

    it("does not promote derived rooms when clearing default display names", () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["project_room"],
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["project_room"],
          },
        ],
        rooms: [
          {
            id: "project_room",
            display_name: "Project Room",
            description: "",
            agents: ["agent1"],
          },
        ],
      });

      useConfigStore.getState().updateRoom("project_room", {
        display_name: "   ",
      });

      const state = useConfigStore.getState();
      expect(state.config?.rooms).toBeUndefined();
      expect(state.rooms).toEqual([
        {
          id: "project_room",
          display_name: "Project Room",
          description: "",
          agents: ["agent1"],
          model: undefined,
        },
      ]);
      expect(state.dirtyRoots).not.toContain("rooms");
      expect(state.isDirty).toBe(false);
    });

    it("does not serialize derived rooms for membership-only updates", async () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
          agent2: {
            display_name: "Agent 2",
            role: "Test agent 2",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
          {
            id: "agent2",
            display_name: "Agent 2",
            role: "Test agent 2",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
        rooms: [
          {
            id: "lobby",
            display_name: "Lobby",
            description: "",
            agents: ["agent1"],
          },
        ],
      });

      useConfigStore.getState().updateRoom("lobby", {
        agents: ["agent1", "agent2"],
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const payload = JSON.parse((global.fetch as any).mock.calls[0][1].body);
      expect(payload.rooms).toBeUndefined();
      expect(payload.agents.agent1.rooms).toEqual(["lobby"]);
      expect(payload.agents.agent2.rooms).toEqual(["lobby"]);
    });

    it("does not create managed room metadata for membership-only updates", () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
          agent2: {
            display_name: "Agent 2",
            role: "Test agent 2",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
          {
            id: "agent2",
            display_name: "Agent 2",
            role: "Test agent 2",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
        rooms: [
          {
            id: "lobby",
            display_name: "Lobby",
            description: "",
            agents: ["agent1"],
          },
        ],
      });

      useConfigStore.getState().updateRoom("lobby", {
        agents: ["agent1", "agent2"],
      });

      const state = useConfigStore.getState();
      expect(state.config?.rooms).toBeUndefined();
      expect(state.dirtyRoots).not.toContain("rooms");
      expect(state.dirtyRoots).toContain("agents");
    });

    it("does not serialize derived rooms for model-only updates", async () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
          claude: {
            provider: "anthropic",
            id: "claude-sonnet-4-6",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        ],
        rooms: [
          {
            id: "lobby",
            display_name: "Lobby",
            description: "",
            agents: ["agent1"],
          },
        ],
      });

      useConfigStore.getState().updateRoom("lobby", { model: "claude" });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const payload = JSON.parse((global.fetch as any).mock.calls[0][1].body);
      expect(payload.rooms).toBeUndefined();
      expect(payload.room_models).toEqual({
        lobby: "claude",
      });
    });

    it("should create new room", () => {
      const { createRoom } = useConfigStore.getState();
      const newRoomData = {
        display_name: "New Room",
        description: "Test room",
        agents: ["agent1"],
      };

      createRoom(newRoomData);

      const state = useConfigStore.getState();
      expect(state.rooms).toHaveLength(3);
      const newRoom = state.rooms[2];
      expect(newRoom.display_name).toBe("New Room");
      expect(newRoom.id).toBe("new_room");
      expect(state.selectedRoomId).toBe("new_room");

      // Check that agent1 now has new_room in its rooms
      const agent1 = state.agents.find((a) => a.id === "agent1");
      expect(agent1?.rooms).toContain("new_room");
      expect(state.isDirty).toBe(true);
    });

    it("writes newly created managed room metadata to draft config rooms", () => {
      const mockConfig = {
        agents: {},
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [],
        rooms: [],
      });

      useConfigStore.getState().createRoom({
        display_name: "Project Room",
        description: "Planning space",
        agents: [],
      });

      const state = useConfigStore.getState();
      expect(state.config?.rooms).toEqual({
        project_room: {
          display_name: "Project Room",
          description: "Planning space",
        },
      });
      expect(state.rooms).toEqual([
        {
          id: "project_room",
          display_name: "Project Room",
          description: "Planning space",
          agents: [],
          model: undefined,
        },
      ]);
      expect(state.dirtyRoots).toContain("rooms");
    });

    it("writes a model selected during room creation to draft room models", async () => {
      const mockConfig = {
        agents: {},
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
          claude: {
            provider: "anthropic",
            id: "claude-sonnet-4-6",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [],
        rooms: [],
      });

      useConfigStore.getState().createRoom({
        display_name: "Project Room",
        description: "Planning space",
        agents: [],
        model: "claude",
      });

      const state = useConfigStore.getState();
      expect(state.config?.room_models).toEqual({
        project_room: "claude",
      });
      expect(state.rooms[0]).toMatchObject({
        id: "project_room",
        model: "claude",
      });
      expect(state.dirtyRoots).toEqual(
        expect.arrayContaining(["rooms", "room_models"]),
      );

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const payload = JSON.parse((global.fetch as any).mock.calls[0][1].body);
      expect(payload.room_models).toEqual({
        project_room: "claude",
      });
    });

    it("does not serialize team-derived rooms when creating a managed room", async () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        teams: {
          ops: {
            display_name: "Ops Team",
            role: "Coordinates operations",
            agents: ["agent1"],
            rooms: ["war_room"],
            mode: "coordinate",
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
        teams: [
          {
            id: "ops",
            display_name: "Ops Team",
            role: "Coordinates operations",
            agents: ["agent1"],
            rooms: ["war_room"],
            mode: "coordinate",
          },
        ],
        rooms: [
          {
            id: "war_room",
            display_name: "War_room",
            description: "",
            agents: [],
          },
        ],
      });

      useConfigStore.getState().createRoom({
        display_name: "Project Room",
        description: "Planning space",
        agents: [],
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const payload = JSON.parse((global.fetch as any).mock.calls[0][1].body);
      expect(payload.rooms).toEqual({
        project_room: {
          display_name: "Project Room",
          description: "Planning space",
        },
      });
    });

    it("preserves omitted fields on existing managed rooms when creating another room", async () => {
      const mockConfig = {
        agents: {},
        rooms: {
          analysis_room: {},
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        rooms: [
          {
            id: "analysis_room",
            display_name: "Analysis_room",
            description: "",
            agents: [],
          },
        ],
      });

      useConfigStore.getState().createRoom({
        display_name: "Project Room",
        description: "Planning space",
        agents: [],
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const payload = JSON.parse((global.fetch as any).mock.calls[0][1].body);
      expect(payload.rooms).toEqual({
        analysis_room: {},
        project_room: {
          display_name: "Project Room",
          description: "Planning space",
        },
      });
    });

    it("should persist a newly created room even before agents are assigned", async () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
        rooms: [],
      });

      const { createRoom, saveConfig } = useConfigStore.getState();
      createRoom({
        display_name: "Project Room",
        description: "Planning space",
        agents: [],
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await saveConfig();

      const saveCall = (global.fetch as any).mock.calls[0];
      const payload = JSON.parse(saveCall[1].body);
      expect(payload.rooms).toEqual({
        project_room: {
          display_name: "Project Room",
          description: "Planning space",
        },
      });

      const state = useConfigStore.getState();
      expect(state.rooms).toEqual([
        {
          id: "project_room",
          display_name: "Project Room",
          description: "Planning space",
          agents: [],
          model: undefined,
        },
      ]);
      expect(state.config?.rooms).toEqual({
        project_room: {
          display_name: "Project Room",
          description: "Planning space",
        },
      });
    });

    it("should delete room and update agents", () => {
      const { deleteRoom } = useConfigStore.getState();
      deleteRoom("lobby");

      const state = useConfigStore.getState();
      expect(state.rooms).toHaveLength(1);
      expect(state.rooms[0].id).toBe("dev");

      // Check that agent1 no longer has lobby in its rooms
      const agent1 = state.agents.find((a) => a.id === "agent1");
      expect(agent1?.rooms).not.toContain("lobby");
      expect(state.selectedRoomId).toBe(null);
      expect(state.isDirty).toBe(true);
    });

    it("deleting a derived room does not dirty managed room metadata", () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        ],
        rooms: [
          {
            id: "lobby",
            display_name: "Lobby",
            description: "",
            agents: ["agent1"],
          },
        ],
      });

      useConfigStore.getState().deleteRoom("lobby");

      const state = useConfigStore.getState();
      expect(state.config?.rooms).toBeUndefined();
      expect(state.dirtyRoots).not.toContain("rooms");
      expect(state.dirtyRoots).toEqual(expect.arrayContaining(["agents"]));
      expect(state.agents[0].rooms).toEqual([]);
    });

    it("deleting an unassigned managed room does not dirty responder collections", async () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        },
        teams: {
          team1: {
            display_name: "Team 1",
            role: "Test team",
            agents: ["agent1"],
            rooms: ["lobby"],
            mode: "coordinate",
          },
        },
        rooms: {
          project_room: {
            display_name: "Project Room",
            description: "Planning space",
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        ],
        teams: [
          {
            id: "team1",
            display_name: "Team 1",
            role: "Test team",
            agents: ["agent1"],
            rooms: ["lobby"],
            mode: "coordinate",
          },
        ],
        rooms: [
          {
            id: "lobby",
            display_name: "Lobby",
            description: "",
            agents: ["agent1"],
          },
          {
            id: "project_room",
            display_name: "Project Room",
            description: "Planning space",
            agents: [],
          },
        ],
      });

      useConfigStore.getState().deleteRoom("project_room");

      const state = useConfigStore.getState();
      expect(state.dirtyRoots).toContain("rooms");
      expect(state.dirtyRoots).not.toContain("agents");
      expect(state.dirtyRoots).not.toContain("teams");

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const payload = JSON.parse((global.fetch as any).mock.calls[0][1].body);
      expect(payload.agents).toEqual(mockConfig.agents);
      expect(payload.teams).toEqual(mockConfig.teams);
      expect(payload.rooms).toEqual({});
    });

    it("does not serialize agent-derived rooms when deleting a managed room", async () => {
      const mockConfig = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        },
        rooms: {
          project_room: {
            display_name: "Project Room",
            description: "Planning space",
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        ],
        rooms: [
          {
            id: "lobby",
            display_name: "Lobby",
            description: "",
            agents: ["agent1"],
          },
          {
            id: "project_room",
            display_name: "Project Room",
            description: "Planning space",
            agents: [],
          },
        ],
      });

      useConfigStore.getState().deleteRoom("project_room");

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const payload = JSON.parse((global.fetch as any).mock.calls[0][1].body);
      expect(payload.rooms).toEqual({});
      expect(payload.agents.agent1.rooms).toEqual(["lobby"]);
    });

    it("preserves omitted fields on existing managed rooms when deleting another room", async () => {
      const mockConfig = {
        agents: {},
        rooms: {
          analysis_room: {},
          project_room: {
            display_name: "Project Room",
            description: "Planning space",
          },
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      } as Config;

      useConfigStore.setState({
        config: mockConfig,
        loadedConfig: mockConfig,
        rooms: [
          {
            id: "analysis_room",
            display_name: "Analysis_room",
            description: "",
            agents: [],
          },
          {
            id: "project_room",
            display_name: "Project Room",
            description: "Planning space",
            agents: [],
          },
        ],
      });

      useConfigStore.getState().deleteRoom("project_room");

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const payload = JSON.parse((global.fetch as any).mock.calls[0][1].body);
      expect(payload.rooms).toEqual({
        analysis_room: {},
      });
    });

    it("should add agent to room", () => {
      const { addAgentToRoom } = useConfigStore.getState();
      addAgentToRoom("dev", "agent1");

      const state = useConfigStore.getState();
      const devRoom = state.rooms.find((r) => r.id === "dev");
      expect(devRoom?.agents).toContain("agent1");

      const agent1 = state.agents.find((a) => a.id === "agent1");
      expect(agent1?.rooms).toContain("dev");
      expect(state.isDirty).toBe(true);
    });

    it("should remove agent from room", () => {
      const { removeAgentFromRoom } = useConfigStore.getState();
      removeAgentFromRoom("lobby", "agent1");

      const state = useConfigStore.getState();
      const lobbyRoom = state.rooms.find((r) => r.id === "lobby");
      expect(lobbyRoom?.agents).not.toContain("agent1");

      const agent1 = state.agents.find((a) => a.id === "agent1");
      expect(agent1?.rooms).not.toContain("lobby");
      expect(state.isDirty).toBe(true);
    });
  });

  describe("saveConfig with teams", () => {
    it("should save configuration with teams and room models", async () => {
      const mockConfig: Config = {
        agents: {
          agent1: {
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        teams: {
          team1: {
            display_name: "Team 1",
            role: "Test team",
            agents: ["agent1"],
            rooms: ["lobby"],
            mode: "coordinate",
          },
        },
        room_models: {
          lobby: "default",
        },
        memory: {
          embedder: {
            provider: "ollama",
            config: {
              model: "nomic-embed-text",
              host: "http://localhost:11434",
            },
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
      };

      useConfigStore.setState({
        config: mockConfig,
        agents: [
          {
            id: "agent1",
            display_name: "Agent 1",
            role: "Test agent",
            tools: [],
            skills: [],
            instructions: [],
            rooms: [],
          },
        ],
        teams: [
          {
            id: "team1",
            display_name: "Team 1",
            role: "Test team",
            agents: ["agent1"],
            rooms: ["lobby"],
            mode: "coordinate",
          },
        ],
        rooms: [
          {
            id: "lobby",
            display_name: "Lobby",
            description: "",
            agents: ["agent1"],
            model: "default",
          },
        ],
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      const { saveConfig } = useConfigStore.getState();
      await saveConfig();

      expect(global.fetch).toHaveBeenCalledTimes(1);
      expect(global.fetch).toHaveBeenNthCalledWith(1, "/api/config/save", {
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          "x-mindroom-config-generation": "0",
        },
        body: JSON.stringify(mockConfig),
      });

      const state = useConfigStore.getState();
      expect(state.syncStatus).toBe("synced");
      expect(state.isDirty).toBe(false);
    });
  });

  describe("tool overrides", () => {
    it("normalizes structured tool entries on load and exposes remembered overrides", async () => {
      const mockConfig = {
        agents: {
          openclaw: {
            display_name: "OpenClaw",
            role: "Coding agent",
            tools: [
              "browser",
              { shell: { extra_env_passthrough: ["GITEA_TOKEN"] } },
            ],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-3-small",
            },
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { openclaw: makeAgentPolicy("openclaw") },
        }),
      });

      await useConfigStore.getState().loadConfig();

      const state = useConfigStore.getState();
      expect(state.agents[0].tools).toEqual(["browser", "shell"]);
      expect(state.getAgentToolOverrides("openclaw", "shell")).toEqual({
        extra_env_passthrough: ["GITEA_TOKEN"],
      });
    });

    it("updates overrides, marks the draft dirty, and rebuilds structured tool entries on save", async () => {
      const mockConfig = {
        agents: {
          openclaw: {
            display_name: "OpenClaw",
            role: "Coding agent",
            tools: [
              "browser",
              { shell: { extra_env_passthrough: ["GITEA_TOKEN"] } },
            ],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-3-small",
            },
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { openclaw: makeAgentPolicy("openclaw") },
        }),
      });

      await useConfigStore.getState().loadConfig();
      useConfigStore.getState().updateAgentToolOverrides("openclaw", "shell", {
        shell_path_prepend: ["/run/wrappers/bin"],
      });

      expect(useConfigStore.getState().isDirty).toBe(true);
      expect(
        useConfigStore.getState().getAgentToolOverrides("openclaw", "shell"),
      ).toEqual({
        extra_env_passthrough: ["GITEA_TOKEN"],
        shell_path_prepend: ["/run/wrappers/bin"],
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const saveCall = (global.fetch as any).mock.calls[2];
      expect(saveCall[0]).toBe("/api/config/save");
      expect(saveCall[1]).toMatchObject({
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          "x-mindroom-config-generation": "0",
        },
      });
      expect(JSON.parse(saveCall[1].body)).toMatchObject({
        agents: {
          openclaw: {
            ...mockConfig.agents.openclaw,
            tools: [
              "browser",
              {
                shell: {
                  extra_env_passthrough: ["GITEA_TOKEN"],
                  shell_path_prepend: ["/run/wrappers/bin"],
                },
              },
            ],
          },
        },
        knowledge_bases: {},
      });
    });

    it("preserves remembered overrides when updateRoom replaces the config object", async () => {
      const mockConfig = {
        agents: {
          openclaw: {
            display_name: "OpenClaw",
            role: "Coding agent",
            tools: [
              "browser",
              {
                shell: {
                  extra_env_passthrough: ["GITEA_TOKEN"],
                  shell_path_prepend: ["/run/wrappers/bin"],
                },
              },
            ],
            skills: [],
            instructions: [],
            rooms: ["lobby"],
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
          claude: {
            provider: "anthropic",
            id: "claude-sonnet-4-6",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-3-small",
            },
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { openclaw: makeAgentPolicy("openclaw") },
        }),
      });

      await useConfigStore.getState().loadConfig();
      useConfigStore.getState().updateRoom("lobby", { model: "claude" });

      expect(
        useConfigStore.getState().getAgentToolOverrides("openclaw", "shell"),
      ).toEqual({
        extra_env_passthrough: ["GITEA_TOKEN"],
        shell_path_prepend: ["/run/wrappers/bin"],
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const saveCall = (global.fetch as any).mock.calls[2];
      expect(saveCall[0]).toBe("/api/config/save");
      expect(JSON.parse(saveCall[1].body)).toMatchObject({
        ...mockConfig,
        room_models: {
          lobby: "claude",
        },
      });
    });

    it("collapses an empty override object back to a plain string entry on save", async () => {
      const mockConfig = {
        agents: {
          openclaw: {
            display_name: "OpenClaw",
            role: "Coding agent",
            tools: [{ shell: { extra_env_passthrough: ["GITEA_TOKEN"] } }],
            skills: [],
            instructions: [],
            rooms: [],
          },
        },
        models: {
          default: {
            provider: "ollama",
            id: "test-model",
          },
        },
        defaults: {
          markdown: true,
        },
        router: {
          model: "default",
        },
        memory: {
          embedder: {
            provider: "openai",
            config: {
              model: "text-embedding-3-small",
            },
          },
        },
      };

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => mockConfig,
      });
      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          agent_policies: { openclaw: makeAgentPolicy("openclaw") },
        }),
      });

      await useConfigStore.getState().loadConfig();
      useConfigStore.getState().updateAgentToolOverrides("openclaw", "shell", {
        extra_env_passthrough: null,
      });

      (global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      });

      await useConfigStore.getState().saveConfig();

      const saveCall = (global.fetch as any).mock.calls[2];
      expect(saveCall[0]).toBe("/api/config/save");
      expect(saveCall[1]).toMatchObject({
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          "x-mindroom-config-generation": "0",
        },
      });
      expect(JSON.parse(saveCall[1].body)).toMatchObject({
        agents: {
          openclaw: {
            ...mockConfig.agents.openclaw,
            tools: ["shell"],
          },
        },
        knowledge_bases: {},
      });
    });
  });
});
