import type * as ClientReducer from '../../../src/shared/agent-session/structured-agent-session-reducer' import type * as ClientProjection from '../../../src/shared/agent-session/view/structured-agent-session-projection' import type * as ClientSchemas from '../../../src/shared/agent-session/journal/agent-session-journal-schemas' import type { sendPlan } from '../../../src/main/native-chat/agent-session-wire/operations/structured-agent-session-mutation-plans' import { importReleaseCheckoutModule, materializeReleaseCheckout, type ReleaseCheckout } from './release-checkout' // Read capability strings and registered methods from each real build rather than assuming what the older peer knows. export const WORKING_TREE = 'working-tree' as const export type AgentSessionClientProjection = Pick< typeof ClientReducer, 'EMPTY_STRUCTURED_AGENT_SESSION' | 'reduceStructuredAgentSession' > & Pick & Pick // Each module graph owns its host slot; installing into current source cannot populate a release dispatcher. const STRUCTURED_HOST_REGISTRY = '/src/main/native-chat/agent-session-wire/structured-agent-session-registry.ts' const MUTATION_PLANS = '/src/main/native-chat/agent-session-wire/structured-agent-session-mutation-plans.ts' const MUTATION_ADMISSION = '/src/main/native-chat/agent-session-wire/structured-agent-session-mutation-admission.ts' export type RpcReply = { id: string ok: boolean streaming?: true result?: unknown error?: { code: string; message: string } } export type RpcClientIdentity = { clientKind?: 'mobile' | 'runtime' clientCapabilities?: readonly string[] updateClientCapabilities?: (capabilities: readonly string[]) => void connectionId?: string clientId?: string } export type AgentSessionDispatcher = { dispatchStreaming: ( request: { id: string; authToken: string; method: string; params?: unknown }, reply: (message: string) => void, options?: RpcClientIdentity ) => Promise } export type AgentSessionWireBuild = { /** Human label used in test names and failure messages. */ label: string /** `working-tree` for current code, otherwise the resolved release commit. */ revision: string /** Capability strings this build defines. A peer cannot advertise — nor a client * ask for — a string its own source never names. */ capabilities: readonly string[] protocolVersion: number /** RPC method names the build registers, read from source. */ methodNames: readonly string[] /** A dispatcher carrying a method set this build really ships, so an * unknown-method answer is about the method and not an empty registry. */ createDispatcher: (runtime: unknown) => AgentSessionDispatcher /** Put a host in *this* build's slot. Loaded on call so a release that predates * the surface stays loadable, and throws rather than no-opping so a build with * no slot cannot read as a surface that answered. */ installStructuredHost: (host: unknown) => Promise /** This build's own admission of the `agentSession.send` params its host was handed. With no * journal it stops after the fingerprint check: a fingerprint it derives differently refuses * as `fingerprintMismatch`, one it agrees with as `sessionNotAttached`. */ admitSend: (sent: SentMessage) => Promise clientProjection: () => Promise } async function loadClientProjection( load: (path: string) => Promise> ): Promise { const modules = await Promise.all([ load('/src/shared/structured-agent-session-reducer.ts'), load('/src/shared/structured-agent-session-projection.ts'), load('/src/shared/agent-session-journal-schemas.ts') ]) const client: Record = Object.assign({}, ...modules) const itemSchema = client.AgentJournalRenderItemSchema if ( typeof client.reduceStructuredAgentSession !== 'function' || typeof client.projectStructuredItemsToNativeChat !== 'function' || !client.EMPTY_STRUCTURED_AGENT_SESSION || typeof itemSchema !== 'object' || itemSchema === null || !('parse' in itemSchema) || typeof itemSchema.parse !== 'function' ) { throw new Error('Release does not export the structured transcript reader') } // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: checked release exports; skew tests exercise admission, reducer and transcript signatures against real host frames. return client as AgentSessionClientProjection } type DispatcherModule = { RpcDispatcher: new (options: { runtime: unknown; methods: unknown[] }) => AgentSessionDispatcher } function registeredMethodNames(methods: readonly unknown[]): string[] { return methods .flatMap((method) => { if (!method || typeof method !== 'object' || !('name' in method)) { return [] } const { name } = method return typeof name === 'string' ? [name] : [] }) .sort() } function applyStructuredHost(module: Record, label: string, host: unknown): void { const install = module.setStructuredAgentSessionHost if (typeof install !== 'function') { throw new Error(`Build ${label} publishes no structured agent-session host registry`) } ;(install as (next: unknown) => void)(host) } /** The `agentSession.send` params a host is handed. */ export type SentMessage = Parameters[0] type SendAdmissionModules = { sendPlan: (sent: SentMessage) => unknown admitAndRunAgentSessionMutation: (request: { plan: unknown envelope: SentMessage['envelope'] journal: () => undefined store: { getRecord: () => null; selectWritableRecord: () => { kind: 'missing' } } }) => Promise } async function admitSend( plans: Record, admission: Record, sent: SentMessage ): Promise { // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the build's own send plan and admission; the absent session and journal stop both admission routes after fingerprint validation. A drifted export or shape fails this check. const build = { ...plans, ...admission } as unknown as SendAdmissionModules return build.admitAndRunAgentSessionMutation({ plan: build.sendPlan(sent), envelope: sent.envelope, journal: () => undefined, store: { getRecord: () => null, selectWritableRecord: () => ({ kind: 'missing' }) } }) } function capabilityStrings(module: Record): readonly string[] { const declared = module.RUNTIME_CAPABILITIES if (!Array.isArray(declared) || declared.length === 0) { throw new Error('Cross-version harness found no RUNTIME_CAPABILITIES to compare') } return declared as readonly string[] } async function loadWorkingTreeBuild(): Promise { const [protocol, dispatcher, methodRegistry] = await Promise.all([ import('../../../src/shared/protocol-version'), import('../../../src/main/runtime/rpc/dispatcher'), import('../../../src/main/runtime/rpc/methods/rpc-method-registry') ]) const module = dispatcher as unknown as DispatcherModule const methods = methodRegistry.ALL_RPC_METHODS as unknown[] return { label: WORKING_TREE, revision: WORKING_TREE, capabilities: capabilityStrings(protocol as unknown as Record), protocolVersion: protocol.RUNTIME_PROTOCOL_VERSION, methodNames: registeredMethodNames(methods), clientProjection: async () => { const [reducer, projection, schemas] = await Promise.all([ import('../../../src/shared/agent-session/structured-agent-session-reducer'), import('../../../src/shared/agent-session/view/structured-agent-session-projection'), import('../../../src/shared/agent-session/journal/agent-session-journal-schemas') ]) return { ...reducer, ...projection, ...schemas } }, createDispatcher: (runtime) => new module.RpcDispatcher({ runtime, methods }), installStructuredHost: async (host) => { const registry = await import('../../../src/main/native-chat/agent-session-wire/host/structured-agent-session-registry') applyStructuredHost(registry as unknown as Record, WORKING_TREE, host) }, admitSend: async (sent) => { const [plans, admission] = await Promise.all([ import('../../../src/main/native-chat/agent-session-wire/operations/structured-agent-session-mutation-plans'), import('../../../src/main/native-chat/agent-session-wire/operations/structured-agent-session-mutation-admission') ]) return admitSend(plans, admission, sent) } } } async function loadReleaseBuild(checkout: ReleaseCheckout): Promise { const [protocol, dispatcher, methodRegistry] = await Promise.all([ importReleaseCheckoutModule(checkout, '/src/shared/protocol-version.ts'), importReleaseCheckoutModule(checkout, '/src/main/runtime/rpc/dispatcher.ts'), importReleaseCheckoutModule(checkout, '/src/main/runtime/rpc/methods/rpc-method-registry.ts') ]) const module = dispatcher as unknown as DispatcherModule const methods = methodRegistry.ALL_RPC_METHODS as unknown[] return { label: checkout.ref, revision: checkout.commit, capabilities: capabilityStrings(protocol), protocolVersion: protocol.RUNTIME_PROTOCOL_VERSION as number, methodNames: registeredMethodNames(methods), clientProjection: () => loadClientProjection((path) => importReleaseCheckoutModule(checkout, path)), createDispatcher: (runtime) => new module.RpcDispatcher({ runtime, methods }), installStructuredHost: async (host) => { applyStructuredHost( await importReleaseCheckoutModule(checkout, STRUCTURED_HOST_REGISTRY), checkout.ref, host ) }, admitSend: async (sent) => { const [plans, admission] = await Promise.all([ importReleaseCheckoutModule(checkout, MUTATION_PLANS), importReleaseCheckoutModule(checkout, MUTATION_ADMISSION) ]) return admitSend(plans, admission, sent) } } } // Import current source for WORKING_TREE; extract any other git ref into the cached checkout. export async function loadAgentSessionWireBuild(ref: string): Promise { if (ref === WORKING_TREE) { return loadWorkingTreeBuild() } return loadReleaseBuild(await materializeReleaseCheckout(ref)) }