Files
orca/src/main/runtime/orca-runtime-resolve-recovered-structured-tui-transcript.ts
T
Neil 946627f2ce fix(runtime): route runtime filesystem commands by resolved execution host (#18325)
`ResolvedRuntimeFileTarget` carried `connectionId?: string` and no host id, so
`undefined` spelled three different answers at once — "runtime: host", "unresolved"
and "genuinely local". Its sole resolver read `store.getRepo(worktree.repoId)?.connectionId`
and never looked at `worktree.hostId`, which outranks every repo row, so one
arbitrarily chosen row decided the execution host for ~30 filesystem dispatches.
This is #18307's defect in the same file family; it was deliberately left out of
that PR rather than doubling an already-36-site diff.

The target now carries `executionHostId: ExecutionHostId` (never null, never
optional), resolved through `resolveWorktreeHostRouting` — the same adapter #18307
added — and dispatched through #18296's `resolveFilesystemRouteForHost`. Dispatch
sites call `requireRuntimeFileProvider`, where `null` means exactly one thing: the
host is `local` and the read happens here.

Four answers that used to collapse into one:

- `ssh:x` with a rival row on `ssh:y` — routes to x. Previously the first row won.
- `local` with a surviving `connectionId` — a row contradicting itself; no SSH
  connection is handed out.
- `runtime:<env>` — throws `ExecutionHostNotDispatchableError`. Its repo row's
  connection names a target in the *server's* namespace; reading it here reaches a
  same-named target on this client.
- rival rows disagreeing with no worktree host — `worktree_execution_host_unresolved`,
  matching the launch and Git paths rather than guessing a row.

Two further reads stop degrading. `assertRuntimeFileMutationExpectation` recomputed
the host from `connectionId`, so a client's host expectation could pass against a
host the workspace never named; it now compares the resolved host. And the
cross-workspace terminal tap coalesced `knownWorkspaceTarget?.connectionId ??
connectionId`, so a sibling workspace resolved as `local` inherited the origin
worktree's SSH target and statted a local path on the remote box; a non-optional
host id replaces rather than coalesces.

An unreachable SSH host still throws `SSH_FILESYSTEM_PROVIDER_UNAVAILABLE_MESSAGE`;
loss of contact is never evidence of locality (docs/reference/ssh-execution-boundary.md).
Quick-open listing and path search keep degrading to empty for an unreachable host —
that is a false negative, not a local answer — and now do so only for a host that
really is remote.

The whole `runtime-file-commands-*` family carries `@ts-nocheck` from a mechanical
class split, so removing the field could not raise the compile errors that made
#18307 safe. `runtime-file-command-target.ts` is deliberately checked, and a ratchet
test stands in for the errors the family cannot produce.

No wire change: `ResolvedRuntimeFileTarget` is main-process internal, and the SSH
watcher-release and grant keys are byte-identical to before.
2026-09-02 20:47:09 -07:00

194 lines
8.0 KiB
TypeScript

// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithStopStructuredSessionProcess } from './orca-runtime-stop-structured-session-process'
import type { AgentSessionOwnerBinding } from '../../shared/agent-session-host-authority'
import { agentSessionOwnerBindingsEqual } from '../../shared/claimed-agent-pty-owner-snapshot'
import { resolvePinnedCodexRolloutProof } from '../codex/codex-tui-rollout-proof'
import { getStructuredAgentSessionHost } from '../native-chat/agent-session-wire/structured-agent-session-registry'
import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
import type { AgentStatusIpcPayload } from '../../shared/agent-status-types'
import { getLocalProjectWorktreeGitOptions } from '../project-runtime-git-options'
import type { AgentSessionAttachParams } from '../native-chat/agent-session-wire/structured-agent-session-attach'
import { getSystemCodexHomePath } from '../codex/codex-home-paths'
import { resolveTuiAgentLaunchEnv } from '../../shared/tui-agent-launch-defaults'
import { hasPersistedStructuredAgentSessionStore as hasPersistedStructuredAgentSessionStoreOnDisk } from './structured-agent-session-runtime'
import { getProfileUserDataPath } from '../orca-profiles/profile-storage-paths'
export class OrcaRuntimeWithResolveRecoveredStructuredTuiTranscript extends OrcaRuntimeWithStopStructuredSessionProcess {
protected async resolveRecoveredStructuredTuiTranscript(input: {
handle: string
paneKey: string
threadId: string
codexHome: string
durableOwner: { binding: AgentSessionOwnerBinding; incarnationId: string }
}): Promise<{ transcriptPath: string; leafUuid?: never }> {
const assertDurableOwner = (): void => {
const pty = this.getLivePtyForHandle(input.handle)?.pty
if (
!pty?.connected ||
pty.paneKey !== input.paneKey ||
pty.incarnationId !== input.durableOwner.incarnationId ||
!pty.agentSessionOwners.some((owner) =>
agentSessionOwnerBindingsEqual(owner, input.durableOwner.binding)
)
) {
throw new Error('The resumed terminal lost its durable owner identity.')
}
}
assertDurableOwner()
const transcriptPath = await resolvePinnedCodexRolloutProof(input.codexHome, input.threadId)
assertDurableOwner()
if (!transcriptPath) {
throw new Error('The agent terminal did not prove the expected Codex rollout.')
}
return { transcriptPath }
}
async getStructuredAgentSessionCreateSupport(
worktreeSelector: string,
agent: 'codex'
): Promise<{ supported: boolean; reason?: 'agent' | 'remote' | 'wsl' }> {
const location = await this.resolveStructuredAgentSessionLocation(worktreeSelector)
await this.ensureStructuredAgentSessionHost()
if (getStructuredAgentSessionHost()?.supportsCreate(location, agent)) {
return { supported: true }
}
return {
supported: false,
reason:
location.executionHostId !== LOCAL_EXECUTION_HOST_ID
? 'remote'
: location.wslDistro
? 'wsl'
: 'agent'
}
}
protected hasProviderSessionObservationSource(): boolean {
return (
this.getAgentProviderSessionRowsForPaneFn !== null ||
this.getAgentProviderSessionSnapshotFn !== null
)
}
protected findAdoptedProviderSession(
paneKey: string,
provider: 'claude' | 'codex',
providerSessionId: string
): AgentStatusIpcPayload | undefined {
const rows =
this.getAgentProviderSessionRowsForPaneFn?.(paneKey) ??
(this.getAgentProviderSessionSnapshotFn?.() ?? []).filter((row) => row.paneKey === paneKey)
return rows
.filter((row) => row.agentType === provider && row.providerSession?.id === providerSessionId)
.reduce<AgentStatusIpcPayload | undefined>(
(latest, row) => (!latest || row.receivedAt > latest.receivedAt ? row : latest),
undefined
)
}
protected async resolveStructuredAgentSessionLocation(worktreeSelector: string) {
const target = await this.resolveRuntimeFileTarget(worktreeSelector)
const repo = this.store?.getRepo(target.worktree.repoId)
// WSL routing describes *this* machine; no remote or runtime host may inherit it.
const wslDistro =
repo && target.executionHostId === LOCAL_EXECUTION_HOST_ID
? (getLocalProjectWorktreeGitOptions(this.requireStore(), repo).wslDistro ?? null)
: null
const folderWorkspace = this.store
?.getFolderWorkspaces?.()
.some((workspace) => workspace.id === target.worktree.id)
return {
executionHostId: target.executionHostId,
wslDistro,
workspaceId: target.worktree.id,
workspaceKind: folderWorkspace ? ('folder' as const) : ('git-worktree' as const)
}
}
async resolveStructuredAgentSessionCreateIntent(input: {
envelope: { sessionId: string; clientOperationId: string }
worktree: string
agent: 'codex'
}): Promise<AgentSessionAttachParams> {
return this.resolveStructuredAgentSessionIntent(input, async ({ workspacePath, launchEnv }) => {
// A create has no process yet, so the current selection is what it must follow.
const preparedHome = await this.prepareCodexStructuredLaunchFn?.({ workspacePath, launchEnv })
const configuredHome = launchEnv.CODEX_HOME
return (
preparedHome?.trim() ||
(this.prepareCodexStructuredLaunchFn ? getSystemCodexHomePath() : configuredHome?.trim()) ||
getSystemCodexHomePath()
)
})
}
protected async resolveStructuredAgentSessionIntent(
input: {
envelope: { sessionId: string; clientOperationId: string }
worktree: string
agent: 'codex'
},
resolveAccountHomePath: (context: {
workspacePath: string
launchEnv: NodeJS.ProcessEnv
}) => string | Promise<string>
): Promise<AgentSessionAttachParams> {
const support = await this.getStructuredAgentSessionCreateSupport(input.worktree, input.agent)
if (!support.supported) {
throw new Error('structured_agent_session_unsupported')
}
const settings = this.requireStore().getSettings()
const launchEnv = resolveTuiAgentLaunchEnv(input.agent, settings.agentDefaultEnv)
const location = await this.resolveStructuredAgentSessionLocation(input.worktree)
const workspacePath = (await this.resolveRuntimeFileTarget(input.worktree)).worktree.path
return {
envelope: {
sessionId: input.envelope.sessionId,
clientOperationId: input.envelope.clientOperationId,
expectedRuntimeFence: null,
payloadFingerprint: ''
},
location,
provider: input.agent,
agent: input.agent,
accountHome: {
variable: 'CODEX_HOME',
path: await resolveAccountHomePath({ workspacePath, launchEnv })
},
runtimeKind: 'native'
}
}
restoreStructuredAgentSessionTabs(): Promise<void> {
this.structuredAgentSessionTabRestorePromise ??=
this.restoreStructuredAgentSessionTabsOnce().catch((error) => {
this.structuredAgentSessionTabRestorePromise = null
throw error
})
return this.structuredAgentSessionTabRestorePromise
}
prepareStructuredAgentSessionStartupRestoration(): Promise<void> {
this.structuredAgentSessionStartupRestorePromise ??=
this.prepareStructuredAgentSessionStartupRestorationOnce().catch((error) => {
this.structuredAgentSessionStartupRestorePromise = null
throw error
})
return this.structuredAgentSessionStartupRestorePromise
}
protected async prepareStructuredAgentSessionStartupRestorationOnce(): Promise<void> {
if (!this.hasPersistedStructuredAgentSessionStore()) {
return
}
// Durable agent records must exist before daemon inventory can be reconciled against them.
await this.ensureStructuredAgentSessionHost()
await this.refreshMobileSessionPtyRecords()
await getStructuredAgentSessionHost()?.reconcileRestartLeases()
}
protected hasPersistedStructuredAgentSessionStore(): boolean {
return hasPersistedStructuredAgentSessionStoreOnDisk(getProfileUserDataPath())
}
}