Repository navigation
Switch chats between Chat UI and a terminal without forking - #31595
brennanb2025 wants to merge 383 commits into
Conversation
|
Not ready — 2026-10-27. Targeted QA found two Codex return failures. The review-completed marker is withheld. This PR keeps a Codex conversation in the same tab when moving between chat and CLI, brings terminal turns back into chat, and makes switch failures actionable. The correction uses one bounded host-owned operation, shared renderer dispatch and cheap live availability facts. Readable history appears before provider startup completes. Older peers and other agents retain their supported existing paths. Published head: CI is not green. On the preceding head, the four root compilers, layout-writer checks, E2E type ratchet and mobile checks passed; a stale generated catalog failed the static job and dependent aggregate. The catalog is corrected in this head. New main conflicts now suppress the source workflows: the single current-head watch saw only the successful branch-clean check and skipped bot review. Conflict resolution and full CI remain owed. Targeted Mac QA ran on
Windows is still completing its originally assigned targeted flows. No completed cross-platform verdict is claimed. Paired slow/lost-answer actions, the selected committed start-failure/unused-destination outcomes, cleanup with newer work, and the suspended-tooltip screenshot remain incomplete. A Mac failure attempt recovered automatically with a real reply, which does not prove the once-only committed-failure case. Earlier accepted History, recovery, Chat UI-off and WSL evidence covers only unchanged flows and retains its original limits; it does not fill these gaps. Both return responses were captured from one real click with matching source identity. Missing guard/holder evidence is not grounds to remove safety checks. A later prompt in the saved-return rig was sent after the refusal and earns no successful return or reply. Mac cleanup is verified: owned apps, observers and recorded roots are gone, admitted ports are free, and all five real-config hashes are unchanged. For Brennan’s reviewLearned-ID return: the terminal remains after the refused switch. This frame shows the ordinary exited shell and tooltip, not a captured refusal toast. Saved cold return: actual inherited history is readable, but the selected start is refused. This is a failure screenshot, not a passing return. Claude’s existing continuation completed a real inherited-context reply. Layout-writer ratchetThe window is the shipped layout owner. These counted writes are the switch's fenced retirement/restoration and launch-verdict producers; they will be ported when the main-owned layout path replaces the window path. The additional main call is post-dispatch recovery with distinct preconditions from fresh/replacement binding. The coordinator authorized exactly these three counted rows; scanner rules and allowed paths are unchanged. The base dependency #30930 and inert foundation #34384 are merged. No merge or readiness approval is requested while the failures, incomplete flows and source CI remain outstanding. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments: 1
src/renderer/src/components/native-chat/NativeChatTopRightControls.tsx: 1
34-42:Trivial · Performance & Scalability · Quick win Cache the identity-derived host scope.
useAppStoreevaluates this selector on store updates.resolveTabSessionHistorySubjectthen builds the full session-list request, including worktree indexing and project/scope derivation, although this component only needsexecutionHostScope.The primitive result limits component re-renders, but it does not avoid the selector work.
NativeChatResumeInCliButton.tsxalready uses the relevant memoized selector pattern. Reuse it here, but cache the result ofresolveNativeChatCliForkIdentity; caching the full subject with only the identity selector’s inputs would omit dependencies used byresolveAiVaultPanelSessionListRequest.

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
f57fe104-a0e2-4c98-83d0-f8f79f117491
Files ignored due to path filters: 1-
src/shared/rpc-contract/rpc-params-catalog.generated.tsis excluded by!**/*.generated.*
Files selected for processing: 232-
config/main-layout-writer-baseline.txtconfig/scripts/check-layout-writer-ratchets.mjsconfig/scripts/check-layout-writer-ratchets.test.mjsmobile/src/session/use-mobile-session-tab-application-launch.test.tssrc/main/agent-hooks/managed-hook-stdin-lifecycle.test.tssrc/main/agent-hooks/server-launch-resume-identity.test.tssrc/main/ai-vault/structured-chat-cli-fork.tssrc/main/ai-vault/structured-session-ownership.test.tssrc/main/ai-vault/structured-session-ownership.tssrc/main/daemon/daemon-pty-session-control.tssrc/main/daemon/daemon-shutdown-final-checkpoint-caller-deadline.test.tssrc/main/ipc/pty/host-env/saved-resume-admission.tssrc/main/ipc/pty/ipc/renderer-kill.tssrc/main/ipc/pty/provider/shutdown-detect.tssrc/main/ipc/pty/runtime/controller-deps.tssrc/main/ipc/pty/runtime/kill-same-id-respawn.test.tssrc/main/ipc/pty/runtime/kill.tssrc/main/ipc/pty/runtime/spawn-commit-response.tssrc/main/ipc/pty/runtime/spawn-state.tssrc/main/ipc/pty/session.tssrc/main/ipc/pty/spawn-commit-assembly.tssrc/main/ipc/pty/spawn-commit-publication.tssrc/main/native-chat/agent-session-wire/structured-agent-session-conversation-lifetime.tssrc/main/native-chat/agent-session-wire/structured-agent-session-create-adoption.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-failure-text.tssrc/main/native-chat/agent-session-wire/structured-agent-session-host-mutations.tssrc/main/native-chat/agent-session-wire/structured-agent-session-host-types.tssrc/main/native-chat/agent-session-wire/structured-agent-session-host.tssrc/main/native-chat/agent-session-wire/structured-agent-session-idle-sweep.tssrc/main/native-chat/agent-session-wire/structured-agent-session-switch-admission.tssrc/main/native-chat/agent-session-wire/structured-agent-session-switch-to-chat.tssrc/main/native-chat/agent-session-wire/structured-agent-session-terminal-writer-acquire.test.tssrc/main/native-chat/agent-session-wire/structured-provider-session-ownership.tssrc/main/persistence/loading-store/profile-state-delayed-authority-fixture.tssrc/main/persistence/loading-store/pty-spawn-commit-dependencies-fixture.tssrc/main/persistence/terminal-topology/layout-command-persistence.tssrc/main/persistence/terminal-topology/original-terminal-close-store.test.tssrc/main/providers/local-pty-provider-shutdown.test.tssrc/main/providers/local-pty-provider.tssrc/main/providers/local-pty-termination.tssrc/main/runtime/__fixtures__/orca-runtime-terminal-close-continuity-fixtures.tssrc/main/runtime/__fixtures__/terminal-close-runtime.test-fixture.tssrc/main/runtime/mobile-session-layout-projection.tssrc/main/runtime/mobile-session-switch-placement.test.tssrc/main/runtime/mobile-session-tab-close-outcome.tssrc/main/runtime/orca-runtime-original-terminal-close-store.test.tssrc/main/runtime/orca-runtime-original-terminal-close.test.tssrc/main/runtime/orca-runtime-terminal-close-continuity.test.tssrc/main/runtime/orca-runtime.tssrc/main/runtime/orchestration/chat-assignee.test.tssrc/main/runtime/orchestration/chat-assignee.tssrc/main/runtime/rpc/methods/session-tabs.tssrc/main/runtime/rpc/methods/structured-agent-session-gate-classification.test-fixture.tssrc/main/runtime/rpc/methods/structured-agent-session-switch-to-chat.tssrc/main/runtime/rpc/methods/structured-agent-session-switch-to-terminal.tssrc/main/runtime/rpc/methods/structured-agent-session.test.tssrc/main/runtime/rpc/methods/structured-agent-session.tssrc/main/runtime/rpc/methods/terminal/terminal-lifecycle-methods.tssrc/main/runtime/rpc/runtime-close-attribution.test.tssrc/main/runtime/runtime-layout-apply.test.tssrc/main/runtime/runtime-layout-apply.tssrc/main/runtime/runtime-notifier-contract.tssrc/main/runtime/runtime-pty-controller-contract.tssrc/main/runtime/runtime-terminal-contracts.tssrc/main/runtime/session-graph/session-window-graph.tssrc/main/runtime/session-lifecycle/close/close-mobile-row-in-main-layout.tssrc/main/runtime/session-lifecycle/close/close-mobile-session-tab.tssrc/main/runtime/session-lifecycle/projection/build-headless-mobile-session-browser-tabs.tssrc/main/runtime/session-lifecycle/projection/prune-mobile-session-tab-group-layout.tssrc/main/runtime/session-tabs/explicit-terminal-close-pty-stops.tssrc/main/runtime/session-tabs/explicit-terminal-close-stops.tssrc/main/runtime/session-tabs/explicit-terminal-tab-close.tssrc/main/runtime/session-tabs/paired-host-terminal-close.tssrc/main/runtime/session-tabs/structured-session-terminal-launch-retirement.tssrc/main/runtime/session-tabs/terminal-surface-close-commit.tssrc/main/runtime/structured-agent-session-runtime.tssrc/main/runtime/structured-agent-session-windowless-layout-host.test.tssrc/main/runtime/structured-chat-coordinator-mail-rig.test-fixture.tssrc/main/runtime/structured-chat-dispatch-worker-lifecycle.test.tssrc/main/runtime/terminal-intentional-stops.tssrc/main/runtime/terminal-surface-close.tssrc/main/runtime/terminal/identity/terminal-handle-bindings.tssrc/main/runtime/terminal/launch/failed-start-pane.tssrc/main/runtime/terminal/launch/mobile-terminal-create.tssrc/main/runtime/terminal/launch/mobile-terminal-owned-create.tssrc/main/runtime/terminal/launch/mobile-terminal-renderer-create.tssrc/main/runtime/terminal/lifecycle/floating-session-liveness.tssrc/main/runtime/terminal/output/provider-buffer-capture.tssrc/main/runtime/terminal/query/pty-terminal-summary.tssrc/main/runtime/terminal/recovery/resolve-terminal-pane.tssrc/main/runtime/workspace-session-terminal-tab-retirement-identity.tssrc/main/runtime/workspaces/workspace-process-listing.tssrc/main/window/terminal-tab-close-request-relay.tssrc/preload/api/pty-api.tssrc/preload/api/pty-bridge-session-control.tssrc/renderer/src/components/native-chat/NativeChatPaneHeader.test.tsxsrc/renderer/src/components/native-chat/NativeChatPaneHeader.tsxsrc/renderer/src/components/native-chat/NativeChatResumeInCliButton.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.history-read-state.test.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.tsxsrc/renderer/src/components/native-chat/NativeChatTopRightControls.tsxsrc/renderer/src/components/native-chat/native-chat-header-ownership.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-context-menu.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-context-menu.tsxsrc/renderer/src/components/right-sidebar/AiVaultPanel.legacy-filter.test.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionActionMenuItems.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionDetails.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionDetailsSwitchButtons.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionRow.structured-title.test.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionRow.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionSurfaceSwitchMenuItems.tsxsrc/renderer/src/components/right-sidebar/AiVaultVirtualRow.test.tsxsrc/renderer/src/components/right-sidebar/SessionRowTrailingActions.tsxsrc/renderer/src/components/right-sidebar/ai-vault-history-native-return.test.tsxsrc/renderer/src/components/right-sidebar/ai-vault-session-cli-fork-copy.tsxsrc/renderer/src/components/right-sidebar/ai-vault-session-in-place-switch.tssrc/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.tssrc/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-workspace.test.tssrc/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-workspace.tssrc/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat.tssrc/renderer/src/components/right-sidebar/use-ai-vault-panel-session-resume.tssrc/renderer/src/components/tab-bar/CliChatResumeMenuItems.tsxsrc/renderer/src/components/tab-bar/NativeChatCliForkMenuItems.tsxsrc/renderer/src/components/tab-bar/SortableTab.rename-shortcut.test.tsxsrc/renderer/src/components/tab-bar/SortableTab.tsxsrc/renderer/src/components/tab-bar/SortableTabContextMenu.test.tsxsrc/renderer/src/components/tab-bar/SortableTabContextMenu.tsxsrc/renderer/src/components/tab-bar/TabHoverCard.tsxsrc/renderer/src/components/tab-bar/TabSessionSurfaceSwitchMenuItems.test.tsxsrc/renderer/src/components/tab-bar/TabSessionSwitchHoverAction.tsxsrc/renderer/src/components/tab-bar/cli-chat-resume-exit-return.test-fixture.tssrc/renderer/src/components/tab-bar/cli-chat-resume-feedback.tssrc/renderer/src/components/tab-bar/cli-chat-resume-mocks.test-fixture.tssrc/renderer/src/components/tab-bar/cli-chat-resume-parked-retirement.test-fixture.tssrc/renderer/src/components/tab-bar/cli-chat-resume-run.test.tssrc/renderer/src/components/tab-bar/cli-chat-resume-run.tssrc/renderer/src/components/tab-bar/cli-chat-resume-source.tssrc/renderer/src/components/tab-bar/cli-chat-resume-terminal-capture.tssrc/renderer/src/components/tab-bar/cli-chat-resume-transport.test-fixture.tssrc/renderer/src/components/tab-bar/native-chat-cli-switch-run.test.tssrc/renderer/src/components/tab-bar/native-chat-cli-switch-run.tssrc/renderer/src/components/tab-bar/tab-session-history-switch.test.tssrc/renderer/src/components/tab-bar/tab-session-history-switch.tssrc/renderer/src/components/tab-bar/tab-title-tooltip.test.tsxsrc/renderer/src/components/terminal-pane/TerminalContextMenu.tsxsrc/renderer/src/components/terminal-pane/TerminalPaneResumeInChatButton.test.tsxsrc/renderer/src/components/terminal-pane/TerminalPaneResumeInChatButton.tsxsrc/renderer/src/components/terminal-pane/pty-connection-types.tssrc/renderer/src/components/terminal-pane/pty-transport-types.tssrc/renderer/src/components/terminal-pane/terminal-pane-lifecycle-close.tssrc/renderer/src/components/terminal-pane/terminal-pane-mount-cleanup.tssrc/renderer/src/components/terminal-pane/terminal-pane-mount-context.tssrc/renderer/src/components/terminal-pane/terminal-pane-mount-preparation.tssrc/renderer/src/components/terminal-pane/terminal-pane-mount-registration.tssrc/renderer/src/components/terminal-pane/terminal-pane-pane-closed.tssrc/renderer/src/components/terminal-pane/terminal-parked-watcher-registry.tssrc/renderer/src/components/terminal-pane/terminal-presentation-retirement.test-fixture.tssrc/renderer/src/components/terminal-pane/terminal-presentation-retirement.test.tssrc/renderer/src/components/terminal-pane/terminal-presentation-retirement.tssrc/renderer/src/components/terminal-pane/use-terminal-pane-mount-lifecycle.tssrc/renderer/src/components/ui/hover-card.tsxsrc/renderer/src/hooks/ipc-events/agent-status-partial-updates.tssrc/renderer/src/hooks/ipc-events/mobile-terminal-close-ipc-bridge.tssrc/renderer/src/hooks/ipc-events/terminal-request-ipc-bridge-placement.test.tssrc/renderer/src/hooks/ipc-events/terminal-request-ipc-bridge.tssrc/renderer/src/hooks/useIpcEvents-agent-conversation-cap.test.tssrc/renderer/src/hooks/useIpcEvents-agent-status-batch-projection.test.tssrc/renderer/src/hooks/useIpcEvents-agent-status-owner-ordering.test.tssrc/renderer/src/i18n/en-runtime-required.jsonsrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/fr.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/renderer/src/lib/activate-ai-vault-structured-session.tssrc/renderer/src/lib/ai-vault-tab-title-requests.tssrc/renderer/src/lib/in-place-switch-support.test.tsxsrc/renderer/src/lib/in-place-switch-support.tssrc/renderer/src/lib/launch-ai-vault-session-placement.test.tssrc/renderer/src/lib/launch-ai-vault-session.test.tssrc/renderer/src/lib/launch-ai-vault-session.tssrc/renderer/src/lib/pane-manager/pane-inline-images.tssrc/renderer/src/lib/pane-manager/pane-lifecycle.tssrc/renderer/src/lib/pane-manager/pane-manager-tree-mutations.tssrc/renderer/src/lib/pane-manager/pane-manager-types.tssrc/renderer/src/lib/pane-manager/pane-manager.tssrc/renderer/src/lib/pane-manager/pane-split-close.tssrc/renderer/src/lib/pane-manager/pane-webgl-renderer.tssrc/renderer/src/lib/pending-conversation-surface-switches.tssrc/renderer/src/runtime/mounted-terminal-tab-registry.tssrc/renderer/src/runtime/structured-agent-session-client.tssrc/renderer/src/runtime/web-runtime-session-test-harness.tssrc/renderer/src/runtime/web-runtime-session-types.tssrc/renderer/src/runtime/web-runtime-terminal-create-operation.tssrc/renderer/src/runtime/web-runtime-terminal-placement-settlement.tssrc/renderer/src/runtime/web-runtime-terminal-switch-placement.test.tssrc/renderer/src/runtime/web-session-tabs-sync/agent-status-primitives.tssrc/renderer/src/runtime/web-session-tabs-sync/state.tssrc/renderer/src/store/agent-conversation-retention.tssrc/renderer/src/store/index.tssrc/renderer/src/store/slices/agent-pane-conversation.tssrc/renderer/src/store/slices/agent-status-authority-actions.tssrc/renderer/src/store/slices/agent-status-drop-actions.tssrc/renderer/src/store/slices/agent-status-drop-reducer.tssrc/renderer/src/store/slices/agent-status-live-actions.tssrc/renderer/src/store/slices/agent-status-live-reducer.tssrc/renderer/src/store/slices/agent-status-provider-session-actions.tssrc/renderer/src/store/slices/agent-status-provider-session.test.tssrc/renderer/src/store/slices/agent-status-runtime.tssrc/renderer/src/store/slices/agent-status-slice-contract.tssrc/renderer/src/store/slices/agent-status.tssrc/renderer/src/store/slices/store-test-helpers.tssrc/renderer/src/store/slices/worktrees/teardown/worktree-purge-surface-patch.tssrc/renderer/src/store/terminals/terminal-actions.tssrc/renderer/src/store/terminals/terminal-state.tssrc/shared/protocol-version.tssrc/shared/rpc-contract/session-tabs-schemas-params.tssrc/shared/rpc-contract/terminal-unary-params.tssrc/shared/runtime-terminal-contracts.tssrc/shared/session-tab-placement-work.test-fixture.tssrc/shared/session-tab-placement.tssrc/shared/structured-agent-cli-conversation-adoption.tssrc/shared/terminal-layout-leaf-retirement.tssrc/shared/terminal-tab-close.tstests/e2e/cli-chat-resume-terminal-capture.unit.test.tstests/e2e/cross-version-wire/cross-version-agent-session-wire.unit.test.tstests/e2e/cross-version-wire/structured-agent-session-host-fixture.tstests/e2e/cross-version-wire/structured-agent-session-surface-calls.tstests/e2e/cross-version-wire/structured-agent-session-surface-execution.tstests/e2e/cross-version-wire/structured-agent-session-surface-manifest.tstests/e2e/terminal-presentation-retirement.unit.test.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.
-
There was a problem hiding this comment.
Actionable comments posted: 1

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
872da579-cac4-4025-a060-9f4b64c88389
Files ignored due to path filters: 1-
src/shared/rpc-contract/rpc-params-catalog.generated.tsis excluded by!**/*.generated.*
Files selected for processing: 8-
src/main/native-chat/agent-session-wire/structured-agent-session-conversation-lifetime.tssrc/main/native-chat/agent-session-wire/structured-agent-session-host.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/fr.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.json
Files skipped from review as they are similar to previous changes: 5-
- src/renderer/src/i18n/locales/en.json
- src/renderer/src/i18n/locales/ko.json
- src/renderer/src/i18n/locales/es.json
- src/renderer/src/i18n/locales/zh.json
- src/renderer/src/i18n/locales/ja.json
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.
-
[High impact] The PR changes conversation switching across shared chat, terminal, and process-lifecycle paths. The PR is not ready to merge because four earlier blocking findings remain unresolved. Findings
SummaryThis PR lets users move the same conversation between Chat UI and a terminal without making a copy.
The PR description explicitly says not to merge.
|
| const result = await this.client.request<CreateOrAttachResult>( | ||
| 'createOrAttach', | ||
| { | ||
| sessionId: dispatchedSessionId, | ||
| cols: context.effectiveCols, | ||
| rows: context.effectiveRows, | ||
| attachOnly: true | ||
| }, | ||
| 5000 | ||
| ) |
There was a problem hiding this comment.
Failed launches block recovery
When a switch uses an older daemon’s ordinary creation path, a create rejected before any child starts leaves its completion promise unresolved. The later attachOnly request throws SessionNotFoundError, but this branch never settles or removes the retained operation. Later native starts keep waiting, and Restart Terminal Daemon fails at settleSwitchWorkForReplacement.
Settle explicit creation failures through the original request’s outcome. Do not treat an uncertain missing session as proof of exit.
| const prepared = await prepareLegacyTranscriptImport({ | ||
| agent: source.agent, | ||
| piAfterEntry, | ||
| sessionId: source.sessionId, | ||
| options: { filePath: source.filePath } | ||
| }) |
There was a problem hiding this comment.
This call passes only filePath, so a paginated Codex conversation with history_base cannot return to chat. prepareLegacyTranscriptImport follows that base through readCodexLocalHistory, but the resolver throws without account roots. Every Codex return calls this helper, even after the source shell has stopped.
Forward the source’s account storage so the importer can read the earlier history.
| @@ -66,14 +82,14 @@ export class DaemonPtySpawnPreparations { | |||
| this.finish(sessionId, preparation) | |||
| }, 720_000) | |||
There was a problem hiding this comment.
If a prepare reply is lost, the daemon keeps an entry in retained that nobody can release. The client only records recovery after receiving the anchor. The daemon's expiry callback converts the entry into a completed record but does not delete it. Since hasOwedOutcomes() counts every retained record, idle shutdown stays blocked even after all terminals and clients leave.
This leaves an empty daemon running indefinitely and adds retained records on each repeat. Keep enough request identity to release an unanswered prepare, or discard abandoned preparations that never started a child.
| // Shutdown already accepted must finish even when the observing request expires. | ||
| try { | ||
| await host.trackSwitchPhysical(sessionId, close.stop()) |
There was a problem hiding this comment.
Cross-workspace returns close the terminal
When the conversation already has a native chat in another workspace, this path still stops the source terminal and later calls close.retire(). switchRetainedOwner can select that other workspace, but neither close checks the destination workspace. The source terminal disappears instead of staying open as the PR describes.
Compare the source and destination workspaces before stopping the source, and skip its retirement for this case.
…s its chat right beside that tab
…tched tab moves by its terminal, never by an id it no longer holds
…ner first says which tab, the chat-tab record commits, then the tab changes; a late or lost answer never undoes a committed switch
…st authoritative inventory, and no other frame changes a tab
…ch on the host changes what that tab holds, keeping its place, pin and selection, also after a restart
…rminal, by the tab or by its terminal; split focus and orphan recovery read the terminal a tab shows
…t tab, also when the chat was open in another tab, which closes as a duplicate
…or Session History: a whole tab keeps its selection because the same tab holds the chat
… row by the conversation, and keys the stop record the way Pi's import keys its rows
…itting anything to it, so that terminal keeps its size and screen
…b already shows, asked of the window that holds the tab, so a late tab graph never starts a second CLI
…e chat reopened under its own row, and a chat's lifetimes are told apart by the row each publishes
…place, pin and id through a host switch and a restart whatever it first showed, and two hosts' tabs never take each other's place
…nswer is asked again of that tab instead of opening the chat or terminal elsewhere, a tab the user changed keeps their change with the chat opening in a free tab beside it, and a retry naming no tab finds the switched tab through its owner
…nal that ran this run, even one whose process since exited, and retires the recovered terminals' records in one write
…b the record names takes the chat in place when it still holds just the stopped terminal, and a tab the user changed keeps their change with the chat opening in a free tab
…e lookup, by the tab's saved host tab and what it holds, so a tab first seen as a chat or one whose id another host took first is closed, pinned, focused, moved and anchored on its own host tab, also before the first frame after a restart
…ndow publishes for it, so a chat reopened under its own row is never moved by its retired session row
…rmation only while the tab still holds what the close named, so a tab a switch changed meanwhile keeps its new content
…n owns acts only while each row it named still names that tab when main writes it, so a switch queued ahead of it, or an earlier step of the same reorder, never moves or pins the new content, and a row naming a tab's current terminal pins that tab The window's rename of a terminal main names now renames only a terminal it holds, never a chat a switch left in the tab that once had that terminal's id.
…rors in both drawing modes, so a main-owned host's switch keeps its place, selection and recent order too, and the switched chat's status, notifications, close and focus name that chat rather than what its tab id once held The main-owned frame codec now finds each desktop tab through the same saved host-tab index as the snapshot mirror and saves the association; new tabs take their terminal's own id in both modes. A retired terminal's closed marker never refuses the chat a switch left in a tab with that id; a container id is never read as a terminal's (status retraction, sidebar attribution, orphan group cleanup, split focus, launch completion); a paired chat's phone notification carries the host's pane key; a chat's close and close intent name the row it publishes; and this window's own chat takes the tab main recorded for it.
…es: the window applies it at most once, a lost answer is asked about without changing anything again, a reloaded page never answers for the old one, and a chat the user closed or switched meanwhile stays closed rather than reopening, while a switch the window refused opens the chat in the tab the window itself picks
… tab for it, also beside a tab that already has that id, and a paired host's chat takes over the window's tab holding that same conversation, as before
…s before terminals reconnect puts the chat back in the tab it was switching, only when that tab still holds just the stopped terminal and nothing runs in it; otherwise the terminal keeps its tab and the chat opens beside it, a window that reloads keeps a switch it hadn't saved yet, and opening a chat never replaces what another tab holds
…n the host is an older Orca, a chat opened where its usual tab id is taken records the tab it actually opens in, and a chat's status counts under the workspace of the tab showing it whatever that tab's id looks like
…he user hasn't changed them since it opened, so nothing the user did in the first seconds is replaced; a changed one keeps its stopped terminal with the chat opening beside it Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
A use case for this PR. We're considering moving long-lived Claude agents from terminals into native chat, and found this while planning the way back (from reading 1.4.247, not from doing it). When an agent has to go from a chat back to a terminal, "Resume in New Terminal" runs Two things we would need on top: a CLI form of the switch, so a script can do it without the UI, and the closed chat staying refused while the terminal owns the conversation, as Orca already does today. |



Brennan requested an ad hoc build from the finished branch to try it. Do not merge this PR. The separate proposal to keep the reply going across the switch is on hold.
The problem
A conversation with an agent could live in Chat UI or in the agent's own terminal, but moving it meant opening a copy in a second tab ("Continue in Chat UI" / "Continue with Terminal Agent"). That left two views of one conversation, sometimes two agents writing to it, and tabs that piled up.
What changes for users
Switch between Chat UI and the agent's terminal in the same tab, continuing the same conversation with the same agent. The destination takes the source's place, position and pin. Switching one pane of a split keeps its siblings and opens the chat right beside the split.
The switch appears in the pane header, the tab hover card, the tab menu and Session History while the Chat UI setting is on. Claude, Codex, Grok, OpenCode, OpenCode 2 and Pi switch in place when their host supports it. omp, and any agent on a host that can't switch, keep main's "Continue in Chat UI" / "Continue with Terminal Agent", which open a copy beside the source.
The switch never asks. It stops the agent the ordinary way, even mid-reply, waits for proof that it exited, brings in any conversation the terminal wrote, and then shows the destination. A stopped turn reads "Interrupted". Switching from a terminal closes that whole terminal, as "Continue in Chat UI" does on main: an agent paused with Ctrl+Z,
exec claude, or a job running beside the agent ends with it. In a split, only the switched pane closes.If a switch fails after the agent was stopped, the source tab stays where it was with " stopped" and its last screen, plus Restart for a fresh shell and Switch to Chat UI to try again. Paired desktops show the same. Failures name the agent and say what to do, once.
How it works
The host runs the switch as one operation for local and paired windows alike: ordinary Stop, proof of process exit, import of what the terminal wrote, then one layout write that replaces the source tab. A retry after a lost reply reuses the first attempt's terminal instead of starting another. Placement and pin travel as optional hints on the existing tab messages, so nothing new is saved and mixed client and server versions keep working; there is no minimum server version.
Compatibility and differences
Intended:
Temporary:
Verification
CI is green on
7f5e913. Running-app QA used isolated test copies, with agents signed in through the load balancers.7f5e913(M4Air):94db3a9(flows unchanged since):sleep 600 &beside Claude: the terminal closes and the chat works.13b8381, including a graceful restart.7f5e913reverts only how a Ctrl+Z-paused turn is labelled, and the macOS run re-proved that labelling on the final head.4d27b2e).Details, screenshots and what wasn't verified are in the review status comment. The ownership prerequisite (#30930) is merged; this PR targets main.
Author: @BrennanKB5