diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index ffe001286..60356a289 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -133,9 +133,10 @@ import { } from "@/screens/workspace/workspace-route-state"; import { renderWorkspaceRouteGate } from "@/screens/workspace/workspace-route-state-views"; import { + buildWorkspaceTabSnapshot, deriveWorkspaceAgentVisibility, workspaceAgentVisibilityEqual, -} from "@/screens/workspace/workspace-agent-visibility"; +} from "@/workspace-tabs/agent-visibility"; import { deriveWorkspacePaneState } from "@/screens/workspace/workspace-pane-state"; import { buildWorkspacePaneContentModel, @@ -1919,16 +1920,17 @@ function WorkspaceScreenContent({ return pending?.serverId === normalizedServerId && pending.lifecycle === "active"; }); - reconcileWorkspaceTabs(persistenceKey, { - agentsHydrated: hasHydratedAgents, - terminalsHydrated: terminalsQuery.isSuccess, - activeAgentIds: Array.from(workspaceAgentVisibility.activeAgentIds), - autoOpenAgentIds: Array.from(workspaceAgentVisibility.autoOpenAgentIds), - knownAgentIds: Array.from(workspaceAgentVisibility.knownAgentIds), - knownTerminalIds, - standaloneTerminalIds, - hasActivePendingDraftCreate: hasActivePendingDraftCreateInWorkspace, - }); + reconcileWorkspaceTabs( + persistenceKey, + buildWorkspaceTabSnapshot({ + agentVisibility: workspaceAgentVisibility, + agentsHydrated: hasHydratedAgents, + terminalsHydrated: terminalsQuery.isSuccess, + knownTerminalIds, + standaloneTerminalIds, + hasActivePendingDraftCreate: hasActivePendingDraftCreateInWorkspace, + }), + ); }, [ hasHydratedAgents, hasHydratedWorkspaceLayoutStore, diff --git a/packages/app/src/stores/workspace-subagents-integration.test.ts b/packages/app/src/stores/workspace-subagents-integration.test.ts index 37af647b3..1b3499111 100644 --- a/packages/app/src/stores/workspace-subagents-integration.test.ts +++ b/packages/app/src/stores/workspace-subagents-integration.test.ts @@ -1,9 +1,10 @@ import type { DaemonClient } from "@server/client/daemon-client"; import { afterEach, describe, expect, it, vi } from "vitest"; import { + buildWorkspaceTabSnapshot, deriveWorkspaceAgentVisibility, type WorkspaceAgentVisibility, -} from "@/screens/workspace/workspace-agent-visibility"; +} from "@/workspace-tabs/agent-visibility"; import { selectSubagentsForParent } from "@/subagents"; import { buildWorkspaceTabPersistenceKey, useWorkspaceLayoutStore } from "./workspace-layout-store"; import { useSessionStore, type Agent } from "./session-store"; @@ -151,15 +152,17 @@ function deriveVisibilityFromSession(): WorkspaceAgentVisibility { } function reconcileWorkspaceTabs(workspaceKey: string, visibility: WorkspaceAgentVisibility): void { - useWorkspaceLayoutStore.getState().reconcileTabs(workspaceKey, { - agentsHydrated: true, - terminalsHydrated: true, - activeAgentIds: visibility.activeAgentIds, - autoOpenAgentIds: visibility.autoOpenAgentIds, - knownAgentIds: visibility.knownAgentIds, - standaloneTerminalIds: [], - hasActivePendingDraftCreate: false, - }); + useWorkspaceLayoutStore.getState().reconcileTabs( + workspaceKey, + buildWorkspaceTabSnapshot({ + agentVisibility: visibility, + agentsHydrated: true, + terminalsHydrated: true, + knownTerminalIds: [], + standaloneTerminalIds: [], + hasActivePendingDraftCreate: false, + }), + ); } function getWorkspaceTabIds(workspaceKey: string): string[] { diff --git a/packages/app/src/subagents/policies.ts b/packages/app/src/subagents/policies.ts index ae7597691..b8a29d07d 100644 --- a/packages/app/src/subagents/policies.ts +++ b/packages/app/src/subagents/policies.ts @@ -1,5 +1,5 @@ // Pure-data entry point for callers that don't want React Native deps in -// their dependency graph (e.g. workspace-agent-visibility.ts is plain JS +// their dependency graph (e.g. workspace-tabs/agent-visibility.ts is plain // data derivation and its tests run without an RN environment). // // The full module entry `@/subagents` re-exports these too, alongside the diff --git a/packages/app/src/screens/workspace/workspace-agent-visibility.test.ts b/packages/app/src/workspace-tabs/agent-visibility.test.ts similarity index 90% rename from packages/app/src/screens/workspace/workspace-agent-visibility.test.ts rename to packages/app/src/workspace-tabs/agent-visibility.test.ts index f02dc4daa..3e4c9bb36 100644 --- a/packages/app/src/screens/workspace/workspace-agent-visibility.test.ts +++ b/packages/app/src/workspace-tabs/agent-visibility.test.ts @@ -1,10 +1,11 @@ import { describe, expect, it } from "vitest"; import type { Agent } from "@/stores/session-store"; import { + buildWorkspaceTabSnapshot, deriveWorkspaceAgentVisibility, shouldPruneWorkspaceAgentTab, workspaceAgentVisibilityEqual, -} from "@/screens/workspace/workspace-agent-visibility"; +} from "@/workspace-tabs/agent-visibility"; function makeAgent(input: { id: string; @@ -265,6 +266,34 @@ describe("workspace agent visibility", () => { expect(result.knownAgentIds).toEqual(new Set(["recent-agent"])); }); + 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 = { diff --git a/packages/app/src/screens/workspace/workspace-agent-visibility.ts b/packages/app/src/workspace-tabs/agent-visibility.ts similarity index 76% rename from packages/app/src/screens/workspace/workspace-agent-visibility.ts rename to packages/app/src/workspace-tabs/agent-visibility.ts index f0ad8c1fb..45235f857 100644 --- a/packages/app/src/screens/workspace/workspace-agent-visibility.ts +++ b/packages/app/src/workspace-tabs/agent-visibility.ts @@ -1,4 +1,5 @@ import type { Agent } from "@/stores/session-store"; +import type { WorkspaceTabSnapshot } from "@/stores/workspace-layout-actions"; import { shouldAutoOpenAgentTab } from "@/subagents/policies"; import { normalizeWorkspacePath } from "@/utils/workspace-identity"; @@ -52,6 +53,26 @@ export function deriveWorkspaceAgentVisibility(input: { return { activeAgentIds, autoOpenAgentIds, knownAgentIds }; } +export function buildWorkspaceTabSnapshot(input: { + agentVisibility: WorkspaceAgentVisibility; + agentsHydrated: boolean; + terminalsHydrated: boolean; + knownTerminalIds: Iterable; + standaloneTerminalIds: Iterable; + hasActivePendingDraftCreate: boolean; +}): WorkspaceTabSnapshot { + return { + agentsHydrated: input.agentsHydrated, + terminalsHydrated: input.terminalsHydrated, + activeAgentIds: input.agentVisibility.activeAgentIds, + autoOpenAgentIds: input.agentVisibility.autoOpenAgentIds, + knownAgentIds: input.agentVisibility.knownAgentIds, + knownTerminalIds: input.knownTerminalIds, + standaloneTerminalIds: input.standaloneTerminalIds, + hasActivePendingDraftCreate: input.hasActivePendingDraftCreate, + }; +} + export function workspaceAgentVisibilityEqual( a: WorkspaceAgentVisibility, b: WorkspaceAgentVisibility,