mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 08:03:12 +00:00
`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.
194 lines
8.0 KiB
TypeScript
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())
|
|
}
|
|
}
|