| 1 | import { useEffect, useMemo, useRef, useState, type CSSProperties } from "react"; |
| 2 | import { useCommittedCommand } from "../lib/useCommittedCommand"; |
| 3 | import { projectSessionAvailability } from "../lib/sessionAvailability"; |
| 4 | import type { RemoteSessionApi } from "../lib/useRemoteSession"; |
| 5 | import { activeLeaseBlockedTab } from "../lib/tabMetaRefresh"; |
| 6 | import { topicTitle } from "../lib/sessionTitles"; |
| 7 | import { composerDraftKeyForTab } from "../lib/composerDraftKey"; |
| 8 | import { useWindowStatePersistence, useViewportHeightVar } from "../lib/windowState"; |
| 9 | import { useManagementWorkspace } from "../lib/useManagementWorkspace"; |
| 10 | import { reportPendingRevisionFailure, usePendingPlanRevisions } from "../lib/usePendingPlanRevisions"; |
| 11 | import { useComposerModeActions } from "../lib/useComposerModeActions"; |
| 12 | import { useRemoteComposerRuntimeActions, useRemoteComposerSend } from "../lib/useRemoteComposerIntegration"; |
| 13 | import type { RemoteNavigationCommand } from "../lib/remoteNavigationCommands"; |
| 14 | import type { CollaborationMode, TabMeta } from "../lib/types"; |
| 15 | import type { ComposerProfile, UserPlanModeIntents } from "../lib/composerProfile"; |
| 16 | import type { State } from "../lib/useController"; |
| 17 | import type { Translator } from "../lib/i18n"; |
| 18 | import type { useAppRuntimeAdapter } from "./useAppRuntimeAdapter"; |
| 19 | import type { useNavigationSurface } from "../lib/useNavigationSurface"; |
| 20 | import { desktopBridge } from "./desktopBridgeAdapter"; |
| 21 | import { useSessionOperations } from "./useSessionOperations"; |
| 22 | import { useComposerInsertCommands } from "./useComposerInsertCommands"; |
| 23 | import { useSessionClearCommands } from "./useSessionClearCommands"; |
| 24 | import { useRuntimeStatus } from "./useRuntimeStatus"; |
| 25 | import { useAppDiagnostics, useRecoverableErrorToasts, useSidebarConnectionValidity } from "./useAppEffectHosts"; |
| 26 | import { useActiveTabUiReset, useDecisionSurfaceFocus } from "./useLocalUiLifecycles"; |
| 27 | import { useActiveTabMirrorCommit } from "./activeTabMirror"; |
| 28 | import { useInvocationMetadata } from "./useInvocationMetadata"; |
| 29 | import { useFooterHeightLifecycle } from "./useFooterHeightLifecycle"; |
| 30 | import { useNativeSettingsEvent } from "./useNativeSettingsEvent"; |
| 31 | import { useWindowsMaximisedSync } from "./useNativeWindowController"; |
| 32 | import { useShellGeometry } from "./useShellGeometry"; |
| 33 | import { useTopicSummary } from "./useTopicSummary"; |
| 34 | import { useComposerProfileProjection } from "./useComposerProfileProjection"; |
| 35 | import { useTabBarCommands } from "./useTabBarCommands"; |
| 36 | import { useExtensionSurface } from "./useExtensionSurface"; |
| 37 | import { useTabProjectionLifecycle } from "./useTabProjectionLifecycle"; |
| 38 | import { useSessionUndo } from "./useSessionUndo"; |
| 39 | import { useSessionSubmission } from "../lib/useSessionSubmission"; |
| 40 | import { useControllerProfileCommands } from "../lib/useControllerProfileCommands"; |
| 41 | import { noteNavigationComposerEnabled } from "../lib/sessionDiagnostics"; |
| 42 | import { useSessionPromptCommands } from "./useSessionPromptCommands"; |
| 43 | import { useSessionControlCommands } from "./useSessionControlCommands"; |
| 44 | import { useTodoPanelCommands } from "./useTodoPanelCommands"; |
| 45 | import { useSessionExportCommands } from "./useSessionExportCommands"; |
| 46 | import { useComposerRouter } from "./useComposerRouter"; |
| 47 | import { useComposerGoalCommands } from "./useComposerGoalCommands"; |
| 48 | import { useRuntimeEventHandlers } from "./useRuntimeEventHandlers"; |
| 49 | import { probeProviderSetupState } from "./StartupGateLifecycle"; |
| 50 | import { useSessionBannerCommands } from "./useSessionBannerCommands"; |
| 51 | import { useWorkspacePanelCommands } from "./useWorkspacePanelCommands"; |
| 52 | import { useTurnVerificationCommands } from "./useTurnVerificationCommands"; |
| 53 | import { useTerminalPanelCommands } from "./useTerminalPanelCommands"; |
| 54 | import { useRemoteWorkspaceCommands } from "./useRemoteWorkspaceCommands"; |
| 55 | import { useAutomationNavigation } from "./useAutomationNavigation"; |
| 56 | import { useDesktopNavigation } from "./useDesktopNavigation"; |
| 57 | import { useTranscriptSurfaceProjection } from "./useTranscriptSurfaceProjection"; |
| 58 | import { useDeliveryContinueCommands } from "./useDeliveryContinueCommands"; |
| 59 | import { projectConversation, projectConversationLayout, projectWorkspaceScopeKey, projectWorkspaceTreeMemoryKey } from "./conversationProjection"; |
| 60 | import { projectControllerProfiles, projectVisibleTabs } from "./controllerProfileOwner"; |
| 61 | import { projectDecisionSurface, type AppDecisionSurfaceKind } from "./decisionSurfaceProjection"; |
| 62 | import { createSubmissionPorts, projectSubmissionResources } from "./desktopSubmissionAdapter"; |
| 63 | import type { useAppShellStores } from "./useAppShellStores"; |
| 64 | |
| 65 | function setRemoteComposerProfileForSessionAction( |
| 66 | tabId: string, |
| 67 | mode: CollaborationMode, |
| 68 | approvalMode: import("../lib/types").ToolApprovalMode, |
| 69 | goal: string, |
| 70 | ) { |
| 71 | return desktopBridge.setRemoteTabComposerProfile(tabId, mode, approvalMode, goal); |
| 72 | } |
| 73 | |
| 74 | const WORKSPACE_RESIZER_WIDTH = 8; |
| 75 | |
| 76 | type Runtime = ReturnType<typeof useAppRuntimeAdapter>; |
| 77 | type Shell = ReturnType<typeof useAppShellStores>; |
| 78 | type Surface = ReturnType<typeof useNavigationSurface>; |
| 79 | type LiveStore = Runtime["snapshot"]["liveStore"]; |
| 80 | |
| 81 | export type AppSessionCompositionInput = { |
| 82 | runtime: Runtime; |
| 83 | t: Translator; |
| 84 | showToast: (message: string, level?: "info" | "warn" | "error", options?: { durationMs?: number }) => void; |
| 85 | shell: Shell; |
| 86 | core: { |
| 87 | state: State; |
| 88 | liveStore: LiveStore; |
| 89 | activeTabId: string | undefined; |
| 90 | notice: Runtime["snapshot"]["notice"]; |
| 91 | activeTab: TabMeta | undefined; |
| 92 | remoteSurfaceActive: boolean; |
| 93 | remoteSession: RemoteSessionApi; |
| 94 | remoteComposerReady: boolean; |
| 95 | remoteSend: (text: string) => Promise<void>; |
| 96 | remoteCancel: (queuedItemIDs?: string[]) => Promise<import("../lib/inboxCancel").CancelOutcome>; |
| 97 | activeSessionIdentity: string; |
| 98 | sessionSurfaceFence: ReturnType<typeof import("./sessionTarget").createSessionSurfaceFence>; |
| 99 | sessionOperations: ReturnType<typeof useSessionOperations>; |
| 100 | }; |
| 101 | surface: Surface; |
| 102 | stores: { |
| 103 | composerProfilesByTab: Record<string, ComposerProfile>; |
| 104 | setComposerProfilesByTab: React.Dispatch<React.SetStateAction<Record<string, ComposerProfile>>>; |
| 105 | tabMetas: TabMeta[]; |
| 106 | setTabMetas: React.Dispatch<React.SetStateAction<TabMeta[]>>; |
| 107 | tabOrderIds: string[]; |
| 108 | setTabOrderIds: React.Dispatch<React.SetStateAction<string[]>>; |
| 109 | userPlanModeByTabRef: { current: UserPlanModeIntents }; |
| 110 | }; |
| 111 | local: { |
| 112 | setHistView: React.Dispatch<React.SetStateAction<import("./historyViewProjection").HistoryViewState | null>>; |
| 113 | setTabRevealSignal: React.Dispatch<React.SetStateAction<number>>; |
| 114 | sidebarImDetailConnectionId: string; |
| 115 | setSidebarImDetailConnectionId: React.Dispatch<React.SetStateAction<string>>; |
| 116 | workspaceScopeActiveTabRef: { current: string | undefined }; |
| 117 | workspaceControllerEpoch: number; |
| 118 | setWorkspaceControllerEpoch: React.Dispatch<React.SetStateAction<number>>; |
| 119 | dockRefreshKey: number; |
| 120 | setDockRefreshKey: React.Dispatch<React.SetStateAction<number>>; |
| 121 | fileRefRefreshKey: number; |
| 122 | setFileRefRefreshKey: React.Dispatch<React.SetStateAction<number>>; |
| 123 | projectRevision: number; |
| 124 | setProjectRevision: React.Dispatch<React.SetStateAction<number>>; |
| 125 | }; |
| 126 | goal: { |
| 127 | runGoalAction: ReturnType<typeof import("../lib/goalAction").useGoalActionHandler>["runGoalAction"]; |
| 128 | handleGoalActionError: ReturnType<typeof import("../lib/goalAction").useGoalActionHandler>["handleGoalActionError"]; |
| 129 | }; |
| 130 | }; |
| 131 | |
| 132 | /** |
| 133 | * Session/composer composition: runs every session-domain owner hook in the |
| 134 | * App body's original order and returns the bags the navigation composition |
| 135 | * and the shell view consume. Pure relocation — hook order within the |
| 136 | * segment is unchanged. |
| 137 | */ |
| 138 | export function useAppSessionComposition(input: AppSessionCompositionInput) { |
| 139 | const { t, showToast, shell, runtime } = input; |
| 140 | useRecoverableErrorToasts(showToast); |
| 141 | const { |
| 142 | state, liveStore, activeTabId, notice, activeTab, remoteSurfaceActive, remoteSession, remoteComposerReady, |
| 143 | remoteSend, activeSessionIdentity, sessionSurfaceFence, sessionOperations, |
| 144 | } = input.core; |
| 145 | // remoteCancel is consumed by the shell view through core. |
| 146 | const { |
| 147 | transitioning: runtimeTransitioning, dataReady: navigationTargetDataReady, |
| 148 | preserved: preservedTranscriptSurface, commitRendered: commitRenderedTranscriptSurface, |
| 149 | begin: beginNavigationSurface, maskTarget: settleNavigationSurface, commitPaint: commitNavigationSurfacePaint, |
| 150 | } = input.surface; |
| 151 | const { |
| 152 | sendToTab, runShellForTab, steerForTab, cancel, cancelForTab, |
| 153 | setControllerModeForTab, setCollaborationMode: setControllerCollaborationMode, |
| 154 | setCollaborationModeForTab: setControllerCollaborationModeForTab, |
| 155 | setToolApprovalModeForTab, |
| 156 | setComposerProfileForTab: setControllerComposerProfileForTab, setGoalForTab: setControllerGoalForTab, |
| 157 | editGoalForTab: editControllerGoalForTab, |
| 158 | resumeGoalForTab: resumeControllerGoalForTab, pauseGoalForTab: pauseControllerGoalForTab, |
| 159 | clearGoalForTab: clearControllerGoalForTab, |
| 160 | setModelForTab, setEffortForTab, |
| 161 | } = runtime.composer; |
| 162 | const { |
| 163 | recoverDeliveryToTab, approveForTab, isPromptCurrentForTab, resolvePlanDecisionForTab, resolveRecoveryForTab, |
| 164 | answerQuestionForTab, answerMCPInteractionForTab, dismissExtensionForm, drainExtensionNotifications, |
| 165 | clearSession, newSession, loadOlderHistory, loadNewerHistory, rewindForTab, rewindForTabDetailed, undoRewindForTab, forkTurnForTab, |
| 166 | listSessions, openChannelSession, resumeSession, |
| 167 | } = runtime.sessionActions; |
| 168 | const { |
| 169 | switchTab, switchRemoteTab, closeTab, reorderTabs, createIsolatedWorktree, |
| 170 | noteNavigationIntent, registeredNavigationIntent, isNavigationIntentCurrent, reassertVisibleTabAfterStaleNavigation, |
| 171 | commitSingleSurfaceNavigation, activateTopic, |
| 172 | ensureBlankSurface, openCanonicalSession, |
| 173 | } = runtime.navigation; |
| 174 | const { |
| 175 | setTransientOverlayDismissSignal, managementActive, |
| 176 | windowsFramelessChrome, rightDockMode, |
| 177 | workspacePanelOpen, workspacePanelMaximized, liveTerminalHeight, setLiveWorkspacePanelRenderWidth, |
| 178 | setRightDockTreeWidth, terminalPanelOpen, setSettingsTarget, enterConversation, |
| 179 | } = shell; |
| 180 | const { sidebarImConnections, reloadConfigWarnings } = shell.preferences; |
| 181 | const { |
| 182 | composerProfilesByTab, setComposerProfilesByTab, tabMetas, setTabMetas, tabOrderIds, setTabOrderIds, |
| 183 | userPlanModeByTabRef, |
| 184 | } = input.stores; |
| 185 | const { |
| 186 | setHistView, setTabRevealSignal, |
| 187 | sidebarImDetailConnectionId, setSidebarImDetailConnectionId, |
| 188 | workspaceScopeActiveTabRef, workspaceControllerEpoch, setWorkspaceControllerEpoch, |
| 189 | setDockRefreshKey, projectRevision, setProjectRevision, |
| 190 | } = input.local; |
| 191 | const { runGoalAction, handleGoalActionError } = input.goal; |
| 192 | const insertCommands = useComposerInsertCommands({ |
| 193 | activeTabId, |
| 194 | sessionKey: activeSessionIdentity, |
| 195 | approval: state.approval, |
| 196 | operations: sessionOperations, |
| 197 | t, |
| 198 | showToast, |
| 199 | ports: { terminalOutput: (tabId, terminalSessionId) => desktopBridge.terminalOutputForTab(tabId, terminalSessionId) }, |
| 200 | }); |
| 201 | const { |
| 202 | setInsertTarget: setWorkspaceInsertTarget, replaceComposerInsert, |
| 203 | } = insertCommands; |
| 204 | useWindowsMaximisedSync(windowsFramelessChrome); |
| 205 | const clearCommands = useSessionClearCommands({ |
| 206 | activeTabId, |
| 207 | activeSessionIdentity, |
| 208 | remote: remoteSurfaceActive, |
| 209 | t, |
| 210 | notice, |
| 211 | operations: sessionOperations, |
| 212 | refreshDock: () => setDockRefreshKey((value) => value + 1), |
| 213 | ports: { |
| 214 | clearSession, |
| 215 | clearRemoteSession: (tabId) => desktopBridge.clearRemoteTabSession(tabId), |
| 216 | retryRemoteHydration: () => remoteSession.retryHydration(), |
| 217 | }, |
| 218 | }); |
| 219 | const { clearContextPending, setClearContextPending } = clearCommands; |
| 220 | const appRef = useRef<HTMLDivElement>(null); |
| 221 | const layoutRef = useRef<HTMLDivElement>(null); |
| 222 | useManagementWorkspace(layoutRef, managementActive); |
| 223 | |
| 224 | // Persist window geometry across launches. |
| 225 | useWindowStatePersistence(); |
| 226 | useViewportHeightVar(); |
| 227 | |
| 228 | const { backgroundRuntimes, workspaceConflict, setWorkspaceConflict, refreshBackgroundRuntimes } = useRuntimeStatus({ |
| 229 | tabId: activeTabId, sessionKey: activeSessionIdentity, running: state.running, |
| 230 | }); |
| 231 | |
| 232 | const closeTransientOverlays = useCommittedCommand(() => { |
| 233 | setTransientOverlayDismissSignal((signal) => signal + 1); |
| 234 | }); |
| 235 | |
| 236 | useSidebarConnectionValidity({ connections: sidebarImConnections, setConnectionId: setSidebarImDetailConnectionId }); |
| 237 | |
| 238 | useNativeSettingsEvent({ closeTransientOverlays, setSettingsTarget }); |
| 239 | |
| 240 | const [footerHeight, setFooterHeight] = useState(0); |
| 241 | const footerRef = useRef<HTMLElement>(null); |
| 242 | const commitFooterHeight = useCommittedCommand((height: number) => setFooterHeight(height)); |
| 243 | useFooterHeightLifecycle(footerRef, commitFooterHeight); |
| 244 | useActiveTabMirrorCommit(activeTabId); |
| 245 | const { invocationMetadataByTab, handleInvocationMetadataChange } = useInvocationMetadata(); |
| 246 | const shellGeometry = useShellGeometry({ appRef, layoutRef }); |
| 247 | const { |
| 248 | rightDockTreeWidthClamp, chatReservedWidth, |
| 249 | workspacePanelAvailableWidth, workspacePanelRenderWidth, workspacePanelOverlay, workspacePanelRenderable, |
| 250 | workspacePanelGridOpen, sidebarRenderWidth, terminalRenderHeight, |
| 251 | } = shellGeometry; |
| 252 | |
| 253 | // Remote tab became ready: refresh the tab list so the spectator banner |
| 254 | // (takenOver) renders. The agent:ready event only fires for local tabs; |
| 255 | // remote tabs publish readiness via remote-tab:<id>:state, which |
| 256 | const conversationView = projectConversation({ local: state, remote: remoteSurfaceActive ? remoteSession : undefined, |
| 257 | tab: activeTab, activeTabId, backgroundRuntimes, connectingLabel: t("status.connecting") }); |
| 258 | const visibleRuntimeState = conversationView.runtime; |
| 259 | const sidebarImDetailConnection = useMemo( |
| 260 | () => sidebarImConnections.find((connection) => connection.id === sidebarImDetailConnectionId) ?? null, |
| 261 | [sidebarImConnections, sidebarImDetailConnectionId], |
| 262 | ); |
| 263 | const chatSurfaceVisible = true; |
| 264 | const { dockVisible: surfaceWorkspacePanelRenderable, dockGridOpen: surfaceWorkspacePanelGridOpen, |
| 265 | dockOverlay: surfaceWorkspacePanelOverlay, |
| 266 | terminalOpen: terminalSurfaceOpen } = projectConversationLayout({ |
| 267 | chatVisible: chatSurfaceVisible, localToolsEnabled: conversationView.localToolsEnabled, dockMode: rightDockMode, |
| 268 | dockRenderable: workspacePanelRenderable, dockGridOpen: workspacePanelGridOpen, dockOverlay: workspacePanelOverlay, |
| 269 | dockOpen: workspacePanelOpen, dockMaximized: workspacePanelMaximized, terminalOpen: terminalPanelOpen, |
| 270 | }); |
| 271 | const statusBarVisible = chatSurfaceVisible && !sidebarImDetailConnection; |
| 272 | const composerSessionKey = useMemo(() => { |
| 273 | return composerDraftKeyForTab(activeTab, activeTabId); |
| 274 | }, [activeTab, activeTabId]); |
| 275 | const transcriptGeometrySessionKey = activeSessionIdentity; |
| 276 | const workspaceScopeKey = projectWorkspaceScopeKey({ |
| 277 | activeTabId, sessionKey: activeSessionIdentity, |
| 278 | cwd: state.meta?.cwd, sessionGen: state.sessionGen, workspaceControllerEpoch, |
| 279 | }); |
| 280 | const workspaceTreeMemoryKey = projectWorkspaceTreeMemoryKey({ |
| 281 | scope: activeTab?.scope, workspaceRoot: activeTab?.workspaceRoot, cwd: state.meta?.cwd, |
| 282 | }); |
| 283 | const { activeTopicTurns } = useTopicSummary({ activeTab, revision: projectRevision }); |
| 284 | const visibleUserTurns = visibleRuntimeState.items.reduce((count, item) => (item.kind === "user" ? count + 1 : count), 0); |
| 285 | const currentTabTurns = Math.max(visibleRuntimeState.checkpoints.length, visibleUserTurns); |
| 286 | const sessionTurns = currentTabTurns > 0 ? currentTabTurns : remoteSurfaceActive ? 0 : activeTopicTurns ?? 0; |
| 287 | const startupSplashHold = !activeTabId && state.meta?.ready !== true && !state.meta?.startupErr; |
| 288 | const profileProjection = useComposerProfileProjection({ |
| 289 | activeTabId, |
| 290 | activeTab, |
| 291 | meta: state.meta, |
| 292 | profilesByTab: composerProfilesByTab, |
| 293 | setProfilesByTab: setComposerProfilesByTab, |
| 294 | tabMetas, |
| 295 | remote: remoteSurfaceActive, |
| 296 | remoteSession, |
| 297 | planIntentsRef: userPlanModeByTabRef, |
| 298 | }); |
| 299 | const { |
| 300 | composerProfile, goal, collaborationMode, toolApprovalMode, |
| 301 | patchComposerProfileForTab, patchActivatedGoalForTab, |
| 302 | } = profileProjection; |
| 303 | const controllerReady = |
| 304 | state.meta?.ready === true && |
| 305 | (!state.meta.runtime || state.meta.runtime.phase === "ready") && |
| 306 | !state.meta.startupErr && |
| 307 | !state.backendActivationPending && |
| 308 | !runtimeTransitioning; |
| 309 | useEffect(() => { |
| 310 | if (controllerReady && activeTabId && !remoteSurfaceActive) noteNavigationComposerEnabled(activeTabId); |
| 311 | }, [activeTabId, controllerReady, remoteSurfaceActive]); |
| 312 | useAppDiagnostics({ activeTabId, tabCount: tabMetas.length, ready: controllerReady, running: state.running, |
| 313 | hydrating: state.hydrating, runtimeTransitioning, contentRevision: state.historyLayoutRevision }); |
| 314 | |
| 315 | const tabBarCommands = useTabBarCommands({ |
| 316 | activeTabId, |
| 317 | tabMetas, |
| 318 | deliveryWorktreeRoot: state.meta?.workspaceRoot || state.meta?.workspacePath || state.meta?.cwd, |
| 319 | t, |
| 320 | showToast, |
| 321 | setTabMetas, |
| 322 | setTabOrderIds, |
| 323 | setComposerProfilesByTab, |
| 324 | setTabRevealSignal, |
| 325 | clearWorkspaceConflict: () => setWorkspaceConflict(null), |
| 326 | ports: { |
| 327 | closeTab, |
| 328 | reorderTabs, |
| 329 | switchTab, |
| 330 | switchRemoteTab, |
| 331 | refreshTabMetas: (apply, options) => refreshTabMetas(apply, options), |
| 332 | refreshBackgroundRuntimes, |
| 333 | cancelActive: () => void handleCancelActive(), |
| 334 | noteNavigationIntent, |
| 335 | beginNavigationSurface, |
| 336 | settleNavigationSurface, |
| 337 | isNavigationIntentCurrent, |
| 338 | reassertVisibleTabAfterStaleNavigation, |
| 339 | enterChatView: enterConversation, |
| 340 | createIsolatedWorktree, |
| 341 | }, |
| 342 | }); |
| 343 | const { pendingClose, setPendingClose } = tabBarCommands; |
| 344 | |
| 345 | const decisionSurface = useMemo((): AppDecisionSurfaceKind | null => projectDecisionSurface({ |
| 346 | approval: state.approval, ask: state.ask, mcpInteraction: state.mcpInteraction, extensionForm: state.extensionForm, |
| 347 | workspaceConflict, pendingClose, clearContextPending, |
| 348 | }), [clearContextPending, pendingClose, state.approval, state.ask, state.extensionForm, state.mcpInteraction, workspaceConflict]); |
| 349 | const visibleDecisionSurface = decisionSurface; |
| 350 | // Navigation used to hide the entire composer until the controller/runtime |
| 351 | // activation ticket and the transcript paint ticket both settled. That made |
| 352 | // a local session switch look like a frozen blank surface even though its |
| 353 | // history was already available (or could be shown from the bounded |
| 354 | // transcript cache). Keep the composer mounted during a local transition: |
| 355 | // the submission resources still use `controllerReady` as the write fence, |
| 356 | // so drafts remain editable while send/control actions stay disabled until |
| 357 | // the target runtime is ready. Decision surfaces remain exclusive. |
| 358 | const composerSurfaceHidden = (runtimeTransitioning && remoteSurfaceActive) || Boolean(decisionSurface); |
| 359 | useDecisionSurfaceFocus({ surface: decisionSurface, activeTabId, closeOverlays: closeTransientOverlays }); |
| 360 | |
| 361 | // Extension form surface (stage 8b2): submit delivers the structured values |
| 362 | // to the owning sidecar; cancel reports values{"cancelled": true} over the |
| 363 | // same channel. A failed cancel still dismisses — the sidecar that could not |
| 364 | // be reached is gone either way. |
| 365 | const extensionSurface = useExtensionSurface({ |
| 366 | activeTabId, |
| 367 | hostId: state.meta?.session?.hostId, |
| 368 | sessionId: state.meta?.sessionId, |
| 369 | sessionGeneration: state.meta?.sessionGeneration, |
| 370 | form: state.extensionForm, |
| 371 | notifications: state.extensionNotifications, |
| 372 | dismissForm: dismissExtensionForm, |
| 373 | drainNotifications: drainExtensionNotifications, |
| 374 | showToast, |
| 375 | }); |
| 376 | const extensionStatusList = useMemo(() => Object.values(state.extensionStatuses ?? {}), [state.extensionStatuses]); |
| 377 | const visibleTabId = activeTabId; |
| 378 | const visibleTabs = useMemo(() => projectVisibleTabs({ |
| 379 | tabs: tabMetas, orderIds: tabOrderIds, profiles: composerProfilesByTab, visibleTabId, running: state.running, |
| 380 | }), [composerProfilesByTab, state.running, tabMetas, tabOrderIds, visibleTabId]); |
| 381 | |
| 382 | useTabProjectionLifecycle({ |
| 383 | tabs: tabMetas, activeTabId, activeMeta: activeTab, meta: state.meta, |
| 384 | planIntentsRef: userPlanModeByTabRef, |
| 385 | setOrder: setTabOrderIds, setProfiles: setComposerProfilesByTab, |
| 386 | }); |
| 387 | |
| 388 | |
| 389 | const controllerProfiles = projectControllerProfiles(tabMetas, composerProfilesByTab, { |
| 390 | target: { tabId: activeTabId ?? "", sessionKey: activeSessionIdentity }, profile: composerProfile, remote: remoteSurfaceActive, |
| 391 | }); |
| 392 | const controllerProfileCommands = useControllerProfileCommands({ |
| 393 | target: { tabId: activeTabId ?? "", sessionKey: activeSessionIdentity }, profiles: controllerProfiles, |
| 394 | ready: controllerReady, remote: remoteSurfaceActive, runtimeEpoch: state.meta?.runtime?.epoch, operations: sessionOperations, |
| 395 | ports: { model: setModelForTab, profile: setControllerComposerProfileForTab }, |
| 396 | remoteModel: remoteSession.setModel, report: handleGoalActionError, |
| 397 | }); |
| 398 | const { switchModel, applyProfile: applyControllerProfile } = controllerProfileCommands; |
| 399 | const hydratePlaceholderActive = Boolean( |
| 400 | state.hydrating && |
| 401 | state.items.length === 0 && |
| 402 | state.hydratePlaceholderItems?.length, |
| 403 | ); |
| 404 | const sessionUndoCommands = useSessionUndo({ |
| 405 | activeTabId, |
| 406 | activeTabReadOnly: Boolean(activeTab?.readOnly), |
| 407 | items: state.items, |
| 408 | hydratePlaceholderActive, |
| 409 | controllerReady, running: state.running, messageActionOpen: state.messageAction != null, |
| 410 | approvalOpen: state.approval != null, askOpen: state.ask != null, clearContextPending, |
| 411 | ports: { |
| 412 | rewindForTab, rewindForTabDetailed, forkTurnForTab, |
| 413 | refreshTabMetas: () => void refreshTabMetas(undefined, { afterMutation: true }), |
| 414 | undoRewindForTab, sendToTab, |
| 415 | composeInsert: replaceComposerInsert, |
| 416 | refreshDock: () => setDockRefreshKey((value) => value + 1), |
| 417 | refreshProject: () => setProjectRevision((value) => value + 1), |
| 418 | }, |
| 419 | }); |
| 420 | const { |
| 421 | rewindState, rewindCommitting, rewindSignal, setRewindStateForTab, |
| 422 | handleSessionRevertCommitted, handleMessageAction, handleForkTurn, handleUndoRewind, handleEditPrompt, |
| 423 | } = sessionUndoCommands; |
| 424 | const clearSubmissionUndo = useCommittedCommand((tab: string) => setRewindStateForTab(tab, null)); |
| 425 | const { commitThenSend, submit: submitComposerTurn, applyGoalForTab, applyGoal, sendRevision } = useSessionSubmission({ |
| 426 | target: { tabId: activeTabId ?? "", sessionKey: activeSessionIdentity }, operations: sessionOperations, |
| 427 | resources: projectSubmissionResources(controllerProfiles, tabMetas, composerProfilesByTab, |
| 428 | { tabId: activeTabId ?? "", profile: composerProfile, ready: controllerReady }, |
| 429 | { starting: t("composer.workspaceStarting"), readOnly: t("composer.readOnlyChannel") }), |
| 430 | missingSource: t("composer.workspaceStarting"), |
| 431 | ports: createSubmissionPorts({ send: sendToTab, setGoal: setControllerGoalForTab, clearGoal: clearControllerGoalForTab, |
| 432 | clearUndo: clearSubmissionUndo, patchGoal: patchActivatedGoalForTab, profile: applyControllerProfile }), |
| 433 | }); |
| 434 | const patchPlanExitProfileForTab = useCommittedCommand((tabId: string, mode: CollaborationMode) => { |
| 435 | patchComposerProfileForTab(tabId, { |
| 436 | collaborationMode: mode, |
| 437 | goalDraftMode: false, |
| 438 | goal: "", |
| 439 | }, ["collaborationMode", "goal"]); |
| 440 | }); |
| 441 | const drainRemoteApprovalsForTab = useCommittedCommand((tabId: string, ids: string[]) => { |
| 442 | if (activeTabId === tabId) remoteSession.drainApprovals(ids); |
| 443 | }); |
| 444 | const modeActions = useComposerModeActions({ |
| 445 | target: { tabId: activeTabId ?? "", sessionKey: activeSessionIdentity }, |
| 446 | remote: remoteSurfaceActive, collaborationMode, toolApprovalMode, goal, |
| 447 | operations: sessionOperations, |
| 448 | planIntentsRef: userPlanModeByTabRef, |
| 449 | ports: { |
| 450 | setMode: setControllerModeForTab, setCollaboration: setControllerCollaborationModeForTab, |
| 451 | setApproval: setToolApprovalModeForTab, clearGoal: clearControllerGoalForTab, |
| 452 | setRemote: setRemoteComposerProfileForSessionAction, drainRemote: drainRemoteApprovalsForTab, |
| 453 | patch: patchComposerProfileForTab, |
| 454 | }, |
| 455 | showError: (message) => showToast(message, "error"), |
| 456 | }); |
| 457 | const { applyCollaborationMode, notePlanModeForTab } = modeActions; |
| 458 | const rememberPlanRevisionForTab = usePendingPlanRevisions({ |
| 459 | visible: { tabId: activeTabId ?? "", sessionKey: activeSessionIdentity }, |
| 460 | resources: controllerProfiles.map(resource => resource.target), running: state.running, |
| 461 | ready: controllerReady && !state.approval && !state.ask && !state.mcpInteraction, |
| 462 | operations: sessionOperations, send: sendRevision, report: reportPendingRevisionFailure, |
| 463 | }); |
| 464 | const promptCommands = useSessionPromptCommands({ |
| 465 | target: { tabId: activeTabId ?? "", sessionKey: activeSessionIdentity }, |
| 466 | session: state.meta?.session, |
| 467 | sessionGeneration: state.meta?.sessionGeneration, |
| 468 | approval: state.approval, |
| 469 | question: state.ask, |
| 470 | mcpInteraction: state.mcpInteraction, |
| 471 | remote: Boolean(activeTab?.remote), goal, toolApprovalMode, |
| 472 | operations: sessionOperations, |
| 473 | ports: { |
| 474 | approveForTab, isPromptCurrentForTab, resolvePlanForTab: resolvePlanDecisionForTab, |
| 475 | resolveRecoveryForTab, answerQuestionForTab, answerMCPForTab: answerMCPInteractionForTab, |
| 476 | setCollaborationModeForTab: setControllerCollaborationModeForTab, |
| 477 | clearGoalForTab: clearControllerGoalForTab, setRemoteComposerProfile: setRemoteComposerProfileForSessionAction, |
| 478 | patchComposerProfile: patchPlanExitProfileForTab, notePlanMode: notePlanModeForTab, |
| 479 | drainRemoteApprovals: drainRemoteApprovalsForTab, rememberRevision: rememberPlanRevisionForTab, |
| 480 | }, |
| 481 | reportError: error => showToast(error instanceof Error ? error.message : String(error), "error"), |
| 482 | }); |
| 483 | const remoteComposerSend = useRemoteComposerSend(activeTab?.remote, activeTabId, collaborationMode, goal, |
| 484 | remoteSession, remoteSend, applyGoalForTab, useCommittedCommand(() => setClearContextPending(true)), |
| 485 | { target: { tabId: activeTabId ?? "", sessionKey: activeSessionIdentity }, operations: sessionOperations, |
| 486 | navigateRemote: useCommittedCommand<RemoteNavigationCommand>((remote, options) => openRemoteProject(remote, options)) }); |
| 487 | const controlCommands = useSessionControlCommands({ |
| 488 | activeTabId, |
| 489 | resources: controllerProfiles.map(resource => resource.target), |
| 490 | operations: sessionOperations, |
| 491 | showToast, |
| 492 | clearWorkspaceConflict: () => setWorkspaceConflict(null), |
| 493 | ports: { |
| 494 | cancel, |
| 495 | cancelForTab, |
| 496 | acceptDelivery: (tabId) => desktopBridge.acceptDeliveryToTab(tabId), |
| 497 | disconnectRemote: (hostId) => desktopBridge.disconnectRemoteHost(hostId), |
| 498 | cancelJobForTab: (tabId, jobId) => desktopBridge.cancelJobForTab(tabId, jobId), |
| 499 | refreshBackgroundRuntimes, |
| 500 | }, |
| 501 | }); |
| 502 | const { handleCancelActive } = controlCommands; |
| 503 | // Shift+Tab toggles only the collaboration axis. Permission presets are |
| 504 | // changed explicitly through the composer permission selector. |
| 505 | const cycleMode = useCommittedCommand(() => { |
| 506 | runGoalAction(() => applyCollaborationMode(collaborationMode === "plan" ? "normal" : "plan")); |
| 507 | }); |
| 508 | |
| 509 | const todoPanelCommands = useTodoPanelCommands({ |
| 510 | items: visibleRuntimeState.items, |
| 511 | running: visibleRuntimeState.running, |
| 512 | pendingPrompt: visibleRuntimeState.pendingPrompt, |
| 513 | meta: state.meta, |
| 514 | activeTab, |
| 515 | activeTabId, |
| 516 | remote: remoteSurfaceActive, |
| 517 | remoteReady: remoteComposerReady, |
| 518 | controllerReady, |
| 519 | sessionKey: activeSessionIdentity, |
| 520 | operations: sessionOperations, |
| 521 | t, |
| 522 | ports: { |
| 523 | remoteSend: (text) => remoteSend(text), |
| 524 | sendToTab: (tabId, text) => sendToTab(tabId, text), |
| 525 | }, |
| 526 | }); |
| 527 | const { showTodos, scopedTodoBatch, todos, dismissTodos, handleTodoContinue } = todoPanelCommands; |
| 528 | |
| 529 | const sessionTitle = topicTitle(activeTab); |
| 530 | const exportItems = remoteSurfaceActive ? remoteSession.transcript.items : state.items; |
| 531 | const exportLive = remoteSurfaceActive |
| 532 | ? remoteSession.transcript.live |
| 533 | : liveStore.getSnapshot(activeTabId) ?? state.live; |
| 534 | const sessionHasContent = exportItems.length > 0 || Boolean(exportLive?.text || exportLive?.reasoning); |
| 535 | |
| 536 | const sessionExportCommands = useSessionExportCommands({ |
| 537 | selector: activeTab?.session?.sessionId ? { ref: activeTab.session } : activeTab?.sessionPath ? { sessionPath: activeTab.sessionPath } : { topicId: activeTab?.topicId }, |
| 538 | tabId: activeTabId, |
| 539 | remote: remoteSurfaceActive, |
| 540 | sessionTitle, |
| 541 | items: exportItems, |
| 542 | live: exportLive, |
| 543 | hasContent: sessionHasContent, |
| 544 | t, |
| 545 | showToast, |
| 546 | }); |
| 547 | |
| 548 | useActiveTabUiReset({ activeTabId, setClearPending: setClearContextPending, setInsertTarget: setWorkspaceInsertTarget }); |
| 549 | |
| 550 | const routerCommands = useComposerRouter({ |
| 551 | activeTabId, |
| 552 | goalDraftActive: collaborationMode === "goal" && !goal.trim(), |
| 553 | t, |
| 554 | notice, |
| 555 | showToast, |
| 556 | ports: { |
| 557 | runShellForTab, |
| 558 | switchModel: (name, tabId) => switchModel(name, tabId), |
| 559 | newSession: () => newSession(), |
| 560 | setSettingsTarget: (tab) => setSettingsTarget(tab), |
| 561 | setClearContextPending, |
| 562 | clearWorkspaceConflict: () => setWorkspaceConflict(null), |
| 563 | setWorkspaceConflict: (value) => setWorkspaceConflict(value), |
| 564 | setPendingClose: (value) => setPendingClose(value), |
| 565 | submitComposerTurn: (tab, display, submit, structured) => submitComposerTurn(tab, display, submit, structured), |
| 566 | steerForTab, |
| 567 | isRemoteTab: (tabId) => tabMetas.some((tab) => tab.id === tabId && tab.remote), |
| 568 | }, |
| 569 | }); |
| 570 | |
| 571 | const editGoal = async (objective: string, maxGoalRounds: number | null) => { |
| 572 | if (!activeTabId) return; |
| 573 | if (remoteSurfaceActive) await remoteSession.editGoal(objective, maxGoalRounds); |
| 574 | else await editControllerGoalForTab(activeTabId, objective, maxGoalRounds); |
| 575 | }; |
| 576 | const goalCommands = useComposerGoalCommands({ applyCollaborationMode, applyGoal, editGoal }); |
| 577 | const remoteGoalActions = useRemoteComposerRuntimeActions({ |
| 578 | target: { tabId: activeTabId ?? "", sessionKey: activeSessionIdentity }, operations: sessionOperations, |
| 579 | remote: remoteSurfaceActive, session: remoteSession, runGoalAction, |
| 580 | pauseLocal: pauseControllerGoalForTab, resumeLocal: resumeControllerGoalForTab, |
| 581 | setLocalEffort: setEffortForTab, showError: (message) => showToast(message, "error"), |
| 582 | }); |
| 583 | |
| 584 | const { |
| 585 | refreshTabMetas, seedActiveTabMeta, |
| 586 | handleRuntimeEvent, handleRuntimeReady, handleRuntimeRebuilt, |
| 587 | handleRemoteStatus, handleRemoteForwards, handleRemoteServer, |
| 588 | handleInitialRemoteHosts, handleInitialRemoteStatuses, |
| 589 | } = useRuntimeEventHandlers({ |
| 590 | activeTabId, |
| 591 | workspaceScopeKey, |
| 592 | workspaceScopeActiveTabRef, |
| 593 | userPlanModeByTabRef, |
| 594 | setTabMetas, |
| 595 | setTabOrderIds, |
| 596 | setComposerProfilesByTab, |
| 597 | setDockRefreshKey, |
| 598 | setProjectRevision, |
| 599 | setWorkspaceControllerEpoch, |
| 600 | setControllerCollaborationMode, |
| 601 | }); |
| 602 | |
| 603 | const refreshProviderSetupState = useCommittedCommand(() => probeProviderSetupState()); |
| 604 | |
| 605 | const leaseBlockedTab = activeLeaseBlockedTab(tabMetas, activeTab?.id ?? activeTabId); |
| 606 | const bannerCommands = useSessionBannerCommands({ |
| 607 | remote: Boolean(activeTab?.remote), |
| 608 | reloadConfigWarnings, |
| 609 | }); |
| 610 | |
| 611 | const workspacePanelCommands = useWorkspacePanelCommands({ |
| 612 | sessionId: activeTabId ?? "", |
| 613 | workspaceRoot: activeTab?.workspaceRoot ?? state.meta?.cwd ?? "", |
| 614 | visible: surfaceWorkspacePanelRenderable, |
| 615 | closeOverlays: closeTransientOverlays, clearLiveWidth: setLiveWorkspacePanelRenderWidth, |
| 616 | availableWidth: workspacePanelAvailableWidth, clampTreeWidth: rightDockTreeWidthClamp, setTreeWidth: setRightDockTreeWidth, |
| 617 | gridOpen: surfaceWorkspacePanelGridOpen, |
| 618 | t, |
| 619 | }); |
| 620 | const { openRightDockMode } = workspacePanelCommands; |
| 621 | |
| 622 | const turnVerificationCommands = useTurnVerificationCommands({ |
| 623 | activeTabId, |
| 624 | turnStartAt: state.turnStartAt, |
| 625 | completionSummary: state.completionSummary, |
| 626 | sessionPath: state.meta?.sessionPath, |
| 627 | openChangedDock: () => openRightDockMode("changed"), |
| 628 | }); |
| 629 | |
| 630 | const terminalPanelCommands = useTerminalPanelCommands({ |
| 631 | tabId: activeTabId, enabled: conversationView.localToolsEnabled, shortcutsEnabled: !managementActive, |
| 632 | }); |
| 633 | |
| 634 | const remoteWorkspaceCommands = useRemoteWorkspaceCommands({ t, showToast }); |
| 635 | |
| 636 | const layoutStyle = useMemo( |
| 637 | () => |
| 638 | ({ |
| 639 | "--sidebar-expanded-width": `${sidebarRenderWidth}px`, |
| 640 | "--chat-min-width": `${chatReservedWidth}px`, |
| 641 | "--workspace-width": `${workspacePanelRenderWidth}px`, |
| 642 | "--workspace-resizer-width": `${WORKSPACE_RESIZER_WIDTH}px`, |
| 643 | "--terminal-height": `${terminalSurfaceOpen ? liveTerminalHeight ?? terminalRenderHeight : 0}px`, |
| 644 | }) as CSSProperties, |
| 645 | [chatReservedWidth, liveTerminalHeight, sidebarRenderWidth, terminalRenderHeight, terminalSurfaceOpen, workspacePanelRenderWidth], |
| 646 | ); |
| 647 | |
| 648 | // Coalesce tab-bar switches through the same last-click-wins scheduler that |
| 649 | // openTopic/blank/resume navigation uses, so rapidly clicking between two |
| 650 | // running sessions can't run two switchTab() calls concurrently. Concurrent |
| 651 | // switches race on the backend SetActiveTab/confirmBackendActiveTab ordering, |
| 652 | const availability = projectSessionAvailability({ local: state, remote: remoteSurfaceActive ? remoteSession : undefined }); |
| 653 | const presentationTransitioning = runtimeTransitioning && remoteSurfaceActive; |
| 654 | const { |
| 655 | transcriptHydrating, emptyHero, |
| 656 | visibleTranscriptItems, visibleTranscriptTabId, visibleTranscriptGeometryKey, |
| 657 | handleLoadOlderHistory, handleLoadNewerHistory, handleSurfacePaintReady, latestGuidanceConsumed, handleTranscriptPrompt, |
| 658 | } = useTranscriptSurfaceProjection({ |
| 659 | hydrating: state.hydrating, |
| 660 | hydrateHistoryLoaded: state.hydrateHistoryLoaded, |
| 661 | hydratePlaceholderItems: state.hydratePlaceholderItems, |
| 662 | hydratePlaceholderActive, |
| 663 | items: state.items, |
| 664 | remote: remoteSurfaceActive, |
| 665 | remoteItems: remoteSession.transcript.items, |
| 666 | activeTabId, |
| 667 | geometrySessionKey: transcriptGeometrySessionKey, |
| 668 | transitioning: presentationTransitioning, |
| 669 | navigationDataReady: navigationTargetDataReady, |
| 670 | preserved: preservedTranscriptSurface, |
| 671 | controllerReady, |
| 672 | availability, |
| 673 | sessionActivity: Boolean(conversationView.runtime.running || conversationView.runtime.pendingPrompt |
| 674 | || conversationView.runtime.approval || conversationView.runtime.ask || conversationView.runtime.extensionForm |
| 675 | || conversationView.runtime.mcpInteraction || (remoteSurfaceActive && (remoteSession.promptError || remoteSession.error))), |
| 676 | imDetailActive: Boolean(sidebarImDetailConnection), |
| 677 | sessionHasContent, |
| 678 | commitRendered: commitRenderedTranscriptSurface, |
| 679 | commitPaint: commitNavigationSurfacePaint, |
| 680 | commitSingleSurface: commitSingleSurfaceNavigation, |
| 681 | ports: { |
| 682 | loadOlderHistory: (tabId, targetTurn, trigger) => loadOlderHistory(tabId, targetTurn, trigger), |
| 683 | loadNewerHistory: (tabId, latest) => loadNewerHistory(tabId, latest), |
| 684 | commitThenSend: (tabId, text, submitText) => commitThenSend(tabId, text, submitText), |
| 685 | }, |
| 686 | }); |
| 687 | |
| 688 | const { handleDeliveryContinue } = useDeliveryContinueCommands({ |
| 689 | surfaceFence: sessionSurfaceFence, |
| 690 | ready: controllerReady, |
| 691 | goal: state.meta?.goal, |
| 692 | t, |
| 693 | ports: { |
| 694 | resumeGoal: resumeControllerGoalForTab, |
| 695 | recoverDelivery: recoverDeliveryToTab, |
| 696 | }, |
| 697 | }); |
| 698 | |
| 699 | const { openAutomationTopic, topicAccepted } = useAutomationNavigation({ noteIntent: noteNavigationIntent, |
| 700 | enqueue: useCommittedCommand((intent, seq) => enqueueNavigationWithIntent(intent, seq)) }); const { |
| 701 | enqueueNavigation, enqueueNavigationWithIntent, openRemoteProject, |
| 702 | } = useDesktopNavigation({ |
| 703 | visible: { tabId: activeTabId ?? "", sessionKey: activeSessionIdentity }, |
| 704 | ports: { isNavigationIntentCurrent, activateTopic, |
| 705 | ensureBlankSurface, openCanonicalSession, createIsolatedWorktree, openChannelSession, resumeSession, |
| 706 | registeredNavigationIntent, switchRemoteTab, openRemoteProject: desktopBridge.openRemoteProjectTab, |
| 707 | listTabs: desktopBridge.listTabs, applyTabs: setTabMetas, seedTab: seedActiveTabMeta, listSessions, topicAccepted, |
| 708 | prepareSession: desktopBridge.prepareSession, getSessionPreparation: desktopBridge.getSessionPreparation }, |
| 709 | setTabRevealSignal, setProjectRevision, setHistory: setHistView, t, showToast, |
| 710 | noteIntent: noteNavigationIntent, beginSurface: beginNavigationSurface, settleSurface: settleNavigationSurface, |
| 711 | showChat: enterConversation, |
| 712 | }); |
| 713 | return { |
| 714 | insertCommands, |
| 715 | clearCommands, |
| 716 | tabBarCommands, |
| 717 | extensionSurface, |
| 718 | promptCommands, |
| 719 | controlCommands, |
| 720 | routerCommands, |
| 721 | goalCommands, |
| 722 | remoteGoalActions, |
| 723 | modeActions, |
| 724 | controllerProfileCommands, |
| 725 | profileProjection, |
| 726 | sessionExportCommands, |
| 727 | workspacePanelCommands, |
| 728 | turnVerificationCommands, |
| 729 | terminalPanelCommands, |
| 730 | remoteWorkspaceCommands, |
| 731 | bannerCommands, |
| 732 | runtimeEventCommands: { |
| 733 | refreshTabMetas, seedActiveTabMeta, |
| 734 | handleRuntimeEvent, handleRuntimeReady, handleRuntimeRebuilt, |
| 735 | handleRemoteStatus, handleRemoteForwards, handleRemoteServer, |
| 736 | handleInitialRemoteHosts, handleInitialRemoteStatuses, |
| 737 | }, |
| 738 | sessionUndo: { |
| 739 | rewindState, rewindCommitting, rewindSignal, handleSessionRevertCommitted, handleMessageAction, handleForkTurn, handleUndoRewind, handleEditPrompt, |
| 740 | }, |
| 741 | todoPanel: { showTodos, scopedTodoBatch, todos, dismissTodos, handleTodoContinue }, |
| 742 | delivery: { handleDeliveryContinue }, |
| 743 | transcript: { |
| 744 | transcriptHydrating, emptyHero, availability, |
| 745 | visibleTranscriptItems, visibleTranscriptTabId, visibleTranscriptGeometryKey, |
| 746 | handleLoadOlderHistory, handleLoadNewerHistory, handleSurfacePaintReady, latestGuidanceConsumed, handleTranscriptPrompt, |
| 747 | }, |
| 748 | automation: { openAutomationTopic }, |
| 749 | desktopNavigation: { enqueueNavigation, enqueueNavigationWithIntent, openRemoteProject }, |
| 750 | invocation: { invocationMetadataByTab, handleInvocationMetadataChange }, |
| 751 | sessionHasContent, |
| 752 | conversationView, |
| 753 | visibleRuntimeState, |
| 754 | sidebarImDetailConnection, |
| 755 | surfaceWorkspacePanelRenderable, |
| 756 | surfaceWorkspacePanelGridOpen, |
| 757 | surfaceWorkspacePanelOverlay, |
| 758 | terminalSurfaceOpen, |
| 759 | statusBarVisible, |
| 760 | chatSurfaceVisible, |
| 761 | composerSessionKey, |
| 762 | workspaceScopeKey, |
| 763 | workspaceTreeMemoryKey, |
| 764 | sessionTurns, |
| 765 | startupSplashHold, |
| 766 | controllerReady, |
| 767 | decisionSurface, |
| 768 | visibleDecisionSurface, |
| 769 | composerSurfaceHidden, |
| 770 | extensionStatusList, |
| 771 | visibleTabs, |
| 772 | visibleTabId, |
| 773 | hydratePlaceholderActive, |
| 774 | leaseBlockedTab, |
| 775 | layoutStyle, |
| 776 | cycleMode, |
| 777 | remoteComposerSend, |
| 778 | closeTransientOverlays, |
| 779 | refreshProviderSetupState, |
| 780 | shellGeometry, |
| 781 | appRef, |
| 782 | layoutRef, |
| 783 | footerHeight, |
| 784 | footerRef, |
| 785 | backgroundRuntimes, |
| 786 | workspaceConflict, |
| 787 | }; |
| 788 | } |
| 789 |