diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index 08091f5ab..e24b26c3f 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -86,6 +86,7 @@ export interface AgentStreamViewProps { pendingPermissions: Map; routeBottomAnchorRequest?: BottomAnchorRouteRequest | null; isAuthoritativeHistoryReady?: boolean; + onOpenWorkspaceFile?: (input: { filePath: string }) => void; } export const AgentStreamView = forwardRef(function AgentStreamView({ @@ -96,6 +97,7 @@ export const AgentStreamView = forwardRef(null); const { theme } = useUnistyles(); @@ -159,6 +161,11 @@ export const AgentStreamView = forwardRef void; }) { const resolvedAgentId = agentId?.trim() || undefined; const resolvedServerId = serverId?.trim() || undefined; @@ -129,6 +131,7 @@ export function AgentReadyScreen({ isConnected={runtimeIsConnected} connectionStatus={connectionStatus} showExplorerSidebar={showExplorerSidebar} + onOpenWorkspaceFile={onOpenWorkspaceFile} /> ); @@ -146,6 +149,7 @@ type AgentScreenContentProps = { isConnected: boolean; connectionStatus: HostRuntimeConnectionStatus; showExplorerSidebar: boolean; + onOpenWorkspaceFile?: (input: { filePath: string }) => void; }; function toErrorMessage(error: unknown): string { @@ -166,6 +170,7 @@ function AgentScreenContent({ isConnected, connectionStatus, showExplorerSidebar, + onOpenWorkspaceFile, }: AgentScreenContentProps) { const { theme } = useUnistyles(); const toast = useToast(); @@ -904,6 +909,7 @@ function AgentScreenContent({ pendingPermissions={pendingPermissions} routeBottomAnchorRequest={routeBottomAnchorRequest} isAuthoritativeHistoryReady={hasAppliedAuthoritativeHistory} + onOpenWorkspaceFile={onOpenWorkspaceFile} /> diff --git a/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx b/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx index 9b2ff3262..1e84f5b6e 100644 --- a/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx +++ b/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx @@ -29,6 +29,7 @@ type WorkspaceDraftAgentTabProps = { tabId: string draftId: string onCreated: (snapshot: AgentSnapshotPayload) => void + onOpenWorkspaceFile: (input: { filePath: string }) => void } export function WorkspaceDraftAgentTab({ @@ -37,6 +38,7 @@ export function WorkspaceDraftAgentTab({ tabId, draftId, onCreated, + onOpenWorkspaceFile, }: WorkspaceDraftAgentTabProps) { const { client, isConnected } = useHostRuntimeSession(serverId) const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null) @@ -204,6 +206,7 @@ export function WorkspaceDraftAgentTab({ agent={draftAgent} streamItems={optimisticStreamItems} pendingPermissions={EMPTY_PENDING_PERMISSIONS} + onOpenWorkspaceFile={onOpenWorkspaceFile} /> ) : ( diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index 68ab81911..fc35a918b 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -756,6 +756,17 @@ function WorkspaceScreenContent({ [closeToAgent, isMobile, navigateToTabId, normalizedServerId, normalizedWorkspaceId, openOrFocusTab] ); + const handleOpenFileFromChat = useCallback( + ({ filePath }: { filePath: string }) => { + const normalizedFilePath = filePath.trim(); + if (!normalizedFilePath) { + return; + } + handleOpenFileFromExplorer(normalizedFilePath); + }, + [handleOpenFileFromExplorer] + ); + const [isTabSwitcherOpen, setIsTabSwitcherOpen] = useState(false); const [hoveredTabKey, setHoveredTabKey] = useState(null); const [hoveredCloseTabKey, setHoveredCloseTabKey] = useState( @@ -1258,6 +1269,7 @@ function WorkspaceScreenContent({ workspaceId={normalizedWorkspaceId} tabId={activeTabId ?? target.draftId} draftId={target.draftId} + onOpenWorkspaceFile={handleOpenFileFromChat} onCreated={(agentSnapshot) => { const tabId = activeTabId ?? target.draftId; const normalized = normalizeAgentSnapshot(agentSnapshot, normalizedServerId); @@ -1292,6 +1304,7 @@ function WorkspaceScreenContent({ agentId={target.agentId} showExplorerSidebar={false} wrapWithExplorerSidebarProvider={false} + onOpenWorkspaceFile={handleOpenFileFromChat} /> ); } diff --git a/packages/app/src/stores/workspace-tabs-store.test.ts b/packages/app/src/stores/workspace-tabs-store.test.ts index 559dc9483..ad34b3529 100644 --- a/packages/app/src/stores/workspace-tabs-store.test.ts +++ b/packages/app/src/stores/workspace-tabs-store.test.ts @@ -136,4 +136,38 @@ describe("workspace-tabs-store retargetTab", () => { expect(matchingTabs).toHaveLength(1); expect(order).toEqual([draftTabId]); }); + + it("openOrFocusTab re-focuses an existing file tab after the workspace focus changed", () => { + const key = buildWorkspaceTabPersistenceKey({ serverId: SERVER_ID, workspaceId: WORKSPACE_ID }); + expect(key).toBeTruthy(); + const workspaceKey = key as string; + + const fileTabId = useWorkspaceTabsStore.getState().openOrFocusTab({ + serverId: SERVER_ID, + workspaceId: WORKSPACE_ID, + target: { kind: "file", path: "/repo/worktree/src/index.ts" }, + }); + const terminalTabId = useWorkspaceTabsStore.getState().openOrFocusTab({ + serverId: SERVER_ID, + workspaceId: WORKSPACE_ID, + target: { kind: "terminal", terminalId: "term-1" }, + }); + + expect(fileTabId).toBe("file_/repo/worktree/src/index.ts"); + expect(terminalTabId).toBe("terminal_term-1"); + expect(useWorkspaceTabsStore.getState().focusedTabIdByWorkspace[workspaceKey]).toBe( + terminalTabId + ); + + const reopenedFileTabId = useWorkspaceTabsStore.getState().openOrFocusTab({ + serverId: SERVER_ID, + workspaceId: WORKSPACE_ID, + target: { kind: "file", path: "/repo/worktree/src/index.ts" }, + }); + + expect(reopenedFileTabId).toBe(fileTabId); + expect(useWorkspaceTabsStore.getState().focusedTabIdByWorkspace[workspaceKey]).toBe( + fileTabId + ); + }); });