import { describe, expect, it } from "vitest"; import type { Agent } from "@/stores/session-store"; import { buildWorkspaceTabSnapshot, deriveWorkspaceAgentVisibility, shouldPruneWorkspaceAgentTab, workspaceAgentVisibilityEqual, } from "@/workspace-tabs/agent-visibility"; function makeAgent(input: { id: string; cwd: string; workspaceId?: string; parentAgentId?: string | null; archivedAt?: Date | null; createdAt?: Date; lastActivityAt?: Date; }): Agent { const createdAt = input.createdAt ?? new Date("2026-03-04T00:00:00.000Z"); const lastActivityAt = input.lastActivityAt ?? createdAt; return { serverId: "srv", id: input.id, provider: "codex", status: "idle", activeTurn: null, createdAt, updatedAt: createdAt, lastUserMessageAt: null, lastActivityAt, capabilities: { supportsStreaming: true, supportsSessionPersistence: true, supportsDynamicModes: true, supportsMcpServers: true, supportsReasoningStream: true, supportsToolInvocations: true, }, currentModeId: null, availableModes: [], pendingPermissions: [], persistence: null, runtimeInfo: { provider: "codex", sessionId: null, }, title: null, cwd: input.cwd, workspaceId: input.workspaceId, model: null, thinkingOptionId: null, parentAgentId: input.parentAgentId ?? null, labels: {}, requiresAttention: false, attentionReason: null, attentionTimestamp: null, archivedAt: input.archivedAt ?? null, }; } const WORKSPACE_ID = "ws-1"; describe("workspace agent visibility", () => { it("keeps subagents active and known while excluding them from auto-open", () => { const parent = makeAgent({ id: "parent-agent", cwd: "/repo/worktree", workspaceId: WORKSPACE_ID, }); const child = makeAgent({ id: "child-agent", cwd: "/repo/worktree", workspaceId: WORKSPACE_ID, parentAgentId: "parent-agent", }); const result = deriveWorkspaceAgentVisibility({ sessionAgents: new Map([ [parent.id, parent], [child.id, child], ]), workspaceId: WORKSPACE_ID, }); expect(result.activeAgentIds).toEqual(new Set(["parent-agent", "child-agent"])); expect(result.autoOpenAgentIds).toEqual(new Set(["parent-agent"])); expect(result.knownAgentIds).toEqual(new Set(["parent-agent", "child-agent"])); }); it("keeps archived subagents known but excludes them from active and auto-open", () => { const archivedChild = makeAgent({ id: "archived-child", cwd: "/repo/worktree", workspaceId: WORKSPACE_ID, parentAgentId: "parent-agent", archivedAt: new Date("2026-03-04T00:01:00.000Z"), }); const result = deriveWorkspaceAgentVisibility({ sessionAgents: new Map([[archivedChild.id, archivedChild]]), workspaceId: WORKSPACE_ID, }); expect(result.activeAgentIds).toEqual(new Set()); expect(result.autoOpenAgentIds).toEqual(new Set()); expect(result.knownAgentIds).toEqual(new Set(["archived-child"])); }); it("excludes a child from auto-open even when its snapshot arrives before the parent", () => { const child = makeAgent({ id: "child-agent", cwd: "/repo/worktree", workspaceId: WORKSPACE_ID, parentAgentId: "parent-agent", }); const parent = makeAgent({ id: "parent-agent", cwd: "/repo/worktree", workspaceId: WORKSPACE_ID, }); const result = deriveWorkspaceAgentVisibility({ sessionAgents: new Map([ [child.id, child], [parent.id, parent], ]), workspaceId: WORKSPACE_ID, }); expect(result.activeAgentIds).toEqual(new Set(["child-agent", "parent-agent"])); expect(result.autoOpenAgentIds).toEqual(new Set(["parent-agent"])); expect(result.knownAgentIds).toEqual(new Set(["child-agent", "parent-agent"])); }); it("auto-opens a subagent whose parent belongs to another workspace", () => { const parent = makeAgent({ id: "parent-agent", cwd: "/repo", workspaceId: "ws-parent", }); const child = makeAgent({ id: "child-agent", cwd: "/repo/worktree", workspaceId: WORKSPACE_ID, parentAgentId: parent.id, }); const result = deriveWorkspaceAgentVisibility({ sessionAgents: new Map([ [parent.id, parent], [child.id, child], ]), workspaceId: WORKSPACE_ID, }); expect(result.activeAgentIds).toEqual(new Set(["child-agent"])); expect(result.autoOpenAgentIds).toEqual(new Set(["child-agent"])); expect(result.knownAgentIds).toEqual(new Set(["child-agent"])); }); it("keeps archived agents out of activeAgentIds but present in knownAgentIds", () => { const visible = makeAgent({ id: "visible-agent", cwd: "/repo/worktree", workspaceId: WORKSPACE_ID, createdAt: new Date("2026-03-04T00:00:00.000Z"), }); const archived = makeAgent({ id: "archived-agent", cwd: "/repo/worktree", workspaceId: WORKSPACE_ID, archivedAt: new Date("2026-03-04T00:01:00.000Z"), createdAt: new Date("2026-03-04T00:01:00.000Z"), }); const otherWorkspace = makeAgent({ id: "other-workspace-agent", cwd: "/repo/other", workspaceId: "ws-other", }); const sessionAgents = new Map([ [visible.id, visible], [archived.id, archived], [otherWorkspace.id, otherWorkspace], ]); const result = deriveWorkspaceAgentVisibility({ sessionAgents, workspaceId: WORKSPACE_ID, }); expect(result.activeAgentIds).toEqual(new Set(["visible-agent"])); expect(result.autoOpenAgentIds).toEqual(new Set(["visible-agent"])); expect(result.knownAgentIds.has("visible-agent")).toBe(true); expect(result.knownAgentIds.has("archived-agent")).toBe(true); expect(result.knownAgentIds.has("other-workspace-agent")).toBe(false); }); it("treats lazy historical details as known without making them active", () => { const active = makeAgent({ id: "active-agent", cwd: "/repo/worktree", workspaceId: WORKSPACE_ID, }); const historicalDetail = makeAgent({ id: "historical-agent", cwd: "/repo/worktree", workspaceId: WORKSPACE_ID, archivedAt: new Date("2026-03-04T00:01:00.000Z"), }); const result = deriveWorkspaceAgentVisibility({ sessionAgents: new Map([[active.id, active]]), agentDetails: new Map([[historicalDetail.id, historicalDetail]]), workspaceId: WORKSPACE_ID, }); expect(result.activeAgentIds).toEqual(new Set(["active-agent"])); expect(result.knownAgentIds).toEqual(new Set(["active-agent", "historical-agent"])); }); it("prunes archived agent tabs so archiving on one client closes tabs on all clients", () => { const activeAgentIds = new Set(); expect( shouldPruneWorkspaceAgentTab({ agentId: "archived-agent", agentsHydrated: true, activeAgentIds, }), ).toBe(true); }); it("prunes pinned archived agent tabs because archive state is authoritative", () => { expect( shouldPruneWorkspaceAgentTab({ agentId: "archived-agent", agentsHydrated: true, activeAgentIds: new Set(), }), ).toBe(true); }); it("does not prune active agent tabs", () => { const activeAgentIds = new Set(["active-agent"]); expect( shouldPruneWorkspaceAgentTab({ agentId: "active-agent", agentsHydrated: true, activeAgentIds, }), ).toBe(false); }); it("prunes agent tabs once agents are hydrated and the agent is missing from activeAgentIds", () => { expect( shouldPruneWorkspaceAgentTab({ agentId: "missing-agent", agentsHydrated: true, activeAgentIds: new Set(), }), ).toBe(true); }); it("matches agents by workspaceId regardless of cwd", () => { const sessionAgents = new Map([ [ "stamped-agent", makeAgent({ id: "stamped-agent", cwd: "/repo/subdir", workspaceId: "ws-1", }), ], ]); const result = deriveWorkspaceAgentVisibility({ sessionAgents, workspaceId: "ws-1", }); expect(result.activeAgentIds).toEqual(new Set(["stamped-agent"])); expect(result.knownAgentIds).toEqual(new Set(["stamped-agent"])); }); it("excludes a stamped agent whose workspaceId belongs to another workspace sharing the cwd", () => { const sessionAgents = new Map([ [ "other-ws-agent", makeAgent({ id: "other-ws-agent", cwd: "/repo/worktree", workspaceId: "ws-2", }), ], ]); const result = deriveWorkspaceAgentVisibility({ sessionAgents, workspaceId: "ws-1", }); expect(result.activeAgentIds).toEqual(new Set()); expect(result.knownAgentIds).toEqual(new Set()); }); it("excludes agents without a workspaceId", () => { const sessionAgents = new Map([ ["ownerless-agent", makeAgent({ id: "ownerless-agent", cwd: "/repo/worktree" })], ]); const result = deriveWorkspaceAgentVisibility({ sessionAgents, workspaceId: "ws-1", }); expect(result.activeAgentIds).toEqual(new Set()); expect(result.knownAgentIds).toEqual(new Set()); }); it("builds the tab reconciliation snapshot without callers unpacking agent visibility", () => { const agentVisibility = { activeAgentIds: new Set(["active-agent"]), autoOpenAgentIds: new Set(["root-agent"]), knownAgentIds: new Set(["active-agent", "archived-agent"]), }; expect( buildWorkspaceTabSnapshot({ agentVisibility, agentsHydrated: true, terminalsHydrated: true, knownTerminalIds: ["terminal-1", "script-terminal"], standaloneTerminalIds: ["terminal-1"], hasActivePendingDraftCreate: false, }), ).toEqual({ agentsHydrated: true, terminalsHydrated: true, activeAgentIds: agentVisibility.activeAgentIds, autoOpenAgentIds: agentVisibility.autoOpenAgentIds, knownAgentIds: agentVisibility.knownAgentIds, knownTerminalIds: ["terminal-1", "script-terminal"], standaloneTerminalIds: ["terminal-1"], hasActivePendingDraftCreate: false, }); }); describe("workspaceAgentVisibilityEqual", () => { it("returns true for identical sets", () => { const a = { activeAgentIds: new Set(["a", "b"]), autoOpenAgentIds: new Set(["a"]), knownAgentIds: new Set(["a", "b", "c"]), }; const b = { activeAgentIds: new Set(["a", "b"]), autoOpenAgentIds: new Set(["a"]), knownAgentIds: new Set(["a", "b", "c"]), }; expect(workspaceAgentVisibilityEqual(a, b)).toBe(true); }); it("returns false when activeAgentIds differ", () => { const a = { activeAgentIds: new Set(["a"]), autoOpenAgentIds: new Set(["a"]), knownAgentIds: new Set(["a"]), }; const b = { activeAgentIds: new Set(["b"]), autoOpenAgentIds: new Set(["a"]), knownAgentIds: new Set(["a"]), }; expect(workspaceAgentVisibilityEqual(a, b)).toBe(false); }); it("returns false when autoOpenAgentIds differ", () => { const a = { activeAgentIds: new Set(["a", "b"]), autoOpenAgentIds: new Set(["a"]), knownAgentIds: new Set(["a", "b"]), }; const b = { activeAgentIds: new Set(["a", "b"]), autoOpenAgentIds: new Set(["b"]), knownAgentIds: new Set(["a", "b"]), }; expect(workspaceAgentVisibilityEqual(a, b)).toBe(false); }); it("returns false when knownAgentIds differ", () => { const a = { activeAgentIds: new Set(["a"]), autoOpenAgentIds: new Set(["a"]), knownAgentIds: new Set(["a"]), }; const b = { activeAgentIds: new Set(["a"]), autoOpenAgentIds: new Set(["a"]), knownAgentIds: new Set(["a", "b"]), }; expect(workspaceAgentVisibilityEqual(a, b)).toBe(false); }); it("returns true for empty sets", () => { const a = { activeAgentIds: new Set(), autoOpenAgentIds: new Set(), knownAgentIds: new Set(), }; const b = { activeAgentIds: new Set(), autoOpenAgentIds: new Set(), knownAgentIds: new Set(), }; expect(workspaceAgentVisibilityEqual(a, b)).toBe(true); }); }); });