diff --git a/packages/app/src/panels/agent-tracks.tsx b/packages/app/src/panels/agent-tracks.tsx index b675aeb3c..ddc0be9b5 100644 --- a/packages/app/src/panels/agent-tracks.tsx +++ b/packages/app/src/panels/agent-tracks.tsx @@ -1,8 +1,10 @@ import { memo, useCallback, type ReactElement } from "react"; import { AgentTaskList } from "@/composer/task-list"; import { ComposerTrackBar } from "@/composer/tracks"; +import { supportsDesktopPaneSplits, useIsCompactFormFactor } from "@/constants/layout"; import { usePaneContext } from "@/panels/pane-context"; import { useSessionStore } from "@/stores/session-store"; +import { useWorkspaceLayoutStore } from "@/stores/workspace-layout-store"; import { type ArchiveFinishedStatus, useArchiveSubagent, @@ -12,6 +14,7 @@ import { import { SubagentsTrack } from "@/subagents/track"; import type { TodoEntry } from "@/types/stream"; import { navigateToAgent } from "@/utils/navigate-to-agent"; +import { buildWorkspaceTabPersistenceKey } from "@/workspace-tabs/model"; /** * The pane's trackers — its subagents and its task list — as a row of pills above the composer. @@ -33,7 +36,10 @@ export const AgentTracks = memo(function AgentTracks({ archiveFinishedStatus: ArchiveFinishedStatus; onArchiveFinished: () => void; }): ReactElement | null { - const { openTab } = usePaneContext(); + const { workspaceId, tabId, openTab } = usePaneContext(); + const isCompact = useIsCompactFormFactor(); + const canSplit = supportsDesktopPaneSplits() && !isCompact; + const workspaceKey = buildWorkspaceTabPersistenceKey({ serverId, workspaceId }); const canDetachSubagents = useSessionStore( (state) => state.sessions[serverId]?.serverInfo?.features?.agentDetach === true, ); @@ -41,15 +47,35 @@ export const AgentTracks = memo(function AgentTracks({ const detachSubagent = useDetachSubagent({ serverId }); const handleOpenSubagent = useCallback( (subagentId: string) => { + const session = useSessionStore.getState().sessions[serverId]; + const agent = session?.agents.get(subagentId) ?? session?.agentDetails.get(subagentId); + if (agent?.workspaceId && agent.workspaceId !== workspaceId) { + navigateToAgent({ serverId, agentId: subagentId }); + return; + } + if (canSplit && workspaceKey) { + useWorkspaceLayoutStore.getState().openTabInExplorerPaneFocused(workspaceKey, { + target: { kind: "agent", agentId: subagentId }, + parentTabId: tabId, + }); + return; + } navigateToAgent({ serverId, agentId: subagentId }); }, - [serverId], + [canSplit, serverId, tabId, workspaceId, workspaceKey], ); const handleOpenProviderSubagent = useCallback( (parentAgentId: string, subagentId: string) => { + if (canSplit && workspaceKey) { + useWorkspaceLayoutStore.getState().openTabInExplorerPaneFocused(workspaceKey, { + target: { kind: "provider_subagent", parentAgentId, subagentId }, + parentTabId: tabId, + }); + return; + } openTab({ kind: "provider_subagent", parentAgentId, subagentId }); }, - [openTab], + [canSplit, openTab, tabId, workspaceKey], ); if (!hasAgentTracks({ subagentRows, tasks, archiveFinishedStatus })) { diff --git a/packages/app/src/stores/workspace-subagents-integration.test.ts b/packages/app/src/stores/workspace-subagents-integration.test.ts index 4dbaac712..580a8c6be 100644 --- a/packages/app/src/stores/workspace-subagents-integration.test.ts +++ b/packages/app/src/stores/workspace-subagents-integration.test.ts @@ -7,6 +7,7 @@ import { } from "@/workspace-tabs/agent-visibility"; import { buildWorkspaceTabPersistenceKey } from "@/workspace-tabs/model"; import { selectSubagentsForParent } from "@/subagents/select"; +import { findPaneById, findPaneContainingTab } from "./workspace-layout-actions"; import { useWorkspaceLayoutStore } from "./workspace-layout-store"; import { useSessionStore, type Agent } from "./session-store"; @@ -251,4 +252,63 @@ describe("workspace subagents integration", () => { ).map((row) => row.id), ).toEqual([child.id]); }); + + it("opens a subagent in the explorer pane when focused", () => { + const workspaceKey = buildWorkspaceTabPersistenceKey({ + serverId: SERVER_ID, + workspaceId: WORKSPACE_ID, + }); + expect(workspaceKey).toBeTruthy(); + + const parent = makeAgent({ + id: "parent-agent", + title: "Parent agent", + }); + const child = makeAgent({ + id: "child-agent", + parentAgentId: parent.id, + title: "Child agent", + }); + + initializeAgents([parent, child]); + reconcileWorkspaceTabs(workspaceKey!, deriveVisibilityFromSession()); + + const store = useWorkspaceLayoutStore.getState(); + const tabId = store.openTabInExplorerPaneFocused(workspaceKey!, { + target: { kind: "agent", agentId: child.id }, + parentTabId: `agent_${parent.id}`, + }); + + const state = useWorkspaceLayoutStore.getState(); + const layout = state.layoutByWorkspace[workspaceKey!]; + const explorerPaneId = state.explorerPaneIdByWorkspace[workspaceKey!]; + + expect(explorerPaneId).toBeTruthy(); + expect(findPaneContainingTab(layout.root, tabId!)?.id).toBe(explorerPaneId); + expect(findPaneById(layout.root, explorerPaneId!)?.hidden).toBeUndefined(); + expect(layout.focusedPaneId).toBe(explorerPaneId); + }); + + it("opens a provider subagent in the explorer pane when focused", () => { + const workspaceKey = buildWorkspaceTabPersistenceKey({ + serverId: SERVER_ID, + workspaceId: WORKSPACE_ID, + }); + expect(workspaceKey).toBeTruthy(); + + const store = useWorkspaceLayoutStore.getState(); + const tabId = store.openTabInExplorerPaneFocused(workspaceKey!, { + target: { kind: "provider_subagent", parentAgentId: "parent-agent", subagentId: "task-1" }, + parentTabId: "agent_parent-agent", + }); + + const state = useWorkspaceLayoutStore.getState(); + const layout = state.layoutByWorkspace[workspaceKey!]; + const explorerPaneId = state.explorerPaneIdByWorkspace[workspaceKey!]; + + expect(explorerPaneId).toBeTruthy(); + expect(findPaneContainingTab(layout.root, tabId!)?.id).toBe(explorerPaneId); + expect(findPaneById(layout.root, explorerPaneId!)?.hidden).toBeUndefined(); + expect(layout.focusedPaneId).toBe(explorerPaneId); + }); });