Files
orca/src/shared/agent-session-rewind.ts
T
Brennan Benson bb44f74d00 Keep native chat /clear in the same conversation (#26579)
* Keep clear in the same chat and reset its provider context

* Derive clear pauses only from journal membership and cover older rewind clients

* Point queued command rewind test at the structured hook

* Group queue consume errors and rewind replay assertions
2026-10-08 16:00:29 -07:00

74 lines
2.5 KiB
TypeScript

import { z } from 'zod'
import {
AgentJournalItemBodySchema,
AgentJournalProducerLinkageFields,
AgentJournalTurnScopeSchema
} from './agent-session-journal-schemas'
import { parseAgentJournalItemKey } from './agent-session-journal-item-key'
import type { AgentSessionMutationEnvelope } from './agent-session-wire'
export const AGENT_SESSION_REWIND_REASONS = [
'unsupported',
'history-not-paginated',
'busy',
'stale-epoch',
'invalid-target',
'history-limit',
'provider-refused',
'proof-mismatch',
'outcome-unknown'
] as const
export type AgentSessionRewindReason = (typeof AGENT_SESSION_REWIND_REASONS)[number]
export type AgentSessionRewindSupport =
| { supported: true }
| { supported: false; reason: AgentSessionRewindReason }
export type AgentSessionRewindParams = {
envelope: AgentSessionMutationEnvelope
itemId: string
expectedEpoch: string
}
export type AgentSessionRewindResult = {
itemId: string
epoch: string
sequence?: number
}
const Key = z.string().min(1).max(4096)
export const AgentSessionRewindRecordSchema = z.object({
operationId: Key,
callerKey: Key,
itemId: Key,
providerItemId: Key.optional(),
expectedEpoch: Key,
contextClearOperationId: Key.optional(),
contextClearSequence: z.number().int().positive().optional(),
phase: z.enum(['prepared', 'provider-succeeded', 'completed', 'refused']),
epoch: Key.optional(),
sequence: z.number().int().nonnegative().optional(),
hydrationVerified: z.boolean().optional(),
reason: z.string().min(1).max(512).optional(),
retained: z
.array(
z.object({
itemId: Key.refine((key) => parseAgentJournalItemKey(key) !== null),
body: AgentJournalItemBodySchema,
observedAt: z.number().finite(),
// Absent on records written before rows stated them: the rebuild then places the row by position.
turnScope: AgentJournalTurnScopeSchema.optional(),
...AgentJournalProducerLinkageFields
})
)
.max(10_000)
})
export type AgentSessionRewindRecord = z.infer<typeof AgentSessionRewindRecordSchema>
export const isAgentSessionRewindRecord = (value: unknown): value is AgentSessionRewindRecord =>
AgentSessionRewindRecordSchema.safeParse(value).success
export function isAgentSessionRewindResult(value: unknown): value is AgentSessionRewindResult {
if (!value || typeof value !== 'object') {
return false
}
const result = value as Partial<AgentSessionRewindResult>
return typeof result.itemId === 'string' && typeof result.epoch === 'string'
}