Files
orca/tests/e2e/cross-version-wire/cross-version-stop-note-test-rig.ts
Neil 1c950baf33 refactor(main): group session lifecycle and journal modules by owner (#38638)
* refactor: group structured session modules by owner

* test: retire status history work-count oracle suite

* refactor(main): organize journal storage and projection owners

* Consolidate restart recovery fixture imports

* Consolidate concrete session option model imports
2026-11-06 06:00:08 -08:00

259 lines
9.5 KiB
TypeScript

import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeAll, beforeEach, expect } from 'vitest'
import { StructuredAgentSessionHost } from '../../../src/main/native-chat/agent-session-wire/host/structured-agent-session-host'
import { NO_STRUCTURED_AGENTS } from '../../../src/main/native-chat/agent-session-wire/providers/testing/structured-agent-session-adapter-router-test-support'
import { startsWhenPublished } from '../../../src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-instant-start.test-support'
import { setStructuredAgentSessionHost } from '../../../src/main/native-chat/agent-session-wire/host/structured-agent-session-registry'
import { createStructuredAgentSessionLogger } from '../../../src/main/native-chat/agent-session-wire/host/structured-agent-session-logger'
import { openTestAgentSessionRecordStore } from '../../../src/main/runtime/agent-session/records/agent-session-record-store-test-harness'
import {
closeTestJournalHostDatabase,
openTestJournalHostDatabase
} from '../../../src/main/native-chat/agent-session-journal/store-lifecycle/journal-host-database-test-support'
import type { AgentSessionJournal } from '../../../src/main/native-chat/agent-session-journal/journal-store'
import { agentJournalItemKey } from '../../../src/shared/agent-session/journal/agent-session-journal-item-key'
import { agentSessionFailureFact } from '../../../src/shared/agent-session/failures/agent-session-failure'
import { agentSessionFailureWords } from '../../../src/shared/agent-session/failures/agent-session-failure-words'
import {
AGENT_JOURNAL_THREAD_SCOPE,
type AgentJournalItemBody,
type AgentJournalRenderItem,
type AgentJournalTurnLifecycle
} from '../../../src/shared/agent-session/journal/agent-session-journal-types'
import type { AgentJournalCursor } from '../../../src/shared/agent-session/journal/agent-session-journal-identity'
import { isAdmissibleAgentJournalRenderItem } from '../../../src/shared/agent-session/journal/agent-session-journal-schemas'
import type {
AgentSessionHistoryPage,
AgentSessionHistoryResult,
AgentSessionSubscribeEvent
} from '../../../src/shared/agent-session/agent-session-wire'
import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer'
import { codexProviderHandle } from '../../../src/shared/agent-session/lifecycle/agent-session-provider-handle-encoding'
import {
attachParams,
NOW,
resetOperationIds,
SESSION,
THREAD
} from './structured-agent-session-surface-manifest'
import {
loadAgentSessionWireBuild,
type AgentSessionClientProjection,
type AgentSessionWireBuild,
type RpcClientIdentity,
type RpcReply
} from './versioned-agent-session-wire'
export type ScenarioPort = {
build: () => AgentSessionWireBuild
callBuild: (
build: AgentSessionWireBuild,
method: string,
params: unknown,
client: RpcClientIdentity,
runtime?: unknown
) => Promise<RpcReply[]>
runtimeStub: () => unknown
}
export const turnIdentity = {
provider: 'orca',
clientMessageId: 'turn-stop-test'
} as const
export const turnItemId = agentJournalItemKey(turnIdentity)
export const noteIdentity = {
provider: 'orca',
clientMessageId: 'stop:turn-stop-test'
} as const
export const noteId = agentJournalItemKey(noteIdentity)
export const scope = { kind: 'turn', turnItemId } as const
export const ordinary = { kind: 'status', text: 'Cancellation requested.' }
export const unconfirmed: AgentJournalItemBody = {
kind: 'status',
...agentSessionFailureWords(agentSessionFailureFact('cancelUnconfirmed'), {
surface: 'row'
})
}
export function createReleasedStopNoteRig(port: ScenarioPort, ref: string) {
let released: AgentSessionWireBuild
let client: AgentSessionClientProjection
let root: string
let host: StructuredAgentSessionHost
let journal: AgentSessionJournal
beforeAll(async () => {
released = await loadAgentSessionWireBuild(ref)
client = await released.clientProjection()
}, 180_000)
beforeEach(async () => {
resetOperationIds()
root = await mkdtemp(join(tmpdir(), 'orca-released-stop-note-'))
const store = await openTestAgentSessionRecordStore(root)
host = new StructuredAgentSessionHost({
agents: NO_STRUCTURED_AGENTS,
store,
journalDatabase: openTestJournalHostDatabase(root),
claimKeyId: 'key-1',
logger: createStructuredAgentSessionLogger(),
now: () => NOW,
// A ready provider proves its start as soon as it is published.
adapter: startsWhenPublished(
{
supportsCreate: () => true,
acquire: async ({ fence, spawnToken }) => ({
acquisitionGeneration: `stop-note-${fence}`,
process: {
hostId: 'local',
pid: 4242,
processStartTimeMs: NOW,
spawnToken
},
link: {
linkId: `link-${fence}`,
handle: codexProviderHandle(THREAD),
origin: 'created',
mintedAtFence: fence,
observedAt: NOW
}
}),
dispatch: async () => ({ state: 'unknown', reason: 'unused' }),
cancelTurn: async () => ({ cancelled: true }),
answerPrompt: async () => {},
setOption: async () => {}
},
() => host
)
})
setStructuredAgentSessionHost(host)
expect(await host.attach({ callerKey: 'released-client' }, attachParams(null))).toMatchObject({
ok: true
})
journal = host['sessions'].get(SESSION)!.journal
})
afterEach(async () => {
setStructuredAgentSessionHost(null)
await host.flushAllStreamedEvents()
await journal.close()
await closeTestJournalHostDatabase(root)
await rm(root, { recursive: true, force: true })
})
function call(method: string, params: unknown) {
return port.callBuild(
port.build(),
method,
params,
{
clientKind: 'runtime',
clientCapabilities: released.capabilities,
clientId: 'released-client'
},
port.runtimeStub()
)
}
/** The released client's schema decides admission; this build's guard types what it kept. */
function admit(item: unknown): AgentJournalRenderItem {
const admitted = client.AgentJournalRenderItemSchema.parse(item)
if (!isAdmissibleAgentJournalRenderItem(admitted)) {
throw new Error('released client admitted an item this build cannot read')
}
return admitted
}
async function page(params: Record<string, unknown>): Promise<AgentSessionHistoryPage> {
const reply = (await call('agentSession.history', { sessionId: SESSION, ...params }))[0]
expect(reply?.ok).toBe(true)
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: real host history RPC answer; every item is admitted with the released client's schema below.
const result = reply?.result as AgentSessionHistoryResult
if (!result.ok) {
throw new Error(result.reset)
}
return {
...result.page,
items: result.page.items.map(admit)
}
}
async function subscribe(
cursor: AgentJournalCursor | null = journal.cursor()
): Promise<RpcReply[]> {
return call('agentSession.subscribe', { sessionId: SESSION, ...(cursor ? { cursor } : {}) })
}
function events(replies: RpcReply[]): AgentSessionSubscribeEvent[] {
return replies.map((reply) => {
expect(reply.ok).toBe(true)
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: real streaming RPC reply; release admission validates each item before reduction.
const event = reply.result as AgentSessionSubscribeEvent
return event.type === 'batch'
? {
...event,
batch: {
...event.batch,
items: event.batch.items.map(admit)
}
}
: event
})
}
function reduce(state: StructuredAgentSessionState, replies: RpcReply[]) {
return events(replies).reduce(
(state, event) => client.reduceStructuredAgentSession(state, { type: 'event', event }),
state
)
}
function render(state: StructuredAgentSessionState) {
const item = state.items.find((item) => item.itemId === noteId)!
return client.projectStructuredItemsToNativeChat([item])[0]?.blocks[0]
}
async function turn(state: AgentJournalTurnLifecycle['state'], batch = false) {
const body = { kind: 'turn' as const, turnId: 'turn-stop-test', state }
await (batch
? journal.appendLifecycleBatch({
settlementId: `end-${state}`,
fence: 1,
mutations: [
{
kind: 'item',
identity: turnIdentity,
body,
turnScope: AGENT_JOURNAL_THREAD_SCOPE
}
]
})
: journal.appendItem(turnIdentity, body, {
fence: 1,
turnScope: AGENT_JOURNAL_THREAD_SCOPE
}))
}
async function note(body: AgentJournalItemBody = unconfirmed) {
await journal.appendItem(noteIdentity, body, {
fence: 1,
turnScope: scope
})
}
async function seed() {
await turn('running')
await note()
const initial = await page({ direction: 'tail' })
return client.reduceStructuredAgentSession(client.EMPTY_STRUCTURED_AGENT_SESSION, {
type: 'history-page',
page: initial
})
}
return {
page,
subscribe,
events,
reduce,
render,
turn,
note,
seed,
get journal() {
return journal
},
get client() {
return client
}
}
}