返回 DeepSeek-Reasonix
AppRuntimeView.tsx
根目录 / desktop / frontend / src / app-shell / AppRuntimeView.tsx
1 import { lazy, Suspense, useMemo, useState, useLayoutEffect, type CSSProperties } from "react";
2 import { DockNavigation } from "./dockNavigation";
3 import { useFileNavigationRuntime } from "../app-runtime/useFileNavigationRuntime";
4 import { useActiveRemoteRef } from "../app-runtime/useActiveRemoteRef";
5 import { fileNavigationKey } from "../lib/fileNavigationOwner";
6 import { useActivityBarStore } from "../store/activityBar";
7 import { ShellExpandProvider } from "../lib/shellExpand";
8 import { RemoteNavigationContext } from "../lib/remoteNavigationCommands";
9 import { UpdaterProvider } from "../lib/useUpdater";
10 import type { State } from "../lib/useController";
11 import type { TabMeta } from "../lib/types";
12 import type { RemoteSessionApi } from "../lib/useRemoteSession";
13 import type { Translator } from "../lib/i18n";
14 import type { useAppRuntimeAdapter } from "../app-runtime/useAppRuntimeAdapter";
15 import type { useNavigationSurface } from "../lib/useNavigationSurface";
16 import type { useAppShellStores } from "../app-runtime/useAppShellStores";
17 import type { useAppSessionComposition } from "../app-runtime/useAppSessionComposition";
18 import type { useAppNavigationComposition } from "../app-runtime/useAppNavigationComposition";
19 import type { useSessionDraftSurface } from "../app-runtime/useSessionDraftSurface";
20 import { creationHeroVisible } from "./draftPresentation";
21 import type { HistoryViewState } from "../app-runtime/historyViewProjection";
22 import { ShellHotkeys, TextSizeHotkeys } from "./HotkeyRegistrations";
23 import { WindowChromeLifecycle } from "../app-runtime/WindowChromeLifecycle";
24 import { StartupGateLifecycle } from "../app-runtime/StartupGateLifecycle";
25 import { AppRuntimeEffects } from "../app-runtime/AppRuntimeEffects";
26 import { ThemeBackground } from "../components/ThemeBackground";
27 import { useTopicbarHeightVar } from "../lib/useTopicbarHeightVar";
28 import { SidebarRegion } from "./SidebarRegion";
29 import { TopicbarRegion } from "./TopicbarRegion";
30 import { buildTopicbarView, TopicbarActionsStack } from "./TopicbarActionsStack";
31 import { DraftTopicbarActions } from "./DraftTopicbarActions";
32 import { DockToggleButton } from "./DockToggleButton";
33 import { LauncherToggleButton } from "./LauncherToggleButton";
34 import { SessionStatusBanners } from "./SessionStatusBanners";
35 import { ChatPaneRegion } from "./ChatPaneRegion";
36 import { DecisionFooterRegion } from "./DecisionFooterRegion";
37 import { WorkspaceDockRegion } from "./WorkspaceDockRegion";
38 import { AppBottomRegions } from "./AppBottomRegions";
39 import { AppOverlayHost } from "./AppOverlayHost";
40 import { buildAppShellClassNames, buildSessionStatusBannerProps, buildSidebarRegionProps } from "./chromeRegionBuilders";
41 import { buildBottomRegionsProps, buildWorkspaceDockProps } from "./dockRegionBuilders";
42 import { buildOverlayHostProps } from "./overlayBuilders";
43 import { buildComposerSurface, buildDecisionFooterSurface, buildFooterTodo, buildFooterUndo } from "./decisionFooterBuilders";
44
45 const WindowsWindowControls = lazy(() => import("./WindowsWindowControls").then((module) => ({ default: module.WindowsWindowControls })));
46 const DockLauncher = lazy(() => import("../components/DockLauncher").then((module) => ({ default: module.DockLauncher })));
47
48 const WORKSPACE_RESIZER_WIDTH = 8;
49 const SHOW_CONTEXT_DOCK = true;
50
51 type Runtime = ReturnType<typeof useAppRuntimeAdapter>;
52 type Shell = ReturnType<typeof useAppShellStores>;
53 type SessionComposition = ReturnType<typeof useAppSessionComposition>;
54 type NavigationComposition = ReturnType<typeof useAppNavigationComposition>;
55 type LiveStore = Runtime["snapshot"]["liveStore"];
56
57 export type AppRuntimeViewProps = {
58 core: {
59 state: State;
60 activeTab: TabMeta | undefined;
61 activeTabId: string | undefined;
62 liveStore: LiveStore;
63 remoteSurfaceActive: boolean;
64 remoteSession: RemoteSessionApi;
65 remoteComposerReady: boolean;
66 remoteCancel: (queuedItemIDs?: string[]) => Promise<import("../lib/inboxCancel").CancelOutcome>;
67 surface: ReturnType<typeof useNavigationSurface>;
68 t: Translator;
69 locale: string;
70 onOpenLink: (url: string) => void;
71 };
72 shell: Shell;
73 session: SessionComposition;
74 navigation: NavigationComposition;
75 runtime: Runtime;
76 draft: ReturnType<typeof useSessionDraftSurface>;
77 local: {
78 tasksOpen: false | "session" | "all";
79 setTasksOpen: React.Dispatch<React.SetStateAction<false | "session" | "all">>;
80 sidebarImDetailConnectionId: string;
81 setSidebarImDetailConnectionId: React.Dispatch<React.SetStateAction<string>>;
82 tabRevealSignal: number;
83 histView: HistoryViewState | null;
84 projectRevision: number;
85 dockRefreshKey: number;
86 composerFileRefRefreshKey: string;
87 refreshComposerFileRefs: () => void;
88 terminalContentVisible: boolean;
89 terminalFitEnabled: boolean;
90 prefetchTerminalPanel: () => void;
91 };
92 };
93
94 /**
95 * Pure assembly of the App shell tree: every region receives its props from
96 * the session/navigation composition bags and the caller's stores. No hooks
97 * beyond value memoization live here; ownership stays in the compositions.
98 */
99 export function AppRuntimeView(props: AppRuntimeViewProps) {
100 const [dockNavigation] = useState(() => new DockNavigation());
101 const fileNavigation = useFileNavigationRuntime();
102 useLayoutEffect(() => dockNavigation.attach(), [dockNavigation]);
103 useLayoutEffect(() => useActivityBarStore.subscribe(state => dockNavigation.reconcile(state.tabs.map(tab => tab.id))), [dockNavigation]);
104 // The open dock tabs are the set of live records: closing a tab, or switching
105 // to a project whose tab list has none of them, ends those records. A session
106 // change inside one project keeps the dock and only rebinds its credentials.
107 useLayoutEffect(() => {
108 const retain = () => fileNavigation.retain(
109 useActivityBarStore.getState().tabs.map(tab => fileNavigationKey({ sessionTabId: "", dockTabId: tab.id })),
110 );
111 retain();
112 return useActivityBarStore.subscribe(retain);
113 }, [fileNavigation]);
114 useTopicbarHeightVar();
115 const { core, shell, session, navigation, runtime, local, draft } = props;
116 const { state, activeTab, activeTabId, t, locale } = core;
117 const { windowsFramelessChrome, mainWindowMaximised } = shell;
118 const draftActive = Boolean(draft.surface);
119 const activeRemoteRef = useActiveRemoteRef(draftActive ? undefined : activeTab);
120 const {
121 conversationView, visibleRuntimeState, sidebarImDetailConnection,
122 surfaceWorkspacePanelRenderable, surfaceWorkspacePanelGridOpen, surfaceWorkspacePanelOverlay, terminalSurfaceOpen,
123 controllerReady, decisionSurface, visibleDecisionSurface, composerSurfaceHidden,
124 shellGeometry, appRef, layoutRef, footerHeight, footerRef,
125 } = session;
126 const { chromeCommands, navigationCommands } = navigation;
127 const runtimeTransitioning = core.surface.transitioning;
128 const presentationTransitioning = runtimeTransitioning && core.remoteSurfaceActive;
129 const browserPreviewChrome = navigation.browserPreviewChrome;
130 const workspaceContextProject = Boolean(
131 activeTab?.remote || (activeTab?.scope === "project" && activeTab.workspaceRoot),
132 );
133 const workspaceContextRoot = workspaceContextProject
134 ? (activeTab?.workspaceRoot ?? state.meta?.workspaceRoot ?? state.meta?.cwd ?? "")
135 : "";
136
137 const workbenchChromeHidden = true;
138 const sidebarClassName = [
139 "sidebar",
140 shell.sidebarCollapsed ? "sidebar--collapsed" : "",
141 "sidebar--workbench",
142 ].filter(Boolean).join(" ");
143 const startupSplashHold = !activeTabId && state.meta?.ready !== true && !state.meta?.startupErr;
144
145 const layoutStyle = useMemo(
146 () =>
147 ({
148 "--sidebar-expanded-width": `${shellGeometry.sidebarRenderWidth}px`,
149 "--chat-min-width": `${shellGeometry.chatReservedWidth}px`,
150 "--workspace-width": `${shellGeometry.workspacePanelRenderWidth}px`,
151 "--workspace-resizer-width": `${WORKSPACE_RESIZER_WIDTH}px`,
152 "--terminal-height": `${!draftActive && terminalSurfaceOpen ? shell.liveTerminalHeight ?? shellGeometry.terminalRenderHeight : 0}px`,
153 }) as CSSProperties,
154 [draftActive, shellGeometry.chatReservedWidth, shell.liveTerminalHeight, shellGeometry.sidebarRenderWidth, shellGeometry.terminalRenderHeight, terminalSurfaceOpen, shellGeometry.workspacePanelRenderWidth],
155 );
156
157 const shellClassNames = buildAppShellClassNames({
158 platform: shell.desktopPlatform,
159 windowsFrameless: windowsFramelessChrome,
160 browserPreview: browserPreviewChrome,
161 imDetailActive: Boolean(sidebarImDetailConnection),
162 sidebarCollapsed: shell.sidebarCollapsed,
163 sidebarResizing: shell.sidebarResizing,
164 dockGridOpen: !draftActive && surfaceWorkspacePanelGridOpen,
165 dockOverlay: !draftActive && surfaceWorkspacePanelOverlay,
166 terminalOpen: !draftActive && terminalSurfaceOpen,
167 terminalResizing: shell.terminalResizing,
168 dockOpen: !draftActive && shell.workspacePanelOpen,
169 dockMaximized: shell.workspacePanelMaximized,
170 dockResizing: shell.workspacePanelResizing,
171 });
172 const footerTodo = buildFooterTodo({
173 show: session.todoPanel.showTodos,
174 identity: session.todoPanel.scopedTodoBatch,
175 todos: session.todoPanel.todos,
176 running: visibleRuntimeState.running,
177 pendingPrompt: visibleRuntimeState.pendingPrompt,
178 continueReady: Boolean(activeTabId && !activeTab?.readOnly && (core.remoteSurfaceActive ? core.remoteComposerReady : controllerReady)),
179 onContinue: session.todoPanel.handleTodoContinue,
180 onDismiss: session.todoPanel.dismissTodos,
181 });
182 const footerUndo = buildFooterUndo({ rewindState: session.sessionUndo.rewindState, activeTabId, onUndo: session.sessionUndo.handleUndoRewind });
183 const decisionFooterSurface = buildDecisionFooterSurface({
184 view: {
185 surface: visibleDecisionSurface,
186 activeTabId,
187 cwd: state.meta?.cwd,
188 workspaceScopeKey: session.workspaceScopeKey,
189 approval: state.approval,
190 ask: state.ask,
191 mcpInteraction: state.mcpInteraction,
192 extensionForm: state.extensionForm,
193 workspaceConflict: session.workspaceConflict,
194 toolApprovalMode: session.profileProjection.toolApprovalMode,
195 insertRequest: session.insertCommands.activePlanRevisionInsertRequest,
196 },
197 prompts: session.promptCommands,
198 extension: session.extensionSurface,
199 tabs: session.tabBarCommands,
200 clear: session.clearCommands,
201 onStop: session.controlCommands.handleStopActive,
202 cancelWorkspaceConflict: session.controlCommands.cancelWorkspaceConflict,
203 onOpenLink: core.onOpenLink,
204 onRevisionActiveChange: session.insertCommands.handleRevisionActiveChange,
205 t,
206 });
207
208 return (
209 <ShellExpandProvider>
210 <RemoteNavigationContext.Provider value={session.desktopNavigation.openRemoteProject}>
211 <UpdaterProvider>
212 <ShellHotkeys />
213 <TextSizeHotkeys />
214 <WindowChromeLifecycle />
215 <StartupGateLifecycle />
216 <AppRuntimeEffects
217 running={state.running}
218 onEvent={session.runtimeEventCommands.handleRuntimeEvent}
219 onReady={session.runtimeEventCommands.handleRuntimeReady}
220 onRebuilt={session.runtimeEventCommands.handleRuntimeRebuilt}
221 onRemoteStatus={session.runtimeEventCommands.handleRemoteStatus}
222 onRemoteForwards={session.runtimeEventCommands.handleRemoteForwards}
223 onRemoteServer={session.runtimeEventCommands.handleRemoteServer}
224 onInitialRemoteHosts={session.runtimeEventCommands.handleInitialRemoteHosts}
225 onInitialRemoteStatuses={session.runtimeEventCommands.handleInitialRemoteStatuses}
226 />
227 <div
228 ref={appRef}
229 onDoubleClickCapture={chromeCommands.handleChromeTitlebarDoubleClick}
230 className={shellClassNames.app}
231 >
232 <ThemeBackground />
233 <div
234 ref={layoutRef}
235 className={shellClassNames.layout}
236 style={layoutStyle}
237 >
238 <a className="skip-to-composer" href="#composer-input">
239 {t("shortcuts.skipToComposer")}
240 </a>
241
242 <SidebarRegion {...buildSidebarRegionProps({
243 className: sidebarClassName,
244 shell,
245 t,
246 geometry: shellGeometry,
247 projectTree: {
248 activeTab: draftActive ? undefined : activeTab,
249 activeRemote: activeRemoteRef,
250 activeScope: draft.surface?.draft.scope,
251 activeWorkspaceRoot: draft.surface?.draft.workspaceRoot,
252 imTopicSources: shell.preferences.imTopicSources, refreshSignal: local.projectRevision,
253 searchExpanded: true, searchFocusSignal: shell.sidebarSearchFocusSignal,
254 showShortcutBadges: navigation.topicShortcuts.showTopicBadges, shortcutPlatform: shell.desktopPlatform,
255 onVisibleTopicsChange: navigation.topicShortcuts.handleVisibleTopicsChange,
256 draftSummaries: draft.summaries,
257 onOpenDraft: draft.open,
258 },
259 topics: navigation.projectTopicCommands,
260 paletteShortcut: navigation.commandPaletteShortcut,
261 commands: {
262 onNewSession: () => void navigationCommands.handleNewTab(),
263 onOpenPalette: () => void navigation.paletteCommands.openPalette(),
264 onOpenTrash: () => void navigation.historyCommands.openTrash(),
265 onOpenAutomation: () => shell.openPage({ kind: "automation" }),
266 onOpenSettings: chromeCommands.openSidebarSettings,
267 onOpenTopic: navigationCommands.handleOpenTopic,
268 },
269 })} />
270
271 <TopicbarRegion view={buildTopicbarView({
272 t, locale, activeTab: draftActive ? undefined : activeTab, cwd: draft.surface?.draft.workspaceRoot ?? state.meta?.cwd,
273 imDetail: draftActive ? null : sidebarImDetailConnection, imTopicSources: shell.preferences.imTopicSources,
274 chromeHidden: workbenchChromeHidden, windowsBrand: windowsFramelessChrome,
275 automationReturn: shell.automationReturn,
276 sidebar: { title: navigation.sidebarToggleTitle, blocked: navigation.sidebarExpandBlocked, pressed: shell.sidebarTogglePressed, collapsed: shell.sidebarCollapsed },
277 rename: { editing: !draftActive && navigation.projectTopicCommands.topicbarEditing, draft: navigation.projectTopicCommands.topicTitleDraft },
278 draft: draft.surface ? {
279 title: t("draft.badge"),
280 workspaceLabel: draft.surface.draft.scope === "project"
281 ? draft.surface.draft.workspaceRoot.replace(/[\\/]+$/, "").split(/[\\/]/).pop() || draft.surface.draft.workspaceRoot
282 : t("draft.globalWorkspace"),
283 } : undefined,
284 })} commands={{
285 openAutomation: () => shell.openPage({ kind: "automation" }), toggleSidebar: shellGeometry.toggleSidebar,
286 setTitleDraft: navigation.projectTopicCommands.setTopicTitleDraft, commitRename: navigation.projectTopicCommands.commitActiveTopicRename, cancelRename: navigation.projectTopicCommands.cancelActiveTopicRename,
287 startRename: navigation.projectTopicCommands.startActiveTopicRename, openWorktree: navigation.worktreeMergeCommands.openWorktreeMerge,
288 }}>
289 {draft.surface ? <DraftTopicbarActions
290 t={t}
291 draft={draft.surface}
292 onSetMCPEnabled={draft.setMCPEnabled}
293 onDiscard={() => void draft.confirmDiscard({
294 title: t("draft.discardTitle"),
295 message: t("draft.discardTitle"),
296 detail: t("draft.discardDetail"),
297 confirmLabel: t("draft.discardConfirm"),
298 cancelLabel: t("common.cancel"),
299 })}
300 /> : <TopicbarActionsStack
301 t={t}
302 activeTab={activeTab}
303 activeTabId={activeTabId}
304 imDetailActive={Boolean(sidebarImDetailConnection)}
305 dismissSignal={shell.transientOverlayDismissSignal}
306 sessionHasContent={session.sessionHasContent}
307 exportCommands={session.sessionExportCommands}
308 terminal={{ toggle: session.terminalPanelCommands.toggleTerminalPanel, enabled: !core.remoteSurfaceActive, open: shell.terminalPanelOpen && !core.remoteSurfaceActive, prefetch: local.prefetchTerminalPanel }}
309 tasksOpen={local.tasksOpen}
310 setTasksOpen={local.setTasksOpen}
311 onCloseTasks={() => local.setTasksOpen(false)}
312 onOpenTaskSession={navigationCommands.openTaskMonitorSession}
313 dockToggle={<DockToggleButton renderable={surfaceWorkspacePanelRenderable} t={t} onToggle={session.workspacePanelCommands.toggleWorkspacePanel} />}
314 launcherToggle={<LauncherToggleButton
315 visible={session.workspacePanelCommands.launcherCard.visible}
316 t={t}
317 onToggle={session.workspacePanelCommands.toggleLauncherCard}
318 />}
319 />}
320 </TopicbarRegion>
321
322 <section className={`chat-pane${creationHeroVisible(draft.surface, session.transcript.emptyHero) ? " chat-pane--creation-empty" : ""}`}>
323 {!draft.surface && <SessionStatusBanners {...buildSessionStatusBannerProps({
324 t,
325 activeTab,
326 leaseBlocked: session.leaseBlockedTab ? { tabId: session.leaseBlockedTab.id, message: session.leaseBlockedTab.runtime!.issue!.message } : null,
327 meta: state.meta,
328 configWarnings: shell.preferences.configLoadWarnings,
329 dismissConfigWarnings: shell.preferences.dismissConfigWarnings,
330 updateChecksEnabled: shell.preferences.startupUpdateChecksEnabled === true,
331 shell,
332 banners: session.bannerCommands,
333 onboarding: navigation.onboardingCommands,
334 })} historical={core.remoteSurfaceActive ? undefined : { tab: activeTab, navigate: session.desktopNavigation.enqueueNavigation,
335 captureNavigation: () => { const intent = runtime.navigation.currentNavigationIntent(); return () => runtime.navigation.isNavigationIntentCurrent(intent); },
336 }} />}
337
338 <ChatPaneRegion
339 // Local navigation is now history-first: keep the transcript
340 // mounted while the controller is rebuilt in the background. The
341 // transcript's own hydrating/readiness state still reports progress
342 // and the controllerReady fence keeps writes disabled. Remote
343 // surfaces retain their existing transition gate because they do
344 // not share the local runtime/cache ownership model.
345 transitioning={presentationTransitioning}
346 t={t}
347 imDetail={sidebarImDetailConnection ? {
348 connection: sidebarImDetailConnection,
349 onClose: () => local.setSidebarImDetailConnectionId(""),
350 onOpenSettings: chromeCommands.openBotSettings,
351 onManageAllowlist: chromeCommands.openBotAllowlistSettings,
352 onOpenSession: (connection) => void navigationCommands.openSidebarImConnectionSession(connection),
353 } : null}
354 remote={activeTab?.remote ? { tab: activeTab, session: core.remoteSession } : undefined}
355 draft={draft.surface ? {
356 surface: draft.surface,
357 onUseSaved: draft.useSavedConflict,
358 onKeepLocal: () => void draft.keepLocalConflict(),
359 onRetrySave: () => void draft.retrySave(),
360 onDismissTaskError: () => draft.reportTaskError(draft.surface!.draft.id, draft.surface!.generation, ""),
361 onResume: () => void draft.resumeSubmission(),
362 onOpenSession: () => void draft.openAcceptedSession(),
363 onCheckSubmission: () => void draft.refreshSubmission(),
364 } : undefined}
365 launcher={!draftActive && session.workspacePanelCommands.launcherCardMounted && !core.remoteSurfaceActive ? (
366 <Suspense fallback={null}>
367 <DockLauncher
368 tabId={activeTabId ?? ""}
369 scopeKey={session.workspaceScopeKey}
370 workspaceRoot={activeTab?.workspaceRoot ?? state.meta?.cwd ?? ""}
371 visible={!shell.managementActive && !sidebarImDetailConnection}
372 onSelect={session.workspacePanelCommands.openDockEntry}
373 gitBranch={state.meta?.gitBranch}
374 onSpaceModeChange={session.workspacePanelCommands.setLauncherSpaceMode}
375 overlay={session.workspacePanelCommands.launcherCardOverlay}
376 />
377 </Suspense>
378 ) : null}
379 transcript={{
380 state,
381 items: session.transcript.visibleTranscriptItems,
382 tabId: session.transcript.visibleTranscriptTabId,
383 geometrySessionKey: session.transcript.visibleTranscriptGeometryKey,
384 footerHeight,
385 invocationMetadata: session.transcript.visibleTranscriptTabId ? session.invocation.invocationMetadataByTab[session.transcript.visibleTranscriptTabId] : undefined,
386 surfaceCommitToken: core.surface.surfaceCommitToken,
387 liveStore: core.liveStore,
388 transcriptHydrating: session.transcript.transcriptHydrating,
389 navigationDataReady: core.surface.dataReady,
390 readOnly: Boolean(activeTab?.readOnly),
391 controllerReady,
392 hydratePlaceholderActive: session.hydratePlaceholderActive,
393 clearContextPending: session.clearCommands.clearContextPending,
394 emptyHero: session.transcript.emptyHero,
395 availability: session.transcript.availability,
396 rewind: { stateActive: session.sessionUndo.rewindState != null, committing: session.sessionUndo.rewindCommitting },
397 }}
398 onRetryHistory={() => runtime.sessionActions.retrySessionHistory(activeTabId)}
399 commands={{
400 onPrompt: session.transcript.handleTranscriptPrompt,
401 onFork: (turnId) => session.sessionUndo.handleForkTurn(turnId),
402 onLoadOlderHistory: session.transcript.handleLoadOlderHistory,
403 onLoadNewerHistory: session.transcript.handleLoadNewerHistory,
404 onSurfacePaintReady: session.transcript.handleSurfacePaintReady,
405 }}
406 />
407 <DecisionFooterRegion
408 hidden={Boolean(sidebarImDetailConnection)}
409 className={["footer", terminalSurfaceOpen ? "footer--compact" : "", visibleDecisionSurface ? "footer--decision" : "", presentationTransitioning ? "footer--navigation-hidden" : ""].filter(Boolean).join(" ")}
410 footerRef={footerRef}
411 style={core.surface.surface?.phase === "source-retained" && footerHeight > 0 ? { height: footerHeight, minHeight: footerHeight, boxSizing: "border-box" } : undefined}
412 todo={draft.surface ? undefined : footerTodo}
413 undo={draft.surface ? undefined : footerUndo}
414 decision={draft.surface ? undefined : decisionFooterSurface}
415 composer={buildComposerSurface({
416 view: {
417 hidden: composerSurfaceHidden,
418 inert: runtimeTransitioning,
419 hero: session.transcript.emptyHero,
420 headline: t("welcome.creation.title"),
421 remote: core.remoteSurfaceActive,
422 rewindCommitting: session.sessionUndo.rewindCommitting,
423 messageActionPending: state.messageAction != null,
424 decisionActive: Boolean(decisionSurface),
425 runtimeTransitioning: presentationTransitioning,
426 controllerReady: controllerReady && session.transcript.availability.kind === "ready",
427 submitDisabledReason: session.transcript.availability.kind !== "ready" && session.transcript.availability.source !== "runtime"
428 ? t("sessionRecovery.sendAfterRecovery") : undefined,
429 showContextWindowRing: false,
430 draftHint: t("draft.createOnSend"),
431 },
432 base: conversationView.composer,
433 tab: activeTab,
434 tabId: activeTabId,
435 profile: session.profileProjection,
436 router: session.routerCommands,
437 modes: session.modeActions,
438 goals: session.goalCommands,
439 remoteGoal: session.remoteGoalActions,
440 modelSwitch: session.controllerProfileCommands,
441 inserts: session.insertCommands,
442 control: session.controlCommands,
443 remoteComposer: {
444 send: session.remoteComposerSend,
445 cancel: core.remoteCancel,
446 ready: core.remoteComposerReady,
447 profileReady: session.profileProjection.remoteComposerProfileReady,
448 liveStore: core.remoteSession.liveStore,
449 },
450 localLiveStore: core.liveStore,
451 onInvocationMetadataChange: session.invocation.handleInvocationMetadataChange,
452 onCycleMode: session.cycleMode,
453 transientDismissSignal: shell.transientOverlayDismissSignal,
454 sessionKey: session.composerSessionKey,
455 workspaceScopeKey: session.workspaceScopeKey,
456 workspaceContext: {
457 scope: workspaceContextProject ? "project" : "global",
458 workspaceRoot: workspaceContextRoot,
459 workspaceName: workspaceContextProject
460 ? activeTab?.workspaceName ?? state.meta?.workspaceName
461 : undefined,
462 gitBranch: workspaceContextProject && !activeTab?.remote ? state.meta?.gitBranch : undefined,
463 tabId: activeTabId,
464 scopeKey: session.workspaceScopeKey,
465 remote: Boolean(activeTab?.remote),
466 onSwitchWorkspace: navigation.projectTopicCommands.onAddProject,
467 onWorkWithoutProject: () => navigationCommands.openBlankSession("global", ""),
468 onRefreshProjects: navigation.projectTopicCommands.refreshProjectsAndTabs,
469 },
470 fileRefRefreshKey: local.composerFileRefRefreshKey,
471 guidance: session.transcript.latestGuidanceConsumed,
472 guidanceQueuePreviewItems: navigation.guidanceQueueMockItems,
473 draft,
474 })}
475 />
476 </section>
477
478 <WorkspaceDockRegion navigation={dockNavigation} fileNavigation={fileNavigation} workspaceRoot={activeTab?.workspaceRoot ?? state.meta?.cwd ?? ""} {...buildWorkspaceDockProps({
479 surface: { renderable: !draftActive && surfaceWorkspacePanelRenderable, overlay: !draftActive && surfaceWorkspacePanelOverlay, gridOpen: !draftActive && surfaceWorkspacePanelGridOpen },
480 showContext: SHOW_CONTEXT_DOCK,
481 remote: core.remoteSurfaceActive,
482 t,
483 context: conversationView.context,
484 sessionTurns: session.sessionTurns,
485 contextRefreshKey: local.dockRefreshKey + visibleRuntimeState.contextPanelSeq,
486 workspaceKey: session.workspaceTreeMemoryKey,
487 workspaceScopeKey: session.workspaceScopeKey,
488 mode: shell.rightDockMode,
489 meta: state.meta,
490 tabId: activeTabId,
491 completionSummary: state.completionSummary,
492 turnStartAt: state.turnStartAt,
493 layout: { treeWidth: shell.rightDockTreeWidth, previewWidth: shell.rightDockPreviewWidth, maximized: shell.workspacePanelMaximized },
494 geometry: shellGeometry,
495 panels: session.workspacePanelCommands,
496 inserts: session.insertCommands,
497 verification: session.turnVerificationCommands,
498 onFileTreeRefresh: local.refreshComposerFileRefs,
499 onSessionRevertCommitted: session.sessionUndo.handleSessionRevertCommitted,
500 onOpenInTerminal: core.remoteSurfaceActive ? undefined : session.terminalPanelCommands.openTerminalForPath,
501 })} />
502 <AppBottomRegions {...buildBottomRegionsProps({
503 t,
504 chatSurfaceVisible: session.chatSurfaceVisible,
505 surfaceOpen: !draftActive && terminalSurfaceOpen,
506 contentVisible: local.terminalContentVisible,
507 remote: core.remoteSurfaceActive,
508 readOnly: Boolean(activeTab?.readOnly && !activeTab.takenOver),
509 tabId: activeTabId,
510 meta: state.meta,
511 fitEnabled: local.terminalFitEnabled,
512 liveTerminalHeight: shell.liveTerminalHeight,
513 geometry: shellGeometry,
514 terminal: {
515 onClose: session.terminalPanelCommands.closeTerminalPanel,
516 onAddOutput: (sessionId) => void session.insertCommands.addTerminalOutputToComposer(sessionId),
517 onAddToChat: session.insertCommands.addTerminalSelectionToComposer,
518 },
519 status: draftActive || !session.statusBarVisible ? undefined : {
520 base: conversationView.status,
521 rewindCommitting: session.sessionUndo.rewindCommitting,
522 sessionTurns: session.sessionTurns,
523 labelStyle: shell.preferences.statusBarStyle,
524 items: shell.preferences.statusBarItems,
525 extensionStatuses: session.extensionStatusList,
526 remoteHosts: shell.remoteHosts,
527 remoteStatuses: shell.remoteStatuses,
528 onCancelJob: core.remoteSurfaceActive ? core.remoteSession.cancelJob : runtime.composer.cancelJob,
529 onCancelRuntimeJob: session.controlCommands.cancelRuntimeJob,
530 onRevealRuntime: session.tabBarCommands.revealBackgroundRuntime,
531 onConnectRemote: session.remoteWorkspaceCommands.connectAndOpenRemoteWorkspace,
532 onDisconnectRemote: session.controlCommands.handleDisconnectRemote,
533 onManageRemote: () => shell.setSettingsTarget("remote"),
534 onOpenRemote: shell.requestRemoteExplorer,
535 onOpenRemoteWorkspace: session.remoteWorkspaceCommands.openRemoteWorkspaceFromStatus,
536 },
537 })} />
538 </div>
539
540 <AppOverlayHost {...buildOverlayHostProps({
541 t,
542 running: state.running,
543 histView: local.histView,
544 pageKind: shell.page.kind,
545 activeTab,
546 activeTabId,
547 cwd: state.meta?.cwd,
548 paletteItems: navigation.paletteCommands.paletteItems,
549 startupSplashHold,
550 automationTopic: session.automation.openAutomationTopic,
551 shell,
552 history: navigation.historyCommands,
553 navigation: navigationCommands,
554 chrome: chromeCommands,
555 onboarding: navigation.onboardingCommands,
556 worktree: navigation.worktreeMergeCommands,
557 prefillSubagentCommand: session.insertCommands.prefillSubagentCommand,
558 sessionActions: {
559 previewSession: runtime.sessionActions.previewSession,
560 listTrashedSessions: runtime.sessionActions.listTrashedSessions,
561 restoreSession: runtime.sessionActions.restoreSession,
562 purgeTrashedSession: runtime.sessionActions.purgeTrashedSession,
563 },
564 setSettingsTarget: shell.setSettingsTarget,
565 })} />
566 {windowsFramelessChrome && (
567 <WindowsWindowControls
568 maximised={mainWindowMaximised}
569 onMinimize={chromeCommands.minimizeMainWindow}
570 onToggleMaximize={chromeCommands.toggleMainWindowMaximized}
571 onClose={chromeCommands.closeMainWindow}
572 />
573 )}
574 </div>
575 </UpdaterProvider>
576 </RemoteNavigationContext.Provider>
577 </ShellExpandProvider>
578 );
579 }
580
580 lines Plain Text