From 556bd0d36fc7ac7559b9299e8549c1b1385b6e4e Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 19 Aug 2026 10:56:44 +0200 Subject: [PATCH] feat(app): open composer subagent tabs in the explorer pane Tapping a subagent or provider subagent from the composer track now opens its tab in the explorer companion pane when desktop split panes are active, keeping the parent agent visible in the primary pane. Mobile and compact layouts continue using single-pane tab navigation, and cross-workspace subagents continue routing to their own workspace. --- packages/app/src/panels/agent-tracks.tsx | 32 +++++++++- .../workspace-subagents-integration.test.ts | 60 +++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) 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); + }); });