Files
orca/src/main/native-chat/structured-agent-cli-source.ts
T

97 lines
3.4 KiB
TypeScript

import type { AgentStatusIpcPayload } from '../../shared/agent-status-types'
import type { StructuredAgentSessionResumeSource } from '../../shared/structured-agent-session-create'
import type { StructuredAgentId } from '../../shared/agent-session-provider-handle'
import { agentSessionRefusalError } from '../../shared/agent-session-wire-refusals'
import {
adoptsCliConversations,
chatAgentsShareConversations,
CLI_CONVERSATION_ADOPTING_AGENTS
} from '../../shared/structured-agent-cli-conversation-adoption'
import { isWslUncPath } from '../../shared/wsl-paths'
import { needsWslHostResolution } from './host-readable-transcript-path'
import { parseSshTranscriptPath } from './ssh-transcript-path'
export type StructuredAgentCliSourceEvidence =
| { readonly rows: readonly Readonly<AgentStatusIpcPayload>[] }
| { readonly error: unknown }
export function captureStructuredAgentCliSource(
readRows: () => readonly AgentStatusIpcPayload[]
): StructuredAgentCliSourceEvidence {
try {
return Object.freeze({
rows: Object.freeze(
readRows().map((row) => Object.freeze({
...row,
...(row.providerSession
? { providerSession: Object.freeze({ ...row.providerSession }) }
: {})
}))
)
})
} catch (error) {
// Capture cannot override an existing owner or committed replay.
return Object.freeze({ error })
}
}
export function structuredAgentCliSourcePathFromEvidence(input: {
agent: StructuredAgentId
resumeFrom: StructuredAgentSessionResumeSource
workspaceId: string
evidence?: StructuredAgentCliSourceEvidence
}): string | undefined {
const evidence = input.evidence
if (input.resumeFrom.cliSource && evidence && 'error' in evidence) {
throw evidence.error
}
return structuredAgentCliSourcePath({
...input,
rows: evidence && 'rows' in evidence ? evidence.rows : []
})
}
/** A client names a pane; only this host supplies its provider path. */
export function structuredAgentCliSourcePath(input: {
agent: StructuredAgentId
resumeFrom: StructuredAgentSessionResumeSource
workspaceId: string
rows: readonly AgentStatusIpcPayload[]
}): string | undefined {
const source = input.resumeFrom.cliSource
if (!source) {
return undefined
}
const row = input.rows.find(
(candidate) =>
source.workspaceId === input.workspaceId &&
candidate.worktreeId === source.workspaceId &&
candidate.paneKey === source.paneKey &&
!candidate.connectionId &&
!candidate.structuredHost &&
adoptsCliConversations(candidate.agentType) &&
// A 2.x pane started as `opencode` reports `opencode`; the OpenCode 2 chat opens its conversation.
CLI_CONVERSATION_ADOPTING_AGENTS[candidate.agentType].some((agent) =>
chatAgentsShareConversations(agent, input.agent)
) &&
candidate.providerSession?.id === input.resumeFrom.providerSessionId
)
if (!row) {
throw agentSessionRefusalError(
'agent_session_identity_required',
{ reason: 'transcriptUnreadable' },
'The source conversation is no longer available.'
)
}
const path = row.providerSession?.transcriptPath?.trim()
if (
path &&
(isWslUncPath(path) || needsWslHostResolution(path) || parseSshTranscriptPath(path))
) {
throw agentSessionRefusalError('structured_agent_session_unsupported', {
reason: 'hostUnsupported'
})
}
return path || undefined
}