refactor(runtime): check session graphs and tab lifecycle (#27694)

This commit is contained in:
Neil
2026-10-14 19:11:03 -07:00
committed by GitHub
parent 8148480655
commit 1bdbc23648
45 changed files with 1091 additions and 335 deletions
+3 -3
View File
@@ -54,12 +54,12 @@
3 src/main/runtime/mobile-session-layout-projection.ts
2 src/main/runtime/orca-runtime-adopt-terminal-orphans-from-inventory.ts
2 src/main/runtime/orca-runtime-apply-mobile-session-tab-navigation.ts
2 src/main/runtime/orca-runtime-attach-window.ts
2 src/main/runtime/session-graph/session-graph-promotion.ts
2 src/main/runtime/orca-runtime-build-headless-mobile-session-browser-tabs.ts
1 src/main/runtime/orca-runtime-close-structured-agent-session-tab.ts
1 src/main/runtime/session-tabs/structured-session-tab-close.ts
1 src/main/runtime/orca-runtime-get-runtime-id.ts
2 src/main/runtime/orca-runtime-persist-headless-terminal-title.ts
1 src/main/runtime/orca-runtime-persist-terminal-surface-retirements.ts
1 src/main/runtime/session-tabs/terminal-surface-retirements.ts
1 src/main/runtime/orca-runtime-pty-foreground-process-reads.ts
3 src/main/runtime/orca-runtime-stop-terminals-for-worktree.ts
1 src/main/runtime/orca-runtime-terminal-drivers.ts
@@ -37,7 +37,10 @@ const STACK_WRITERS = [
'src/renderer/src/runtime/sync-runtime-graph.ts',
'src/renderer/src/runtime/web-session-tabs-sync/store-patch.ts',
'src/main/runtime/orca-runtime-split-terminal.ts',
'src/main/runtime/orca-runtime-sync-window-graph.ts',
'src/main/runtime/session-graph/session-window-graph.ts',
'src/main/runtime/session-tabs/mobile-session-hydration.ts',
'src/main/runtime/session-tabs/mobile-session-reconciliation.ts',
'src/main/runtime/session-tabs/mobile-session-preservation.ts',
'src/main/runtime/mobile-session-layout-projection.ts',
'src/main/runtime/headless-runtime-graph.ts',
'src/main/persistence/loading-store/session-host-partitions.ts',
@@ -38,6 +38,10 @@ const LAYOUT_ORACLE_PREFIXES = [
'src/main/runtime/terminal-surface-close.ts',
'src/main/runtime/terminal/output/terminal-derived-agent-facts.ts',
'src/main/runtime/terminal/output/terminal-output-generation.ts',
'src/main/runtime/session-graph/session-window-graph.ts',
'src/main/runtime/session-tabs/mobile-session-hydration.ts',
'src/main/runtime/session-tabs/mobile-session-reconciliation.ts',
'src/main/runtime/session-tabs/mobile-session-preservation.ts',
'src/renderer/src/components/terminal/terminal-tab-actions.ts',
'src/renderer/src/components/tab-bar/web-runtime-tab-move-mirror.ts',
'src/shared/rpc-contract/workspace-layout-params.ts',
-17
View File
@@ -5,13 +5,9 @@
# Regenerate/prune: pnpm check:ts-nocheck-ratchet --prune (removes stale entries only)
src/main/runtime/orca-runtime-adopt-terminal-orphans-from-inventory.ts
src/main/runtime/orca-runtime-apply-mobile-session-tab-navigation.ts
src/main/runtime/orca-runtime-attach-window.ts
src/main/runtime/orca-runtime-build-headless-mobile-session-browser-tabs.ts
src/main/runtime/orca-runtime-build-pty-terminal-summary.ts
src/main/runtime/orca-runtime-close-headless-mobile-terminal-tab.ts
src/main/runtime/orca-runtime-close-mobile-session-tab.ts
src/main/runtime/orca-runtime-close-structured-agent-session-tab.ts
src/main/runtime/orca-runtime-collect-mobile-visible-graph-changed-worktrees.ts
src/main/runtime/orca-runtime-controller-knows-pty-is-live.ts
src/main/runtime/orca-runtime-create-agent-session.ts
src/main/runtime/orca-runtime-create-mobile-session-terminal.ts
@@ -26,29 +22,23 @@ src/main/runtime/orca-runtime-focus-terminal.ts
src/main/runtime/orca-runtime-get-agent-session-execution-namespace.ts
src/main/runtime/orca-runtime-get-pty-record-for-pane-key.ts
src/main/runtime/orca-runtime-get-runtime-id.ts
src/main/runtime/orca-runtime-get-structured-agent-session-create-support.ts
src/main/runtime/orca-runtime-get-worktree-ps.ts
src/main/runtime/orca-runtime-get-worktree-terminal-provisioning-host.ts
src/main/runtime/orca-runtime-has-exact-persisted-terminal-surface-identity.ts
src/main/runtime/orca-runtime-has-live-or-persisted-serve-or-ssh-owned-pty-binding.ts
src/main/runtime/orca-runtime-has-terminals-for-worktree.ts
src/main/runtime/orca-runtime-hydrate-headless-mobile-session-tabs-from-workspace-session.ts
src/main/runtime/orca-runtime-invalidate-all-handles-for-pty.ts
src/main/runtime/orca-runtime-linear-commands.ts
src/main/runtime/orca-runtime-list-managed-worktrees.ts
src/main/runtime/orca-runtime-merge-preserved-headless-mobile-session-tabs.ts
src/main/runtime/orca-runtime-notify-ssh-state-changed.ts
src/main/runtime/orca-runtime-on-pty-exit.ts
src/main/runtime/orca-runtime-perform-mobile-session-pty-records-refresh.ts
src/main/runtime/orca-runtime-persist-headless-terminal-title.ts
src/main/runtime/orca-runtime-persist-terminal-surface-retirements.ts
src/main/runtime/orca-runtime-prepare-pty-execution-context.ts
src/main/runtime/orca-runtime-preserved-branch-cleanup.ts
src/main/runtime/orca-runtime-prune-mobile-session-tab-group-layout.ts
src/main/runtime/orca-runtime-pty-foreground-process-reads.ts
src/main/runtime/orca-runtime-publish-pty-backed-mobile-session-terminal.ts
src/main/runtime/orca-runtime-reconcile-headless-mobile-session-browser-tabs.ts
src/main/runtime/orca-runtime-refresh-floating-workspace-pty-liveness.ts
src/main/runtime/orca-runtime-refresh-pty-worktree-records-from-controller.ts
src/main/runtime/orca-runtime-refresh-pty-worktree-records-with-controller-inventory.ts
src/main/runtime/orca-runtime-refuse-unattributed-mobile-session-tab-close.ts
@@ -57,8 +47,6 @@ src/main/runtime/orca-runtime-resolve-browser-network-execution-host-for-worktre
src/main/runtime/orca-runtime-resolve-known-workspace-file-target.ts
src/main/runtime/orca-runtime-resolve-terminal-pane.ts
src/main/runtime/orca-runtime-resolve-terminal-split-source-authority.ts
src/main/runtime/orca-runtime-restore-live-paired-renderer-session-owned-mobile-terminals.ts
src/main/runtime/orca-runtime-restore-structured-agent-session-tabs-once.ts
src/main/runtime/orca-runtime-run-create-mobile-session-terminal.ts
src/main/runtime/orca-runtime-runtime-id.ts
src/main/runtime/orca-runtime-schedule-mobile-session-tabs-changed.ts
@@ -68,16 +56,11 @@ src/main/runtime/orca-runtime-split-pty-backed-terminal.ts
src/main/runtime/orca-runtime-split-terminal.ts
src/main/runtime/orca-runtime-state-fields.ts
src/main/runtime/orca-runtime-stop-exact-terminals-for-worktree.ts
src/main/runtime/orca-runtime-stop-explicitly-closed-tab-ptys.ts
src/main/runtime/orca-runtime-stop-requested-pty-ids.ts
src/main/runtime/orca-runtime-stop-terminals-for-worktree.ts
src/main/runtime/orca-runtime-stored-mobile-snapshot-has-stale-preserved-tab.ts
src/main/runtime/orca-runtime-sync-mobile-session-tabs.ts
src/main/runtime/orca-runtime-sync-window-graph.ts
src/main/runtime/orca-runtime-terminal-create-deduplication.ts
src/main/runtime/orca-runtime-terminal-drivers.ts
src/main/runtime/orca-runtime-touch-mobile-session-tabs-for-worktree.ts
src/main/runtime/orca-runtime-transition-graph-reload-to-terminal-state.ts
src/main/runtime/orca-runtime-verify-orchestration-compatibility-caller.ts
src/main/runtime/orca-runtime-wait-for-leaf-pty-id.ts
src/main/runtime/orca-runtime-wait-for-mobile-terminal-surface.ts
+1 -1
View File
@@ -9,7 +9,7 @@ import {
import { AGENT_TRUST_KEYED_BY_START_FOLDER } from './execution-host-workspace-trust'
type AgentWorkspaceTrustSetting =
| Pick<GlobalSettings, 'agentWorkspaceTrustEnabled'>
| Partial<Pick<GlobalSettings, 'agentWorkspaceTrustEnabled'>>
| null
| undefined
@@ -6,7 +6,7 @@ import { LOCAL_EXECUTION_HOST_ID } from '../../../shared/execution-host'
import type { TerminalPaneLayoutNode } from '../../../shared/terminal-tab-types'
import type { WorkspaceSessionState } from '../../../shared/workspace-session-state-types'
import { TEST_LEAF_1, TEST_LEAF_2 } from '../../persistence-session-fixtures'
import type { OrcaRuntimeWithCloseHeadlessMobileTerminalTab } from '../../runtime/orca-runtime-close-headless-mobile-terminal-tab'
import type { OrcaRuntimeWithCloseHeadlessMobileTerminalTab } from '../../runtime/session-tabs/headless-terminal-tab-close'
import { OrcaRuntimeService } from '../../runtime/orca-runtime'
import type { Store } from '../loading-store/store'
import { closeLeafOrTab } from './terminal-topology-commit'
@@ -1,98 +0,0 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithNotifySshStateChanged } from './orca-runtime-notify-ssh-state-changed'
import { HEADLESS_RUNTIME_WINDOW_ID } from '../../shared/runtime-types'
import type { ExecutionHostId } from '../../shared/execution-host'
import type { WorkspaceSessionState } from '../../shared/workspace-session-state-types'
import type { RuntimePtyWorktreeRecord } from './runtime-terminal-state-records'
export class OrcaRuntimeWithAttachWindow extends OrcaRuntimeWithNotifySshStateChanged {
attachWindow(windowId: number): void {
if (this.authoritativeWindowId === HEADLESS_RUNTIME_WINDOW_ID) {
if (
this.pendingHeadlessPromotionWindowId !== null &&
windowId !== this.pendingHeadlessPromotionWindowId
) {
return
}
// Why: promotion is a renderer reload of the same graph owner, not a new
// runtime; stale handles must transition before the real window publishes.
this.persistWindowlessPtyBindingsForDesktopAttach()
this.pendingHeadlessPromotionWindowId = windowId
this.authoritativeWindowId = windowId
this.beginGraphReload(windowId)
return
}
if (this.authoritativeWindowId === null) {
// Why: a promoted serve can close and later reopen its window while new
// background PTYs keep arriving; every windowless gap needs this handoff.
this.persistWindowlessPtyBindingsForDesktopAttach()
this.authoritativeWindowId = windowId
}
}
protected persistWindowlessPtyBindingsForDesktopAttach(): void {
if (!this.store?.getWorkspaceSession || !this.store.setWorkspaceSession) {
return
}
const partitions = new Map<
ExecutionHostId,
{ session: WorkspaceSessionState; ptys: RuntimePtyWorktreeRecord[] }
>()
for (const pty of this.ptysById.values()) {
if (!pty.connected || !pty.tabId) {
continue
}
const hostId = this.getWorkspaceSessionHostIdForWorktree(pty.worktreeId)
const session = this.store.getWorkspaceSession(hostId)
const tab = session.tabsByWorktree[pty.worktreeId]?.find(
(candidate) => candidate.id === pty.tabId
)
if (!tab) {
continue
}
const layoutPtyIds = Object.values(
session.terminalLayoutsByTabId[pty.tabId]?.ptyIdsByLeafId ?? {}
)
if (tab.ptyId !== pty.ptyId && !layoutPtyIds.includes(pty.ptyId)) {
continue
}
const partition = partitions.get(hostId) ?? { session, ptys: [] }
partition.ptys.push(pty)
partitions.set(hostId, partition)
}
for (const [hostId, { session, ptys }] of partitions) {
// Why: windowless SSH PTYs must be handed to the desktop through their SSH partition, never the local session.
const activeWorktreeIdsOnShutdown = [
...new Set([
...(session.activeWorktreeIdsOnShutdown ?? []),
...ptys.map((pty) => pty.worktreeId)
])
]
const activeConnectionIdsAtShutdown = [
...new Set([
...(session.activeConnectionIdsAtShutdown ?? []),
...ptys
.map((pty) => pty.connectionId)
.filter((connectionId): connectionId is string => connectionId !== null)
])
]
const remoteSessionIdsByTabId = { ...session.remoteSessionIdsByTabId }
for (const pty of ptys) {
if (pty.connectionId && pty.tabId) {
remoteSessionIdsByTabId[pty.tabId] = pty.ptyId
}
}
this.store.setWorkspaceSession(
{
...session,
activeWorktreeIdsOnShutdown,
...(activeConnectionIdsAtShutdown.length > 0 ? { activeConnectionIdsAtShutdown } : {}),
...(Object.keys(remoteSessionIdsByTabId).length > 0 ? { remoteSessionIdsByTabId } : {})
},
hostId
)
}
}
}
@@ -1,5 +1,5 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithPersistTerminalSurfaceRetirements } from './orca-runtime-persist-terminal-surface-retirements'
import { OrcaRuntimeWithPersistTerminalSurfaceRetirements } from './session-tabs/terminal-surface-retirements'
import type {
RuntimeMobileSessionBrowserTab,
RuntimeMobileSessionTabsResult,
@@ -33,7 +33,7 @@ import type { ResolvedWorktree } from './runtime-worktree-path-identity'
import type { Repo } from '../../shared/repo-types'
export abstract class OrcaRuntimeWithFileCommands extends OrcaRuntimeWithPreservedBranchCleanup {
protected abstract requireStore(): RuntimeStore
abstract requireStore(): RuntimeStore
protected abstract resolveWorktreeSelector(selector: string): Promise<ResolvedWorktree>
@@ -1,5 +1,5 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce } from './orca-runtime-restore-structured-agent-session-tabs-once'
import { OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce } from './session-tabs/structured-session-tab-restoration'
import { DEFAULT_WORKTREE_LIST_LIMIT } from './orca-runtime-postlude'
import type { RuntimeWorktreeListResult } from '../../shared/runtime-types'
import type { DetectedWorktreeListResult, Worktree } from '../../shared/worktree/types'
@@ -194,7 +194,7 @@ export abstract class OrcaRuntimeWithPersistHeadlessTerminalTitle extends Runtim
// Why: same defect as `resolveRuntimeGitTarget` above, in ~30 filesystem dispatches. `getRepo(id)`
// is host-blind and never read `worktree.hostId`, and the `connectionId` it returned spelled
// "runtime host", "unresolved" and "genuinely local" all as `undefined` (#11163).
protected async resolveRuntimeFileTarget(worktreeSelector: string): Promise<{
async resolveRuntimeFileTarget(worktreeSelector: string): Promise<{
worktree: ResolvedWorktree
executionHostId: ExecutionHostId
}> {
@@ -1,5 +1,5 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithHydrateHeadlessMobileSessionTabsFromWorkspaceSession } from './orca-runtime-hydrate-headless-mobile-session-tabs-from-workspace-session'
import { OrcaRuntimeWithHydrateHeadlessMobileSessionTabsFromWorkspaceSession } from './session-tabs/mobile-session-hydration'
import type {
RuntimeMobileSessionBrowserTab,
RuntimeMobileSessionSnapshotTab,
@@ -1,5 +1,5 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithTransitionGraphReloadToTerminalState } from './orca-runtime-transition-graph-reload-to-terminal-state'
import { OrcaRuntimeWithTransitionGraphReloadToTerminalState } from './session-graph/session-graph-authority'
import type { ExecutionHostId } from '../../shared/execution-host'
import {
floatingWorkspaceToWorktree,
@@ -1,5 +1,5 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithStoredMobileSnapshotHasStalePreservedTab } from './orca-runtime-stored-mobile-snapshot-has-stale-preserved-tab'
import { OrcaRuntimeWithStoredMobileSnapshotHasStalePreservedTab } from './session-tabs/mobile-session-publication-preservation'
import type {
BrowserTabInfo,
RuntimeMobileSessionClientTab,
@@ -1,5 +1,5 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithStopExplicitlyClosedTabPtys } from './orca-runtime-stop-explicitly-closed-tab-ptys'
import { OrcaRuntimeWithStopExplicitlyClosedTabPtys } from './session-tabs/explicit-terminal-close-stops'
import type { TerminalPaneSplitSource } from '../../shared/feature-education-telemetry'
import type { RuntimeTerminalSplit } from '../../shared/runtime-types'
import { randomUUID } from 'node:crypto'
@@ -149,7 +149,7 @@ class RemovalRuntime extends OrcaRuntimeService {
return super.localRemovalJobHost(runtimeStore)
}
protected override requireStore(): RuntimeStore {
override requireStore(): RuntimeStore {
if (this.stopAtStore) {
throw this.helperFailure
}
@@ -1,5 +1,5 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithRestoreLivePairedRendererSessionOwnedMobileTerminals } from './orca-runtime-restore-live-paired-renderer-session-owned-mobile-terminals'
import { OrcaRuntimeWithRestoreLivePairedRendererSessionOwnedMobileTerminals } from './session-tabs/renderer-session-terminal-rescue'
import type { TerminalOscLinkRange } from '../../shared/terminal-osc-link-ranges'
export class OrcaRuntimeWithWaitForLeafPtyId extends OrcaRuntimeWithRestoreLivePairedRendererSessionOwnedMobileTerminals {
@@ -1,6 +1,6 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { isRemoteRuntimePtyId } from '../../shared/remote-runtime-pty-id'
import { OrcaRuntimeWithRefreshFloatingWorkspacePtyLiveness } from './orca-runtime-refresh-floating-workspace-pty-liveness'
import { OrcaRuntimeWithRefreshFloatingWorkspacePtyLiveness } from './terminal/lifecycle/floating-session-liveness'
import { writeOrchestrationPointerWithSettlement } from './orchestration/mailbox-pointer-pty-write'
import type { WriteSettlement } from '../../shared/pty-write-settlement'
import type { RuntimeLeafRecord } from './runtime-terminal-state-records'
@@ -3,7 +3,7 @@ import type {
RuntimeMobileSessionBrowserTab,
RuntimeMobileSessionTabsSnapshot
} from '../../shared/runtime-types'
import { OrcaRuntimeWithCloseStructuredAgentSessionTab } from './orca-runtime-close-structured-agent-session-tab'
import { OrcaRuntimeWithCloseStructuredAgentSessionTab } from './session-tabs/structured-session-tab-close'
import { OrcaRuntimeWithReconcileHeadlessMobileSessionBrowserTabs } from './orca-runtime-reconcile-headless-mobile-session-browser-tabs'
const rendererPage: RuntimeMobileSessionBrowserTab = {
@@ -28,7 +28,7 @@ describe('runtime migration catalog capabilities', () => {
it('re-enters requireStore after checking the captured store capabilities', () => {
const seen: OrcaRuntimeService[] = []
class Probe extends OrcaRuntimeService {
protected override requireStore(): RuntimeStore {
override requireStore(): RuntimeStore {
seen.push(this)
return super.requireStore()
}
@@ -41,7 +41,7 @@ describe('runtime migration catalog capabilities', () => {
it('retains the native missing-method error on a different returned store', () => {
const seen: OrcaRuntimeService[] = []
class Probe extends OrcaRuntimeService {
protected override requireStore(): RuntimeStore {
override requireStore(): RuntimeStore {
seen.push(this)
return store
}
@@ -54,7 +54,7 @@ describe('runtime migration catalog capabilities', () => {
it('mutates the returned store before its native missing-flush rejection', async () => {
const stage = vi.fn(() => staged)
class Probe extends OrcaRuntimeService {
protected override requireStore(): RuntimeStore {
override requireStore(): RuntimeStore {
return { ...store, stageOrcadMigrationCatalog: stage }
}
}
@@ -88,6 +88,8 @@ export type RuntimeStore = {
getMobileClientTabSelections?: Store['getMobileClientTabSelections']
setMobileClientTabSelections?: Store['setMobileClientTabSelections']
getSettings(): {
agentWorkspaceTrustEnabled?: GlobalSettings['agentWorkspaceTrustEnabled']
codexManagedAccounts?: GlobalSettings['codexManagedAccounts']
nestedWorkerMaxDepth?: GlobalSettings['nestedWorkerMaxDepth']
workspaceDir: string
nestWorkspaces: boolean
@@ -1,23 +1,31 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithHasTerminalsForWorktree } from './orca-runtime-has-terminals-for-worktree'
import { HEADLESS_RUNTIME_WINDOW_ID } from '../../shared/runtime-types'
import type { FolderWorkspace } from '../../shared/folder-workspace-types'
import type { OrcaRuntimeWithResolveBrowserNetworkExecutionHostForWorktree as ResolveBrowserNetworkExecutionHostForWorktreeMethods } from '../orca-runtime-resolve-browser-network-execution-host-for-worktree'
import { OrcaRuntimeWithHasTerminalsForWorktree } from './session-graph-reload-lifecycle'
import { HEADLESS_RUNTIME_WINDOW_ID } from '../../../shared/runtime-types'
import type { FolderWorkspace } from '../../../shared/folder-workspace-types'
import {
assertFolderWorkspacePathUsable,
getFolderWorkspacePathStatus,
inferFolderWorkspacePathConnection
} from '../project-groups/folder-workspace-path-status'
import type { TerminalWorkspaceLaunchScope } from './runtime-legacy-worker-terminal-recovery-types'
import { folderWorkspaceKey, parseWorkspaceKey } from '../../shared/workspace-scope'
import type { ResolvedWorktree } from './runtime-worktree-path-identity'
import type { BrowserExecutionHostKeyResolution } from './runtime-browser-client-page-adoption'
import { browserNetworkExecutionHostKey } from '../browser/browser-network-execution-route'
import type { ClientHostedBrowserRpcRoute } from './runtime-browser-client-automation'
import { routeRuntimeBrowserClientAutomation } from './runtime-browser-client-automation'
import { getRuntimeBrowserPageRegistry } from './runtime-browser-page-registry'
import { getBrowserHostLeaseRegistry } from './browser-host-lease-registry-instance'
} from '../../project-groups/folder-workspace-path-status'
import type { TerminalWorkspaceLaunchScope } from '../runtime-legacy-worker-terminal-recovery-types'
import { folderWorkspaceKey, parseWorkspaceKey } from '../../../shared/workspace-scope'
import type { ResolvedWorktree } from '../runtime-worktree-path-identity'
import type { BrowserExecutionHostKeyResolution } from '../runtime-browser-client-page-adoption'
import { browserNetworkExecutionHostKey } from '../../browser/browser-network-execution-route'
import type { ClientHostedBrowserRpcRoute } from '../runtime-browser-client-automation'
import { routeRuntimeBrowserClientAutomation } from '../runtime-browser-client-automation'
import { getRuntimeBrowserPageRegistry } from '../runtime-browser-page-registry'
import { getBrowserHostLeaseRegistry } from '../browser-host-lease-registry-instance'
export abstract class OrcaRuntimeWithTransitionGraphReloadToTerminalState extends OrcaRuntimeWithHasTerminalsForWorktree {
protected abstract resolveBrowserNetworkExecutionHostForWorktree(
...args: Parameters<
ResolveBrowserNetworkExecutionHostForWorktreeMethods['resolveBrowserNetworkExecutionHostForWorktree']
>
): ReturnType<
ResolveBrowserNetworkExecutionHostForWorktreeMethods['resolveBrowserNetworkExecutionHostForWorktree']
>
export class OrcaRuntimeWithTransitionGraphReloadToTerminalState extends OrcaRuntimeWithHasTerminalsForWorktree {
protected transitionGraphReloadToTerminalState(windowId: number): void {
if (this.shouldRestoreHeadlessGraph(windowId)) {
this.restoreHeadlessGraphAuthority()
@@ -0,0 +1,192 @@
import type { OrcaRuntimeWithHasTerminalsForWorktree as HasTerminalsForWorktreeMethods } from './session-graph-reload-lifecycle'
import type { OrcaRuntimeWithBindPtyIncarnationHandle as BindPtyIncarnationHandleMethods } from '../terminal/identity/terminal-handle-bindings'
import type { OrcaRuntimeWithResolveExitWaiters as ResolveExitWaitersMethods } from '../terminal/wait/terminal-wait-settlements'
import type { OrcaRuntimeWithCollectMobileVisibleGraphChangedWorktrees as CollectMobileVisibleGraphChangedWorktreesMethods } from '../session-tabs/session-tabs-inventory'
import type { OrcaRuntimeWithGetPtyRecordForPaneKey as GetPtyRecordForPaneKeyMethods } from '../orca-runtime-get-pty-record-for-pane-key'
import type { OrcaRuntimeWithResolveWaiter as ResolveWaiterMethods } from '../orca-runtime-resolve-waiter'
import type { OrcaRuntimeWithMarkPtyLivenessUnverifiable as MarkPtyLivenessUnverifiableMethods } from '../terminal/layout/terminal-input-floor'
import type { OrcaRuntimeWithRecordPtyWorktree as RecordPtyWorktreeMethods } from '../terminal/identity/pty-worktree-record'
import type { RuntimeSessionTabsInventoryPublication as SessionTabsInventoryPublicationMethods } from '../session-tabs/session-tabs-inventory-publication'
import type { OrcaRuntimeWithRefreshFloatingWorkspacePtyLiveness as RefreshFloatingWorkspacePtyLivenessMethods } from '../terminal/lifecycle/floating-session-liveness'
import type { OrcaRuntimeWithTouchMobileSessionTabsForWorktree as TouchMobileSessionTabsForWorktreeMethods } from '../orca-runtime-touch-mobile-session-tabs-for-worktree'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from '../orca-runtime-schedule-mobile-session-tabs-changed'
import type { OrcaRuntimeWithSyncMobileSessionTabs as SyncMobileSessionTabsMethods } from '../session-tabs/mobile-session-reconciliation'
import { OrcaRuntimeWithNotifySshStateChanged } from '../orca-runtime-notify-ssh-state-changed'
import { HEADLESS_RUNTIME_WINDOW_ID } from '../../../shared/runtime-types'
import type { ExecutionHostId } from '../../../shared/execution-host'
import type { WorkspaceSessionState } from '../../../shared/workspace-session-state-types'
import type { RuntimePtyWorktreeRecord } from '../runtime-terminal-state-records'
export abstract class OrcaRuntimeWithAttachWindow extends OrcaRuntimeWithNotifySshStateChanged {
protected abstract beginGraphReload(
...args: Parameters<HasTerminalsForWorktreeMethods['beginGraphReload']>
): ReturnType<HasTerminalsForWorktreeMethods['beginGraphReload']>
protected abstract adoptFirstPtyForLeafHandle(
...args: Parameters<BindPtyIncarnationHandleMethods['adoptFirstPtyForLeafHandle']>
): ReturnType<BindPtyIncarnationHandleMethods['adoptFirstPtyForLeafHandle']>
protected abstract adoptPreAllocatedHandle(
...args: Parameters<BindPtyIncarnationHandleMethods['adoptPreAllocatedHandle']>
): ReturnType<BindPtyIncarnationHandleMethods['adoptPreAllocatedHandle']>
protected abstract checkDeliverySettledAndArmRecheck(
...args: Parameters<ResolveExitWaitersMethods['checkDeliverySettledAndArmRecheck']>
): ReturnType<ResolveExitWaitersMethods['checkDeliverySettledAndArmRecheck']>
protected abstract collectMobileVisibleGraphChangedWorktrees(
...args: Parameters<
CollectMobileVisibleGraphChangedWorktreesMethods['collectMobileVisibleGraphChangedWorktrees']
>
): ReturnType<
CollectMobileVisibleGraphChangedWorktreesMethods['collectMobileVisibleGraphChangedWorktrees']
>
protected abstract deliverPendingMessagesForLeaf(
...args: Parameters<GetPtyRecordForPaneKeyMethods['deliverPendingMessagesForLeaf']>
): ReturnType<GetPtyRecordForPaneKeyMethods['deliverPendingMessagesForLeaf']>
protected abstract getLeafKey(
...args: Parameters<ResolveWaiterMethods['getLeafKey']>
): ReturnType<ResolveWaiterMethods['getLeafKey']>
abstract getPtyLivenessVerdict(
...args: Parameters<MarkPtyLivenessUnverifiableMethods['getPtyLivenessVerdict']>
): ReturnType<MarkPtyLivenessUnverifiableMethods['getPtyLivenessVerdict']>
protected abstract invalidateLeafHandle(
...args: Parameters<BindPtyIncarnationHandleMethods['invalidateLeafHandle']>
): ReturnType<BindPtyIncarnationHandleMethods['invalidateLeafHandle']>
protected abstract makeRuntimePaneKey(
...args: Parameters<RecordPtyWorktreeMethods['makeRuntimePaneKey']>
): ReturnType<RecordPtyWorktreeMethods['makeRuntimePaneKey']>
abstract markGraphReady(
...args: Parameters<HasTerminalsForWorktreeMethods['markGraphReady']>
): ReturnType<HasTerminalsForWorktreeMethods['markGraphReady']>
protected abstract markSessionTabsInventoryPublished(
...args: Parameters<SessionTabsInventoryPublicationMethods['markSessionTabsInventoryPublished']>
): ReturnType<SessionTabsInventoryPublicationMethods['markSessionTabsInventoryPublished']>
protected abstract nextTitleObservationSequence(
...args: Parameters<GetPtyRecordForPaneKeyMethods['nextTitleObservationSequence']>
): ReturnType<GetPtyRecordForPaneKeyMethods['nextTitleObservationSequence']>
protected abstract rebuildLeafPtyIndex(
...args: Parameters<RefreshFloatingWorkspacePtyLivenessMethods['rebuildLeafPtyIndex']>
): ReturnType<RefreshFloatingWorkspacePtyLivenessMethods['rebuildLeafPtyIndex']>
protected abstract reconcileMobileSessionRetirementFences(
...args: Parameters<
TouchMobileSessionTabsForWorktreeMethods['reconcileMobileSessionRetirementFences']
>
): ReturnType<TouchMobileSessionTabsForWorktreeMethods['reconcileMobileSessionRetirementFences']>
protected abstract reconcilePtyIncarnationHandles(
...args: Parameters<BindPtyIncarnationHandleMethods['reconcilePtyIncarnationHandles']>
): ReturnType<BindPtyIncarnationHandleMethods['reconcilePtyIncarnationHandles']>
protected abstract recordPtyWorktree(
...args: Parameters<RecordPtyWorktreeMethods['recordPtyWorktree']>
): ReturnType<RecordPtyWorktreeMethods['recordPtyWorktree']>
protected abstract scheduleMobileSessionTabsChanged(
...args: Parameters<ScheduleMobileSessionTabsChangedMethods['scheduleMobileSessionTabsChanged']>
): ReturnType<ScheduleMobileSessionTabsChangedMethods['scheduleMobileSessionTabsChanged']>
protected abstract syncMobileSessionTabs(
...args: Parameters<SyncMobileSessionTabsMethods['syncMobileSessionTabs']>
): ReturnType<SyncMobileSessionTabsMethods['syncMobileSessionTabs']>
attachWindow(windowId: number): void {
if (this.authoritativeWindowId === HEADLESS_RUNTIME_WINDOW_ID) {
if (
this.pendingHeadlessPromotionWindowId !== null &&
windowId !== this.pendingHeadlessPromotionWindowId
) {
return
}
// Why: promotion is a renderer reload of the same graph owner, not a new
// runtime; stale handles must transition before the real window publishes.
this.persistWindowlessPtyBindingsForDesktopAttach()
this.pendingHeadlessPromotionWindowId = windowId
this.authoritativeWindowId = windowId
this.beginGraphReload(windowId)
return
}
if (this.authoritativeWindowId === null) {
// Why: a promoted serve can close and later reopen its window while new
// background PTYs keep arriving; every windowless gap needs this handoff.
this.persistWindowlessPtyBindingsForDesktopAttach()
this.authoritativeWindowId = windowId
}
}
protected persistWindowlessPtyBindingsForDesktopAttach(): void {
if (!this.store?.getWorkspaceSession || !this.store.setWorkspaceSession) {
return
}
const partitions = new Map<
ExecutionHostId,
{ session: WorkspaceSessionState; ptys: RuntimePtyWorktreeRecord[] }
>()
for (const pty of this.ptysById.values()) {
if (!pty.connected || !pty.tabId) {
continue
}
const hostId = this.getWorkspaceSessionHostIdForWorktree(pty.worktreeId)
const session = this.store.getWorkspaceSession(hostId)
const tab = session.tabsByWorktree[pty.worktreeId]?.find(
(candidate) => candidate.id === pty.tabId
)
if (!tab) {
continue
}
const layoutPtyIds = Object.values(
session.terminalLayoutsByTabId[pty.tabId]?.ptyIdsByLeafId ?? {}
)
if (tab.ptyId !== pty.ptyId && !layoutPtyIds.includes(pty.ptyId)) {
continue
}
const partition = partitions.get(hostId) ?? { session, ptys: [] }
partition.ptys.push(pty)
partitions.set(hostId, partition)
}
for (const [hostId, { session, ptys }] of partitions) {
// Why: windowless SSH PTYs must be handed to the desktop through their SSH partition, never the local session.
const activeWorktreeIdsOnShutdown = [
...new Set([
...(session.activeWorktreeIdsOnShutdown ?? []),
...ptys.map((pty) => pty.worktreeId)
])
]
const activeConnectionIdsAtShutdown = [
...new Set([
...(session.activeConnectionIdsAtShutdown ?? []),
...ptys
.map((pty) => pty.connectionId)
.filter((connectionId): connectionId is string => connectionId !== null)
])
]
const remoteSessionIdsByTabId = { ...session.remoteSessionIdsByTabId }
for (const pty of ptys) {
if (pty.connectionId && pty.tabId) {
remoteSessionIdsByTabId[pty.tabId] = pty.ptyId
}
}
this.store.setWorkspaceSession(
{
...session,
activeWorktreeIdsOnShutdown,
...(activeConnectionIdsAtShutdown.length > 0 ? { activeConnectionIdsAtShutdown } : {}),
...(Object.keys(remoteSessionIdsByTabId).length > 0 ? { remoteSessionIdsByTabId } : {})
},
hostId
)
}
}
}
@@ -1,9 +1,59 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithStopExactTerminalsForWorktree } from './orca-runtime-stop-exact-terminals-for-worktree'
import type { RuntimeRendererReloadFence } from './orca-runtime-core'
import { HEADLESS_RUNTIME_WINDOW_ID } from '../../shared/runtime-types'
import type { OrcaRuntimeWithTransitionGraphReloadToTerminalState as TransitionGraphReloadToTerminalStateMethods } from './session-graph-authority'
import type { OrcaRuntimeWithBindPtyIncarnationHandle as BindPtyIncarnationHandleMethods } from '../terminal/identity/terminal-handle-bindings'
import type { OrcaRuntimeWithResolveWaiter as ResolveWaiterMethods } from '../orca-runtime-resolve-waiter'
import { OrcaRuntimeWithStopExactTerminalsForWorktree } from '../orca-runtime-stop-exact-terminals-for-worktree'
import type { RuntimeRendererReloadFence } from '../orca-runtime-core'
import { HEADLESS_RUNTIME_WINDOW_ID } from '../../../shared/runtime-types'
export abstract class OrcaRuntimeWithHasTerminalsForWorktree extends OrcaRuntimeWithStopExactTerminalsForWorktree {
protected abstract assertStableReadyGraph(
...args: Parameters<TransitionGraphReloadToTerminalStateMethods['assertStableReadyGraph']>
): ReturnType<TransitionGraphReloadToTerminalStateMethods['assertStableReadyGraph']>
protected abstract captureReadyGraphEpoch(
...args: Parameters<TransitionGraphReloadToTerminalStateMethods['captureReadyGraphEpoch']>
): ReturnType<TransitionGraphReloadToTerminalStateMethods['captureReadyGraphEpoch']>
protected abstract clearPtyIncarnationHandles(
...args: Parameters<BindPtyIncarnationHandleMethods['clearPtyIncarnationHandles']>
): ReturnType<BindPtyIncarnationHandleMethods['clearPtyIncarnationHandles']>
protected abstract invalidatePtyIncarnationHandle(
...args: Parameters<BindPtyIncarnationHandleMethods['invalidatePtyIncarnationHandle']>
): ReturnType<BindPtyIncarnationHandleMethods['invalidatePtyIncarnationHandle']>
protected abstract refreshWritableFlags(
...args: Parameters<BindPtyIncarnationHandleMethods['refreshWritableFlags']>
): ReturnType<BindPtyIncarnationHandleMethods['refreshWritableFlags']>
protected abstract rejectAllWaiters(
...args: Parameters<ResolveWaiterMethods['rejectAllWaiters']>
): ReturnType<ResolveWaiterMethods['rejectAllWaiters']>
protected abstract rejectWaitersForHandle(
...args: Parameters<ResolveWaiterMethods['rejectWaitersForHandle']>
): ReturnType<ResolveWaiterMethods['rejectWaitersForHandle']>
protected abstract rememberDetachedPreAllocatedLeaves(
...args: Parameters<BindPtyIncarnationHandleMethods['rememberDetachedPreAllocatedLeaves']>
): ReturnType<BindPtyIncarnationHandleMethods['rememberDetachedPreAllocatedLeaves']>
protected abstract restoreHeadlessGraphAuthority(
...args: Parameters<
TransitionGraphReloadToTerminalStateMethods['restoreHeadlessGraphAuthority']
>
): ReturnType<TransitionGraphReloadToTerminalStateMethods['restoreHeadlessGraphAuthority']>
protected abstract shouldRestoreHeadlessGraph(
...args: Parameters<TransitionGraphReloadToTerminalStateMethods['shouldRestoreHeadlessGraph']>
): ReturnType<TransitionGraphReloadToTerminalStateMethods['shouldRestoreHeadlessGraph']>
protected abstract transitionGraphReloadToTerminalState(
...args: Parameters<
TransitionGraphReloadToTerminalStateMethods['transitionGraphReloadToTerminalState']
>
): ReturnType<TransitionGraphReloadToTerminalStateMethods['transitionGraphReloadToTerminalState']>
export class OrcaRuntimeWithHasTerminalsForWorktree extends OrcaRuntimeWithStopExactTerminalsForWorktree {
async hasTerminalsForWorktree(worktreeSelector: string): Promise<boolean> {
const graphEpoch = this.captureReadyGraphEpoch()
const worktree = await this.resolveWorktreeSelector(worktreeSelector)
@@ -1,14 +1,13 @@
/* eslint-disable unicorn/no-useless-spread */
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithAttachWindow } from './orca-runtime-attach-window'
import { OrcaRuntimeWithAttachWindow } from './session-graph-promotion'
import type {
RuntimeRendererSyncWindowGraph,
RuntimeSyncedTab,
RuntimeSyncWindowGraph,
RuntimeSyncWindowGraphResult
} from '../../shared/runtime-types'
import { HEADLESS_RUNTIME_WINDOW_ID } from '../../shared/runtime-types'
import type { RuntimeLeafRecord } from './runtime-terminal-state-records'
} from '../../../shared/runtime-session-contracts'
import { HEADLESS_RUNTIME_WINDOW_ID } from '../../../shared/runtime-types'
import type { RuntimeLeafRecord } from '../runtime-terminal-state-records'
/** The runtime indexes graph tabs by bare id, so duplicate ids cannot be routed safely. */
function assertUniqueRuntimeGraphTabIds(tabs: readonly RuntimeSyncedTab[]): void {
@@ -21,7 +20,10 @@ function assertUniqueRuntimeGraphTabIds(tabs: readonly RuntimeSyncedTab[]): void
}
}
export class OrcaRuntimeWithSyncWindowGraph extends OrcaRuntimeWithAttachWindow {
export abstract class OrcaRuntimeWithSyncWindowGraph extends OrcaRuntimeWithAttachWindow {
declare protected terminalGroupGridSizes?: RuntimeSyncWindowGraph['terminalGroupGridSizes']
declare protected singleGroupWorkspaceGridSize?: RuntimeSyncWindowGraph['singleGroupWorkspaceGridSize']
shouldRelayTerminalBrowserOpens(): boolean {
return this.authoritativeWindowId === HEADLESS_RUNTIME_WINDOW_ID
}
@@ -1,18 +1,17 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithFocusTerminal } from './orca-runtime-focus-terminal'
import { EXPLICIT_TERMINAL_CLOSE_STOP_TIMEOUT_MS } from './orca-runtime-core'
import { SSH_PROVIDER_UNREGISTERED_REASON } from '../../shared/pty-liveness-verdict'
import type { RuntimeTerminalClose } from '../../shared/runtime-types'
import type { RuntimePtyTabCloseAuthority } from './runtime-terminal-state-records'
import { parsePaneKey } from '../../shared/stable-pane-id'
import { errorMessage } from '../../shared/error-message'
import { OrcaRuntimeWithFocusTerminal } from '../orca-runtime-focus-terminal'
import { EXPLICIT_TERMINAL_CLOSE_STOP_TIMEOUT_MS } from '../orca-runtime-core'
import { SSH_PROVIDER_UNREGISTERED_REASON } from '../../../shared/pty-liveness-verdict'
import type { RuntimeTerminalClose } from '../../../shared/runtime-types'
import type { RuntimePtyTabCloseAuthority } from '../runtime-terminal-state-records'
import { parsePaneKey } from '../../../shared/stable-pane-id'
import { errorMessage } from '../../../shared/error-message'
/** How an explicit close's stop of its addressed PTY ended. */
type ExplicitCloseStop = { stopped: boolean }
const NO_STOP: ExplicitCloseStop = { stopped: false }
export class OrcaRuntimeWithStopExplicitlyClosedTabPtys extends OrcaRuntimeWithFocusTerminal {
export abstract class OrcaRuntimeWithStopExplicitlyClosedTabPtys extends OrcaRuntimeWithFocusTerminal {
protected async stopExplicitlyClosedTabPtys(
ptyIds: readonly string[],
addressedPtyId: string
@@ -1,4 +1,4 @@
import { OrcaRuntimeWithCloseHeadlessMobileTerminalTab } from '../orca-runtime-close-headless-mobile-terminal-tab'
import { OrcaRuntimeWithCloseHeadlessMobileTerminalTab } from './headless-terminal-tab-close'
import type { WorkspaceSessionState } from '../../../shared/workspace-session-state-types'
import type { RuntimeMobileSessionTabsSnapshot } from '../../../shared/runtime-session-contracts'
import type { RuntimeMobileSessionSnapshotTab } from '../../../shared/runtime-mobile-session-tab-contracts'
@@ -1,19 +1,58 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithCloseStructuredAgentSessionTab } from './orca-runtime-close-structured-agent-session-tab'
import type { RuntimeHeadlessTabProperties as HeadlessTabPropertiesMethods } from './headless-tab-properties'
import type { RuntimeHeadlessTabGroups as HeadlessTabGroupsMethods } from './headless-tab-group-moves'
import type { OrcaRuntimeWithPersistHeadlessTerminalTitle as PersistHeadlessTerminalTitleMethods } from '../orca-runtime-persist-headless-terminal-title'
import type { OrcaRuntimeWithRestoreLivePairedRendererSessionOwnedMobileTerminals as RestoreLivePairedRendererSessionOwnedMobileTerminalsMethods } from './renderer-session-terminal-rescue'
import { OrcaRuntimeWithCloseStructuredAgentSessionTab } from './structured-session-tab-close'
import type {
RuntimeMobileSessionTabMove,
RuntimeMobileSessionTabMoveResult,
RuntimeMobileSessionTabsSnapshot,
RuntimeMobileSessionTerminalTab
} from '../../shared/runtime-types'
import { parseAppSshPtyId } from '../../shared/ssh-pty-id'
import { buildHeadlessMobileSessionTabGroups } from './mobile-session-layout-projection'
import { appendRetiredTerminalSurfaceProofs } from './mobile-session-terminal-retirement-proof'
import type { RuntimePtyWorktreeRecord } from './runtime-terminal-state-records'
import type { TerminalPaneLayoutNode } from '../../shared/terminal-tab-types'
import type { RuntimeSessionTabCloseReason } from '../../shared/runtime-session-contracts'
} from '../../../shared/runtime-types'
import { parseAppSshPtyId } from '../../../shared/ssh-pty-id'
import { buildHeadlessMobileSessionTabGroups } from '../mobile-session-layout-projection'
import { appendRetiredTerminalSurfaceProofs } from '../mobile-session-terminal-retirement-proof'
import type { RuntimePtyWorktreeRecord } from '../runtime-terminal-state-records'
import type { TerminalPaneLayoutNode } from '../../../shared/terminal-tab-types'
import type { RuntimeSessionTabCloseReason } from '../../../shared/runtime-session-contracts'
export abstract class OrcaRuntimeWithCloseHeadlessMobileTerminalTab extends OrcaRuntimeWithCloseStructuredAgentSessionTab {
protected abstract applyHeadlessSessionTabPropsToSnapshot(
...args: Parameters<HeadlessTabPropertiesMethods['applyHeadlessSessionTabPropsToSnapshot']>
): ReturnType<HeadlessTabPropertiesMethods['applyHeadlessSessionTabPropsToSnapshot']>
protected abstract applyHeadlessTerminalPaneLayoutToSnapshot(
...args: Parameters<HeadlessTabPropertiesMethods['applyHeadlessTerminalPaneLayoutToSnapshot']>
): ReturnType<HeadlessTabPropertiesMethods['applyHeadlessTerminalPaneLayoutToSnapshot']>
protected abstract moveHeadlessMobileSessionTab(
...args: Parameters<HeadlessTabGroupsMethods['moveHeadlessMobileSessionTab']>
): ReturnType<HeadlessTabGroupsMethods['moveHeadlessMobileSessionTab']>
protected abstract normalizeMobileSessionTabOrder(
...args: Parameters<PersistHeadlessTerminalTitleMethods['normalizeMobileSessionTabOrder']>
): ReturnType<PersistHeadlessTerminalTitleMethods['normalizeMobileSessionTabOrder']>
protected abstract persistHeadlessSessionTabProps(
...args: Parameters<HeadlessTabPropertiesMethods['persistHeadlessSessionTabProps']>
): ReturnType<HeadlessTabPropertiesMethods['persistHeadlessSessionTabProps']>
protected abstract persistHeadlessTerminalPaneLayout(
...args: Parameters<HeadlessTabPropertiesMethods['persistHeadlessTerminalPaneLayout']>
): ReturnType<HeadlessTabPropertiesMethods['persistHeadlessTerminalPaneLayout']>
protected abstract resolveMobileSessionHostTabId(
...args: Parameters<PersistHeadlessTerminalTitleMethods['resolveMobileSessionHostTabId']>
): ReturnType<PersistHeadlessTerminalTitleMethods['resolveMobileSessionHostTabId']>
protected abstract setPairedRendererSessionOwnership(
...args: Parameters<
RestoreLivePairedRendererSessionOwnedMobileTerminalsMethods['setPairedRendererSessionOwnership']
>
): ReturnType<
RestoreLivePairedRendererSessionOwnedMobileTerminalsMethods['setPairedRendererSessionOwnership']
>
export class OrcaRuntimeWithCloseHeadlessMobileTerminalTab extends OrcaRuntimeWithCloseStructuredAgentSessionTab {
protected async closeHeadlessMobileTerminalTab(
worktreeId: string,
snapshot: RuntimeMobileSessionTabsSnapshot,
@@ -1,35 +1,97 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import type { OrcaRuntimeWithBuildHeadlessMobileSessionBrowserTabs as BuildHeadlessMobileSessionBrowserTabsMethods } from '../orca-runtime-build-headless-mobile-session-browser-tabs'
import type { OrcaRuntimeWithStoredMobileSnapshotHasStalePreservedTab as StoredMobileSnapshotHasStalePreservedTabMethods } from './mobile-session-publication-preservation'
import type { OrcaRuntimeWithReconcileHeadlessMobileSessionBrowserTabs as ReconcileHeadlessMobileSessionBrowserTabsMethods } from '../orca-runtime-reconcile-headless-mobile-session-browser-tabs'
import type { BrowserWindow } from 'electron'
import { RuntimeSessionTabsInventoryPublication } from './session-tabs/session-tabs-inventory-publication'
import type { WorkspaceSessionState } from '../../shared/workspace-session-state-types'
import { getRuntimeBrowserPageRegistry } from './runtime-browser-page-registry'
import { splitWorktreeIdForFilesystem } from '../../shared/worktree/id'
import { buildHeadlessMobileSessionTerminalTabs } from './mobile-session-terminal-projection'
import { buildHeadlessMobileSessionEditorTabs } from './mobile-session-editor-projection'
import { RuntimeSessionTabsInventoryPublication } from './session-tabs-inventory-publication'
import type { WorkspaceSessionState } from '../../../shared/workspace-session-state-types'
import { getRuntimeBrowserPageRegistry } from '../runtime-browser-page-registry'
import { splitWorktreeIdForFilesystem } from '../../../shared/worktree/id'
import { buildHeadlessMobileSessionTerminalTabs } from '../mobile-session-terminal-projection'
import { buildHeadlessMobileSessionEditorTabs } from '../mobile-session-editor-projection'
import type {
RuntimeMobileSessionSnapshotTab,
RuntimeMobileSessionTabGroup,
RuntimeMobileSessionTabsSnapshot,
RuntimeMobileSessionTerminalTab
} from '../../shared/runtime-types'
} from '../../../shared/runtime-types'
import {
collectHeadlessParentTabOrder,
distributeHeadlessTabsAcrossGroups,
getHeadlessMobileSessionGroupId,
pickHeadlessActiveTerminalTab,
pickRestoredActiveGroupId
} from './mobile-session-layout-projection'
} from '../mobile-session-layout-projection'
import {
mergeMobileSessionSnapshotTabs,
mergeMobileSessionTabGroups
} from './mobile-session-tab-merge'
} from '../mobile-session-tab-merge'
import {
appendBrowserTabOrder,
collectBrowserGroupAssignment
} from './mobile-session-browser-group-projection'
import { headlessMobileSnapshotContentUnchanged } from './mobile-session-snapshot-equality'
} from '../mobile-session-browser-group-projection'
import { headlessMobileSnapshotContentUnchanged } from '../mobile-session-snapshot-equality'
export abstract class OrcaRuntimeWithHydrateHeadlessMobileSessionTabsFromWorkspaceSession extends RuntimeSessionTabsInventoryPublication {
protected abstract buildHeadlessMobileSessionBrowserTabs(
...args: Parameters<
BuildHeadlessMobileSessionBrowserTabsMethods['buildHeadlessMobileSessionBrowserTabs']
>
): ReturnType<
BuildHeadlessMobileSessionBrowserTabsMethods['buildHeadlessMobileSessionBrowserTabs']
>
protected abstract getMergedMobileSessionPublicationEpoch(
...args: Parameters<
StoredMobileSnapshotHasStalePreservedTabMethods['getMergedMobileSessionPublicationEpoch']
>
): ReturnType<
StoredMobileSnapshotHasStalePreservedTabMethods['getMergedMobileSessionPublicationEpoch']
>
protected abstract hasRecentExpiredSshLeasePane(
...args: Parameters<
ReconcileHeadlessMobileSessionBrowserTabsMethods['hasRecentExpiredSshLeasePane']
>
): ReturnType<ReconcileHeadlessMobileSessionBrowserTabsMethods['hasRecentExpiredSshLeasePane']>
protected abstract hasServeOrSshOwnedBinding(
...args: Parameters<
ReconcileHeadlessMobileSessionBrowserTabsMethods['hasServeOrSshOwnedBinding']
>
): ReturnType<ReconcileHeadlessMobileSessionBrowserTabsMethods['hasServeOrSshOwnedBinding']>
protected abstract isHeadlessBuiltMobileSessionPublicationBase(
...args: Parameters<
StoredMobileSnapshotHasStalePreservedTabMethods['isHeadlessBuiltMobileSessionPublicationBase']
>
): ReturnType<
StoredMobileSnapshotHasStalePreservedTabMethods['isHeadlessBuiltMobileSessionPublicationBase']
>
protected abstract reconcileHeadlessMobileSessionBrowserTabs(
...args: Parameters<
ReconcileHeadlessMobileSessionBrowserTabsMethods['reconcileHeadlessMobileSessionBrowserTabs']
>
): ReturnType<
ReconcileHeadlessMobileSessionBrowserTabsMethods['reconcileHeadlessMobileSessionBrowserTabs']
>
protected abstract workspaceSessionHasRuntimeOwnedPtyCandidate(
...args: Parameters<
ReconcileHeadlessMobileSessionBrowserTabsMethods['workspaceSessionHasRuntimeOwnedPtyCandidate']
>
): ReturnType<
ReconcileHeadlessMobileSessionBrowserTabsMethods['workspaceSessionHasRuntimeOwnedPtyCandidate']
>
protected abstract workspaceSessionWorktreeHasRuntimeOwnedPtyCandidate(
...args: Parameters<
ReconcileHeadlessMobileSessionBrowserTabsMethods['workspaceSessionWorktreeHasRuntimeOwnedPtyCandidate']
>
): ReturnType<
ReconcileHeadlessMobileSessionBrowserTabsMethods['workspaceSessionWorktreeHasRuntimeOwnedPtyCandidate']
>
protected abstract getAvailableAuthoritativeWindow(): BrowserWindow | null
protected hydrateHeadlessMobileSessionTabsFromWorkspaceSession(
@@ -1,20 +1,28 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithSyncMobileSessionTabs } from './orca-runtime-sync-mobile-session-tabs'
import type { OrcaRuntimeWithStoredMobileSnapshotHasStalePreservedTab as StoredMobileSnapshotHasStalePreservedTabMethods } from './mobile-session-publication-preservation'
import { OrcaRuntimeWithSyncMobileSessionTabs } from './mobile-session-reconciliation'
import type {
RuntimeMobileSessionSnapshotTab,
RuntimeMobileSessionTabGroup,
RuntimeMobileSessionTabsSnapshot,
RuntimeMobileSessionTerminalTab
} from '../../shared/runtime-types'
} from '../../../shared/runtime-types'
import {
getMobileSessionSnapshotTabIdentityKeys,
mergeMobileSessionSnapshotTabs,
mergeMobileSessionTabGroups
} from './mobile-session-tab-merge'
import { terminalLayoutContainsLeaf } from '../../shared/workspace-session-pane-ownership'
import { getHeadlessMobileSessionGroupId } from './mobile-session-layout-projection'
} from '../mobile-session-tab-merge'
import { terminalLayoutContainsLeaf } from '../../../shared/workspace-session-pane-ownership'
import { getHeadlessMobileSessionGroupId } from '../mobile-session-layout-projection'
export abstract class OrcaRuntimeWithMergePreservedHeadlessMobileSessionTabs extends OrcaRuntimeWithSyncMobileSessionTabs {
protected abstract collectPreservedHeadlessMobileSessionTabs(
...args: Parameters<
StoredMobileSnapshotHasStalePreservedTabMethods['collectPreservedHeadlessMobileSessionTabs']
>
): ReturnType<
StoredMobileSnapshotHasStalePreservedTabMethods['collectPreservedHeadlessMobileSessionTabs']
>
export class OrcaRuntimeWithMergePreservedHeadlessMobileSessionTabs extends OrcaRuntimeWithSyncMobileSessionTabs {
protected mergePreservedHeadlessMobileSessionTabs(
snapshot: RuntimeMobileSessionTabsSnapshot,
existing: RuntimeMobileSessionTabsSnapshot | undefined
@@ -1,16 +1,25 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithMergePreservedHeadlessMobileSessionTabs } from './orca-runtime-merge-preserved-headless-mobile-session-tabs'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from '../orca-runtime-schedule-mobile-session-tabs-changed'
import { OrcaRuntimeWithMergePreservedHeadlessMobileSessionTabs } from './mobile-session-preservation'
import type {
RuntimeMobileSessionSnapshotTab,
RuntimeMobileSessionTabsRemovedResult,
RuntimeMobileSessionTabsSnapshot
} from '../../shared/runtime-types'
import { getMobileSessionSnapshotTabIdentityKeys } from './mobile-session-tab-merge'
import { getRuntimeBrowserPageRegistry } from './runtime-browser-page-registry'
import { sameRuntimeBrowserPlacement } from '../../shared/runtime-browser-placement'
import type { ClientHostedBrowserRowsEvent } from '../../shared/client-hosted-browser-rows'
} from '../../../shared/runtime-types'
import { getMobileSessionSnapshotTabIdentityKeys } from '../mobile-session-tab-merge'
import { getRuntimeBrowserPageRegistry } from '../runtime-browser-page-registry'
import { sameRuntimeBrowserPlacement } from '../../../shared/runtime-browser-placement'
import type { ClientHostedBrowserRowsEvent } from '../../../shared/client-hosted-browser-rows'
export abstract class OrcaRuntimeWithStoredMobileSnapshotHasStalePreservedTab extends OrcaRuntimeWithMergePreservedHeadlessMobileSessionTabs {
protected abstract getListedBrowserTabsByPageId(
...args: Parameters<ScheduleMobileSessionTabsChangedMethods['getListedBrowserTabsByPageId']>
): ReturnType<ScheduleMobileSessionTabsChangedMethods['getListedBrowserTabsByPageId']>
protected abstract notifyMobileSessionTabsChangedNow(
...args: Parameters<
ScheduleMobileSessionTabsChangedMethods['notifyMobileSessionTabsChangedNow']
>
): ReturnType<ScheduleMobileSessionTabsChangedMethods['notifyMobileSessionTabsChangedNow']>
export class OrcaRuntimeWithStoredMobileSnapshotHasStalePreservedTab extends OrcaRuntimeWithMergePreservedHeadlessMobileSessionTabs {
// Why: the accepted-revision no-op gate must not fossilize preserved runtime
// tabs. A stored merged snapshot's tabs absent from the accepted renderer
// publication exist only via preservation; if any such tab no longer
@@ -1,11 +1,44 @@
import type { OrcaRuntimeWithMergePreservedHeadlessMobileSessionTabs as MergePreservedHeadlessMobileSessionTabsMethods } from './mobile-session-preservation'
import type { OrcaRuntimeWithStoredMobileSnapshotHasStalePreservedTab as StoredMobileSnapshotHasStalePreservedTabMethods } from './mobile-session-publication-preservation'
/* eslint-disable unicorn/no-useless-spread */
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithWriteOrchestrationPointerPty } from './orca-runtime-write-orchestration-pointer-pty'
import type { RuntimeMobileSessionTabsSnapshot } from '../../shared/runtime-types'
import type { WorkspaceSessionState } from '../../shared/workspace-session-state-types'
import { getMobileSessionSnapshotTabIdentityKeys } from './mobile-session-tab-merge'
import { OrcaRuntimeWithWriteOrchestrationPointerPty } from '../orca-runtime-write-orchestration-pointer-pty'
import type { RuntimeMobileSessionTabsSnapshot } from '../../../shared/runtime-types'
import type { WorkspaceSessionState } from '../../../shared/workspace-session-state-types'
import { getMobileSessionSnapshotTabIdentityKeys } from '../mobile-session-tab-merge'
export abstract class OrcaRuntimeWithSyncMobileSessionTabs extends OrcaRuntimeWithWriteOrchestrationPointerPty {
protected abstract buildPreservedHeadlessMobileSessionSnapshot(
...args: Parameters<
MergePreservedHeadlessMobileSessionTabsMethods['buildPreservedHeadlessMobileSessionSnapshot']
>
): ReturnType<
MergePreservedHeadlessMobileSessionTabsMethods['buildPreservedHeadlessMobileSessionSnapshot']
>
protected abstract mergePreservedHeadlessMobileSessionTabs(
...args: Parameters<
MergePreservedHeadlessMobileSessionTabsMethods['mergePreservedHeadlessMobileSessionTabs']
>
): ReturnType<
MergePreservedHeadlessMobileSessionTabsMethods['mergePreservedHeadlessMobileSessionTabs']
>
protected abstract releaseRuntimeSessionOwnershipForRendererRetiredTabs(
...args: Parameters<
MergePreservedHeadlessMobileSessionTabsMethods['releaseRuntimeSessionOwnershipForRendererRetiredTabs']
>
): ReturnType<
MergePreservedHeadlessMobileSessionTabsMethods['releaseRuntimeSessionOwnershipForRendererRetiredTabs']
>
protected abstract storedMobileSnapshotHasStalePreservedTab(
...args: Parameters<
StoredMobileSnapshotHasStalePreservedTabMethods['storedMobileSnapshotHasStalePreservedTab']
>
): ReturnType<
StoredMobileSnapshotHasStalePreservedTabMethods['storedMobileSnapshotHasStalePreservedTab']
>
export class OrcaRuntimeWithSyncMobileSessionTabs extends OrcaRuntimeWithWriteOrchestrationPointerPty {
// Returns the worktrees whose stored snapshot object changed during this
// sync, so the caller can fan out only actually-changed worktrees.
protected syncMobileSessionTabs(
@@ -1,11 +1,15 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithWaitForMobileTerminalSurface } from './orca-runtime-wait-for-mobile-terminal-surface'
import { runtimeWorktreeIdsEqual } from './runtime-worktree-path-identity'
import { parsePaneKey } from '../../shared/stable-pane-id'
import type { RuntimePtyWorktreeRecord } from './runtime-terminal-state-records'
import type { RuntimeMobileSessionCreateTerminalResult } from '../../shared/runtime-types'
import type { OrcaRuntimeWithWaitForLeafPtyId as WaitForLeafPtyIdMethods } from '../orca-runtime-wait-for-leaf-pty-id'
import { OrcaRuntimeWithWaitForMobileTerminalSurface } from '../orca-runtime-wait-for-mobile-terminal-surface'
import { runtimeWorktreeIdsEqual } from '../runtime-worktree-path-identity'
import { parsePaneKey } from '../../../shared/stable-pane-id'
import type { RuntimePtyWorktreeRecord } from '../runtime-terminal-state-records'
import type { RuntimeMobileSessionCreateTerminalResult } from '../../../shared/runtime-types'
export abstract class OrcaRuntimeWithRestoreLivePairedRendererSessionOwnedMobileTerminals extends OrcaRuntimeWithWaitForMobileTerminalSurface {
protected abstract resolveHandleForTab(
...args: Parameters<WaitForLeafPtyIdMethods['resolveHandleForTab']>
): ReturnType<WaitForLeafPtyIdMethods['resolveHandleForTab']>
export class OrcaRuntimeWithRestoreLivePairedRendererSessionOwnedMobileTerminals extends OrcaRuntimeWithWaitForMobileTerminalSurface {
protected restoreLivePairedRendererSessionOwnedMobileTerminals(
worktreeId: string | null,
options: { missingSnapshotOnly?: boolean; notify?: boolean } = {}
@@ -0,0 +1,30 @@
import { chatTabChangesMatchingIndex } from '../structured-agent-session-tab-index-seed'
import { chatTabId, type ChatTabChange } from '../agent-session-tab-layout'
import { structuredSessionTabRetired } from '../structured-worker-custody'
import type { getStructuredAgentSessionHost } from '../../native-chat/agent-session-wire/structured-agent-session-registry'
type ChatTabOpen = Extract<ChatTabChange, { open: unknown }>['open']
/** Chat-tab changes that make each worktree's saved tabs match the restored sessions. */
export function restoredChatTabChanges(
savedTabsByWorktree: Parameters<typeof chatTabChangesMatchingIndex>[0],
host: ReturnType<typeof getStructuredAgentSessionHost>,
restored: readonly { workspaceId: string; sessionId: string; agent: ChatTabOpen['agent'] }[]
): Map<string, ChatTabChange[]> {
return chatTabChangesMatchingIndex(
savedTabsByWorktree,
restored.map((session) => ({
worktreeId: session.workspaceId,
open: {
sessionId: session.sessionId,
agent: session.agent,
tabId: chatTabId(session.sessionId)
}
})),
// A chat a newer Orca wrote is only unreadable here, so it keeps its tab.
(sessionId) =>
!!host &&
structuredSessionTabRetired(host, sessionId) &&
!host.deps?.store?.isSessionUnreadable?.(sessionId)
)
}
@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type {
RuntimeMobileSessionAgentTab,
RuntimeMobileSessionTabGroup,
RuntimeMobileSessionTabMove,
RuntimeMobileSessionTabMoveResult,
@@ -8,6 +9,8 @@ import type {
import type { RuntimeMobileSessionSnapshotTab } from '../../../shared/runtime-mobile-session-tab-contracts'
import type { TabGroupLayoutNode } from '../../../shared/tab-types'
import { OrcaRuntimeService } from '../orca-runtime'
import { setStructuredAgentSessionHost } from '../../native-chat/agent-session-wire/structured-agent-session-registry'
import type { ResolvedWorktree } from '../runtime-worktree-path-identity'
function snapshot(): RuntimeMobileSessionTabsSnapshot {
return {
@@ -283,3 +286,326 @@ describe('original session tab runtime prototypes', () => {
expect(topLevelId).toBe('tab')
})
})
class SessionRuntime extends OrcaRuntimeService {
readonly refreshArgumentCounts: number[] = []
readonly reloadRequests: number[] = []
restoreRequests = 0
restoreResult = Promise.resolve()
override async resolveWorktreeSelector(): Promise<ResolvedWorktree> {
const git = {
path: 'workspace',
head: '',
branch: 'selected',
isBare: false,
isMainWorktree: false
}
return {
...git,
git,
id: 'workspace',
repoId: 'repo',
displayName: '',
comment: '',
linkedIssue: null,
linkedPR: null,
linkedLinearIssue: null,
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0,
parentWorktreeId: null,
childWorktreeIds: [],
lineage: null
}
}
protected override refreshMobileSessionPtyRecords(
...args: Parameters<OrcaRuntimeService['refreshMobileSessionPtyRecords']>
): Promise<Set<string> | null> {
this.refreshArgumentCounts.push(args.length)
return Promise.resolve(null)
}
protected override hydrateHeadlessMobileSessionTabsFromWorkspaceSession(): Set<string> {
return new Set()
}
protected override restoreLivePairedRendererSessionOwnedMobileTerminals(): void {}
protected override beginGraphReload(windowId: number): number {
this.reloadRequests.push(windowId)
return 77
}
protected override hasPersistedStructuredAgentSessionStore(): boolean {
return true
}
override prepareStructuredAgentSessionStartupRestoration(): Promise<void> {
return Promise.resolve()
}
protected override restoreStructuredAgentSessionTabsOnce(): Promise<void> {
this.restoreRequests += 1
return this.restoreResult
}
seedHeadlessSnapshot(snapshot: RuntimeMobileSessionTabsSnapshot): void {
this.mobileSessionTabsByWorktree.set(snapshot.worktree, snapshot)
}
setHostTabResolver(
resolver: (snapshot: RuntimeMobileSessionTabsSnapshot, tabId: string) => string | null
): void {
this.resolveMobileSessionHostTabId = resolver
}
restoreTabs(): Promise<void> {
return this.restoreStructuredAgentSessionTabs()
}
closeTab(tab: RuntimeMobileSessionAgentTab): Promise<void> {
return this.closeStructuredAgentSessionTab(tab, 'user-close')
}
}
describe('session runtime reentry', () => {
afterEach(() => setStructuredAgentSessionHost(null))
it('keeps omitted refresh arguments omitted after asynchronous workspace resolution', async () => {
const runtime = new SessionRuntime()
await runtime.listMobileSessionTabs('selected')
expect(runtime.refreshArgumentCounts).toEqual([0])
})
it('uses public methods replaced after construction', async () => {
const runtime = new SessionRuntime()
const list = vi.spyOn(runtime, 'listAllMobileSessionTabsWithChangeSequence').mockResolvedValue({
snapshots: [],
changeSequence: 73
})
const project = vi
.spyOn(runtime, 'projectStructuredAgentSessionTab')
.mockImplementation(() => {})
const input = {
workspaceId: 'workspace',
sessionId: 'session',
agent: 'claude',
activate: false
} satisfies Parameters<OrcaRuntimeService['publishStructuredAgentSessionTab']>[0]
await expect(runtime.listAllMobileSessionTabs('client')).resolves.toEqual([])
await runtime.publishStructuredAgentSessionTab(input)
expect(list).toHaveBeenCalledExactlyOnceWith('client')
expect(project).toHaveBeenCalledExactlyOnceWith(input)
})
it('re-enters the protected graph reload override', () => {
const runtime = new SessionRuntime()
runtime.attachWindow(1)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
expect(runtime.markRendererReloading(1)).toEqual({ revision: 77, recovery: 'renderer' })
expect(runtime.reloadRequests).toEqual([1])
})
it('shares the protected asynchronous restoration operation', async () => {
const runtime = new SessionRuntime()
const gate = Promise.withResolvers<void>()
runtime.restoreResult = gate.promise
const first = runtime.restoreTabs()
const second = runtime.restoreTabs()
expect(first).toBe(second)
expect(runtime.restoreRequests).toBe(1)
gate.resolve()
await first
})
it('captures the headless callback before evaluating move arguments', async () => {
const runtime = new SessionRuntime()
const snapshot: RuntimeMobileSessionTabsSnapshot = {
worktree: 'workspace',
publicationEpoch: 'epoch',
snapshotVersion: 1,
activeGroupId: null,
activeTabId: null,
activeTabType: null,
tabs: []
}
runtime.seedHeadlessSnapshot(snapshot)
const first = vi.fn(function (
this: OrcaRuntimeService,
received: RuntimeMobileSessionTabsSnapshot,
tabId: string
) {
expect(this).toBe(runtime)
expect(received).toBe(snapshot)
expect(tabId).toBe('tab')
expect(arguments.length).toBe(2)
return null
})
const replacement = vi.fn(function (
this: OrcaRuntimeService,
received: RuntimeMobileSessionTabsSnapshot,
tabId: string
) {
expect(this).toBe(runtime)
expect(received).toBe(snapshot)
expect(tabId).toBe('tab')
return null
})
runtime.setHostTabResolver(first)
await expect(
runtime.moveMobileSessionTab('selected', {
kind: 'reorder',
get tabId() {
runtime.setHostTabResolver(replacement)
return 'tab'
},
targetGroupId: 'group',
tabOrder: ['tab']
})
).rejects.toThrow('tab_not_found')
expect(first).toHaveBeenCalledExactlyOnceWith(snapshot, 'tab')
expect(replacement).not.toHaveBeenCalled()
await expect(
runtime.moveMobileSessionTab('selected', {
kind: 'reorder',
tabId: 'tab',
targetGroupId: 'group',
tabOrder: ['tab']
})
).rejects.toThrow('tab_not_found')
expect(replacement).toHaveBeenCalledExactlyOnceWith(snapshot, 'tab')
})
it('captures the runtime callee before a tab getter replaces it', async () => {
const runtime = new SessionRuntime()
const first = vi.fn(function (this: OrcaRuntimeService, sessionId: string) {
expect(this).toBe(runtime)
expect(sessionId).toBe('session')
return false
})
const replacement = vi.fn(() => false)
runtime.retireStructuredAgentSessionTabFromSnapshot = first
const tab: RuntimeMobileSessionAgentTab = {
type: 'agent-session',
id: 'agent-session:session',
title: '',
agent: 'claude',
isActive: false,
get sessionId() {
runtime.retireStructuredAgentSessionTabFromSnapshot = replacement
return 'session'
}
}
await runtime.closeTab(tab)
expect(first).toHaveBeenCalledOnce()
expect(replacement).not.toHaveBeenCalled()
await runtime.closeTab(tab)
expect(replacement).toHaveBeenCalledExactlyOnceWith('session')
})
})
class GraphRuntime extends OrcaRuntimeService {
get graphTabs() {
return this.tabs
}
get graphLeaves() {
return this.leaves
}
get graphLeavesByPtyId() {
return this.leavesByPtyId
}
get graphAvailability() {
return this.graphStatus
}
get graphEpoch() {
return this.rendererGraphEpoch
}
get detachedLeaves() {
return this.detachedPreAllocatedLeaves
}
get syncCallbacks() {
return this.graphSyncCallbacks
}
}
const graph = {
tabs: [{ tabId: 'tab', worktreeId: 'workspace', activeLeafId: 'leaf', layout: null, title: '' }],
leaves: [
{
tabId: 'tab',
worktreeId: 'workspace',
leafId: 'leaf',
paneRuntimeId: 1,
ptyId: 'pty',
paneTitle: null
}
]
}
describe('runtime graph ownership', () => {
it('shares detached preallocated leaves with terminal identity during renderer reload', async () => {
const runtime = new GraphRuntime()
const handle = runtime.preAllocateHandleForPty('pty')
runtime.attachWindow(1)
runtime.syncWindowGraph(1, graph)
const leaf = runtime.graphLeaves.get('tab::leaf')
runtime.markRendererReloading(1)
expect(runtime.detachedLeaves.get('pty')).toBe(leaf)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
runtime.onPtyData('pty', 'after omitted leaf\n', 100)
await expect(runtime.readTerminal(handle)).resolves.toMatchObject({
tail: ['after omitted leaf']
})
})
it('keeps the installed graph and its PTY index in one per-runtime authority', () => {
const first = new GraphRuntime()
const second = new GraphRuntime()
first.attachWindow(1)
first.syncWindowGraph(1, graph)
const leaf = first.graphLeavesByPtyId.get('pty')?.[0]
expect(leaf).toBe(first.graphLeaves.get('tab::leaf'))
expect(first.graphLeavesByPtyId.has('pty')).toBe(true)
expect(second.graphLeavesByPtyId.has('pty')).toBe(false)
expect(second.graphAvailability).toBe('unavailable')
first.markGraphUnavailable(1)
expect(first.graphTabs.size).toBe(0)
expect(first.graphLeaves.size).toBe(0)
expect(first.graphLeavesByPtyId.has('pty')).toBe(false)
expect(second.graphEpoch).toBe(0)
})
it('drains the captured callback list when a callback changes registration', () => {
const runtime = new GraphRuntime()
runtime.attachWindow(1)
const later = vi.fn()
const original = vi.fn()
runtime.syncCallbacks.push(() => {
runtime.syncCallbacks.splice(1, 1)
runtime.syncCallbacks.push(later)
}, original)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
expect(original).toHaveBeenCalledOnce()
expect(later).not.toHaveBeenCalled()
})
})
@@ -1,4 +1,4 @@
import { OrcaRuntimeWithCollectMobileVisibleGraphChangedWorktrees } from '../orca-runtime-collect-mobile-visible-graph-changed-worktrees'
import { OrcaRuntimeWithCollectMobileVisibleGraphChangedWorktrees } from './session-tabs-inventory'
export abstract class RuntimeSessionTabsInventoryPublication extends OrcaRuntimeWithCollectMobileVisibleGraphChangedWorktrees {
protected waitForSessionTabsInventoryPublication(signal?: AbortSignal): Promise<void> {
@@ -1,11 +1,82 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithSyncWindowGraph } from './orca-runtime-sync-window-graph'
import type { RuntimeMobileSessionTabsResult, RuntimeSyncedTab } from '../../shared/runtime-types'
import type { RuntimeLeafRecord } from './runtime-terminal-state-records'
import type { PtyControllerInventory } from './runtime-pty-controller-contract'
import { parseExecutionHostId } from '../../shared/execution-host'
import type { RuntimeSessionTabsInventoryPublication as SessionTabsInventoryPublicationMethods } from './session-tabs-inventory-publication'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from '../orca-runtime-schedule-mobile-session-tabs-changed'
import type { OrcaRuntimeWithResolveBrowserNetworkExecutionHostForWorktree as ResolveBrowserNetworkExecutionHostForWorktreeMethods } from '../orca-runtime-resolve-browser-network-execution-host-for-worktree'
import type { OrcaRuntimeWithHydrateHeadlessMobileSessionTabsFromWorkspaceSession as HydrateHeadlessMobileSessionTabsFromWorkspaceSessionMethods } from './mobile-session-hydration'
import type { OrcaRuntimeWithBuildHeadlessMobileSessionBrowserTabs as BuildHeadlessMobileSessionBrowserTabsMethods } from '../orca-runtime-build-headless-mobile-session-browser-tabs'
import type { OrcaRuntimeWithRestoreLivePairedRendererSessionOwnedMobileTerminals as RestoreLivePairedRendererSessionOwnedMobileTerminalsMethods } from './renderer-session-terminal-rescue'
import type { OrcaRuntimeWithPruneMobileSessionTabGroupLayout as PruneMobileSessionTabGroupLayoutMethods } from '../orca-runtime-prune-mobile-session-tab-group-layout'
import { OrcaRuntimeWithSyncWindowGraph } from '../session-graph/session-window-graph'
import type {
RuntimeMobileSessionTabsResult,
RuntimeSyncedTab
} from '../../../shared/runtime-types'
import type { RuntimeLeafRecord } from '../runtime-terminal-state-records'
import type { PtyControllerInventory } from '../runtime-pty-controller-contract'
import { parseExecutionHostId } from '../../../shared/execution-host'
export abstract class OrcaRuntimeWithCollectMobileVisibleGraphChangedWorktrees extends OrcaRuntimeWithSyncWindowGraph {
protected abstract getAuthoritativeSessionTabsInventoryEpoch(
...args: Parameters<
SessionTabsInventoryPublicationMethods['getAuthoritativeSessionTabsInventoryEpoch']
>
): ReturnType<SessionTabsInventoryPublicationMethods['getAuthoritativeSessionTabsInventoryEpoch']>
protected abstract getMobileSessionTabsForWorktree(
...args: Parameters<ScheduleMobileSessionTabsChangedMethods['getMobileSessionTabsForWorktree']>
): ReturnType<ScheduleMobileSessionTabsChangedMethods['getMobileSessionTabsForWorktree']>
protected abstract getValidatedExplicitWorktreeIdSelector(
...args: Parameters<
ResolveBrowserNetworkExecutionHostForWorktreeMethods['getValidatedExplicitWorktreeIdSelector']
>
): ReturnType<
ResolveBrowserNetworkExecutionHostForWorktreeMethods['getValidatedExplicitWorktreeIdSelector']
>
protected abstract hydrateHeadlessMobileSessionTabsFromWorkspaceSession(
...args: Parameters<
HydrateHeadlessMobileSessionTabsFromWorkspaceSessionMethods['hydrateHeadlessMobileSessionTabsFromWorkspaceSession']
>
): ReturnType<
HydrateHeadlessMobileSessionTabsFromWorkspaceSessionMethods['hydrateHeadlessMobileSessionTabsFromWorkspaceSession']
>
protected abstract projectMobileSessionTabsForClient(
...args: Parameters<
BuildHeadlessMobileSessionBrowserTabsMethods['projectMobileSessionTabsForClient']
>
): ReturnType<BuildHeadlessMobileSessionBrowserTabsMethods['projectMobileSessionTabsForClient']>
protected abstract refreshMobileSessionPtyInventory(
...args: Parameters<
BuildHeadlessMobileSessionBrowserTabsMethods['refreshMobileSessionPtyInventory']
>
): ReturnType<BuildHeadlessMobileSessionBrowserTabsMethods['refreshMobileSessionPtyInventory']>
protected abstract refreshMobileSessionPtyRecords(
...args: Parameters<
BuildHeadlessMobileSessionBrowserTabsMethods['refreshMobileSessionPtyRecords']
>
): ReturnType<BuildHeadlessMobileSessionBrowserTabsMethods['refreshMobileSessionPtyRecords']>
protected abstract restoreLivePairedRendererSessionOwnedMobileTerminals(
...args: Parameters<
RestoreLivePairedRendererSessionOwnedMobileTerminalsMethods['restoreLivePairedRendererSessionOwnedMobileTerminals']
>
): ReturnType<
RestoreLivePairedRendererSessionOwnedMobileTerminalsMethods['restoreLivePairedRendererSessionOwnedMobileTerminals']
>
protected abstract toMobileSessionTabsResult(
...args: Parameters<PruneMobileSessionTabGroupLayoutMethods['toMobileSessionTabsResult']>
): ReturnType<PruneMobileSessionTabGroupLayoutMethods['toMobileSessionTabsResult']>
protected abstract waitForSessionTabsInventoryPublication(
...args: Parameters<
SessionTabsInventoryPublicationMethods['waitForSessionTabsInventoryPublication']
>
): ReturnType<SessionTabsInventoryPublicationMethods['waitForSessionTabsInventoryPublication']>
export class OrcaRuntimeWithCollectMobileVisibleGraphChangedWorktrees extends OrcaRuntimeWithSyncWindowGraph {
// Why: toMobileSessionTabsResult resolves handles/titles from this.tabs and
// this.leaves, so any tab/leaf delta a graph sync installs can flip the
// client payload (pending-handle → ready, tab title) with zero change to the
@@ -1,34 +1,42 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { agentSessionRefusalError } from '../../shared/agent-session-wire-refusals'
import { OrcaRuntimeWithGetWorktreePs } from './orca-runtime-get-worktree-ps'
import { getStructuredAgentSessionHost } from '../native-chat/agent-session-wire/structured-agent-session-registry'
import type { OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce as RestoreStructuredAgentSessionTabsOnceMethods } from './structured-session-tab-restoration'
import { agentSessionRefusalError } from '../../../shared/agent-session-wire-refusals'
import { OrcaRuntimeWithGetWorktreePs } from '../orca-runtime-get-worktree-ps'
import { getStructuredAgentSessionHost } from '../../native-chat/agent-session-wire/structured-agent-session-registry'
import {
resolveCommittedStructuredAgentSessionAdoptionIntent,
resolveStructuredAgentSessionAdoptionForCreate
} from './structured-agent-session-create-adoption'
import { isNativeLocalLocation, LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
import { getLocalProjectWorktreeGitOptions } from '../project-runtime-git-options'
import type { AgentSessionAttachParams } from '../native-chat/agent-session-wire/structured-agent-session-attach'
} from '../structured-agent-session-create-adoption'
import { isNativeLocalLocation, LOCAL_EXECUTION_HOST_ID } from '../../../shared/execution-host'
import { getLocalProjectWorktreeGitOptions } from '../../project-runtime-git-options'
import type { AgentSessionAttachParams } from '../../native-chat/agent-session-wire/structured-agent-session-attach'
import {
resolveHostStructuredAgentCreateSupport,
resolveHostStructuredAgentLaunchEnvironment
} from './structured-agent-launch-support'
import { structuredAgentRuntimeRegistration } from './structured-agent-runtime-registrations'
import { structuredLaunchSeedOptions } from '../../shared/native-chat-session-option-defaults'
import { hasPersistedStructuredAgentSessionStore as hasPersistedStructuredAgentSessionStoreOnDisk } from './structured-agent-session-runtime'
import { ensureStructuredAgentSessionHostUnlessRefused } from './structured-agent-session-host-refusal'
import { getProfileUserDataPath } from '../orca-profiles/profile-storage-paths'
import { parseWslUncPath } from '../../shared/wsl-paths'
} from '../structured-agent-launch-support'
import { structuredAgentRuntimeRegistration } from '../structured-agent-runtime-registrations'
import { structuredLaunchSeedOptions } from '../../../shared/native-chat-session-option-defaults'
import { hasPersistedStructuredAgentSessionStore as hasPersistedStructuredAgentSessionStoreOnDisk } from '../structured-agent-session-runtime'
import { ensureStructuredAgentSessionHostUnlessRefused } from '../structured-agent-session-host-refusal'
import { getProfileUserDataPath } from '../../orca-profiles/profile-storage-paths'
import { parseWslUncPath } from '../../../shared/wsl-paths'
import {
isFloatingWorkspaceId,
isFloatingWorkspaceSelector
} from '../../shared/floating-workspace-worktree'
import { parseWorkspaceKey } from '../../shared/workspace-scope'
import type { AgentSessionAccountHome } from '../../shared/agent-session-account-home'
import type { StructuredAgentId } from '../../shared/agent-session-provider-handle'
import { readPiSessionHeader, samePiDirectory } from '../pi/rpc-launch-resolution'
} from '../../../shared/floating-workspace-worktree'
import { parseWorkspaceKey } from '../../../shared/workspace-scope'
import type { AgentSessionAccountHome } from '../../../shared/agent-session-account-home'
import type { StructuredAgentId } from '../../../shared/agent-session-provider-handle'
import { readPiSessionHeader, samePiDirectory } from '../../pi/rpc-launch-resolution'
export abstract class OrcaRuntimeWithGetStructuredAgentSessionCreateSupport extends OrcaRuntimeWithGetWorktreePs {
protected abstract restoreStructuredAgentSessionTabsOnce(
...args: Parameters<
RestoreStructuredAgentSessionTabsOnceMethods['restoreStructuredAgentSessionTabsOnce']
>
): ReturnType<
RestoreStructuredAgentSessionTabsOnceMethods['restoreStructuredAgentSessionTabsOnce']
>
export class OrcaRuntimeWithGetStructuredAgentSessionCreateSupport extends OrcaRuntimeWithGetWorktreePs {
async getStructuredAgentSessionCreateSupport(
worktreeSelector: string,
agent: StructuredAgentId
@@ -270,7 +278,7 @@ export class OrcaRuntimeWithGetStructuredAgentSessionCreateSupport extends OrcaR
providerHandle: {
transport: definition.handleTransport,
agent: definition.agent,
nativeId: transcriptImport.handleNamesTranscript
nativeId: transcriptImport!.handleNamesTranscript
? adoption.transcriptPath
: input.resumeFrom.providerSessionId
},
@@ -1,27 +1,46 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithCloseMobileSessionTab } from './orca-runtime-close-mobile-session-tab'
import type { OrcaRuntimeWithPruneMobileSessionTabGroupLayout as PruneMobileSessionTabGroupLayoutMethods } from '../orca-runtime-prune-mobile-session-tab-group-layout'
import type { RuntimeHeadlessTabGroups as HeadlessTabGroupsMethods } from './headless-tab-group-moves'
import { OrcaRuntimeWithCloseMobileSessionTab } from '../orca-runtime-close-mobile-session-tab'
import type {
BrowserTabInfo,
RuntimeMobileSessionAgentTab,
RuntimeMobileSessionBrowserTab,
RuntimeMobileSessionRetiredTerminalSurface,
RuntimeMobileSessionTabsSnapshot,
RuntimeMobileSessionTerminalTab
} from '../../shared/runtime-types'
import type { RuntimePtyWorktreeRecord } from './runtime-terminal-state-records'
import { getMobileSessionSnapshotTabIdentityKeys } from './mobile-session-tab-merge'
import type { BrowserSessionTabSelectionOptions } from './browser-tab-create-publication'
import { getRuntimeBrowserPageRegistry } from './runtime-browser-page-registry'
import { applyBrowserSessionTabSelection } from './browser-session-tab-selection-snapshot'
import { getStructuredAgentSessionHost } from '../native-chat/agent-session-wire/structured-agent-session-registry'
import type { StructuredAgentSessionCloseCause } from '../native-chat/agent-session-wire/structured-agent-session-host-lifetime'
import { retireStructuredAgentSessionTabFrom } from './structured-agent-session-tab-retirement'
import { chatTabId, withChatTabChanges, type ChatTabChange } from './agent-session-tab-layout'
import { savedSessionId } from './saved-structured-agent-session-restoration'
import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
import { structuredSessionTabRetired } from './structured-worker-custody'
import type { StructuredAgentId } from '../../shared/agent-session-provider-handle'
} from '../../../shared/runtime-types'
import type { RuntimePtyWorktreeRecord } from '../runtime-terminal-state-records'
import { getMobileSessionSnapshotTabIdentityKeys } from '../mobile-session-tab-merge'
import type { BrowserSessionTabSelectionOptions } from '../browser-tab-create-publication'
import { getRuntimeBrowserPageRegistry } from '../runtime-browser-page-registry'
import { applyBrowserSessionTabSelection } from '../browser-session-tab-selection-snapshot'
import { getStructuredAgentSessionHost } from '../../native-chat/agent-session-wire/structured-agent-session-registry'
import type { StructuredAgentSessionCloseCause } from '../../native-chat/agent-session-wire/structured-agent-session-host-lifetime'
import { retireStructuredAgentSessionTabFrom } from '../structured-agent-session-tab-retirement'
import { chatTabId, withChatTabChanges, type ChatTabChange } from '../agent-session-tab-layout'
import { savedSessionId } from '../saved-structured-agent-session-restoration'
import { LOCAL_EXECUTION_HOST_ID } from '../../../shared/execution-host'
import { structuredSessionTabRetired } from '../structured-worker-custody'
import type { StructuredAgentId } from '../../../shared/agent-session-provider-handle'
export abstract class OrcaRuntimeWithCloseStructuredAgentSessionTab extends OrcaRuntimeWithCloseMobileSessionTab {
protected abstract getListedBrowserTabsByPageId(worktreeId: string): Map<string, BrowserTabInfo>
protected abstract findPtyForMobileTerminalTab(
...args: Parameters<PruneMobileSessionTabGroupLayoutMethods['findPtyForMobileTerminalTab']>
): ReturnType<PruneMobileSessionTabGroupLayoutMethods['findPtyForMobileTerminalTab']>
protected abstract getMobileTerminalLeafPtyIds(
...args: Parameters<PruneMobileSessionTabGroupLayoutMethods['getMobileTerminalLeafPtyIds']>
): ReturnType<PruneMobileSessionTabGroupLayoutMethods['getMobileTerminalLeafPtyIds']>
protected abstract getMobileTerminalPaneKey(
...args: Parameters<PruneMobileSessionTabGroupLayoutMethods['getMobileTerminalPaneKey']>
): ReturnType<PruneMobileSessionTabGroupLayoutMethods['getMobileTerminalPaneKey']>
protected abstract persistHeadlessTabGroups(
...args: Parameters<HeadlessTabGroupsMethods['persistHeadlessTabGroups']>
): ReturnType<HeadlessTabGroupsMethods['persistHeadlessTabGroups']>
export class OrcaRuntimeWithCloseStructuredAgentSessionTab extends OrcaRuntimeWithCloseMobileSessionTab {
protected async closeStructuredAgentSessionTab(
tab: RuntimeMobileSessionAgentTab,
cause: StructuredAgentSessionCloseCause
@@ -1,46 +1,46 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { defaultAgentChatLabel } from '../../shared/agent-session-chat-label'
import { OrcaRuntimeWithGetStructuredAgentSessionCreateSupport } from './orca-runtime-get-structured-agent-session-create-support'
import { getStructuredAgentSessionHost } from '../native-chat/agent-session-wire/structured-agent-session-registry'
import { collectSavedStructuredAgentSessionIds } from './saved-structured-agent-session-restoration'
import {
chatTabChangesMatchingIndex,
seedStructuredAgentSessionTabIndex
} from './structured-agent-session-tab-index-seed'
import { chatTabId } from './agent-session-tab-layout'
import { structuredSessionTabRetired } from './structured-worker-custody'
import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from '../orca-runtime-schedule-mobile-session-tabs-changed'
import { defaultAgentChatLabel } from '../../../shared/agent-session-chat-label'
import { OrcaRuntimeWithGetStructuredAgentSessionCreateSupport } from './structured-session-create-support'
import { getStructuredAgentSessionHost } from '../../native-chat/agent-session-wire/structured-agent-session-registry'
import { collectSavedStructuredAgentSessionIds } from '../saved-structured-agent-session-restoration'
import { LOCAL_EXECUTION_HOST_ID } from '../../../shared/execution-host'
import { seedStructuredAgentSessionTabIndex } from '../structured-agent-session-tab-index-seed'
import { restoredChatTabChanges } from './restored-chat-tab-changes'
import type {
RuntimeMobileSessionAgentTab,
RuntimeMobileSessionTabsSnapshot,
RuntimeRepoSearchRefs
} from '../../shared/runtime-types'
import { getHeadlessMobileSessionGroupId } from './mobile-session-layout-projection'
import { DEFAULT_REPO_SEARCH_REFS_LIMIT } from './orca-runtime-postlude'
import type { Repo } from '../../shared/repo-types'
import type { GitAdmissionTier } from '../git/command-runner/git-exec-options'
} from '../../../shared/runtime-types'
import { getHeadlessMobileSessionGroupId } from '../mobile-session-layout-projection'
import { DEFAULT_REPO_SEARCH_REFS_LIMIT } from '../orca-runtime-postlude'
import type { Repo } from '../../../shared/repo-types'
import type { GitAdmissionTier } from '../../git/command-runner/git-exec-options'
import {
getLocalProjectGhExecOptions,
type LocalProjectGhExecOptions
} from '../project-runtime-git-options'
} from '../../project-runtime-git-options'
import {
agentLaunchPlatformForScope,
newWorktreeLaunchScope,
resolveAgentLaunchRuntime,
resolveStartupAgentLaunchRuntime
} from './agent-launch-scope-runtime'
import type { TuiAgent } from '../../shared/tui-agent'
import type { AgentLaunchRuntime } from '../../shared/agent-launch-runtime'
import type { TerminalWorkspaceLaunchScope } from './runtime-legacy-worker-terminal-recovery-types'
import { parseAppSshPtyId } from '../../shared/ssh-pty-id'
import type { PtyProcessInspection } from '../providers/pty-process-inspection'
import type { StructuredAgentId } from '../../shared/agent-session-provider-handle'
} from '../agent-launch-scope-runtime'
import type { TuiAgent } from '../../../shared/tui-agent'
import type { AgentLaunchRuntime } from '../../../shared/agent-launch-runtime'
import type { TerminalWorkspaceLaunchScope } from '../runtime-legacy-worker-terminal-recovery-types'
import { parseAppSshPtyId } from '../../../shared/ssh-pty-id'
import type { PtyProcessInspection } from '../../providers/pty-process-inspection'
import type { StructuredAgentId } from '../../../shared/agent-session-provider-handle'
export type AgentLaunchScope = Parameters<
OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce['getAgentLaunchPlatformForWorkspace']
>[0]
export class OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce extends OrcaRuntimeWithGetStructuredAgentSessionCreateSupport {
export abstract class OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce extends OrcaRuntimeWithGetStructuredAgentSessionCreateSupport {
protected abstract notifyMobileSessionTabSnapshots(
...args: Parameters<ScheduleMobileSessionTabsChangedMethods['notifyMobileSessionTabSnapshots']>
): ReturnType<ScheduleMobileSessionTabsChangedMethods['notifyMobileSessionTabSnapshots']>
protected async restoreStructuredAgentSessionTabsOnce(): Promise<void> {
await this.prepareStructuredAgentSessionStartupRestoration()
const host = getStructuredAgentSessionHost()
@@ -73,21 +73,10 @@ export class OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce extends OrcaRu
targets,
restored.map((session) => session.sessionId)
)
const changes = chatTabChangesMatchingIndex(
const changes = restoredChatTabChanges(
this.store?.getWorkspaceSession?.(LOCAL_EXECUTION_HOST_ID)?.unifiedTabs ?? {},
restored.map((session) => ({
worktreeId: session.workspaceId,
open: {
sessionId: session.sessionId,
agent: session.agent,
tabId: chatTabId(session.sessionId)
}
})),
// A chat a newer Orca wrote is only unreadable here, so it keeps its tab.
(sessionId) =>
!!host &&
structuredSessionTabRetired(host, sessionId) &&
!host.deps?.store?.isSessionUnreadable?.(sessionId)
host,
restored
)
for (const [worktreeId, worktreeChanges] of changes) {
this.changeWindowlessChatTabs(worktreeId, worktreeChanges)
@@ -301,7 +290,13 @@ export class OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce extends OrcaRu
connectionId?: string | null
}
): NodeJS.Platform {
return agentLaunchPlatformForScope(this.requireStore(), scope)
// Orchestration passes a repo-only scope; the platform never reads its id or folder.
return agentLaunchPlatformForScope(this.requireStore(), {
id: '',
folderWorkspace: null,
...scope,
connectionId: scope.connectionId ?? null
})
}
protected resolveAgentLaunchRuntime(
@@ -1,14 +1,18 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithTouchMobileSessionTabsForWorktree } from './orca-runtime-touch-mobile-session-tabs-for-worktree'
import type { RetiredTerminalSurface } from './mobile-session-terminal-retirement'
import type { RuntimeMobileSessionRetiredTerminalSurface } from '../../shared/runtime-types'
import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
import { retireTerminalSurfaceFromPersistence } from './mobile-session-terminal-persistence-retirement'
import { retireTerminalSurfacesFromSnapshot } from './mobile-session-terminal-retirement'
import { attachRetirementProofsToSnapshot } from './mobile-session-terminal-retirement-proof'
import { getRepoIdFromWorktreeId } from '../../shared/worktree/id'
import type { OrcaRuntimeWithBindPtyIncarnationHandle as BindPtyIncarnationHandleMethods } from '../terminal/identity/terminal-handle-bindings'
import { OrcaRuntimeWithTouchMobileSessionTabsForWorktree } from '../orca-runtime-touch-mobile-session-tabs-for-worktree'
import type { RetiredTerminalSurface } from '../mobile-session-terminal-retirement'
import type { RuntimeMobileSessionRetiredTerminalSurface } from '../../../shared/runtime-types'
import { LOCAL_EXECUTION_HOST_ID } from '../../../shared/execution-host'
import { retireTerminalSurfaceFromPersistence } from '../mobile-session-terminal-persistence-retirement'
import { retireTerminalSurfacesFromSnapshot } from '../mobile-session-terminal-retirement'
import { attachRetirementProofsToSnapshot } from '../mobile-session-terminal-retirement-proof'
import { getRepoIdFromWorktreeId } from '../../../shared/worktree/id'
export abstract class OrcaRuntimeWithPersistTerminalSurfaceRetirements extends OrcaRuntimeWithTouchMobileSessionTabsForWorktree {
protected abstract findHandleForPtyRecord(
...args: Parameters<BindPtyIncarnationHandleMethods['findHandleForPtyRecord']>
): ReturnType<BindPtyIncarnationHandleMethods['findHandleForPtyRecord']>
export class OrcaRuntimeWithPersistTerminalSurfaceRetirements extends OrcaRuntimeWithTouchMobileSessionTabsForWorktree {
/**
* Retires each surface in the in-memory session partition of the host that owns its worktree.
* Why: an SSH pane's durable surface lives in that connection's partition; retiring it
@@ -34,7 +38,8 @@ export class OrcaRuntimeWithPersistTerminalSurfaceRetirements extends OrcaRuntim
continue
}
try {
this.store.setWorkspaceSession(next, hostId)
// The existing catch handles a missing store setter.
this.store!.setWorkspaceSession!(next, hostId)
accepted.push(surface)
} catch (error) {
// Why: the process is gone whether or not the profile admits the write (quit, maintenance).
@@ -1,11 +1,13 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithRefreshPtyWorktreeRecordsWithControllerInventory } from './orca-runtime-refresh-pty-worktree-records-with-controller-inventory'
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../shared/constants'
import { indexFloatingSnapshotPtyBindings } from './floating-snapshot-pty-bindings'
import type { RuntimeLeafRecord, RuntimePtyWorktreeRecord } from './runtime-terminal-state-records'
import { DISCONNECTED_PTY_RECORD_MAX } from './orca-runtime-postlude'
import { OrcaRuntimeWithRefreshPtyWorktreeRecordsWithControllerInventory } from '../../orca-runtime-refresh-pty-worktree-records-with-controller-inventory'
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants'
import { indexFloatingSnapshotPtyBindings } from '../../floating-snapshot-pty-bindings'
import type {
RuntimeLeafRecord,
RuntimePtyWorktreeRecord
} from '../../runtime-terminal-state-records'
import { DISCONNECTED_PTY_RECORD_MAX } from '../../orca-runtime-postlude'
export class OrcaRuntimeWithRefreshFloatingWorkspacePtyLiveness extends OrcaRuntimeWithRefreshPtyWorktreeRecordsWithControllerInventory {
export abstract class OrcaRuntimeWithRefreshFloatingWorkspacePtyLiveness extends OrcaRuntimeWithRefreshPtyWorktreeRecordsWithControllerInventory {
protected refreshFloatingWorkspacePtyLiveness(): Set<string> | null {
const controller = this.ptyController
if (!controller?.hasPty) {
@@ -11,7 +11,7 @@ import type {
TerminalWaitStatusEvidence
} from './terminal-wait-permission'
import type { AgentLaunchScope } from '../../orca-runtime-restore-structured-agent-session-tabs-once'
import type { AgentLaunchScope } from '../../session-tabs/structured-session-tab-restoration'
import { withTimeout } from '../../runtime-async-boundaries'
import { TERMINAL_INTERACTIVE_WAIT_PROBE_TIMEOUT_MS } from '../../orca-runtime-core'
import { parsePaneKey } from '../../../../shared/stable-pane-id'
@@ -186,7 +186,7 @@ export abstract class RuntimeWorkspaceSelectors extends OrcaRuntimeWithResolveBr
throw new Error('repo_not_found')
}
protected requireStore(): RuntimeStore {
requireStore(): RuntimeStore {
if (!this.store) {
throw new Error('runtime_unavailable')
}
+4 -4
View File
@@ -16,7 +16,7 @@ const UNSUPPORTED_TUI_AGENT_ARGS: Partial<Record<TuiAgent, readonly string[]>> =
export const DEFAULT_TUI_AGENT_ARGS: Partial<Record<TuiAgent, string>> = YOLO_TUI_AGENT_ARGS
export const DEFAULT_TUI_AGENT_ENV: Partial<Record<TuiAgent, Record<string, string>>> =
export const DEFAULT_TUI_AGENT_ENV: Partial<Record<string, Record<string, string>>> =
YOLO_TUI_AGENT_ENV
function argPattern(arg: string): RegExp {
@@ -111,7 +111,7 @@ export function getTuiAgentDefaultArgs(agent: TuiAgent): string {
return DEFAULT_TUI_AGENT_ARGS[agent] ?? ''
}
export function getTuiAgentDefaultEnv(agent: TuiAgent): Record<string, string> {
export function getTuiAgentDefaultEnv(agent: string): Record<string, string> {
return { ...DEFAULT_TUI_AGENT_ENV[agent] }
}
@@ -160,8 +160,8 @@ export function resolvedTuiAgentArgsBypassPermissions(
}
export function resolveTuiAgentLaunchEnv(
agent: TuiAgent,
configuredEnv: Partial<Record<TuiAgent, Record<string, string>>> | null | undefined
agent: string,
configuredEnv: Partial<Record<string, Record<string, string>>> | null | undefined
): Record<string, string> {
if (configuredEnv && Object.hasOwn(configuredEnv, agent)) {
return { ...configuredEnv[agent] }