Files
orca/src/main/runtime/structured-agent-launch-composition.ts
T
Brennan Benson 9ae33a96f9 fix(native-chat): keep damaged chats readable without granting agent actions (#31931)
* fix(native-chat): validate saved next-fence floors before use

* WIP: separate saved conversation observations from runtime proof

* Consolidate duplicate imports for WIP compiler checkpoint

* Preserve saved conversation observations with strict runtime authority

* Consolidate session admission and catalog imports

* fix: align conversation observation contracts and fixtures

* test: update conversation observation and writer fixtures

* test: use checked host fixtures for structured workers

* fix(native-chat): bound conversation projection work per request

* test(native-chat): consolidate scale fixture imports

* test(native-chat): use checked scale fixture types

* test(native-chat): bound unreadable inventory traversal

* Test native CLI admission and durable reference reads

* test: bound cold conversation name subscription work

* test: distinguish native receipt probes from terminal identity

* test: retain exact mutation refusal recovery identity

* fix: explain agent conversation preparation refusals

* test: adapt remaining conversation readers and document CLI timeout

* fix: register preparation refusal English message

* test: align conversation fixtures with checked read APIs

* test(native-chat): use checked conversation in Codex fixture
2026-10-26 13:16:41 -07:00

248 lines
10 KiB
TypeScript

// Compose shared admission, cwd, environment, visuals and child sealing; agent parts add only agent-specific inputs and never set cwd/sealed env.
import type { AgentSessionJournalIdentity } from '../../shared/agent-session-journal-types'
import type { AgentChatPermissionChoice } from '../../shared/agent-chat-permission-mode'
import type { AgentSessionRecord } from '../../shared/agent-session-record'
import { isNativeLocalLocation } from '../../shared/execution-host'
import type { StructuredAgentDefinition } from '../native-chat/agent-session-wire/structured-agent-definition'
import { agentDrivesSession } from '../native-chat/agent-session-wire/structured-agent-session-provider-support'
import type { StructuredAgentCommandSettings } from '../native-chat/structured-agent-command-resolution'
import {
createNativeChatVisualsDelivery,
type NativeChatVisualsLaunch,
type PrepareNativeChatVisuals
} from '../native-chat/native-chat-visuals-delivery'
import { resolveAgentSessionLaunchDirectory } from './agent-session-launch-directory'
import type { AgentSessionRecordStore } from './agent-session-record-store'
import type { StructuredAgentAdapterContext } from './structured-agent-runtime-registrations'
import { sealStructuredSessionChild } from './structured-session-child-env'
/** Where a launch's shared inputs come from. */
export type StructuredAgentLaunchSources = {
store: Pick<
AgentSessionRecordStore,
'selectWritableRecord' | 'requireWritableRecord' | 'pinLaunchDirectory'
>
resolveWorkspacePath: (workspaceId: string) => Promise<string>
resolveBaseEnvironment: () => Promise<Record<string, string>>
/** The base environment with the user's overlay for `agent` laid over it. */
resolveAgentEnvironment: (agent: string) => Promise<Record<string, string>>
/** The user's per-agent environment overlay from settings. */
resolveAgentLaunchEnv?: (agent: string) => Record<string, string>
resolveCommandSettings?: () => StructuredAgentCommandSettings
prepareVisuals?: PrepareNativeChatVisuals
/** Rereads the base environment after a lookup that started at `askedAt` found no program in
* it; absent never retries. */
refreshAfterMissingProgram?: (askedAt: number) => Promise<void>
}
/** Resolve each input lazily once in agent-defined order; refusals before directory reads leave floating records unpinned. */
export type StructuredAgentLaunchBasis = {
/** Exists, belongs to this agent, runs on this host, and pins what this agent drives. */
record: AgentSessionRecord
launchDirectory(): Promise<string>
baseEnvironment(): Promise<Record<string, string>>
/** The user's overlay for this agent; empty when none. */
launchEnv(): Record<string, string>
/** The base environment with the user's overlay laid over it. */
environment(): Promise<Record<string, string>>
/** Null when the chat gets no visuals. */
visuals(): Promise<NativeChatVisualsLaunch | null>
commandSettings(): StructuredAgentCommandSettings
/** After a missing program, refresh both environment reads together; absent means no retry. */
refreshAfterMissingProgram?: (askedAt: number) => Promise<void>
/** The permission mode the host decided this start runs with; absent asks. */
permissionMode?: AgentChatPermissionChoice
}
export type StructuredLaunchRequest = {
identity: AgentSessionJournalIdentity
/** The reservation's token, which the seal hands the child. */
spawnToken: string
permissionMode?: AgentChatPermissionChoice
}
/** What the composition sets on a launch; an agent's part never does. */
type ComposedLaunchFields = 'cwd' | 'env' | 'envToDelete' | 'commandEnv'
export type StructuredAgentLaunchPart<L> = {
launch: Omit<L, ComposedLaunchFields>
/** The agent's environment, before the seal names the child. */
env: Record<string, string>
/** Inherited keys this agent's child must not keep; required, so an agent states even none. */
inheritedEnvToDelete: readonly string[]
/** See `sealStructuredSessionChild`. */
cliRuntimeCommand?: string
}
/** An agent's own part of a launch; it sees the session, never the spawn token. */
export type StructuredAgentLaunchPartResolver<L> = (
basis: StructuredAgentLaunchBasis,
identity: AgentSessionJournalIdentity
) => Promise<StructuredAgentLaunchPart<L>>
export type ComposedStructuredLaunch<L> = Omit<L, ComposedLaunchFields> & {
cwd: string
env: Record<string, string>
envToDelete: string[]
/** See `SealedStructuredSessionChild`; absent from a read, which seals no child. */
commandEnv?: Record<string, string>
}
/** The child to spawn, sealed with the reservation's token. */
export type StructuredAgentLaunchResolver<L> = (
request: StructuredLaunchRequest
) => Promise<ComposedStructuredLaunch<L>>
/** The same launch for a read that starts no child: the agent's own env, unsealed. */
export type StructuredAgentLaunchReader<L> = (
identity: AgentSessionJournalIdentity
) => Promise<ComposedStructuredLaunch<L>>
function once<T>(read: () => T): () => T {
let result: { value: T } | null = null
return () => (result ??= { value: read() }).value
}
function admittedRecord(
definition: StructuredAgentDefinition,
store: StructuredAgentLaunchSources['store'],
sessionId: string
): AgentSessionRecord {
if (store.selectWritableRecord(sessionId).kind === 'missing') {
throw new Error(`no durable agent-session record for ${sessionId}`)
}
const record = store.requireWritableRecord(sessionId)
if (record.provider !== definition.agent) {
throw new Error(`session ${sessionId} is a ${record.provider} session`)
}
const { location } = record
// A session pinned elsewhere belongs to that host; local launch would create a second provider-session writer.
if (!isNativeLocalLocation(location)) {
throw new Error(
`${definition.agent} structured sessions run on the local host, not ${location.executionHostId}`
)
}
const agents = { definition: (agent: string) => (agent === definition.agent ? definition : null) }
if (!agentDrivesSession(agents, record)) {
const pin = definition.accountHomeVariable ?? definition.accountLocatorKind
throw new Error(
`session ${sessionId}'s account home or handles are not ${definition.agent}'s: its sessions pin ${pin} and hold ${definition.handleTransport} handles`
)
}
return record
}
async function composedPart<L>(
definition: StructuredAgentDefinition,
sources: StructuredAgentLaunchSources,
part: StructuredAgentLaunchPartResolver<L>,
identity: AgentSessionJournalIdentity,
permissionMode?: AgentChatPermissionChoice
): Promise<StructuredAgentLaunchPart<L> & { cwd: string }> {
const record = admittedRecord(definition, sources.store, identity.sessionId)
const launchEnv = once(() => sources.resolveAgentLaunchEnv?.(definition.agent) ?? {})
const readEnvironments = () => ({
base: once(sources.resolveBaseEnvironment),
agent: once(() => sources.resolveAgentEnvironment(definition.agent))
})
let environments = readEnvironments()
const refresh = sources.refreshAfterMissingProgram
const basis: StructuredAgentLaunchBasis = {
record,
launchDirectory: once(() => resolveAgentSessionLaunchDirectory(sources, record)),
baseEnvironment: () => environments.base(),
launchEnv,
environment: () => environments.agent(),
visuals: once(async () => (await sources.prepareVisuals?.(record.sessionId)) ?? null),
commandSettings: once(() => sources.resolveCommandSettings?.() ?? {}),
...(refresh
? {
refreshAfterMissingProgram: async (askedAt) => {
await refresh(askedAt)
environments = readEnvironments()
}
}
: {}),
...(permissionMode ? { permissionMode } : {})
}
const composed = await part(basis, identity)
return { ...composed, cwd: await basis.launchDirectory() }
}
export function composeStructuredLaunch<L>(
definition: StructuredAgentDefinition,
sources: StructuredAgentLaunchSources,
part: StructuredAgentLaunchPartResolver<L>
): StructuredAgentLaunchResolver<L> {
return async ({ identity, spawnToken, permissionMode }) => {
const { launch, cwd, env, inheritedEnvToDelete, cliRuntimeCommand } = await composedPart(
definition,
sources,
part,
identity,
permissionMode
)
admittedRecord(definition, sources.store, identity.sessionId)
const child = sealStructuredSessionChild({
sessionId: identity.sessionId,
spawnToken,
env,
inheritedEnvToDelete,
...(cliRuntimeCommand ? { cliRuntimeCommand } : {})
})
return {
...launch,
cwd,
env: child.env,
envToDelete: child.envToDelete,
commandEnv: child.commandEnv
}
}
}
export function composeStructuredLaunchRead<L>(
definition: StructuredAgentDefinition,
sources: StructuredAgentLaunchSources,
part: StructuredAgentLaunchPartResolver<L>
): StructuredAgentLaunchReader<L> {
return async (identity) => {
const { launch, cwd, env, inheritedEnvToDelete } = await composedPart(
definition,
sources,
part,
identity
)
admittedRecord(definition, sources.store, identity.sessionId)
return { ...launch, cwd, env, envToDelete: [...inheritedEnvToDelete] }
}
}
/** The shared launch inputs of the runtime an agent's adapter is built in. */
export function structuredAgentLaunchSources(
context: Pick<StructuredAgentAdapterContext, 'deps' | 'store' | 'environment'>
): StructuredAgentLaunchSources {
const { deps } = context
return {
store: context.store,
resolveWorkspacePath: deps.resolveWorkspacePath,
resolveBaseEnvironment: context.environment.resolveBaseEnvironment,
resolveAgentEnvironment: context.environment.resolveAgentEnvironment,
refreshAfterMissingProgram: context.environment.refreshAfterMissingProgram,
...(deps.resolveAgentLaunchEnv ? { resolveAgentLaunchEnv: deps.resolveAgentLaunchEnv } : {}),
...(deps.resolveAgentCommandSettings
? { resolveCommandSettings: deps.resolveAgentCommandSettings }
: {}),
// Wired by the real hosts only, so a test runtime never loads the bundled skill.
...(deps.nativeChatVisuals
? {
prepareVisuals: createNativeChatVisualsDelivery({
stateDirectory: deps.stateDirectory,
logger: deps.logger,
isEnabled: deps.nativeChatVisuals.isEnabled
})
}
: {})
}
}