mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 08:37:49 +00:00
* Native chat: never mark a message not delivered after a crash; reopening a chat stops reading the transcript After a crash or restart, attach read a window of the agent's saved log and marked any unconfirmed message missing from it as rejected "not delivered". Absence from a log Orca does not own is not proof: the window's anchor moves every turn (STA-7416) and its text filters drop tagged prompts (STA-7453), so messages the agent received were marked undelivered and retyping them duplicated the question. The read also ran on every attach (STA-7434). Delete the inference: the provider history window and its adapter hooks (Claude window and branch proof, OpenCode stored-messages reader), the submission matcher, and the attach step. A send the crash boundary leaves unconfirmed stays an ordinary sent message; the interrupted turn still ends through the existing settlement. Explicit live refusals are unchanged. A queued card whose hand-off a restart left unconfirmed no longer counts as able to return to the queue, so it stops keeping an idle chat loaded. * test(native-chat): update restart fixture calls after history removal * Hide rewind targets without a saved native boundary * Run rewind boundary regression across host and renderer * Keep incoming main package-manager lock unchanged * Test recovered chat consumer outcomes without delivery inference * Assign the complete cursor in recovered phone regression fixture * Supply presentation capture in rewind boundary fixture
268 lines
9.9 KiB
TypeScript
268 lines
9.9 KiB
TypeScript
import { activeProviderContext } from '../../../shared/agent-session-provider-context'
|
||
// Store CAS and idempotency rows make retried create/ensure replay instead of reserving another owner.
|
||
|
||
import type { AgentSessionJournalIdentity } from '../../../shared/agent-session-journal-types'
|
||
import type { AgentSessionOwnerProbe } from '../../../shared/agent-session-lease-adjudication'
|
||
import type {
|
||
StructuredAgentId,
|
||
AgentSessionProviderHandle,
|
||
AgentSessionProviderHandleLink
|
||
} from '../../../shared/agent-session-provider-handle'
|
||
import {
|
||
agentSessionProviderHandleBelongsTo,
|
||
agentSessionProviderHandleFromWire,
|
||
type AgentSessionWireProviderHandle
|
||
} from '../../../shared/agent-session-provider-handle-encoding'
|
||
import type {
|
||
AgentSessionAccountHome,
|
||
AgentSessionExecutionLocation,
|
||
AgentSessionLaunchArgs,
|
||
AgentSessionLaunchEnv,
|
||
AgentSessionRecord
|
||
} from '../../../shared/agent-session-record'
|
||
import {
|
||
AgentSessionRefusalError,
|
||
agentSessionRefusalFromReference,
|
||
agentSessionRefusalReference,
|
||
isAgentSessionWireRefusalCode,
|
||
refuse,
|
||
type AgentSessionMutationEnvelope,
|
||
type AgentSessionRefusalReference,
|
||
type AgentSessionWireRefusal
|
||
} from '../../../shared/agent-session-wire'
|
||
import {
|
||
agentSessionFingerprintConflict,
|
||
computeAgentSessionPayloadFingerprint
|
||
} from '../../../shared/agent-session-mutation-envelope'
|
||
import type { AgentSessionRecordStore } from '../../runtime/agent-session-record-store'
|
||
import type { AgentSessionJournal } from '../agent-session-journal/journal-store'
|
||
import { structuredAgentSessionRefusalMessage } from './structured-agent-session-refusal-message'
|
||
|
||
/** Clients cannot assert spawn tokens, claim keys or owner death; only host observations grant authority. */
|
||
export type AgentSessionAttachParams = {
|
||
envelope: AgentSessionMutationEnvelope
|
||
location: AgentSessionExecutionLocation
|
||
provider: StructuredAgentId
|
||
agent: StructuredAgentId
|
||
accountHome: AgentSessionAccountHome
|
||
/** Retain native in params because fingerprints include it. */
|
||
runtimeKind: 'native'
|
||
/** Host-only create defaults; remote schemas reject these. */
|
||
options?: Readonly<Record<string, string>>
|
||
/** Exclude tab placement from conversation identity/fingerprints. */
|
||
surfaceTabId?: string
|
||
launchArgs?: string[]
|
||
/** Omit only for create intent; fingerprint the client wire form before adapter proof. */
|
||
providerHandle?: AgentSessionWireProviderHandle
|
||
/** Host-only adoption seeds resume/import. Ordinary ensure handles must not imply adoption. */
|
||
adopt?: {
|
||
providerHandle: AgentSessionProviderHandle
|
||
/** Omit only for exact replay of an already-imported journal. */
|
||
transcriptPath?: string
|
||
source?: { providerSessionId: string; workspaceCwd: string }
|
||
}
|
||
}
|
||
|
||
export type AgentSessionAttachAuthority = {
|
||
launchDirectory?: string
|
||
spawnToken: string | (() => string)
|
||
claimKeyId: string
|
||
handoffOperationId: string | null
|
||
probe: AgentSessionOwnerProbe
|
||
launchArgs?: AgentSessionLaunchArgs
|
||
launchEnv?: AgentSessionLaunchEnv
|
||
}
|
||
|
||
/** Exclude transient spawn/probe evidence and re-resolved defaults so unknown-outcome retries still replay. */
|
||
export function attachFingerprintFields(params: AgentSessionAttachParams): Record<string, unknown> {
|
||
return {
|
||
location: params.location,
|
||
provider: params.provider,
|
||
agent: params.agent,
|
||
accountHome: params.accountHome,
|
||
runtimeKind: params.runtimeKind,
|
||
providerHandle: params.providerHandle,
|
||
// Adoption changes identity; transcript location is transient and excluded.
|
||
adoptedProviderHandle: params.adopt?.providerHandle,
|
||
expectedRuntimeFence: params.envelope.expectedRuntimeFence
|
||
}
|
||
}
|
||
|
||
export function admitAttachOrRefuse(
|
||
params: AgentSessionAttachParams
|
||
): { ok: true; fingerprint: string } | { ok: false; refusal: AgentSessionWireRefusal } {
|
||
// Record validation and adapter routing must agree on the agent.
|
||
if (params.agent !== params.provider) {
|
||
return {
|
||
ok: false,
|
||
refusal: refuse(
|
||
'agent_session_operation_invalid',
|
||
{ reason: 'requestMalformed' },
|
||
`A ${params.provider} session cannot be started as ${params.agent}.`
|
||
)
|
||
}
|
||
}
|
||
if (
|
||
params.providerHandle &&
|
||
!agentSessionProviderHandleBelongsTo(
|
||
agentSessionProviderHandleFromWire(params.providerHandle),
|
||
params.provider
|
||
)
|
||
) {
|
||
return {
|
||
ok: false,
|
||
refusal: refuse(
|
||
'agent_session_operation_invalid',
|
||
{ reason: 'requestMalformed' },
|
||
`A ${params.provider} session requires a ${params.provider} provider handle.`
|
||
)
|
||
}
|
||
}
|
||
const fingerprint = computeAgentSessionPayloadFingerprint({
|
||
method: 'agentSession.attach',
|
||
sessionId: params.envelope.sessionId,
|
||
fields: attachFingerprintFields(params)
|
||
})
|
||
const conflict = agentSessionFingerprintConflict(params.envelope, fingerprint)
|
||
return conflict ? { ok: false, refusal: conflict } : { ok: true, fingerprint }
|
||
}
|
||
|
||
export function journalIdentityFor(
|
||
record: AgentSessionRecord,
|
||
params: AgentSessionAttachParams
|
||
): AgentSessionJournalIdentity {
|
||
const active = activeProviderContext(record)
|
||
const head = active.head
|
||
const providerHandle =
|
||
head?.handle ??
|
||
(!active.pendingClear && params.providerHandle
|
||
? agentSessionProviderHandleFromWire(params.providerHandle)
|
||
: null)
|
||
return {
|
||
sessionId: record.sessionId,
|
||
workspaceId: params.location.workspaceId,
|
||
hostId: params.location.executionHostId,
|
||
agent: params.agent,
|
||
providerHandle
|
||
}
|
||
}
|
||
|
||
export type AttachedJournal = {
|
||
journal: AgentSessionJournal
|
||
/** Submissions the crash boundary left `unknown`; kept on the wire for older peers. */
|
||
unconfirmedClientMessageIds: string[]
|
||
}
|
||
|
||
/**
|
||
* The conversation's journal, opened — and its crash boundary settled — by the conversation's own
|
||
* open. A send the boundary could only doubt stays `unknown`: whether the provider took it is never
|
||
* inferred from provider history, since absence from a log Orca does not own proves nothing.
|
||
*/
|
||
export async function attachJournal(input: {
|
||
record: AgentSessionRecord
|
||
/** The host's open conversation, whose journal the attach adopts. */
|
||
openConversation: (record: AgentSessionRecord) => Promise<AgentSessionJournal>
|
||
}): Promise<AttachedJournal> {
|
||
const journal = await input.openConversation(input.record)
|
||
return {
|
||
journal,
|
||
unconfirmedClientMessageIds: journal
|
||
.submissions()
|
||
.filter((entry) => entry.dispatchState === 'unknown' && entry.recovered === true)
|
||
.map((entry) => entry.clientMessageId)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* The first link of an adopting session's chain.
|
||
*
|
||
* `adopted` is the only origin besides `created` a chain will accept at its head, and it is the
|
||
* honest one here: this session did not create the conversation. The adapter appends its own
|
||
* `resumed` link once the provider proves the same identity root — or, when it proves the identical
|
||
* handle at the same fence, the validator elides that as a retry and this link stays the head.
|
||
*/
|
||
const ADOPTED_HANDLE_FENCE = 1
|
||
|
||
function adoptedProviderHandleLink(
|
||
handle: AgentSessionProviderHandle,
|
||
observedAt: number
|
||
): AgentSessionProviderHandleLink {
|
||
return {
|
||
// Pi native IDs are paths; link IDs must be bounded slugs.
|
||
linkId: `${handle.agent}-${ADOPTED_HANDLE_FENCE}-${handle.nativeId}`
|
||
.replace(/[^A-Za-z0-9_-]/g, '_')
|
||
.slice(0, 128),
|
||
handle,
|
||
origin: 'adopted',
|
||
mintedAtFence: ADOPTED_HANDLE_FENCE,
|
||
observedAt
|
||
}
|
||
}
|
||
|
||
export function reserveRequestFor(input: {
|
||
sessionId: string
|
||
params: AgentSessionAttachParams
|
||
authority: AgentSessionAttachAuthority
|
||
callerKey: string
|
||
fingerprint: string
|
||
now: number
|
||
models?: Parameters<AgentSessionRecordStore['reserveOwner']>[0]['models']
|
||
}): Parameters<AgentSessionRecordStore['reserveOwner']>[0] {
|
||
const { params, authority } = input
|
||
return {
|
||
sessionId: input.sessionId,
|
||
location: params.location,
|
||
provider: params.provider,
|
||
accountHome: params.accountHome,
|
||
...(params.options ? { options: params.options, models: input.models } : {}),
|
||
...(params.envelope.expectedRuntimeFence === null && params.surfaceTabId
|
||
? { surfaceTabId: params.surfaceTabId }
|
||
: {}),
|
||
...(authority.launchArgs ? { launchArgs: authority.launchArgs } : {}),
|
||
...(authority.launchEnv ? { launchEnv: authority.launchEnv } : {}),
|
||
...(authority.launchDirectory ? { launchDirectory: authority.launchDirectory } : {}),
|
||
...(params.adopt
|
||
? {
|
||
// New records begin at fence 1, matching the owner probe’s required head fence.
|
||
adoptedHandleLink: adoptedProviderHandleLink(params.adopt.providerHandle, input.now)
|
||
}
|
||
: {}),
|
||
expectedFence: params.envelope.expectedRuntimeFence,
|
||
spawnToken: authority.spawnToken,
|
||
claimKeyId: authority.claimKeyId,
|
||
handoffOperationId: authority.handoffOperationId,
|
||
probe: authority.probe,
|
||
operation: {
|
||
callerKey: input.callerKey,
|
||
operationId: params.envelope.clientOperationId,
|
||
fingerprint: input.fingerprint
|
||
},
|
||
now: input.now
|
||
}
|
||
}
|
||
|
||
/** Only known store codes are client refusals; rethrow unclassified defects. */
|
||
export function classifyStoreFailure(
|
||
error: unknown,
|
||
currentFence: number | null,
|
||
record: AgentSessionRecord | null = null
|
||
): AgentSessionWireRefusal {
|
||
const rawCode = error instanceof Error ? error.message : String(error)
|
||
if (!isAgentSessionWireRefusalCode(rawCode)) {
|
||
throw error
|
||
}
|
||
const emitted: AgentSessionRefusalReference =
|
||
error instanceof AgentSessionRefusalError
|
||
? agentSessionRefusalReference(error.refusal)
|
||
: { code: rawCode }
|
||
// Resolve actionable wording for latched sessions instead of exposing bare store codes.
|
||
const told = structuredAgentSessionRefusalMessage(emitted, record)
|
||
const reference = told?.reference ?? emitted
|
||
return agentSessionRefusalFromReference(
|
||
reference.code === 'agent_session_checkpoint_stale' && currentFence !== null
|
||
? { code: reference.code, details: { ...reference.details, currentFence } }
|
||
: reference,
|
||
told?.message ?? `The session store refused this call: ${rawCode}.`
|
||
)
|
||
}
|