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.
This commit is contained in:
Mohamed Boudra
2026-08-19 11:54:55 +02:00
parent 63be2e4340
commit 556bd0d36f
2 changed files with 89 additions and 3 deletions
+29 -3
View File
@@ -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 })) {
@@ -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);
});
});