diff --git a/architecture.md b/architecture.md index 12e45b4b..e21e294f 100644 --- a/architecture.md +++ b/architecture.md @@ -2152,6 +2152,23 @@ public permission chain. This is the V1 permission ceiling; the portable proof that every provider exposes no ambient tool is tracked separately and does not widen it. `xmd run` keeps its caller-selected Agent permission behavior. +What a turn was allowed to do is retained beside what it said, and the two are +recorded by different things. A permission policy decides every request inside +its scope without deferring outward, so nothing installed inside one — and +nothing installed inside the turn, below a policy a host put around the execution +— can see the decision it makes. The Agent middleware installed with the +Agent itself, outside every policy installed after it, owns both sides of this. +Each `Agent.prompt()` call is given a private ledger and the cold stream it +returns is wrapped, so every event that call produces is read with that ledger +in place and a request raised while it is read inherits that ledger and no +other. A component installs nothing to be audited. The observer reserves a +place, delegates once, and copies the outcome that comes back into that place, so +concurrent turns keep their own audits and two overlapping requests keep the +order they were asked in. A place nobody completed — a policy that raised, a turn +torn down while a request waited — is published as nothing rather than as a +denial somebody invented. The ledger names a destination and confers no authority +to answer anything. + The only thing a workflow Agent receives is the rendered content of an authored ``. It proposes an information request, or a change, by returning XMD. The trusted workflow host passes that exact response to the constrained diff --git a/deno.lock b/deno.lock index da4b608c..0135003e 100644 --- a/deno.lock +++ b/deno.lock @@ -4052,6 +4052,7 @@ "packages/cli": { "dependencies": [ "npm:@bomb.sh/tty@0.9.0", + "npm:@effectionx/stream-helpers@0.8.3", "npm:@standard-schema/spec@1", "npm:zod@^4.3.6" ], diff --git a/packages/cli/deno.json b/packages/cli/deno.json index 5594c615..cade6347 100644 --- a/packages/cli/deno.json +++ b/packages/cli/deno.json @@ -5,6 +5,7 @@ "exports": "./src/deno.ts", "imports": { "@bomb.sh/tty": "npm:@bomb.sh/tty@0.9.0", + "@effectionx/stream-helpers": "npm:@effectionx/stream-helpers@0.8.3", "@standard-schema/spec": "npm:@standard-schema/spec@^1.0.0", "zod": "npm:zod@^4.3.6" } diff --git a/packages/cli/src/agent-stack.ts b/packages/cli/src/agent-stack.ts index 4fdb23ab..e6e63609 100644 --- a/packages/cli/src/agent-stack.ts +++ b/packages/cli/src/agent-stack.ts @@ -151,15 +151,20 @@ export function hostAcpDependencies(stack: PlanWriterStack): AcpxProviderDepende } /** - * Install the agent stack a document runs under: the registration, the - * components with the resolved root provider, the permission mode, and the - * terminal this command has to give away. + * The half of the stack that answers *who* an agent is and *how* a document + * reaches one: the registered provider, and the components over it. + * + * Separated because two commands need exactly this much and disagree about the + * rest. `xmd run` owns a terminal and a readline, so it adds the permission + * mode and the foreground launcher below. The REPL owns neither — it presents + * requests in its own surface and has no terminal to give away — so it installs + * this and its own private policy instead of inheriting one that would ask + * through a readline nobody is looking at. * - * Nothing starts an agent — the provider validates availability on first use, - * and an embedded adapter reaches the disk at that same point. A document that - * asks for no agent installs no adapter. + * Nothing here starts an agent. The provider validates availability on first + * use, and an embedded adapter reaches the disk at that same point. */ -export function* installRunAgentStack(stack: AgentStack): Operation { +export function* installAgentProviderStack(stack: AgentStack): Operation { const acpx = createAcpxProvider(hostAcpDependencies(stack)); yield* registerAgentProvider("acpx", acpx); @@ -174,7 +179,19 @@ export function* installRunAgentStack(stack: AgentStack): Operation { permissionMode, rootProvider: { factory, options: { defaultAgent, permissionMode } }, }); - yield* installPermissionMode(permissionMode); +} + +/** + * Install the agent stack a document runs under: the registration, the + * components with the resolved root provider, the permission mode, and the + * terminal this command has to give away. + * + * A document that asks for no agent still installs no adapter, for the reason + * above: nothing here starts one. + */ +export function* installRunAgentStack(stack: AgentStack): Operation { + yield* installAgentProviderStack(stack); + yield* installPermissionMode(stack.permissionMode); // `xmd run` is the one command that has a terminal to give away. Help, // document inspection and `xmd test` install no launcher, so a document that // reaches under any of them refuses instead of spawning. diff --git a/packages/cli/src/repl/admission.ts b/packages/cli/src/repl/admission.ts new file mode 100644 index 00000000..89b78ee9 --- /dev/null +++ b/packages/cli/src/repl/admission.ts @@ -0,0 +1,43 @@ +/** + * What admits a provisional session: work that went beyond the retained prefix. + * + * A session reconstructing from history is provisional until something proves + * the run reached new work — a queued Agent turn, a question, a fresh record. + * The announcement that proves it is sent on a `Signal`, and a Signal delivers + * only to subscriptions that are already active: whatever it sends before one + * exists is dropped, not buffered. + * + * `spawn()` returns before its child body has run, so a consumer that + * subscribes as its first act subscribes a turn too late. The subscription + * therefore belongs to the caller, which creates it before starting the work + * that can announce; this consumer only iterates what it was handed. Values + * sent after `yield* stream` returns are queued for that active subscription + * even while the consumer has not begun reading, which is exactly the window + * this closes. + */ + +import type { Operation, Subscription } from "effection"; + +/** + * Drain an already-active subscription, admitting once per value it carries. + * + * Generic on purpose: what counts as admitting work is decided by filtering + * the stream before it is subscribed, which is the caller's business and + * happens in the caller's scope. This only reads what it was handed. + * + * Takes the subscription rather than the stream, so there is no way to write + * the consumer that creates its own: the race this exists to prevent cannot be + * reintroduced without changing the signature. + */ +export function consumeAdmissions( + subscription: Subscription, + admit: () => void, +): () => Operation { + return function* (): Operation { + let next = yield* subscription.next(); + while (!next.done) { + admit(); + next = yield* subscription.next(); + } + }; +} diff --git a/packages/cli/src/repl/agent.ts b/packages/cli/src/repl/agent.ts new file mode 100644 index 00000000..debea38a --- /dev/null +++ b/packages/cli/src/repl/agent.ts @@ -0,0 +1,696 @@ +/** + * What this process knows about Agent turns that the Journal does not yet. + * + * A `` is durable work: it returns only once its provider turn has + * settled and its ordinary `agent_prompt` record has appended, and that record + * is the only retained truth about it. Between those two moments there is a + * turn nobody can read — queued, then streaming, then finished but unpublished + * — and a REPL that showed nothing until the append would show nothing for the + * whole of the interesting part. + * + * So this observes. It installs ordinary middleware around `Agent.prompt()` and + * `Agent.requestPermission()` inside the execution, publishes an immutable + * reading of each turn as the provider's own events go past, and removes that + * reading at the exact moment the durable record replaces it. Nothing here is + * retained, nothing here is a Core Api member, and nothing here is visible to a + * document. + * + * ## The observer must not become the consumer + * + * `Agent.prompt()` hands back a *cold* stream, and whoever subscribes owns the + * turn. This wraps that stream rather than subscribing to it: `` stays + * the one subscriber, the one owner of provider cancellation, and the one + * reader of the final value. Each event object travels through untouched and + * unfrozen — what is frozen is the separate reading copied out of it — and an + * observation failure is never allowed to become a Prompt failure. + * + * ## Correlation is the canonical publication, not a resemblance or a queue + * + * Two `` children may run identical prompts against identical responses + * and settle in either order, so nothing about a turn's *content* identifies + * it: not its text, its display name, its agent, its session key, or the order + * it finished in. Nor does where it ran: a queue per coroutine would also hold + * calls that never become records, because the public `Agent.prompt()` is + * reachable by any registered component and journals nothing. + * + * So the journal boundary itself says which turn is which. Core calls this + * owner's `begin()` at the one moment that is both canonical and live — in the + * turn's own scope, as its private audit ledger is placed — and hands back the + * very same value on the publication that ends it. `append()` is the single + * durable handoff, and the live turn it began is removed inside that same + * transition, so no announced snapshot holds a turn both ways or neither. + * + * A direct `Agent.prompt()` call begins nothing. It gets no reading, claims no + * record, and cannot be claimed by one. A turn replay restored asks no + * provider, so it begins nothing either: no association, and no live turn. + * + * ## A request without one owner is an invariant failure + * + * An interactive permission request belongs to the live turn on whose coroutine + * the provider asked. Where there is not exactly one, this publishes nothing, + * denies nothing, and fails the session — reporting to the session owner as + * well as to the permission operation, because an ordinary `` turns a + * provider failure into its own durable failed result and carries on, which + * would leave the REPL running without the identity it needs to present and + * answer requests correctly. + */ + +import { action, createSignal, resource, useScope } from "effection"; +import type { Operation, Scope, Stream } from "effection"; +import { Agent, denyPermission } from "@executablemd/core"; +import type { + AgentPromptEvent, + PermissionMode, + PermissionOption, + PermissionOutcome, + PermissionRequest, +} from "@executablemd/core"; +import { useAgentPromptPublisher } from "@executablemd/core/host"; +import type { + AgentPromptHandle, + AgentPromptPublication, + AgentPromptPublisher, + ExecutionInstallation, +} from "@executablemd/core/host"; +import { DurableContext } from "@executablemd/durable-streams"; +import type { DurableEvent } from "@executablemd/durable-streams"; + +/** The durable record one observed turn is waiting to be replaced by. */ +const AGENT_PROMPT = "agent_prompt"; + +/** How far one live turn has got, as this process saw it. */ +export type ReplLiveTurnState = "queued" | "active" | "terminal"; + +/** One option the provider offered for a pending request, detached from it. */ +export interface ReplLiveChoice { + readonly optionId: string; + readonly name: string; + readonly kind: PermissionOption["kind"]; +} + +/** + * One Agent turn this process is running, as the application reads it. + * + * Everything here arrived from the provider's own events. A member is absent + * until the event carrying it has gone past, so an empty `sessionKey` means + * "not started yet" rather than "no conversation" — a queued turn has no + * conversation to select, and inferring one from the authored options would + * name a session the provider never issued. + */ +export interface ReplLiveTurn { + /** This process's own opaque handle. Never a location, model or Journal value. */ + readonly key: string; + /** The text the Prompt asked. */ + readonly prompt: string; + readonly state: ReplLiveTurnState; + /** What the provider has streamed so far. */ + readonly text: string; + readonly agent: string | undefined; + readonly sessionKey: string | undefined; + readonly agentSessionId: string | undefined; + readonly status: "completed" | "failed" | "cancelled" | undefined; + readonly stopReason: string | undefined; + /** What the provider said went wrong, as one line. */ + readonly failure: string | undefined; +} + +/** One permission request waiting on a person, detached from the request itself. */ +export interface ReplLivePermission { + /** This process's own opaque handle for the request. */ + readonly key: string; + /** The live turn that owns it. */ + readonly turn: string; + readonly toolCallId: string; + readonly title: string | undefined; + readonly kind: string | undefined; + readonly choices: readonly ReplLiveChoice[]; +} + +/** Everything this process knows about live Agent work right now. */ +export interface ReplAgentReading { + /** Live turns in the order their Prompts were observed. */ + readonly turns: readonly ReplLiveTurn[]; + readonly requests: readonly ReplLivePermission[]; +} + +/** + * Settling a pending request, separated from reading it. + * + * A surface that draws requests receives the reading; only the owner of this + * settles one. Both take an opaque key, so an unknown, stale or already + * answered key acts on nothing rather than on whatever is pending now. + */ +export interface ReplAgentAuthority { + /** Answer with one offered option. */ + choose(request: string, option: string): boolean; + /** Dismiss while the session is live, which denies. */ + dismiss(request: string): boolean; +} + +/** The private live Agent owner one session installs into its execution. */ +export interface ReplAgentKernel { + readonly reading: ReplAgentReading; + readonly changes: Stream; + readonly authority: ReplAgentAuthority; + /** What this owner installs inside the execution. */ + readonly installation: ExecutionInstallation; + /** + * The publisher that ties each canonical `` to its live turn. + * + * Installed by `installation`; exposed so a caller can see the one seam this + * owner correlates through. + */ + readonly publisher: AgentPromptPublisher; + /** + * Account for one appended event, without announcing. + * + * The caller owns the transition: it projects the newly acknowledged history, + * calls this, and announces once — so no observable snapshot holds a turn + * twice or neither time. + */ + consume(event: DurableEvent): void; + /** Announce the reading the caller's transition arrived at. */ + announce(): void; + /** + * The first failure that must terminate this session's owner. + * + * Resolves once. A later failure cannot replace it, because the owner is + * already withdrawing the authority the later one would have described. + */ + readonly failed: Operation; +} + +/** An unowned or ambiguously owned interactive permission request. */ +export class ReplPermissionOwnerError extends Error { + constructor(message: string) { + super(message); + this.name = "ReplPermissionOwnerError"; + } +} + +/** An append this process cannot match to the work it completed. */ +export class ReplAgentCorrelationError extends Error { + constructor(message: string) { + super(message); + this.name = "ReplAgentCorrelationError"; + } +} + +/** Mutable bookkeeping for one observed turn. */ +interface LiveTurn { + readonly key: string; + readonly coroutine: string; + readonly prompt: string; + state: ReplLiveTurnState; + text: string; + agent: string | undefined; + sessionKey: string | undefined; + agentSessionId: string | undefined; + status: "completed" | "failed" | "cancelled" | undefined; + stopReason: string | undefined; + failure: string | undefined; +} + +interface LiveRequest { + readonly key: string; + readonly turn: string; + readonly toolCallId: string; + readonly title: string | undefined; + readonly kind: string | undefined; + readonly choices: readonly ReplLiveChoice[]; + /** The one wait this answers, and the request only it may see. */ + settle(outcome: PermissionOutcome): void; + readonly request: PermissionRequest; +} + +function frozenTurn(turn: LiveTurn): ReplLiveTurn { + return Object.freeze({ + key: turn.key, + prompt: turn.prompt, + state: turn.state, + text: turn.text, + agent: turn.agent, + sessionKey: turn.sessionKey, + agentSessionId: turn.agentSessionId, + status: turn.status, + stopReason: turn.stopReason, + failure: turn.failure, + }); +} + +function frozenRequest(request: LiveRequest): ReplLivePermission { + return Object.freeze({ + key: request.key, + turn: request.turn, + toolCallId: request.toolCallId, + title: request.title, + kind: request.kind, + choices: request.choices, + }); +} + +/** The choices a provider offered, copied out of the request it owns. */ +function offeredChoices(request: PermissionRequest): readonly ReplLiveChoice[] { + return Object.freeze( + request.options.map((option) => + Object.freeze({ optionId: option.optionId, name: option.name, kind: option.kind }), + ), + ); +} + +/** The approval a mode would select, or none when the provider offered neither. */ +function approval(request: PermissionRequest): PermissionOutcome | undefined { + const allowed = + request.options.find((option) => option.kind === "allow_once") ?? + request.options.find((option) => option.kind === "allow_always"); + return allowed === undefined ? undefined : { outcome: "selected", optionId: allowed.optionId }; +} + +/** Whether this tool call is one `approve-reads` decides without asking. */ +function isRead(request: PermissionRequest): boolean { + const kind = request.toolCall.kind; + return kind === "read" || kind === "search"; +} + +/** The coroutine this operation is running on, or `""` outside a journal. */ +function* currentCoroutine(): Operation { + const scope = yield* useScope(); + return scope.get(DurableContext)?.coroutineId ?? ""; +} + +/** + * The private live Agent owner for one session, for as long as that session. + * + * A resource, because everything it holds is mutable and session-scoped: + * announcements, the turns and requests presented right now, which publication + * is appending, and who is waiting on the first failure. Acquiring it ties all + * of that to the acquiring scope, and releasing that scope retires it. + * + * Retiring answers nothing and releases nothing. A permission wait still held + * at teardown is abandoned, not denied — the scope that raised it is going away + * too, and a decision invented on the way out would be a decision nobody made. + * The same goes for whoever was waiting on `failed`: there is no failure to + * report, so no one is woken. + * + * `installation` stays a separate, explicit act. Installing the middleware in + * this body would put it in the resource's own child scope, which the execution + * started afterwards does not inherit: the resource owns the kernel, and the + * execution installation is what places the contextual middleware. + */ +export function useReplAgent(mode: PermissionMode): Operation { + return resource(function* (provide) { + const changes = createSignal(); + const turns: LiveTurn[] = []; + const requests: LiveRequest[] = []; + /** The live turn core began in a scope, until that scope's prompt claims it. */ + const begun = new Map(); + /** + * The live turn each handle this owner minted stands for. + * + * The handle is an object of this owner's own making, so a value from + * anywhere else simply is not a key here — which is how a handle is read + * back without asserting anything about what it is. + */ + const minted = new WeakMap(); + /** + * The canonical publication appending right now on each coroutine. + * + * The handle is what identifies the turn; this only says which of several + * concurrent publications an append belongs to. At most one canonical + * publication is ever in flight per coroutine, because expansion inside one + * coroutine is strictly sequential — so this is an index, never a queue, and + * it holds nothing between transitions. + */ + const publishing = new Map(); + let reading: ReplAgentReading = Object.freeze({ + turns: Object.freeze([]), + requests: Object.freeze([]), + }); + let keys = 0; + let failure: Error | undefined; + const failures: Array<(error: Error) => void> = []; + + function allocate(prefix: string): string { + keys += 1; + return `${prefix}-${keys}`; + } + + function project(): void { + reading = Object.freeze({ + turns: Object.freeze(turns.map(frozenTurn)), + requests: Object.freeze(requests.map(frozenRequest)), + }); + } + + function announce(): void { + project(); + changes.send(reading); + } + + /** + * Record the first failure that must end this session, and report it. + * + * Reported to whoever is waiting rather than thrown here: the caller also has + * to raise it into the operation that caused it, and the two are different + * deliveries of one failure. + */ + function fail(error: Error): Error { + if (failure === undefined) { + failure = error; + for (const waiting of failures) { + waiting(error); + } + failures.length = 0; + } + return failure; + } + + /** Take one live turn down, leaving the announcement to the caller. */ + function retire(turn: LiveTurn): void { + const at = turns.indexOf(turn); + if (at >= 0) { + turns.splice(at, 1); + } + } + + function queued(coroutine: string, prompt: string): LiveTurn { + const turn: LiveTurn = { + key: allocate("turn"), + coroutine, + prompt, + state: "queued", + text: "", + agent: undefined, + sessionKey: undefined, + agentSessionId: undefined, + status: undefined, + stopReason: undefined, + failure: undefined, + }; + turns.push(turn); + announce(); + return turn; + } + + /** Copy one provider event's facts into the reading, changing nothing else. */ + function observed(turn: LiveTurn, event: AgentPromptEvent): void { + if (event.type === "started") { + turn.state = "active"; + turn.agent = event.agent; + turn.sessionKey = event.session.sessionKey; + turn.agentSessionId = event.session.agentSessionId; + } else if (event.type === "text_delta") { + turn.state = "active"; + turn.text += event.text; + } else { + turn.state = "terminal"; + turn.status = event.status; + turn.stopReason = event.stopReason; + turn.failure = event.error?.message; + } + announce(); + } + + /** + * Wrap the provider's cold stream so subscribing still starts exactly one + * turn, owned by ``. + * + * Nothing is collected, pre-read or replaced: each event travels back the + * moment it arrives, as the same object the provider produced, and the final + * value is the provider's own. + */ + function watch( + turn: LiveTurn, + stream: Stream, + ): Stream { + return { + *[Symbol.iterator]() { + const subscription = yield* stream; + return { + *next() { + const next = yield* subscription.next(); + if (!next.done) { + observed(turn, next.value); + } + return next; + }, + }; + }, + }; + } + + /** The live turn a permission request on this coroutine belongs to. */ + function owner(coroutine: string): LiveTurn { + const candidates = turns.filter( + (turn) => turn.coroutine === coroutine && turn.state !== "terminal", + ); + const only = candidates[0]; + if (only === undefined || candidates.length > 1) { + throw fail( + new ReplPermissionOwnerError( + candidates.length > 1 + ? "an interactive permission request arrived where more than one live Prompt turn " + + "could own it, so the session cannot say which conversation is asking." + : "an interactive permission request arrived with no live Prompt turn to own it, so " + + "the session cannot present or answer it.", + ), + ); + } + return only; + } + + function remove(request: LiveRequest): void { + const at = requests.indexOf(request); + if (at >= 0) { + requests.splice(at, 1); + } + } + + function* interactive(request: PermissionRequest): Operation { + // Decided before anything is published: an unowned request publishes no + // reading and no key, and never becomes a denial. + const held = owner(yield* currentCoroutine()); + return yield* action(function (resolve) { + let settled = false; + const live: LiveRequest = { + key: allocate("request"), + turn: held.key, + toolCallId: request.toolCall.toolCallId, + title: request.toolCall.title, + kind: request.toolCall.kind, + choices: offeredChoices(request), + request, + settle(outcome: PermissionOutcome): void { + if (settled) { + return; + } + settled = true; + remove(live); + announce(); + resolve(outcome); + }, + }; + // Whatever ends this — an answer, a dismissal, teardown, a failure + // upstream — the reading disappears exactly when the wait does. Returned + // as `action`'s own cleanup, which is registered before this body's + // caller can suspend, and the wait is never resolved synchronously. + const dispose = (): void => { + if (!settled) { + settled = true; + remove(live); + announce(); + } + }; + requests.push(live); + announce(); + return dispose; + }); + } + + /** + * The selected mode, applied exactly, with every other kind asked. + * + * A mode that cannot approve denies through Core's own `denyPermission` + * rather than a rule spelled again here, so an automatic denial is the same + * decision the base handler would have reached. + */ + function* decide(request: PermissionRequest): Operation { + if (mode === "approve-all") { + return approval(request) ?? denyPermission(request); + } + if (mode === "deny-all") { + return denyPermission(request); + } + if (isRead(request)) { + return approval(request) ?? denyPermission(request); + } + return yield* interactive(request); + } + + const authority: ReplAgentAuthority = { + choose(request: string, option: string): boolean { + const live = requests.find((candidate) => candidate.key === request); + if (live === undefined) { + return false; + } + // Only what the provider offered: a stray identifier settles nothing + // rather than selecting an option this turn was never given. + if (!live.choices.some((choice) => choice.optionId === option)) { + return false; + } + live.settle({ outcome: "selected", optionId: option }); + return true; + }, + dismiss(request: string): boolean { + const live = requests.find((candidate) => candidate.key === request); + if (live === undefined) { + return false; + } + // A dismissal while the session continues is a denial the provider turn + // resumes with, decided by the one authoritative rule. + live.settle(denyPermission(live.request)); + return true; + }, + }; + + const publisher: AgentPromptPublisher = { + *begin(input: string): Operation { + // Created before the provider is asked for anything at all, and handed + // back on this turn's own publication — the only thing that will say + // which live turn that record ended. + const turn = queued(yield* currentCoroutine(), input); + begun.set(yield* useScope(), turn); + // An opaque token rather than the turn itself: core carries it back + // untouched, and only this map can say what it stood for. + const handle: object = {}; + minted.set(handle, turn); + return handle; + }, + *publish(publication: AgentPromptPublication): Operation { + const handle = publication.begun; + // A handle this owner did not mint is not a key in this map: another + // host's publisher, or a turn from an execution this session never ran. + // Read back by lookup, never by asserting what the value is. + const turn = typeof handle === "object" && handle !== null ? minted.get(handle) : undefined; + const where = turn?.coroutine; + if (turn !== undefined && where !== undefined) { + publishing.set(where, turn); + } + try { + // The single durable handoff. `consume()` runs inside this append, in + // the caller's one transition, and removes exactly this turn. + yield* publication.append(); + } catch (error) { + // Nothing was retained, so nothing may still be shown as though it is + // about to be. The turn this publication began is taken down and the + // removal announced before the failure travels on — otherwise a + // terminal overlay outlives the record it was waiting for. + if (turn !== undefined) { + retire(turn); + announce(); + } + throw error; + } finally { + if (where !== undefined) { + publishing.delete(where); + } + } + }, + }; + + const installation: ExecutionInstallation = { + *install(): Operation { + // At the ordinary position, not `min`. A provider installs its own + // handlers innermost and answers without delegating, so an observer + // installed there would never see the call it exists to wrap — and a + // policy installed there would be decided for, by whatever the provider + // brought with it. Outermost is where this session's own authority goes: + // it wraps the provider's stream, and it decides permission before + // anything inherited can. + yield* useAgentPromptPublisher(publisher); + yield* Agent.around({ + *prompt([text, options], next) { + // Only the turn core began in this exact scope is journal-owned work. + // A registered component calling the public `Agent.prompt()` arrives + // here having begun nothing: it is delegated untouched, shown in no + // reading, and left unable to claim any record. + const scope = yield* useScope(); + const turn = begun.get(scope); + begun.delete(scope); + const stream = yield* next(text, options); + return turn === undefined ? stream : watch(turn, stream); + }, + *requestPermission([request]) { + return yield* decide(request); + }, + }); + }, + }; + + const kernel: ReplAgentKernel = { + get reading() { + return reading; + }, + changes, + authority, + installation, + publisher, + consume(event: DurableEvent): void { + if (event.type !== "yield" || event.description.type !== AGENT_PROMPT) { + return; + } + const turn = publishing.get(event.coroutineId); + if (turn === undefined) { + // An `agent_prompt` appended where this process observed no turn. The + // session has already been admitted, so there is nothing left to refuse + // atomically: the owner is terminated instead of guessing which reading + // this record replaced. + throw fail( + new ReplAgentCorrelationError( + "an agent turn was recorded that this session never observed, so its live view " + + "cannot be reconciled with the journal.", + ), + ); + } + retire(turn); + project(); + }, + announce, + get failed(): Operation { + return { + *[Symbol.iterator]() { + if (failure !== undefined) { + return failure; + } + return yield* action(function (resolve) { + failures.push(resolve); + return () => { + const at = failures.indexOf(resolve); + if (at >= 0) { + failures.splice(at, 1); + } + }; + }); + }, + }; + }, + }; + + try { + yield* provide(kernel); + } finally { + // Dropped, never called: a held `settle` would answer a request nobody + // decided, and a waiting `failed` resolver would report a failure that + // never happened. Both are abandoned with the scope that owned them. + requests.length = 0; + failures.length = 0; + turns.length = 0; + begun.clear(); + publishing.clear(); + // Projected, not announced. A reader still holding this owner sees a + // retired one; nothing is pushed into consumers that are themselves going + // away. + project(); + } + }); +} diff --git a/packages/cli/src/repl/session.ts b/packages/cli/src/repl/session.ts index e637287c..cc25c7d6 100644 --- a/packages/cli/src/repl/session.ts +++ b/packages/cli/src/repl/session.ts @@ -45,6 +45,7 @@ import { createScope, createSignal, ensure, + scoped, spawn, suspend, until, @@ -53,9 +54,13 @@ import { } from "effection"; import type { Operation, Result, Stream, Task } from "effection"; import { INLINE_SOURCE_PATH, inlineSource, validateDocument } from "@executablemd/core"; -import type { DocumentValidationDiagnostic } from "@executablemd/core"; +import type { DocumentValidationDiagnostic, PermissionMode } from "@executablemd/core"; import { executeInstalled } from "@executablemd/core/host"; -import type { ExecutionInstallation } from "@executablemd/core/host"; +import type { + ExecutionDeclaration, + ExecutionInstallation, + IdentityComponent, +} from "@executablemd/core/host"; import { ContinuePastCloseDivergenceError, DivergenceError, @@ -66,6 +71,10 @@ import { } from "@executablemd/durable-streams"; import type { DurableEvent } from "@executablemd/durable-streams"; +import { useReplAgent } from "./agent.ts"; +import { filter } from "@effectionx/stream-helpers"; +import { consumeAdmissions } from "./admission.ts"; +import type { ReplAgentAuthority, ReplAgentReading } from "./agent.ts"; import { useReplElicitation } from "./elicitation.ts"; import type { ReplElicitations, ReplQuestion } from "./elicitation.ts"; import { useExpansionController } from "./expansion.ts"; @@ -143,6 +152,23 @@ export interface ReplSession { */ readonly controller: ExpansionController | undefined; readonly elicitation: ReplElicitations; + /** + * What this process knows about Agent turns the Journal has not settled. + * + * Empty for an idle execution, a full replay and a document with no Agent + * work — a live reading describes work this process is doing, and replay does + * none. + */ + readonly agent: ReplAgentReading; + /** Every change to that reading, as it changes. */ + readonly agentChanges: Stream; + /** + * Settling a pending permission request. + * + * Separate from reading one, so a surface that draws requests does not + * thereby hold the capability that answers them. + */ + readonly permissions: ReplAgentAuthority; /** Whether an execution is still running in this process. */ readonly live: boolean; /** Each reprojection, as the history grows under it. */ @@ -170,6 +196,14 @@ export interface ReplSessionOptions { readonly installations?: readonly ExecutionInstallation[]; /** The history position a location selected, or none for the head. */ readonly selection?: string; + /** + * How this session answers Agent permission requests. + * + * The REPL presents interactive requests in its own surface, so it installs + * this policy rather than Core's readline one. Absent means `deny-all`, which + * is what an execution with no configured mode already does. + */ + readonly permissionMode?: PermissionMode; } /** Submit one entry into an execution whose history is empty. */ @@ -188,7 +222,7 @@ function* start( options: ReplSessionOptions, submitted: string | undefined, ): Operation> { - const { execution, includes, installations = [], selection } = options; + const { execution, includes, installations = [], selection, permissionMode } = options; const stream = execution.stream; const projection = projectRepl(yield* stream.readAll(), selection); @@ -220,9 +254,28 @@ function* start( // entry. There is no document to run, so there is nothing to admit either. return Ok(idle(execution.id, model)); } + // Named again after the guard, because the execution below runs from a + // hoisted body and the narrowing does not reach it. + const entry: string = source; if (submitted !== undefined) { - const validation = yield* validateDocument({ ...inlineSource(submitted), includes }); + // Validated against the vocabulary the execution will actually install, not + // against the bare registry: a host that declares `` to the + // execution would otherwise have every entry naming one refused here and + // run perfectly if it got past. Preflight and the run answer to one + // environment or preflight is describing a different document. + // In a scope of its own, because admitting a declaration in order to ask + // about it mints the durable identity domain that name answers under. + // Leaving that behind would have the execution resolve `` through + // preflight's admission while issuing its invocations under its own. + const validation = yield* scoped(function* () { + return yield* validateDocument({ + ...inlineSource(submitted), + includes, + components: declaredComponents(installations), + declarations: declaredMarkdown(installations), + }); + }); if (validation.outcome === "invalid") { return Err(new ReplPreflightError(validation.diagnostics)); } @@ -257,6 +310,11 @@ function* start( provisional.run(function* () { const expansion = yield* useExpansionController(); const elicitation = yield* useReplElicitation(); + // Created here and installed into the execution below, so the middleware it + // owns belongs to this session's scope and dies with it. An execution + // elsewhere would otherwise inherit an observer watching for a session that + // is gone. + const agent = yield* useReplAgent(permissionMode ?? "deny-all"); function reproject(): void { const next = projectRepl(retained, selection); @@ -295,6 +353,11 @@ function* start( return live ? expansion : undefined; }, elicitation, + get agent() { + return agent.reading; + }, + agentChanges: agent.changes, + permissions: agent.authority, get live() { return live; }, @@ -318,7 +381,20 @@ function* start( const retained = yield* stream.readAll(); const observe = (event: DurableEvent): void => { retained.push(event); + // One transition, and nothing suspends inside it: the record joins the + // history, the live overlay it completed is removed, and only then does + // anything announce. No observable snapshot holds one turn twice, and + // none holds it neither way. + try { + agent.consume(event); + } catch { + // A correlation failure after admission is not a stale view to carry + // on with. The owner below is already being told; refusing here as + // well would report one failure as two. + return; + } reproject(); + agent.announce(); admit(); }; @@ -334,15 +410,75 @@ function* start( }); stream.onAppend = observe; - const task: Task> = yield* spawn(function* () { + /** + * How this session finished: the execution's own outcome, or the first + * failure that withdrew its authority. + * + * Settled once, by whichever happened first. A withdrawn session may not be + * left waiting on the work it withdrew authority from, so the watcher below + * halts the execution and waits for it before answering — which is what + * makes the provider turn, the held permission wait and the execution task + * all gone by the time `join()` returns. A later failure cannot replace the + * first one, because by then there is nothing left for it to describe. + */ + const finished = withResolvers>(); + let answered = false; + function settle(outcome: Result): void { + if (!answered) { + answered = true; + finished.resolve(outcome); + } + } + + // Both subscriptions belong to this scope, and both exist before the + // document can publish anything. `spawn()` returns before its child body + // has run, and a Signal drops what it sends while no subscription is + // active, so subscribing inside a watcher loses an announcement the + // execution makes immediately — which is the announcement that admits a + // cold session. Effection's contract is explicit that the subscription is + // created in the enclosing scope and only iterated in the child + // (`docs/agents.md`, "Subscription readiness across `spawn()`"). Spawning + // the consumer earlier would not be equivalent: what must precede the + // document is the subscription, not the task that reads it. + // Filtered before either is subscribed, so what reaches the drain is + // already only the announcements that admit. The composition belongs here, + // in the scope that owns the subscription. + const agentReadings = yield* filter(function* (reading: ReplAgentReading) { + return reading.turns.length > 0; + })(agent.changes); + const questions = yield* filter(function* (question: ReplQuestion | undefined) { + return question !== undefined; + })(elicitation.changes); + + const document: Task> = yield* spawn(function* () { + const outcome = yield* runExecution(); + settle(outcome); + return outcome; + }); + + yield* spawn(function* () { + const error = yield* agent.failed; + live = false; + admit(); + // Halted and joined before the failure is answered, in that order. + yield* document.halt(); + settle(Err(error)); + }); + + const task: Operation> = finished.operation; + + function* runExecution(): Operation> { try { const running = yield* executeInstalled( { - ...inlineSource(source), + ...inlineSource(entry), stream, ...(includes === undefined ? {} : { includes: [...includes] }), }, - installations, + // Installed into this exact execution, and nowhere else: the live + // observer and the permission policy are this session's, not the + // process's. + [...installations, agent.installation], ); // Consumed as it arrives, inside this session's scope. Collecting until // the stream closed would leave the overlay empty for the whole of the @@ -379,18 +515,13 @@ function* start( } return Err(raised); } - }); + } - yield* spawn(function* () { - const questions = yield* elicitation.changes; - let next = yield* questions.next(); - while (!next.done) { - if (next.value !== undefined) { - admit(); - } - next = yield* questions.next(); - } - }); + // A queued Agent turn is work beyond the retained prefix, exactly as a new + // record or a question is: replay that reached one is past what the history + // held, so the session is admitted rather than still provisional. + yield* spawn(consumeAdmissions(agentReadings, admit)); + yield* spawn(consumeAdmissions(questions, admit)); // Held open deliberately. This body owns the observer and both tasks, and // finishing it would halt them — so it lasts as long as the scope does, and @@ -398,15 +529,49 @@ function* start( yield* suspend(); }); - const entry = yield* admission.operation; - if (!entry.ok) { + const opened = yield* admission.operation; + if (!opened.ok) { // Before the refusal is returned, not after: the caller is about to be told // there is no session, and everything this one started has to be gone by // the time it hears that. yield* until(dispose()); - return entry; + return opened; } - return entry; + return opened; +} + +const EMPTY_AGENT_READING: ReplAgentReading = Object.freeze({ + turns: Object.freeze([]), + requests: Object.freeze([]), +}); + +/** No live request exists, so no key settles one. */ +const IDLE_PERMISSIONS: ReplAgentAuthority = Object.freeze({ + choose: () => false, + dismiss: () => false, +}); + +/** Every identity component these installations declare, in installation order. */ +function declaredComponents( + installations: readonly ExecutionInstallation[], +): readonly IdentityComponent[] { + // Copied, never the host's own values. Admitting a declaration mints the + // durable identity domain its implementation answers under, and preflight + // admits in this scope while the execution admits in its own — so handing + // both the same object would leave the run resolving an implementation + // minted for a domain its invocations were never issued under. + return installations.flatMap((installation) => + (installation.components ?? []).map((component) => ({ ...component })), + ); +} + +/** Every exact Markdown component these installations declare, in order. */ +function declaredMarkdown( + installations: readonly ExecutionInstallation[], +): readonly ExecutionDeclaration[] { + return installations.flatMap((installation) => + (installation.declarations ?? []).map((declaration) => ({ ...declaration })), + ); } /** An execution with no entry yet: a draft surface and nothing running. */ @@ -425,6 +590,10 @@ function idle(execution: string, model: ReplModel): ReplSession { // Nothing is expanding, so there is nothing to pause or continue. controller: undefined, elicitation, + // Nothing is running, so there is no live Agent work and nothing to settle. + agent: EMPTY_AGENT_READING, + agentChanges: createSignal(), + permissions: IDLE_PERMISSIONS, live: false, changes, // Nothing is running, so nothing will ever write. diff --git a/packages/cli/tests/repl-admission.test.ts b/packages/cli/tests/repl-admission.test.ts new file mode 100644 index 00000000..68c98ba9 --- /dev/null +++ b/packages/cli/tests/repl-admission.test.ts @@ -0,0 +1,68 @@ +/** + * Admission consumes a subscription its caller already created. + * + * The session's provisional-to-admitted transition hangs on a `Signal`, and a + * Signal drops whatever it sends while no subscription is active. These rows + * hold the ordering that makes the announcement survive. + */ + +import { describe, it } from "@executablemd/test-support/bdd"; +import { expect } from "@executablemd/test-support/expect"; +import { createSignal, sleep, spawn } from "effection"; +import { filter } from "@effectionx/stream-helpers"; +import { consumeAdmissions } from "../src/repl/admission.ts"; + +describe("AD — admission survives an announcement made before the consumer runs", () => { + it("AD1: a value announced before the consumer begins is still admitted", function* () { + const changes = createSignal(); + let admitted = 0; + + // The caller subscribes first, exactly as the session does before it + // spawns the document that can announce. + // Filtered before subscribing, exactly as the session composes it, so the + // drain itself stays generic. + const subscription = yield* filter(function* (value: number) { + return value > 0; + })(changes); + // Announced while the consumer does not exist yet, let alone read. + changes.send(1); + + yield* spawn( + consumeAdmissions(subscription, () => { + admitted += 1; + }), + ); + // One turn is all it takes to reach what was already queued for this + // active subscription. + yield* sleep(0); + + expect(admitted).toBe(1); + + // And a value the filter rejects never reaches the drain at all. + changes.send(0); + yield* sleep(0); + expect(admitted).toBe(1); + }); + + it("AD2: the shape AD1 replaced drops that value — the control for AD1", function* () { + const changes = createSignal(); + let admitted = 0; + + // A consumer that subscribes as its first act, which is what `spawn()` + // makes a turn too late. + yield* spawn(function* () { + const subscription = yield* changes; + let next = yield* subscription.next(); + while (!next.done) { + admitted += 1; + next = yield* subscription.next(); + } + }); + changes.send(1); + yield* sleep(0); + + // Dropped. This is why `consumeAdmissions` takes a subscription and not a + // stream: the losing shape cannot be written through that signature. + expect(admitted).toBe(0); + }); +}); diff --git a/packages/cli/tests/repl-agent-execution.test.ts b/packages/cli/tests/repl-agent-execution.test.ts new file mode 100644 index 00000000..3d2d60fc --- /dev/null +++ b/packages/cli/tests/repl-agent-execution.test.ts @@ -0,0 +1,1753 @@ +/** + * The live Agent and permission session kernel (#854 L1–L4, P1–P4). + * + * Every row drives a real `` through real core execution over a real + * `DurableStream`, with the provider installed at the seam a host installs one + * at. Concurrency is authored — `` with two `` children, the + * ordinary language the prerequisite added — rather than simulated by calling + * the kernel's own callbacks, because what these prove is that two ordinary + * Prompt turns can be live at once and still correlate to their own records + * exactly. + * + * The concurrent rows write the ordinary `` path + * in each ``, which is the shape the Story promises and the one the + * product needs: two conversations, each with its own authentic identity, live + * at once. Nothing here substitutes a bare `` for that. + */ + +import { beforeAll, describe, it } from "@executablemd/test-support/bdd"; +import { expect } from "@executablemd/test-support/expect"; +import { + all, + createScope, + race, + scoped, + sleep, + spawn, + until, + useScope, + withResolvers, +} from "effection"; +import type { Operation, Result, Stream } from "effection"; +import { + DurableContext, + InMemoryStream, + serializeDurableEvent, +} from "@executablemd/durable-streams"; +import type { DurableEvent } from "@executablemd/durable-streams"; +import { + Agent, + agentIdentityComponents, + installAgentComponents, + registerComponents, + useTempFileCompiler, +} from "@executablemd/core"; +import type { + AgentPromptEvent, + PermissionMode, + PermissionOption, + PermissionOutcome, + PermissionRequest, + PromptOptions, + Session, +} from "@executablemd/core"; +import type { AgentProviderFactory } from "@executablemd/core"; +import type { ExecutionInstallation } from "@executablemd/core/host"; + +import { API } from "@executablemd/runtime"; + +import { ordinaryEvaluationProfile } from "../src/evaluation-profile.ts"; +import { REFERENCE_DIRECTORY } from "./fixtures/repl/reference.ts"; +import { openReplSession, submitReplEntry } from "../src/repl/session.ts"; +import type { ReplSession } from "../src/repl/session.ts"; +import { useReplAgent } from "../src/repl/agent.ts"; +import type { ReplAgentKernel, ReplAgentReading } from "../src/repl/agent.ts"; +import type { ReplAgentPermission } from "../src/repl/model.ts"; +import type { ReplExecution } from "../src/repl/journal.ts"; + +/** + * How long a signal a correct kernel publishes immediately may go unpublished + * before the wait is called a deadlock. + * + * Never reached by a passing run: every wait below is opened by the kernel, the + * provider or the journal. It bounds only the failure mode, so a defect that + * stops publishing says what it stopped publishing instead of hanging. + */ +const DEADLOCK_MS = 10_000; + +interface Signal { + publish(): void; + readonly published: Operation; +} + +function signal(): Signal { + const resolvers = withResolvers(); + let settled = false; + return { + publish() { + if (!settled) { + settled = true; + resolvers.resolve(true); + } + }, + get published() { + return resolvers.operation; + }, + }; +} + +function* awaiting(what: string, waited: Operation): Operation { + const reached = yield* race([ + waited, + (function* (): Operation { + yield* sleep(DEADLOCK_MS); + return false; + })(), + ]); + if (!reached) { + throw new Error(`${what} never happened`); + } +} + +/** What one stubbed turn does when its gate opens. */ +interface Scripted { + readonly deltas?: readonly string[]; + readonly permission?: { + readonly toolCallId: string; + readonly kind?: string; + readonly title?: string; + readonly options?: readonly PermissionOption[]; + /** Provider-owned input nothing durable may hold, for the canary row. */ + readonly rawInput?: unknown; + }; + /** + * Two requests from one turn, both raised before either is answered. + * + * Concurrent on purpose: the order they are *answered* in is then the test's + * to choose, which is what proves a retained audit keeps the order they + * arrived in instead. + */ + readonly permissions?: readonly { + readonly toolCallId: string; + readonly kind?: string; + readonly title?: string; + }[]; + /** + * One more request, raised after this turn's first delta. + * + * By then a sibling turn has placed its own ledger, so a record correlated by + * whichever ledger was placed most recently puts this audit on the wrong turn. + */ + readonly late?: { + readonly toolCallId: string; + readonly kind?: string; + readonly title?: string; + }; + readonly status?: "completed" | "failed" | "cancelled"; + /** + * Whether this turn waits before it produces anything: `true` for one gate + * shared by every turn with this text, `"each"` for one gate per turn. + */ + readonly gated?: boolean | "each"; + /** + * Whether this turn waits *after* its terminal event and before returning the + * stream's final value. + * + * The one hold that separates finishing from recording. `` writes its + * record only once the stream returns, so a turn held here has produced every + * event it ever will — it is terminal as far as anything watching can see — + * while its sibling can still settle and append first. + */ + readonly settle?: "each"; +} + +const ALL_KINDS: readonly PermissionOption[] = [ + { optionId: "once", name: "Allow once", kind: "allow_once" }, + { optionId: "always", name: "Allow always", kind: "allow_always" }, + { optionId: "no", name: "Reject once", kind: "reject_once" }, + { optionId: "never", name: "Reject always", kind: "reject_always" }, +]; + +interface Stub { + readonly factory: AgentProviderFactory; + /** Every prompt the provider was asked for, in the order it was asked. */ + readonly asked: string[]; + /** The live reading as it stood the instant each prompt reached the provider. */ + readonly seenWhenAsked: ReplAgentReading[]; + /** The outcome each permission request settled with, by tool call id. */ + readonly outcomes: Map; + /** The exact event objects handed to the subscriber, by prompt text. */ + readonly produced: Map; + /** How many times a provider factory was materialized. */ + activations: number; + /** How many times a turn's cold stream was subscribed. */ + subscriptions: number; + /** Where to read the live reading from, at the moment the provider is asked. */ + watch(session: () => ReplAgentReading): void; + arrival(prompt: string): Operation; + release(prompt: string): void; + /** Wait for the turn running on this child coroutine to reach the provider. */ + reached(coroutine: string): Operation; + /** Release the turn running on this child coroutine, and only that one. */ + let_(coroutine: string): void; + /** Wait for the turn on this child coroutine to emit its terminal event. */ + finished(coroutine: string): Operation; + /** Let the turn held after its terminal event return its final value. */ + settle(coroutine: string): void; +} + +function createStub(script: Record = {}): Stub { + const arrivals = new Map(); + const releases = new Map(); + const slot = (map: Map, name: string): Signal => { + const existing = map.get(name); + if (existing !== undefined) { + return existing; + } + const created = signal(); + map.set(name, created); + return created; + }; + let reading: (() => ReplAgentReading) | undefined; + let issued = 0; + + const stub: Stub = { + asked: [], + seenWhenAsked: [], + outcomes: new Map(), + produced: new Map(), + activations: 0, + subscriptions: 0, + watch(session) { + reading = session; + }, + arrival(prompt: string) { + return awaiting( + ` reaching the provider`, + slot(arrivals, prompt).published, + ); + }, + release(prompt: string) { + slot(releases, prompt).publish(); + }, + reached(coroutine: string) { + return awaiting( + `the turn on ${coroutine} reaching the provider`, + slot(arrivals, `@${coroutine}`).published, + ); + }, + let_(coroutine: string) { + slot(releases, `@${coroutine}`).publish(); + }, + finished(coroutine: string) { + return awaiting( + `the turn on ${coroutine} reaching its terminal event`, + slot(arrivals, `~${coroutine}`).published, + ); + }, + settle(coroutine: string) { + slot(releases, `!${coroutine}`).publish(); + }, + factory: function* (options) { + stub.activations++; + yield* Agent.around( + { + // deno-lint-ignore require-yield + *agent([name]) { + return name ?? options.defaultAgent ?? "stub-agent"; + }, + // deno-lint-ignore require-yield + *session([routed]) { + const name = typeof routed === "string" ? routed : routed?.name; + return { sessionKey: `stub:${name ?? "default"}`, cwd: "/stub" }; + }, + // deno-lint-ignore require-yield + *prompt([content, promptOptions]) { + stub.asked.push(content); + // What the session reports at the exact moment the provider is + // asked. A kernel that published after delegating is caught here + // rather than at the end. + if (reading !== undefined) { + stub.seenWhenAsked.push(reading()); + } + issued += 1; + return turn( + stub, + script, + slot, + arrivals, + releases, + options.defaultAgent ?? "stub-agent", + content, + promptOptions, + issued, + ); + }, + }, + { at: "min" }, + ); + }, + }; + return stub; +} + +function turn( + stub: Stub, + script: Record, + slot: (map: Map, name: string) => Signal, + arrivals: Map, + releases: Map, + defaultAgent: string, + content: string, + options: PromptOptions | undefined, + issued: number, +): Stream { + return { + *[Symbol.iterator]() { + stub.subscriptions += 1; + const scripted = script[content] ?? {}; + // The conversation this turn actually belongs to: the one the authored + // `` routed, which is what makes two turns separate when + // everything they say is identical. A prompt written outside a + // `` falls back to one of this turn's own. + const routed = options?.session; + const session: Session = + typeof routed === "object" && routed !== null && "sessionKey" in routed + ? routed + : { sessionKey: `stub:${issued}`, cwd: "/stub" }; + const agent = typeof options?.agent === "string" ? options.agent : defaultAgent; + const deltas = scripted.deltas ?? ["delta"]; + const produced: AgentPromptEvent[] = []; + stub.produced.set(content, produced); + let stage = 0; + let announced = false; + // Where this turn is running. Two spawns may be written identically and + // may reach the provider in either order, so the only stable way to name + // one of them is the child coroutine its `` was given in source + // order. Held across the whole subscription, because the hold after the + // terminal event needs it too. + let where = ""; + let released = false; + let lateAsked = false; + return { + *next() { + if (!announced) { + announced = true; + where = (yield* useScope()).get(DurableContext)?.coroutineId ?? ""; + slot(arrivals, content).publish(); + slot(arrivals, `@${where}`).publish(); + if (scripted.gated === true) { + // Two gates, because two turns may be written identically: one + // keyed by the text a row can name, and one by which turn this + // is, which is the only way to release them in a chosen order. + yield* awaiting( + ` being released`, + slot(releases, content).published, + ); + } + if (scripted.gated === "each") { + yield* awaiting( + `the turn on ${where} being released`, + slot(releases, `@${where}`).published, + ); + } + const wanted = scripted.permission; + if (wanted !== undefined) { + const request: PermissionRequest = { + session, + toolCall: { + toolCallId: wanted.toolCallId, + ...(wanted.title === undefined ? {} : { title: wanted.title }), + ...(wanted.kind === undefined ? {} : { kind: wanted.kind }), + ...(wanted.rawInput === undefined ? {} : { rawInput: wanted.rawInput }), + }, + options: wanted.options ?? ALL_KINDS, + }; + stub.outcomes.set( + wanted.toolCallId, + yield* Agent.operations.requestPermission(request), + ); + } + const pair = scripted.permissions; + if (pair !== undefined) { + // Both raised before either is answered, so the turn is holding two + // decisions at once. + yield* all( + pair.map((one) => + (function* (): Operation { + const outcome = yield* Agent.operations.requestPermission({ + session, + toolCall: { + toolCallId: one.toolCallId, + ...(one.title === undefined ? {} : { title: one.title }), + ...(one.kind === undefined ? {} : { kind: one.kind }), + }, + options: ALL_KINDS, + }); + stub.outcomes.set(one.toolCallId, outcome); + })(), + ), + ); + } + } + if (stage === 0) { + stage = 1; + const event: AgentPromptEvent = { type: "started", agent, session }; + produced.push(event); + return { done: false, value: event }; + } + if (stage <= deltas.length) { + const event: AgentPromptEvent = { type: "text_delta", text: deltas[stage - 1]! }; + stage += 1; + produced.push(event); + return { done: false, value: event }; + } + if (stage === deltas.length + 1 && scripted.late !== undefined && !lateAsked) { + lateAsked = true; + const one = scripted.late; + stub.outcomes.set( + one.toolCallId, + yield* Agent.operations.requestPermission({ + session, + toolCall: { + toolCallId: one.toolCallId, + ...(one.title === undefined ? {} : { title: one.title }), + ...(one.kind === undefined ? {} : { kind: one.kind }), + }, + options: ALL_KINDS, + }), + ); + } + if (stage === deltas.length + 1) { + stage += 1; + const event: AgentPromptEvent = { + type: "terminal", + status: scripted.status ?? "completed", + }; + produced.push(event); + slot(arrivals, `~${where}`).publish(); + return { done: false, value: event }; + } + if (scripted.settle === "each" && !released) { + released = true; + // Finished, but not yet recorded: everything this turn will ever + // produce has gone past, and `` cannot write its record + // until this returns. + yield* awaiting( + `the turn on ${where} being allowed to finish`, + slot(releases, `!${where}`).published, + ); + } + return { done: true, value: deltas.join("") }; + }, + }; + }, + }; +} + +function execution(events: readonly DurableEvent[] = []): ReplExecution { + return { id: "agent-kernel", stream: new InMemoryStream([...events]) }; +} + +/** + * An execution whose journal refuses to record an Agent turn. + * + * The one way a canonical publication fails after the turn has already run: + * everything the provider was going to say has been said, the overlay is + * terminal, and then nothing retains it. + */ +function refusingExecution(): ReplExecution { + const stream = new InMemoryStream(); + const appended = stream.append.bind(stream); + stream.append = function* (event: DurableEvent): Operation { + if (event.type === "yield" && event.description.type === "agent_prompt") { + throw new Error("this journal refused the record"); + } + yield* appended(event); + }; + return { id: "agent-kernel", stream }; +} + +function installations(): readonly ExecutionInstallation[] { + return [{ evaluation: ordinaryEvaluationProfile() }, { components: agentIdentityComponents() }]; +} + +function opened(result: Result): ReplSession { + if (!result.ok) { + throw result.error; + } + return result.value; +} + +function refusal(result: Result): Error { + if (result.ok) { + throw new Error("this session was handed back, and it must be refused"); + } + return result.error; +} + +/** Install the provider on this scope, the way a host installs one. */ +function* useStub(stub: Stub): Operation { + yield* installAgentComponents({ + defaultAgent: "stub-agent", + rootProvider: { + factory: stub.factory, + options: { defaultAgent: "stub-agent", permissionMode: "deny-all" }, + }, + }); +} + +function start( + holder: ReplExecution, + source: string, + permissionMode: PermissionMode = "deny-all", + includes?: readonly string[], +): Operation> { + return submitReplEntry({ + execution: holder, + installations: installations(), + permissionMode, + source, + ...(includes === undefined ? {} : { includes }), + }); +} + +/** + * What a run actually performed, counted where the work happens. + * + * Outside the engine's own handlers, so what these count is the read and the + * compilation themselves rather than the records of them: a replay that restored + * a completed effect reads no source and compiles nothing, and that is the + * difference between restoring and doing again. + */ +interface Performed { + /** Component sources actually read from disk. */ + readonly reads: string[]; + /** Eval blocks actually compiled, which is where a block really runs. */ + compiles: number; +} + +function* countPerformed(): Operation { + const performed: Performed = { reads: [], compiles: 0 }; + yield* API.Fs.around({ + *readTextFile([path], next) { + performed.reads.push(path); + return yield* next(path); + }, + }); + yield* API.Env.around({ + *compile([source, options], next) { + performed.compiles++; + return yield* next(source, options); + }, + }); + return performed; +} + +/** One document that really reads a component and really compiles an eval block. */ +const READS_AND_COMPILES = [ + "```js eval", + 'const plan = { title: "Ship the audit", steps: 2 };', + "```", + "", + "", + "", + '', +].join("\n"); + +/** The exact bytes a journal holds, for a comparison that is about bytes. */ +function serialized(events: readonly DurableEvent[]): string[] { + return events.map((event) => serializeDurableEvent(event)); +} + +/** Wait until the live reading satisfies `holds`, or say it never did. */ +function* reported( + session: ReplSession, + what: string, + holds: (reading: ReplAgentReading) => boolean, +): Operation { + const readings = yield* session.agentChanges; + if (holds(session.agent)) { + return; + } + const reached = yield* race([ + (function* (): Operation { + let next = yield* readings.next(); + while (!next.done) { + if (holds(next.value)) { + return true; + } + next = yield* readings.next(); + } + return false; + })(), + (function* (): Operation { + yield* sleep(DEADLOCK_MS); + return false; + })(), + ]); + if (!reached) { + throw new Error(`the live reading never reported ${what}`); + } +} + +/** + * Wait on the journal rather than on an announcement. + * + * A change signal delivers to whoever is pulling at that moment, so a state a + * turn passes through can be missed by a subscriber that was between reads. + * Records cannot: they only accumulate, and the append callback fires for every + * one. Installed after the session's own observer and chained to it, so the + * signal arrives with the session already reprojected. + */ +function watchAppends(holder: ReplExecution): (count: number) => Operation { + const inner = holder.stream.onAppend; + const signals = new Map(); + const slot = (count: number): Signal => { + const existing = signals.get(count); + if (existing !== undefined) { + return existing; + } + const created = signal(); + signals.set(count, created); + return created; + }; + let seen = 0; + holder.stream.onAppend = (event) => { + inner?.(event); + if (event.type === "yield" && event.description.type === "agent_prompt") { + seen += 1; + slot(seen).publish(); + } + }; + return (count: number) => { + if (seen >= count) { + return (function* () {})(); + } + return awaiting(`${count} agent turn(s) recorded`, slot(count).published); + }; +} + +/** + * Every permission this session's retained turns hold, turn by turn. + * + * Read from the projected model rather than from the ledger: what a row about a + * durable audit is entitled to is what the Journal says, after core parsed it + * back. + */ +function audited(session: ReplSession): readonly ReplAgentPermission[] { + return session.model.turns.flatMap((turn) => turn.permissions); +} + +/** Every appended `agent_prompt`, in journal order, with its coroutine. */ +function appends(events: readonly DurableEvent[]): Array<{ name: string; coroutineId: string }> { + return events + .filter((event) => event.type === "yield" && event.description.type === "agent_prompt") + .map((event) => ({ + name: event.type === "yield" ? event.description.name : "", + coroutineId: event.coroutineId, + })); +} + +const ONE_PROMPT = '\n'; +const TWO_SPAWNS = [ + "", + '', + '', + "", +].join("\n"); + +describe("L1 — transparent queued/start/delta/terminal observation", () => { + beforeAll(() => useTempFileCompiler()); + + it("L1: queued before the provider is asked, then the turn's own facts", function* () { + const stub = createStub({ one: { deltas: ["first", "second"] } }); + yield* useStub(stub); + const holder = execution(); + const seen: ReplAgentReading[] = []; + const session = opened(yield* start(holder, ONE_PROMPT)); + stub.watch(() => session.agent); + yield* spawn(function* () { + const readings = yield* session.agentChanges; + let next = yield* readings.next(); + while (!next.done) { + seen.push(next.value); + next = yield* readings.next(); + } + }); + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + + // Queued before delegation: what the provider saw when it was asked + // already held this turn, and held it as queued. + expect(stub.seenWhenAsked).toHaveLength(1); + const atDelegation = stub.seenWhenAsked[0]!; + expect(atDelegation.turns).toHaveLength(1); + expect(atDelegation.turns[0]!.state).toBe("queued"); + expect(atDelegation.turns[0]!.prompt).toBe("one"); + // A queued turn has no conversation to select yet. + expect(atDelegation.turns[0]!.sessionKey).toBe(undefined); + + // One logical turn under one key, advanced by immutable replacements. + const key = atDelegation.turns[0]!.key; + const mine = seen.map((reading) => reading.turns[0]).filter((one) => one !== undefined); + expect(new Set(mine.map((one) => one.key))).toEqual(new Set([key])); + const states = mine.map((one) => one.state); + expect(states).toContain("active"); + expect(states).toContain("terminal"); + const settled = mine.filter((one) => one.state === "terminal").at(-1)!; + expect(settled.text).toBe("firstsecond"); + expect(settled.agent).toBe("stub-agent"); + expect(settled.sessionKey).toBe("stub:1"); + expect(settled.status).toBe("completed"); + + // Deltas in order, and neither dropped. + const texts = mine.map((one) => one.text); + expect(texts).toContain("first"); + expect(texts).toContain("firstsecond"); + expect(texts.indexOf("first")).toBeLessThan(texts.indexOf("firstsecond")); + + // Every exposed value is detached and frozen. + for (const reading of seen) { + expect(Object.isFrozen(reading)).toBe(true); + expect(Object.isFrozen(reading.turns)).toBe(true); + for (const one of reading.turns) { + expect(Object.isFrozen(one)).toBe(true); + } + } + // `` is the stream's only subscriber: the observer wrapped the + // cold stream rather than consuming it and handing on a second turn. + expect(stub.subscriptions).toBe(1); + // The provider's own event objects reached `` in order, unchanged + // and unfrozen, and its final value is what the document rendered. + const produced = stub.produced.get("one")!; + expect(produced.map((event) => event.type)).toEqual([ + "started", + "text_delta", + "text_delta", + "terminal", + ]); + for (const event of produced) { + expect(Object.isFrozen(event)).toBe(false); + } + expect(session.overlay.output).toContain("firstsecond"); + }); +}); + +describe("L2 — exact atomic live-to-durable replacement", () => { + beforeAll(() => useTempFileCompiler()); + + it("L2: identical concurrent turns replace their own overlays, in reverse order", function* () { + const stub = createStub({ same: { deltas: ["reply"], gated: "each" } }); + yield* useStub(stub); + const holder = execution(); + const snapshots: Array<{ live: number; durable: number }> = []; + const session = opened(yield* start(holder, TWO_SPAWNS)); + stub.watch(() => session.agent); + const recorded = watchAppends(holder); + + // Every announced snapshot, so "no duplicate and no disappearance" is + // checked against all of them rather than against the end state. + yield* spawn(function* () { + const readings = yield* session.agentChanges; + let next = yield* readings.next(); + while (!next.done) { + snapshots.push({ live: next.value.turns.length, durable: session.model.turns.length }); + next = yield* readings.next(); + } + }); + + yield* spawn(function* () { + // Both ordinary Prompt paths reach the provider before either is let go. + yield* stub.reached("root.0"); + yield* stub.reached("root.1"); + yield* reported(session, "both turns live", (reading) => reading.turns.length === 2); + const keys = session.agent.turns.map((one) => one.key); + expect(new Set(keys).size).toBe(2); + expect(session.agent.turns.map((one) => one.prompt)).toEqual(["same", "same"]); + // The second spawn is released first, so its record appends first. + stub.let_("root.1"); + yield* recorded(1); + // Only its own overlay went. Which one survived is named by what it is + // rather than by where it sat: the other spawn is still held before its + // first event, so it is the one that has reached no conversation. + expect(session.agent.turns).toHaveLength(1); + const survivor = session.agent.turns[0]!; + expect(keys).toContain(survivor.key); + expect(survivor.state).toBe("queued"); + expect(survivor.sessionKey).toBe(undefined); + // And the record that replaced the other one describes the turn that + // actually ran, not this one. + expect(session.model.turns).toHaveLength(1); + expect(session.model.turns[0]!.sessionKey).not.toBe(survivor.sessionKey); + stub.let_("root.0"); + }); + + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + + const written = appends(yield* holder.stream.readAll()); + expect(written).toHaveLength(2); + // Completion order in the journal: the second spawn's child coroutine + // appended first, and the document still holds both turns. + expect(written.map((entry) => entry.coroutineId)).toEqual(["root.1", "root.0"]); + expect(session.model.turns).toHaveLength(2); + expect(session.agent.turns).toHaveLength(0); + // Distinct conversations, identical text: nothing correlated by content. + // The two conversations the authored `` elements named, each + // carrying its own retained turn although the text is identical. + expect(new Set(session.model.turns.map((one) => one.sessionKey))).toEqual( + new Set(["stub:planner", "stub:reviewer"]), + ); + expect(new Set(session.model.turns.map((one) => one.text))).toEqual(new Set(["reply"])); + + // No announced snapshot held one turn twice or neither time: from the + // moment both were live, live plus durable is exactly two. + const from = snapshots.findIndex((one) => one.live === 2); + expect(from).toBeGreaterThanOrEqual(0); + for (const snapshot of snapshots.slice(from)) { + expect(snapshot.live + snapshot.durable).toBe(2); + } + }); + + it("L2: the turn that finished first is not the turn the first record replaced", function* () { + // The one arrangement in which finishing and recording come apart. Both + // turns produce every event they ever will, so both are terminal to + // anything watching; only then is one of them allowed to return and write + // its record. A view that matched a record to "the turn that finished + // first" would replace the wrong overlay here, and nowhere else. + const stub = createStub({ same: { deltas: ["reply"], gated: "each", settle: "each" } }); + yield* useStub(stub); + const holder = execution(); + const session = opened(yield* start(holder, TWO_SPAWNS)); + stub.watch(() => session.agent); + const recorded = watchAppends(holder); + + yield* spawn(function* () { + // Both live before either streams anything. + yield* stub.reached("root.0"); + yield* stub.reached("root.1"); + // Then one at a time, so which turn finishes first is decided by this + // row rather than by scheduling: the first spawn reaches its terminal + // event and is held there, and only then does the second start. + stub.let_("root.0"); + yield* stub.finished("root.0"); + stub.let_("root.1"); + yield* stub.finished("root.1"); + yield* reported( + session, + "both turns terminal", + (reading) => reading.turns.filter((one) => one.state === "terminal").length === 2, + ); + expect(session.model.turns).toHaveLength(0); + + // The second spawn finishes second and records first. + stub.settle("root.1"); + yield* recorded(1); + // The record replaced its own overlay and no other: the turn still live + // is the one that reached terminal *first*, and the durable turn is the + // one that got there second. + expect(session.agent.turns.map((one) => one.sessionKey)).toEqual(["stub:planner"]); + expect(session.agent.turns.map((one) => one.state)).toEqual(["terminal"]); + expect(session.model.turns.map((one) => one.sessionKey)).toEqual(["stub:reviewer"]); + + stub.settle("root.0"); + }); + + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + // And the second record replaced only the turn that was left. + expect(session.agent.turns).toEqual([]); + // Both conversations retained, each exactly once. Compared as a set: a + // retained turn is ordered by its Prompt sequence, and two concurrent + // children allocate that sequence in whichever order they reach it — so + // source order is what the *rendering* preserves, not what this list does. + expect(new Set(session.model.turns.map((one) => one.sessionKey))).toEqual( + new Set(["stub:planner", "stub:reviewer"]), + ); + expect(session.model.turns).toHaveLength(2); + expect(appends(yield* holder.stream.readAll()).map((entry) => entry.coroutineId)).toEqual([ + "root.1", + "root.0", + ]); + }); + + it("L2: a replayed turn consumes its record without creating a live overlay", function* () { + const golden = execution(); + yield* scoped(function* () { + const stub = createStub({ one: { deltas: ["reply"] } }); + yield* useStub(stub); + const session = opened(yield* start(golden, ONE_PROMPT)); + yield* session.join(); + expect(stub.asked).toHaveLength(1); + }); + + yield* scoped(function* () { + const stub = createStub({ one: { deltas: ["reply"] } }); + yield* useStub(stub); + const replayed = opened( + yield* openReplSession({ + execution: execution(yield* golden.stream.readAll()), + installations: installations(), + }), + ); + yield* replayed.join(); + // Restored rather than re-run: the provider was never asked, and the + // retained turn produced no reading of its own. + expect(stub.asked).toEqual([]); + expect(replayed.agent.turns).toEqual([]); + expect(replayed.model.turns).toHaveLength(1); + }); + }); +}); + +describe("L3 — execution owns live work, presentation does not", () => { + beforeAll(() => useTempFileCompiler()); + + it("L3: a document with no Agent work publishes nothing and materializes nothing", function* () { + const stub = createStub(); + yield* useStub(stub); + const session = opened(yield* start(execution(), "just text\n")); + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + // The provider was never asked for anything, so no adapter was ever + // materialized: installing the factory only installs middleware. + expect(stub.asked).toEqual([]); + expect(session.agent.turns).toEqual([]); + expect(session.agent.requests).toEqual([]); + }); + + it("L3: a turn completes and publishes with nobody subscribed to changes", function* () { + const stub = createStub({ one: { deltas: ["reply"] } }); + yield* useStub(stub); + // Nothing subscribes to `agentChanges` anywhere in this row. + const session = opened(yield* start(execution(), ONE_PROMPT)); + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + expect(session.model.turns).toHaveLength(1); + expect(session.agent.turns).toEqual([]); + }); + + it("L3: cancelling the session owner cancels and joins a held turn", function* () { + const holder = execution(); + const stub = createStub({ one: { gated: true } }); + const [owner, dispose] = createScope(yield* useScope()); + const held = withResolvers(); + owner.run(function* () { + yield* useStub(stub); + held.resolve(opened(yield* start(holder, ONE_PROMPT))); + yield* sleep(DEADLOCK_MS); + }); + const session = yield* held.operation; + yield* stub.arrival("one"); + yield* reported(session, "a live turn", (reading) => reading.turns.length === 1); + + yield* until(dispose()); + + // Nothing the provider had not finished was recorded, and releasing it + // afterwards moves nothing: the work is gone with its owner. + expect(appends(yield* holder.stream.readAll())).toEqual([]); + stub.release("one"); + expect(appends(yield* holder.stream.readAll())).toEqual([]); + }); + + it("L3: a divergence before admission returns no session and leaves nothing alive", function* () { + const golden = execution(); + yield* scoped(function* () { + const stub = createStub({ one: { deltas: ["reply"] } }); + yield* useStub(stub); + yield* opened(yield* start(golden, ONE_PROMPT)).join(); + }); + + yield* scoped(function* () { + const stub = createStub({ one: { deltas: ["reply"] } }); + yield* useStub(stub); + const events = yield* golden.stream.readAll(); + // The very first recorded effect names something this document does not + // do, so replay meets the divergence before it has run any new work — + // and therefore before a queued turn, a question or an append could have + // admitted the session. + const [first, ...rest] = events; + const doctored = + first !== undefined && first.type === "yield" + ? [{ ...first, description: { ...first.description, name: "__elsewhere__" } }, ...rest] + : events; + const refused = refusal( + yield* openReplSession({ + execution: execution(doctored), + installations: installations(), + }), + ); + expect(refused).toBeInstanceOf(Error); + // Refused before the provider was reached. + expect(stub.asked).toEqual([]); + }); + }); +}); + +describe("L4 — an admitted session failure terminates its owner", () => { + beforeAll(() => useTempFileCompiler()); + + it("L4: a post-admission correlation failure withdraws authority and join returns it", function* () { + const stub = createStub({ + one: { permission: { toolCallId: "call-1", kind: "execute" }, deltas: ["reply"] }, + }); + yield* useStub(stub); + const holder = execution(); + const session = opened(yield* start(holder, ONE_PROMPT, "approve-reads")); + // Admitted on a real queued turn and held at an interactive request. + yield* reported(session, "a pending request", (reading) => reading.requests.length === 1); + expect(session.live).toBe(true); + const pending = session.agent.requests[0]!; + + // An `agent_prompt` on a coroutine this session never observed a turn + // on. Correlation cannot be exact, and the session is already admitted, + // so there is nothing left to refuse atomically. + yield* holder.stream.append({ + type: "yield", + coroutineId: "root.7", + description: { type: "agent_prompt", name: "prompt:elsewhere#0" }, + result: { + status: "ok", + value: { sequence: 9, agent: "x", sessionKey: "y", status: "completed", text: "" }, + }, + }); + + const outcome = yield* session.join(); + expect(outcome.ok).toBe(false); + expect(outcome.ok === false && outcome.error.name).toBe("ReplAgentCorrelationError"); + // The held request and its authority are gone, and a late choice cannot + // replace the first failure. + expect(session.agent.requests).toEqual([]); + expect(session.permissions.choose(pending.key, "once")).toBe(false); + expect(stub.outcomes.has("call-1")).toBe(false); + }); +}); + +describe("P1 — exact policy and per-turn suspension", () => { + beforeAll(() => useTempFileCompiler()); + + const cases: Array<{ + readonly title: string; + readonly mode: PermissionMode; + readonly kind: string; + readonly options: readonly PermissionOption[]; + readonly expected: PermissionOutcome; + }> = [ + { + title: "approve-all selects allow_once first", + mode: "approve-all", + kind: "execute", + options: ALL_KINDS, + expected: { outcome: "selected", optionId: "once" }, + }, + { + title: "approve-all falls back to allow_always", + mode: "approve-all", + kind: "execute", + options: [ALL_KINDS[1]!, ALL_KINDS[2]!], + expected: { outcome: "selected", optionId: "always" }, + }, + { + title: "approve-all denies when neither allow kind is offered", + mode: "approve-all", + kind: "execute", + options: [ALL_KINDS[2]!], + expected: { outcome: "selected", optionId: "no" }, + }, + { + title: "deny-all selects reject_once first", + mode: "deny-all", + kind: "read", + options: ALL_KINDS, + expected: { outcome: "selected", optionId: "no" }, + }, + { + title: "deny-all falls back to reject_always", + mode: "deny-all", + kind: "read", + options: [ALL_KINDS[0]!, ALL_KINDS[3]!], + expected: { outcome: "selected", optionId: "never" }, + }, + { + title: "deny-all cancels when no rejection is offered", + mode: "deny-all", + kind: "read", + options: [ALL_KINDS[0]!], + expected: { outcome: "cancelled" }, + }, + { + title: "approve-reads approves a read", + mode: "approve-reads", + kind: "read", + options: ALL_KINDS, + expected: { outcome: "selected", optionId: "once" }, + }, + { + title: "approve-reads approves a search with allow_always", + mode: "approve-reads", + kind: "search", + options: [ALL_KINDS[1]!], + expected: { outcome: "selected", optionId: "always" }, + }, + { + title: "approve-reads denies a read with no allow kind", + mode: "approve-reads", + kind: "read", + options: [ALL_KINDS[3]!], + expected: { outcome: "selected", optionId: "never" }, + }, + ]; + + for (const decided of cases) { + it(`P1: ${decided.title}`, function* () { + const stub = createStub({ + one: { + permission: { toolCallId: "call-1", kind: decided.kind, options: decided.options }, + }, + }); + yield* useStub(stub); + const session = opened(yield* start(execution(), ONE_PROMPT, decided.mode)); + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + expect(stub.outcomes.get("call-1")).toEqual(decided.expected); + // An automatic decision publishes no pending request, and asks nobody. + expect(session.agent.requests).toEqual([]); + }); + } + + it("P1: one held request does not suspend its sibling spawn", function* () { + const stub = createStub({ + blocked: { permission: { toolCallId: "call-1", kind: "execute" }, deltas: ["held"] }, + continue: { deltas: ["free"] }, + }); + yield* useStub(stub); + const holder = execution(); + const session = opened( + yield* start( + holder, + [ + "", + '', + '', + "", + ].join("\n"), + "approve-reads", + ), + ); + const recorded = watchAppends(holder); + + yield* spawn(function* () { + yield* reported(session, "a pending request", (reading) => reading.requests.length === 1); + const pending = session.agent.requests[0]!; + // The request identifies only its own turn. + const owner = session.agent.turns.find((one) => one.key === pending.turn); + expect(owner?.prompt).toBe("blocked"); + expect(pending.toolCallId).toBe("call-1"); + // While it waits, the sibling starts, streams, settles and appends + // durably — one held request does not suspend the whole ``. + yield* recorded(1); + expect(session.model.turns.map((one) => one.input)).toEqual(["continue"]); + expect(session.agent.requests).toHaveLength(1); + // Answering releases only its own turn. + expect(session.permissions.choose(pending.key, "once")).toBe(true); + }); + + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + expect(stub.outcomes.get("call-1")).toEqual({ outcome: "selected", optionId: "once" }); + expect(appends(yield* holder.stream.readAll())).toHaveLength(2); + expect(session.agent.requests).toEqual([]); + }); +}); + +describe("P2 — one live request, one direct settlement", () => { + beforeAll(() => useTempFileCompiler()); + + it("P2: an unknown option and a stale key settle nothing; the offered one settles once", function* () { + const stub = createStub({ + one: { permission: { toolCallId: "call-1", kind: "execute" }, deltas: ["reply"] }, + }); + yield* useStub(stub); + const session = opened(yield* start(execution(), ONE_PROMPT, "approve-reads")); + yield* spawn(function* () { + yield* reported(session, "a pending request", (reading) => reading.requests.length === 1); + const pending = session.agent.requests[0]!; + // Never an option the provider did not offer, and never a key this + // process did not issue. + expect(session.permissions.choose(pending.key, "invented")).toBe(false); + expect(session.permissions.choose("request-999", "once")).toBe(false); + expect(session.agent.requests).toHaveLength(1); + expect(session.permissions.choose(pending.key, "always")).toBe(true); + // Already settled: the same key acts on nothing a second time. + expect(session.permissions.choose(pending.key, "once")).toBe(false); + expect(session.permissions.dismiss(pending.key)).toBe(false); + }); + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + expect(stub.outcomes.get("call-1")).toEqual({ outcome: "selected", optionId: "always" }); + // And the turn that asked retains exactly that decision, once: the outcome + // the REPL's own authority returned, copied into the place the request + // reserved on its way in. + expect(audited(session)).toEqual([ + { + toolCallId: "call-1", + kind: "execute", + options: ALL_KINDS, + outcome: "selected", + selected: "always", + }, + ]); + }); + + it("P2: dismissal while live denies once and the turn resumes", function* () { + const stub = createStub({ + one: { permission: { toolCallId: "call-1", kind: "execute" }, deltas: ["resumed"] }, + }); + yield* useStub(stub); + const session = opened(yield* start(execution(), ONE_PROMPT, "approve-reads")); + yield* spawn(function* () { + yield* reported(session, "a pending request", (reading) => reading.requests.length === 1); + expect(session.permissions.dismiss(session.agent.requests[0]!.key)).toBe(true); + }); + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + // The established denial rule, and the turn carried on with it. + expect(stub.outcomes.get("call-1")).toEqual({ outcome: "selected", optionId: "no" }); + expect(session.agent.requests).toEqual([]); + expect(session.model.turns).toHaveLength(1); + expect(session.model.turns[0]!.text).toBe("resumed"); + // Retained once, as the denial it was — not as a cancellation, and not twice. + expect(audited(session)).toEqual([ + { + toolCallId: "call-1", + kind: "execute", + options: ALL_KINDS, + outcome: "selected", + selected: "no", + }, + ]); + }); + + it("P2: a cold retained audit has no pending reading and no authority", function* () { + const AUDIT = [ + { + toolCallId: "call-1", + kind: "read", + options: ALL_KINDS, + outcome: "selected", + selected: "once", + }, + ]; + const golden = execution(); + let written: string[] = []; + yield* scoped(function* () { + const stub = createStub({ + one: { permission: { toolCallId: "call-1", kind: "read" }, deltas: ["reply"] }, + }); + yield* useStub(stub); + // Counted where the work happens: this run really reads a component's + // source and really compiles an eval block, so "nothing again" has + // something to be measured against. + const performed = yield* countPerformed(); + const live = opened( + yield* start(golden, READS_AND_COMPILES, "approve-reads", [REFERENCE_DIRECTORY]), + ); + yield* live.join(); + expect(stub.outcomes.has("call-1")).toBe(true); + // The policy answered this one itself, and the record says exactly what it + // answered: the safe fields and the outcome, and nothing of the request. + expect(audited(live)).toEqual(AUDIT); + expect( + performed.reads.filter((path) => path.endsWith("Checklist.md")).length, + ).toBeGreaterThan(0); + expect(performed.compiles).toBeGreaterThan(0); + written = serialized(yield* golden.stream.readAll()); + }); + + yield* scoped(function* () { + const stub = createStub(); + yield* useStub(stub); + const performed = yield* countPerformed(); + // The cold process's own execution, held so the bytes it ends with can be + // compared with the bytes the live run left. + const cold = execution(yield* golden.stream.readAll()); + const reopened = opened( + yield* openReplSession({ + execution: cold, + includes: [REFERENCE_DIRECTORY], + installations: installations(), + }), + ); + yield* reopened.join(); + // The same audit, read from the history rather than observed again. + expect(audited(reopened)).toEqual(AUDIT); + // The audit is retained model data; replay recreates no wait from it. + expect(reopened.agent.requests).toEqual([]); + expect(reopened.permissions.choose("request-1", "once")).toBe(false); + expect(stub.asked).toEqual([]); + // Nothing was performed again: the component's source was not read and the + // eval block was not compiled, which is what "restored" has to mean. + expect(performed.reads.filter((path) => path.endsWith("Checklist.md"))).toEqual([]); + expect(performed.compiles).toBe(0); + // And reconstructing wrote nothing: this execution ends with the exact + // bytes the live run left, event for event. + expect(serialized(yield* cold.stream.readAll())).toEqual(written); + }); + }); +}); + +describe("P3 — whole-session teardown is structured cancellation", () => { + beforeAll(() => useTempFileCompiler()); + + it("P3: teardown cancels and joins the held request without claiming a denial", function* () { + const holder = execution(); + const stub = createStub({ + one: { permission: { toolCallId: "call-1", kind: "execute" }, deltas: ["reply"] }, + }); + const [owner, dispose] = createScope(yield* useScope()); + const held = withResolvers(); + owner.run(function* () { + yield* useStub(stub); + held.resolve(opened(yield* start(holder, ONE_PROMPT, "approve-reads"))); + yield* sleep(DEADLOCK_MS); + }); + const session = yield* held.operation; + yield* reported(session, "a pending request", (reading) => reading.requests.length === 1); + const pending = session.agent.requests[0]!; + + yield* until(dispose()); + + // Absence only. The permission operation was cancelled with its owner, so + // nothing here asserts that it observed a denial or any other outcome. + expect(session.agent.requests).toEqual([]); + expect(stub.outcomes.has("call-1")).toBe(false); + expect(appends(yield* holder.stream.readAll())).toEqual([]); + // A late choice acts on nothing, and settles nothing afterwards either. + expect(session.permissions.choose(pending.key, "once")).toBe(false); + expect(stub.outcomes.has("call-1")).toBe(false); + // And no audit was published for a decision nobody made: the place the + // request reserved was never completed, so nothing names an outcome. + expect(session.model.turns).toEqual([]); + expect(audited(session)).toEqual([]); + }); + + it("P3: releasing the owning scope retires the agent owner and wakes nobody", function* () { + const holder = execution(); + const stub = createStub({ + one: { permission: { toolCallId: "call-1", kind: "execute" }, deltas: ["reply"] }, + }); + const [owner, dispose] = createScope(yield* useScope()); + const held = withResolvers(); + owner.run(function* () { + yield* useStub(stub); + held.resolve(opened(yield* start(holder, ONE_PROMPT, "approve-reads"))); + yield* sleep(DEADLOCK_MS); + }); + const session = yield* held.operation; + yield* reported(session, "a pending request", (reading) => reading.requests.length === 1); + const pending = session.agent.requests[0]!; + + yield* until(dispose()); + yield* sleep(0); + + // The owner is retired: nothing it was presenting is presented any more. + expect(session.agent.turns).toEqual([]); + expect(session.agent.requests).toEqual([]); + // Retiring answered nothing. The held request was abandoned with its + // scope, not decided on the way out. + expect(stub.outcomes.has("call-1")).toBe(false); + expect(session.permissions.choose(pending.key, "once")).toBe(false); + expect(stub.outcomes.has("call-1")).toBe(false); + // Nothing durable was written by any of it. + expect(appends(yield* holder.stream.readAll())).toEqual([]); + }); + + it("P3: retiring the owner wakes nobody who was waiting on its failure", function* () { + // The owner acquired directly, so what is under test is the resource's own + // lifetime rather than a session's use of it. + const [owner, dispose] = createScope(yield* useScope()); + const held = withResolvers(); + owner.run(function* () { + held.resolve(yield* useReplAgent("deny-all")); + yield* sleep(DEADLOCK_MS); + }); + const kernel = yield* held.operation; + + // Waiting from outside the scope that is about to go away, so this task + // outlives the disposal and can say whether anything woke it. + let woken = false; + yield* spawn(function* () { + yield* kernel.failed; + woken = true; + }); + yield* sleep(0); + + yield* until(dispose()); + yield* sleep(0); + + // Released, not failed: there was no failure, so the waiter is left + // exactly as it was rather than told about one. + expect(woken).toBe(false); + expect(kernel.reading.turns).toEqual([]); + expect(kernel.reading.requests).toEqual([]); + }); +}); + +describe("P4 — a request without one live owner fails the session", () => { + beforeAll(() => useTempFileCompiler()); + + it("P4: an unowned request publishes nothing, denies nothing and fails the session", function* () { + const stub = createStub(); + yield* useStub(stub); + const answered: PermissionOutcome[] = []; + const raised: string[] = []; + // An authored component, outside any live Prompt, reaching the public + // operation exactly as a provider would. + yield* registerComponents([ + { + name: "AskOutside", + origin: "tier-854", + props: { type: "object", properties: {}, additionalProperties: false }, + *fn() { + try { + answered.push( + yield* Agent.operations.requestPermission({ + session: { sessionKey: "stub:none", cwd: "/stub" }, + toolCall: { toolCallId: "orphan", kind: "execute" }, + options: ALL_KINDS, + }), + ); + } catch (error) { + // Swallowed and carried on, the way an ordinary `` turns a + // provider failure into its own durable result and continues. If + // the failure reached only this operation, the session would still + // be live — which is exactly what it may not be. + raised.push(error instanceof Error ? error.name : String(error)); + } + return "continued"; + }, + }, + ]); + const holder = execution(); + const session = opened(yield* start(holder, "\n", "approve-reads")); + // Bounded, because the failure mode of not failing the session is that it + // waits forever on a request nobody can answer. A correct kernel never + // reaches the deadline. + const outcome = yield* race([ + session.join(), + (function* (): Operation> { + yield* sleep(DEADLOCK_MS); + throw new Error( + "the session never ended: an unowned request was published, denied, or left waiting", + ); + })(), + ]); + + // The document did not fail on its own — the component carried on — and + // the session failed anyway, because the owner was told too. + expect(outcome.ok).toBe(false); + expect(outcome.ok === false && outcome.error.name).toBe("ReplPermissionOwnerError"); + // Reported to the permission operation as well as to the session owner. + expect(raised).toEqual(["ReplPermissionOwnerError"]); + // Never published, and never converted into a denial. + expect(session.agent.requests).toEqual([]); + expect(answered).toEqual([]); + // No invented audit: the journal holds only what actually happened. + expect(appends(yield* holder.stream.readAll())).toEqual([]); + }); +}); + +/** Two spawned turns, each asking its own question. */ +const TWO_ASKS = [ + "", + '', + '', + "", +].join("\n"); + +describe("P5 — the durable audit of a live REPL turn", () => { + beforeAll(() => useTempFileCompiler()); + + it("P5: two live turns settle in reverse order, and each record holds only its own", function* () { + const stub = createStub({ + first: { permission: { toolCallId: "call-first", kind: "execute" }, deltas: ["a"] }, + second: { permission: { toolCallId: "call-second", kind: "execute" }, deltas: ["b"] }, + }); + yield* useStub(stub); + const holder = execution(); + const session = opened(yield* start(holder, TWO_ASKS, "approve-reads")); + yield* spawn(function* () { + // Both waiting at once, which is what makes ownership a question at all. + yield* reported(session, "two pending requests", (reading) => reading.requests.length === 2); + const asked = session.agent.requests; + const first = asked.find((request) => request.toolCallId === "call-first"); + const second = asked.find((request) => request.toolCallId === "call-second"); + expect(first).toBeDefined(); + expect(second).toBeDefined(); + // Different turns, so neither decision could belong to the other. + expect(first?.turn).not.toBe(second?.turn); + // Answered in the opposite order to the one they arrived in. + expect(session.permissions.choose(second?.key ?? "", "always")).toBe(true); + expect(session.permissions.choose(first?.key ?? "", "once")).toBe(true); + }); + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + + // One audit each, and each on the turn that asked it: nothing correlated by + // which turn was newest or which decision settled first. + const turns = session.model.turns; + expect(turns).toHaveLength(2); + const asking = (text: string): readonly ReplAgentPermission[] => + turns.find((turn) => turn.input === text)?.permissions ?? []; + expect(asking("first").map((one) => one.toolCallId)).toEqual(["call-first"]); + expect(asking("second").map((one) => one.toolCallId)).toEqual(["call-second"]); + expect(asking("first")[0]?.selected).toBe("once"); + expect(asking("second")[0]?.selected).toBe("always"); + }); + + it("P5: a later request from an earlier turn is still that turn's", function* () { + const stub = createStub({ + first: { + // Held until its sibling has asked, so the sibling's ledger is the most + // recently placed one when this turn finally asks anything at all. + gated: "each", + permission: { toolCallId: "call-first", kind: "execute" }, + // And asked again once it is running, with the sibling still waiting. + late: { toolCallId: "call-later", kind: "execute" }, + deltas: ["a"], + }, + second: { permission: { toolCallId: "call-second", kind: "execute" }, deltas: ["b"] }, + }); + yield* useStub(stub); + const session = opened(yield* start(execution(), TWO_ASKS, "approve-reads")); + yield* spawn(function* () { + yield* reported(session, "the sibling's request", (reading) => + reading.requests.some((request) => request.toolCallId === "call-second"), + ); + stub.let_("root.0"); + yield* reported(session, "two pending requests", (reading) => reading.requests.length === 2); + const held = (id: string): string => + session.agent.requests.find((request) => request.toolCallId === id)?.key ?? ""; + // The first turn is released and asks again while the second still waits. + expect(session.permissions.choose(held("call-first"), "once")).toBe(true); + yield* reported(session, "the later request", (reading) => + reading.requests.some((request) => request.toolCallId === "call-later"), + ); + expect(session.permissions.choose(held("call-later"), "always")).toBe(true); + expect(session.permissions.choose(held("call-second"), "no")).toBe(true); + }); + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + const asking = (text: string): string[] => + session.model.turns + .find((turn) => turn.input === text) + ?.permissions.map((one) => one.toolCallId) ?? []; + // Both of the first turn's requests are the first turn's, including the one + // it asked after its sibling had placed a ledger of its own. + expect(asking("first")).toEqual(["call-first", "call-later"]); + expect(asking("second")).toEqual(["call-second"]); + }); + + it("P5: two requests from one turn keep the order they were asked, not the order they settled", function* () { + const stub = createStub({ + one: { + permissions: [ + { toolCallId: "call-a", title: "Asked first", kind: "execute" }, + { toolCallId: "call-b", title: "Asked second", kind: "execute" }, + ], + deltas: ["reply"], + }, + }); + yield* useStub(stub); + const session = opened(yield* start(execution(), ONE_PROMPT, "approve-reads")); + yield* spawn(function* () { + yield* reported(session, "two pending requests", (reading) => reading.requests.length === 2); + const asked = session.agent.requests; + const a = asked.find((request) => request.toolCallId === "call-a"); + const b = asked.find((request) => request.toolCallId === "call-b"); + // One turn, both places already reserved, and the second one answered + // first — which is exactly what a record sorted by completion would show + // the wrong way round. + expect(a?.turn).toBe(b?.turn); + expect(session.permissions.choose(b?.key ?? "", "always")).toBe(true); + expect(session.permissions.choose(a?.key ?? "", "once")).toBe(true); + }); + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + expect(audited(session).map((one) => one.toolCallId)).toEqual(["call-a", "call-b"]); + expect(audited(session).map((one) => one.selected)).toEqual(["once", "always"]); + }); + + it("P5: provider-owned input reaches neither the journal nor the model", function* () { + const canary = "canary-9f3b7c1e-only-in-rawInput"; + const stub = createStub({ + one: { + // A read, so the policy answers it without anybody being asked — and the + // request carries something no record may ever hold. + permission: { + toolCallId: "call-1", + kind: "read", + title: "Read a file", + rawInput: { path: "/etc/passwd", secret: canary }, + }, + deltas: ["reply"], + }, + }); + yield* useStub(stub); + const holder = execution(); + const session = opened(yield* start(holder, ONE_PROMPT, "approve-reads")); + const outcome = yield* session.join(); + expect(outcome.ok).toBe(true); + // The decision was made and retained. + expect(audited(session)).toHaveLength(1); + // And what the provider owned stayed the provider's: not in the bytes, not + // in the model, not under any name. + const written = yield* holder.stream.readAll(); + expect(written.map((event) => serializeDurableEvent(event)).join("\n")).not.toContain(canary); + expect(JSON.stringify(session.model)).not.toContain(canary); + expect(Object.keys(audited(session)[0] ?? {}).sort()).toEqual([ + "kind", + "options", + "outcome", + "selected", + "title", + "toolCallId", + ]); + }); +}); + +describe("P6 — only a canonical Prompt claims a record", () => { + beforeAll(() => useTempFileCompiler()); + + it("P6: a direct public prompt neither claims the canonical record nor lingers", function* () { + const stub = createStub({ + direct: { deltas: ["off-books"] }, + one: { deltas: ["reply"] }, + }); + yield* useStub(stub); + // A registered component reaching the public operation itself. This is + // ordinary — the Api is exported — and it is not journal-owned work: no + // `agent_prompt` record will ever describe it. + yield* registerComponents([ + { + name: "DirectPrompt", + origin: "tier-854", + props: { type: "object", properties: {}, additionalProperties: false }, + *fn() { + const stream = yield* Agent.operations.prompt("direct", {}); + const subscription = yield* stream; + let next = yield* subscription.next(); + while (!next.done) { + next = yield* subscription.next(); + } + return "direct"; + }, + }, + ]); + const holder = execution(); + // Both on the root coroutine, the direct call first: a queue drained on + // append would hand the canonical record the direct call's entry. + const session = opened(yield* start(holder, '\n\n\n')); + const outcome = yield* session.join(); + + expect(outcome.ok).toBe(true); + // The provider answered both, so the direct call really did happen. + expect(stub.asked).toEqual(["direct", "one"]); + // Exactly one record, and it is the canonical Prompt's. + expect(session.model.turns).toHaveLength(1); + expect(session.model.turns[0]?.text).toBe("reply"); + // Nothing live is left over: the canonical publication removed its own + // turn, and the direct call never had one to leave behind. + expect(session.agent.turns).toEqual([]); + expect(session.live).toBe(false); + }); + + it("P6: concurrent spawns with a direct call each retire only their own turn", function* () { + // Two canonical turns running at once, one of them on a coroutine that + // also made a direct public call. Reversed *completion* is held by the L2 + // and P5 rows above, which run the same mechanism; what this adds is a + // direct call in the middle of concurrent work. + const stub = createStub({ + direct: { deltas: ["off-books"] }, + same: { deltas: ["reply"] }, + }); + yield* useStub(stub); + yield* registerComponents([ + { + name: "DirectPrompt", + origin: "tier-854", + props: { type: "object", properties: {}, additionalProperties: false }, + *fn() { + const stream = yield* Agent.operations.prompt("direct", {}); + const subscription = yield* stream; + let next = yield* subscription.next(); + while (!next.done) { + next = yield* subscription.next(); + } + return "direct"; + }, + }, + ]); + const holder = execution(); + const source = [ + "", + '', + '', + "", + ].join("\n"); + const session = opened(yield* start(holder, source)); + const outcome = yield* session.join(); + + expect(outcome.ok).toBe(true); + // The direct call really happened, beside both canonical turns. + expect(stub.asked.filter((asked) => asked === "direct")).toHaveLength(1); + expect(stub.asked.filter((asked) => asked === "same")).toHaveLength(2); + // Two canonical records, and the direct call is in neither. + expect(session.model.turns).toHaveLength(2); + expect(session.model.turns.map((turn) => turn.text)).toEqual(["reply", "reply"]); + // Every live turn retired, including on the coroutine that also made a + // direct call — which had no live turn to leave behind. + expect(session.agent.turns).toEqual([]); + }); + + it("P6: a refused publication leaves the turn neither retained nor mounted", function* () { + const stub = createStub({ one: { deltas: ["reply"] } }); + yield* useStub(stub); + const holder = refusingExecution(); + const session = opened(yield* start(holder, ONE_PROMPT)); + const outcome = yield* session.join(); + + // The provider really ran and really finished, so the overlay was + // terminal at the moment publication was refused. + expect(stub.asked).toEqual(["one"]); + // Nothing retained it. + expect(outcome.ok).toBe(false); + expect(session.model.turns).toEqual([]); + // And nothing is still mounted waiting for a record that will never come. + expect(session.agent.turns).toEqual([]); + expect(session.live).toBe(false); + }); + + const endings: readonly ("failed" | "cancelled")[] = ["failed", "cancelled"]; + for (const ended of endings) { + it(`P6: a ${ended} canonical Prompt hands off exactly as a completed one does`, function* () { + // `association` is absent for every unsuccessful turn, so this is the + // case where nothing but the handle can say which live turn ended. + const stub = createStub({ one: { deltas: ["partial"], status: ended } }); + yield* useStub(stub); + const holder = execution(); + const session = opened(yield* start(holder, ONE_PROMPT)); + const outcome = yield* session.join(); + + // Whether an unsuccessful turn also fails the document is the Prompt + // failure policy's business and not this row's. What this row holds is + // the handoff: the record was appended, and the live turn it began is + // gone — with no `association` to identify it by, only the handle. + expect(outcome).toBeDefined(); + expect(session.model.turns).toHaveLength(1); + expect(session.model.turns[0]?.status).toBe(ended); + expect(session.agent.turns).toEqual([]); + }); + } +}); diff --git a/packages/core/host.ts b/packages/core/host.ts index 7aa33732..cda04dd3 100644 --- a/packages/core/host.ts +++ b/packages/core/host.ts @@ -349,6 +349,7 @@ export type { export { useAgentPromptPublisher } from "./src/agent/publication.ts"; export type { AgentPromptAssociation, + AgentPromptHandle, AgentPromptPublication, AgentPromptPublisher, } from "./src/agent/publication.ts"; diff --git a/packages/core/mod.ts b/packages/core/mod.ts index fa11acf6..b08e2149 100644 --- a/packages/core/mod.ts +++ b/packages/core/mod.ts @@ -323,6 +323,10 @@ export { } from "./src/agent/components.ts"; export type { AgentComponentsOptions } from "./src/agent/components.ts"; export { Agent } from "./src/agent/agent-api.ts"; +// The one deny decision, so a host writing its own permission policy reaches +// the same rule the base handler and every built-in policy reach rather than +// spelling "reject_once, then reject_always, otherwise cancel" again. +export { denyPermission } from "./src/agent/agent-api.ts"; export type { AgentApi, AgentOption, diff --git a/packages/core/src/agent/components.ts b/packages/core/src/agent/components.ts index e23b2a33..8c87d471 100644 --- a/packages/core/src/agent/components.ts +++ b/packages/core/src/agent/components.ts @@ -54,7 +54,11 @@ import { SESSION_PROPS, SessionLaunch, } from "./function-components.ts"; -import { promptFailureFromRecord, readCompletedPrompts } from "./journal.ts"; +import { + observePermissionDecisions, + promptFailureFromRecord, + readCompletedPrompts, +} from "./journal.ts"; export interface AgentComponentsOptions { /** Default agent seeded for `` inheritance. */ @@ -229,6 +233,18 @@ export function* installAgentComponents(options?: AgentComponentsOptions): Opera yield* useAgentComponents(); + // Auditing for this installation, outside every policy installed after it. + // A policy decides without delegating — that is what deciding means — so an + // observer anywhere inside one never sees the decision it makes. Installed + // here, in the Agent installation itself, it wraps the host's own policy and + // the REPL's authority alike. + // + // It owns both sides. Each `Agent.prompt()` call gets a private ledger and a + // wrapped stream, so every event that call produces is read with that + // ledger in place; a request raised while it is read finds that ledger and + // no other. Nothing a component does installs, places or brackets an audit. + yield* observePermissionDecisions(); + const rootProvider = options?.rootProvider; yield* Execution.around({ diff --git a/packages/core/src/agent/function-components.ts b/packages/core/src/agent/function-components.ts index f7b6b388..367e5254 100644 --- a/packages/core/src/agent/function-components.ts +++ b/packages/core/src/agent/function-components.ts @@ -54,11 +54,13 @@ import { installApproveAll, installAskPermission } from "./permission.ts"; import { AgentInternal, formatLocation } from "./internal.ts"; import { serializePromptFailure } from "./errors.ts"; import type { SerializedPromptFailure } from "./errors.ts"; -import { persistPrompt, promptFailureFromRecord, promptPermissionAudit } from "./journal.ts"; +import { completedPromptAudit, persistPrompt, promptFailureFromRecord } from "./journal.ts"; import type { PromptRecord } from "./journal.ts"; import { checkpointOf } from "./checkpoint.ts"; import type { AgentPromptCheckpoint } from "./checkpoint.ts"; -import type { AgentPromptAssociation } from "./publication.ts"; +import type { AgentPromptAssociation, AgentPromptHandle } from "./publication.ts"; +import type { Stream } from "effection"; +import type { AgentPromptEvent } from "./agent-api.ts"; export const AGENT_PROVIDER_PROPS: PropsSchema = { type: "object", @@ -330,11 +332,12 @@ export function* Prompt(props: Record): Operation { const sequence = yield* AgentInternal.operations.nextPromptSequence(); // Held here rather than on the record: what the journal keeps about a prompt // is unchanged, and this is what a host may retain beside it. - const carried: { association?: AgentPromptAssociation } = {}; + const carried: Carried = {}; const record = yield* persistPrompt( { name: `prompt:${location}#${ordinal}`, input: text, position: expansion.position }, () => runPrompt(text, options, sequence, throwOnError, carried), () => carried.association, + () => carried.begun, ); const failure = promptFailureFromRecord(record); @@ -384,17 +387,28 @@ interface ConsumedTurn { checkpoint?: AgentPromptCheckpoint; } +/** + * What this turn hands the publication beside the record: the association a + * host may retain, and the handle that host recognised this exact live turn by. + */ +interface Carried { + association?: AgentPromptAssociation; + begun?: AgentPromptHandle; +} + function* runPrompt( text: string, options: PromptOptions, sequence: number, throwOnError: boolean, - carried: { association?: AgentPromptAssociation }, + carried: Carried, ): Operation { let consumed: ConsumedTurn = { text: "" }; - // Held out here because the audit outlives the turn's own scope: a turn that - // failed still answered whatever it was asked before it did. - const permissions = promptPermissionAudit(); + // The exact stream this turn consumed, held out here because the audit + // outlives the turn's own scope: a turn that failed still answered whatever + // it was asked before it did. Holding the stream is what makes its audit + // readable — nothing else can name it. + let consumedStream: Stream | undefined; // What this turn was authored to run under, and — once the provider has been // reached — the exact value it was reached with. The record below is written // from the second, so the journal describes the turn that actually ran. @@ -405,10 +419,20 @@ function* runPrompt( // document teardown. consumed = yield* scoped(function* (): Operation { const result: ConsumedTurn = { text: "" }; - // Installed before the turn is asked for, so a provider that requests - // permission the moment it is subscribed is already being watched. - yield* permissions.observe(); + // Begun immediately before the provider is asked, in this turn's own + // scope: the one moment that is both canonical and live, so it is the + // only place a host can be handed something identifying this turn and + // nothing else. A component calling the public `Agent.prompt()` never + // reaches here, which is exactly why it can claim no publication later. + const publisher = yield* AgentInternal.operations.promptPublisher; + if (publisher?.begin !== undefined) { + carried.begun = yield* publisher.begin(text); + } + // Auditing is installed by the Agent middleware around this call, not + // here: the stream that comes back already carries its own ledger, and + // holding it is how this turn reads what it was allowed to do. const stream = yield* Agent.operations.prompt(text, options); + consumedStream = stream; const subscription = yield* stream; let next = yield* subscription.next(); while (!next.done) { @@ -492,7 +516,10 @@ function* runPrompt( } // Written once the turn is over, with the rest of the result: a decision is // part of the account of this turn, not an event of its own. - const answered = permissions.completed(); + // Read from the exact stream this turn consumed. A turn that never got one + // was allowed nothing, because it never asked. + const answered = + (consumedStream === undefined ? undefined : yield* completedPromptAudit(consumedStream)) ?? []; if (answered.length > 0) { record.permissions = answered; } diff --git a/packages/core/src/agent/journal.ts b/packages/core/src/agent/journal.ts index 76d910d2..44aed883 100644 --- a/packages/core/src/agent/journal.ts +++ b/packages/core/src/agent/journal.ts @@ -55,9 +55,11 @@ import type { Result as DurableResult, Workflow, } from "@executablemd/durable-streams"; -import type { Operation } from "effection"; +import { createContext } from "effection"; +import type { Operation, Stream } from "effection"; import { Agent } from "./agent-api.ts"; import type { + AgentPromptEvent, PermissionOption, PermissionOutcome, PermissionRequest, @@ -69,7 +71,7 @@ import type { AgentPromptCheckpoint } from "./checkpoint.ts"; import { AgentPromptError, parsePromptFailure } from "./errors.ts"; import type { SerializedPromptFailure } from "./errors.ts"; import { AgentInternal } from "./internal.ts"; -import type { AgentPromptAssociation } from "./publication.ts"; +import type { AgentPromptAssociation, AgentPromptHandle } from "./publication.ts"; import { sourceDescription } from "../source-position.ts"; import type { SourcePosition } from "../types.ts"; @@ -151,48 +153,169 @@ interface PermissionDraft { outcome?: PermissionOutcome; } +/** + * Where one Prompt's audit goes, for whoever answers a request it made. + * + * A destination and nothing else: holding it says which turn a decision belongs + * to, and grants no authority to make one. Private to this module — no caller + * outside it can reach the context, the ledger or a reserved place. + * + * Scope is the correlation. A permission request raised while a Prompt's + * provider stream is being consumed runs inside that Prompt's own scope, so it + * inherits that Prompt's ledger and no other — never the newest turn, the turn + * with matching text, or whichever decision settled first. + */ +const PromptAudit = createContext("xmd.agent.prompt-permission-audit"); + +/** One Prompt's ledger: reserve a place, then complete that same place. */ +interface PromptAuditLedger { + /** + * Take this request's place in the order, and hand back the one way to + * complete it. + * + * None when the request's safe fields do not read as the closed shape a record + * holds: retaining a half-read audit would make the record unparseable, which + * would fail a turn over how it was watched. + */ + reserve(request: PermissionRequest): ((outcome: PermissionOutcome) => void) | undefined; +} + /** What one turn observed of the permission requests made while it ran. */ export interface PromptPermissionAudit { - /** Observe permission requests for as long as the installing scope lives. */ - observe(): Operation; + /** Where a request made beneath this turn's stream records itself. */ + readonly ledger: PromptAuditLedger; /** The requests that were decided, in the order they arrived. */ completed(): readonly PromptPermission[]; } /** - * Observe the permission requests one prompt turn answers. + * Observe every permission decision, from outside every policy that makes one. * - * Ordinary middleware around the existing `Agent.requestPermission()`, so it - * sits outside every installed policy and inside whatever a document composed: - * it sees the request on its way to being decided and the decision on its way - * back, and it delegates both unchanged. It decides nothing, substitutes - * nothing, and swallows nothing — a policy that raises raises through here, and - * the request it was answering is simply never completed. + * At `max`, which is the outermost position there is: a policy decides without + * delegating — that is what deciding means — so an observer anywhere inside one + * never sees the call it exists to record. The REPL's own authority is installed + * at the ordinary position and is therefore inside this, which is the whole + * point: its outcome is the one this copies. * - * The subject is copied when the call begins rather than when it ends, because - * a caller is free to reuse or rewrite the request object it passed once the - * answer is in hand. A request whose safe fields do not read as this closed - * shape is observed as nothing at all: retaining a half-read audit would make - * the record unparseable, which would fail a turn over how it was watched. + * It decides nothing, substitutes nothing and swallows nothing. A policy that + * raises raises through here, and the place it was answering is simply never + * completed. */ -export function promptPermissionAudit(): PromptPermissionAudit { - const drafts: PermissionDraft[] = []; +export function* observePermissionDecisions(): Operation { + // Owned by this installation, not by the module: one execution's audits are + // not another's, and a process-lifetime registry would outlive every run. + yield* PromptAudits.set(new WeakMap()); + yield* Agent.around( + { + *prompt([text, options], next) { + // One ledger for this exact invocation, made here rather than by + // whoever called: nothing outside this middleware creates, places or + // brackets an audit, so no caller can get the lifetime wrong. + const audit = promptPermissionAudit(); + const wrapped = audited(yield* next(text, options), audit.ledger); + // The association is the wrapped stream itself. Only whoever holds the + // exact stream this invocation returned can read its audit, which is + // what makes a canonical `` able to and a bystander not. + (yield* PromptAudits.get())?.set(wrapped, audit); + return wrapped; + }, + *requestPermission([request], next) { + const ledger = yield* PromptAudit.get(); + // Reserved on the way in, because a caller is free to reuse or rewrite the + // request object it passed once the answer is in hand. + const complete = ledger?.reserve(request); + const outcome = yield* next(request); + complete?.(outcome); + return outcome; + }, + }, + { at: "max" }, + ); +} + +/** + * Each wrapped stream's own audit, readable only by whoever holds the stream. + * + * Private to this module and scoped to one Agent installation. Nothing outside + * can reach the map, and holding a stream is the only way to name an entry. + */ +const PromptAudits = + createContext, PromptPermissionAudit>>( + "xmd.agent.prompt-audits", + ); + +/** + * What the turn consumed through this exact stream was allowed to do. + * + * Undefined for a stream this middleware did not wrap, which is every stream a + * caller made itself. + */ +export function* completedPromptAudit( + stream: Stream, +): Operation { + const audits = yield* PromptAudits.get(); + return audits?.get(stream)?.completed(); +} + +/** + * Wrap a cold provider stream so the ledger is in place for every event. + * + * The stream is cold, so subscribing is where the turn actually starts, and a + * provider may raise its first request the moment it is subscribed. Both the + * subscription and every `next()` therefore run beneath the context, and it is + * restored whichever way each of them finishes — returning, raising, or being + * cancelled — because that is what `with` does. + */ +function audited( + stream: Stream, + ledger: PromptAuditLedger, +): Stream { return { - observe() { - return Agent.around({ - *requestPermission([request], next) { - const subject = permissionSubject(request); - if (subject === undefined) { - return yield* next(request); - } - const draft: PermissionDraft = { subject }; - drafts.push(draft); - const outcome = yield* next(request); - draft.outcome = permissionDecision(outcome); - return outcome; + *[Symbol.iterator]() { + // A `Stream` is itself the operation that subscribes, so this is that + // subscription run beneath the ledger — not a copy of it. + const subscription = yield* PromptAudit.with(ledger, () => stream); + return { + *next() { + return yield* PromptAudit.with(ledger, () => subscription.next()); }, - }); + }; + }, + }; +} + +/** + * The audit one prompt turn keeps of the permission requests made while it ran. + * + * The turn owns the ledger and the observer is somewhere else entirely — outside + * every policy, installed once for the execution. What connects them is scope: + * this ledger is placed where the provider's stream is consumed, and a request + * raised from in there finds it. + * + * Only completed places are published. A decision still being made, and one + * whose policy was cancelled before it answered, are both absent — an audit + * naming an outcome nobody reached would be a record of something that did not + * happen. + */ +export function promptPermissionAudit(): PromptPermissionAudit { + const drafts: PermissionDraft[] = []; + const ledger: PromptAuditLedger = { + reserve(request: PermissionRequest) { + const subject = permissionSubject(request); + if (subject === undefined) { + return undefined; + } + // The place is taken now and completed later, so two requests from one + // turn keep the order they arrived in however their answers interleave. + const draft: PermissionDraft = { subject }; + drafts.push(draft); + return (outcome: PermissionOutcome) => { + draft.outcome = permissionDecision(outcome); + }; }, + }; + return { + ledger, completed() { return drafts.flatMap((draft) => { if (draft.outcome === undefined) { @@ -282,6 +405,7 @@ export function* persistPrompt( identity: { name: string; input: string; position?: Readonly }, live: () => Operation, association: () => AgentPromptAssociation | undefined = () => undefined, + begun: () => AgentPromptHandle = () => undefined, ): Workflow { const stored = yield createDurableOperation( { @@ -293,7 +417,7 @@ export function* persistPrompt( function* (): Operation { return serializePromptRecord(yield* live()); }, - { coordinator: promptPublication(association) }, + { coordinator: promptPublication(association, begun) }, ); const parsed = parsePromptRecord(stored); if (!parsed) { @@ -324,6 +448,7 @@ class AgentPromptPublicationError extends Error { */ function promptPublication( association: () => AgentPromptAssociation | undefined, + begun: () => AgentPromptHandle, ): LiveDurableOperationCoordinator { return { *run( @@ -349,6 +474,9 @@ function promptPublication( let appended = false; try { yield* publisher.publish({ + // Carried whatever this turn did: a failed or cancelled Prompt still + // ends the live turn that began, so the handoff is the same one. + begun: begun(), association: published.status === "ok" ? association() : undefined, *append(): Operation { if (appended) { diff --git a/packages/core/src/agent/publication.ts b/packages/core/src/agent/publication.ts index 0884fea2..88d49543 100644 --- a/packages/core/src/agent/publication.ts +++ b/packages/core/src/agent/publication.ts @@ -1,6 +1,6 @@ /** - * Where a completed Prompt's durable result is published, for a host that - * retains something beside it. + * Where a Prompt's durable result is published, for a host that retains + * something beside it. * * An ordinary `xmd run` has no publisher, and publishes exactly as it always * did: the `agent_prompt` event is appended by the durable machinery and @@ -11,9 +11,16 @@ * or not at all, so no association can survive a Prompt that was never * journaled and no journaled Prompt can be left half-described. * - * The Prompt itself runs *before* any of this. A publisher receives a Prompt - * that has already finished talking to its provider, so nothing a host does - * here holds a database open across a conversation. + * A publisher sees one turn at two moments, and only these two. `begin()` is + * called before the provider is asked, so a host that shows live work can + * create it there; `publish()` is called once the turn has finished talking to + * its provider, so nothing a host does at publication holds a database open + * across a conversation. What `begin()` returned comes back on the + * publication, and that is the only thing tying the two together — core never + * reads it. + * + * `begin()` is optional. A publisher that declares none is called exactly as + * it always was, and its publications carry an undefined handle. * * Installing one is a host act and reads as one at the import: this is reached * through `@executablemd/core/host`, and the private Api it seeds is exported @@ -40,8 +47,30 @@ export interface AgentPromptAssociation { readonly sessionKey: string; } +/** + * What a host recognised one canonical turn by, exactly as its own `begin()` + * returned it. + * + * Opaque to core, which never reads it, compares it or writes it anywhere: it + * is carried from the turn that began to the publication that ends it and + * nowhere else. Ephemeral and process-local by construction — it is whatever + * object the host made, so it cannot outlive the process and cannot be + * journaled. + */ +export type AgentPromptHandle = unknown; + /** One Prompt, ready to publish. */ export interface AgentPromptPublication { + /** + * What this host's own `begin()` returned for this exact turn, or nothing + * when it declared no `begin()`. + * + * This is the only thing that says which live turn this publication ends. A + * prompt that never began canonically — a direct `Agent.prompt()` call from + * a component, which core does not journal — reaches no publication at all, + * so it can neither claim this one nor be claimed by it. + */ + readonly begun: AgentPromptHandle; /** * What this completion carries, or nothing. * @@ -60,6 +89,22 @@ export interface AgentPromptPublication { } export interface AgentPromptPublisher { + /** + * A canonical Prompt is about to ask its provider; nothing is durable yet. + * + * Called once per journal-owned turn, in that turn's own scope and at the + * moment its private audit ledger is placed — so a host that shows live work + * can create it here and be handed the same value back in `publish()`. What + * it returns is the host's own, and core only carries it. + * + * `input` is the rendered prompt this turn is about to ask, exactly as the + * record will hold it. + * + * Only the canonical `` boundary calls this. A component that calls + * the public `Agent.prompt()` itself is not journal-owned work: it begins + * nothing, publishes nothing, and appends no record. + */ + begin?(input: string): Operation; /** * Publish one completed Prompt, and whatever this host keeps beside it. * diff --git a/packages/core/tests/agent-function-components.test.ts b/packages/core/tests/agent-function-components.test.ts index 5c45dbdd..44c53321 100644 --- a/packages/core/tests/agent-function-components.test.ts +++ b/packages/core/tests/agent-function-components.test.ts @@ -34,7 +34,7 @@ import type { SessionConfiguration, } from "../src/agent/agent-api.ts"; import { AgentPromptError } from "../src/agent/errors.ts"; -import { executeInstalled } from "../host.ts"; +import { executeInstalled, useAgentPromptPublisher } from "../host.ts"; import { agentIdentityComponents } from "../src/agent/components.ts"; import { installAgentComponents } from "../src/agent/components.ts"; import { registerComponents } from "../src/components/registration.ts"; @@ -1701,3 +1701,63 @@ describe("Tier AF — the permission audit one turn retains", () => { }); }); }); + +describe("AFP — a publisher that never appends retains nothing", () => { + beforeAll(() => useTempFileCompiler()); + + it("AFP0: the same harness with a publisher that appends succeeds and records", function* () { + // The other side of the comparison. Without it, AFP1 and AFP2 would pass + // just as well if installing any publisher at all broke the run. + const { result, events } = yield* runDoc('\n', { + *handler() { + yield* useAgentPromptPublisher({ + *publish(publication) { + yield* publication.append(); + }, + }); + }, + }); + + expect(result.ok).toBe(true); + expect( + events.filter((event) => event.type === "yield" && event.description.type === "agent_prompt"), + ).toHaveLength(1); + }); + + it("AFP1: a raising publisher leaves no agent_prompt and fails the execution", function* () { + const { result, events } = yield* runDoc('\n', { + *handler() { + yield* useAgentPromptPublisher({ + *publish() { + throw new Error("this publisher could not commit"); + }, + }); + }, + }); + + // Failed through the durability path rather than carrying on from a result + // no journal holds. + expect(result.ok).toBe(false); + // And nothing durable describes the turn, so nothing can be presented as + // retained. + expect( + events.filter((event) => event.type === "yield" && event.description.type === "agent_prompt"), + ).toEqual([]); + }); + + it("AFP2: a publisher that returns without appending is the same failure", function* () { + const { result, events } = yield* runDoc('\n', { + *handler() { + yield* useAgentPromptPublisher({ + // Returns cleanly, appends nothing. Silence is not publication. + *publish() {}, + }); + }, + }); + + expect(result.ok).toBe(false); + expect( + events.filter((event) => event.type === "yield" && event.description.type === "agent_prompt"), + ).toEqual([]); + }); +}); diff --git a/specs/acp-client-spec.md b/specs/acp-client-spec.md index fe1b966f..12a0e887 100644 --- a/specs/acp-client-spec.md +++ b/specs/acp-client-spec.md @@ -536,8 +536,28 @@ omits the member entirely, which is also how every record written before this member existed reads. Parsing an older record is unchanged, and nothing rewrites one. -Observing a request changes no decision. The installed `PermissionMode` policy -still answers it, and these records describe decisions rather than making them. +Observing a request changes no decision, and what observes one is deliberately +somewhere else from what answers it. A policy decides every request inside its +scope and never defers outward, so an observer installed inside a policy — or +inside the turn, below a policy a host installed around the execution — sees +nothing of the decision it exists to record. Auditing is therefore installed with the +Agent itself, outside every policy installed after it, and it owns both sides: +each `Agent.prompt()` call is given its own private ledger, and the cold stream +that call returns is wrapped so every event is read with that ledger in place. A +request raised while a turn's stream is being read inherits that turn's ledger, +so two turns asking at the same time keep their audits apart without anything +correlating by recency, prompt text, agent, conversation or the order decisions +settled in. Nothing a turn does installs its own audit, and a turn reads what it +was allowed to do from the exact stream it consumed. + +The ledger is a destination and grants no authority: holding it says which turn a +decision belongs to, never what the decision is. The observer reserves the +request's place on the way in, delegates exactly once to whatever answers it, and +copies the returned outcome into that same place — which is why two overlapping +requests from one turn keep the order they arrived in however their answers +interleave. A place nobody completed is published as nothing: a policy that +raised, and one cancelled with its turn during teardown, both leave no member +rather than a fabricated outcome. On a **full replay** (the journal already holds the root `Close`), completed records are restored from the journal without contacting any provider — diff --git a/test-weights.json b/test-weights.json index 1874ae49..4f5f59b3 100644 --- a/test-weights.json +++ b/test-weights.json @@ -1,1107 +1,1113 @@ { "version": 1, "source": { - "commit": "1d317942eb24810816c172998c083033f5d1165b", - "runUrl": "https://github.com/taras/executable.md/actions/runs/36577302779", + "commit": "084397e1dbca25cbbd35993f237184b8d30534d0", + "runUrl": "https://github.com/taras/executable.md/actions/runs/36655497896", "attempt": 1, "runner": "ubuntu-latest", "deno": "deno 2.9.5 (stable, release, x86_64-unknown-linux-gnu)", - "node": "v22.23.2", + "node": "v22.23.3", "bun": "1.4.0" }, "runtimes": { "deno": { - "packages/acp/tests/acceptance.test.ts": 5052, - "packages/acp/tests/acpx-checkpoint-meta.test.ts": 8265, - "packages/acp/tests/acpx-materialization.test.ts": 8084, - "packages/acp/tests/acpx-transient-env.test.ts": 1772, - "packages/acp/tests/adapter-materialization.test.ts": 76537, - "packages/acp/tests/adapter-protocol.test.ts": 60468, - "packages/acp/tests/codex-surfaces.test.ts": 1819, - "packages/acp/tests/loaded-copy.test.ts": 4625, - "packages/acp/tests/native-launch.test.ts": 5333, - "packages/acp/tests/permission-bridge.test.ts": 4193, - "packages/acp/tests/provider.test.ts": 5345, - "packages/acp/tests/serial-queue.test.ts": 1527, - "packages/acp/tests/session-key.test.ts": 4017, - "packages/acp/tests/session-route.test.ts": 4255, - "packages/acp/tests/terminal-screen.test.ts": 1693, - "packages/cli/tests/agent-adapters.test.ts": 5698, - "packages/cli/tests/agent-cli.test.ts": 37667, - "packages/cli/tests/agent-config.test.ts": 3713, - "packages/cli/tests/agent-options-cli.test.ts": 15490, - "packages/cli/tests/agent-options.test.ts": 3668, - "packages/cli/tests/agent-session-coordinator.test.ts": 4387, - "packages/cli/tests/cli-help.test.ts": 20250, - "packages/cli/tests/command.test.ts": 10303, - "packages/cli/tests/compiled-upgrade.test.ts": 10775, - "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 8964, - "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 119858, - "packages/cli/tests/document-suites/props/props-markdown.test.ts": 8472, - "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 12290, - "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 10519, - "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 15587, - "packages/cli/tests/evaluate-component.test.ts": 11840, - "packages/cli/tests/evaluate-workflow.test.ts": 5549, - "packages/cli/tests/fetch-cli.test.ts": 3840, - "packages/cli/tests/file-stream.test.ts": 1813, - "packages/cli/tests/github-zero-effect.test.ts": 7355, - "packages/cli/tests/inline-cli.test.ts": 33157, - "packages/cli/tests/launch-timeout.test.ts": 11665, - "packages/cli/tests/packaged-document.test.ts": 5100, - "packages/cli/tests/plan-args.test.ts": 1604, - "packages/cli/tests/plan-cli.test.ts": 99258, - "packages/cli/tests/plan-command-document.test.ts": 10015, - "packages/cli/tests/plan-component.test.ts": 12327, - "packages/cli/tests/plan-host-acts.test.ts": 5534, - "packages/cli/tests/plan.test.ts": 16548, - "packages/cli/tests/plugin-cli.test.ts": 41189, - "packages/cli/tests/plugin-host.test.ts": 6252, - "packages/cli/tests/plugin-modules.test.ts": 1573, - "packages/cli/tests/plugin-selection.test.ts": 1563, - "packages/cli/tests/process-retention.test.ts": 5369, - "packages/cli/tests/props-cli.test.ts": 30488, - "packages/cli/tests/props-schema.test.ts": 4164, - "packages/cli/tests/props-sources.test.ts": 2281, - "packages/cli/tests/pull-request-read-fork.test.ts": 7124, - "packages/cli/tests/repl-boundaries.test.ts": 7465, - "packages/cli/tests/repl-composition.test.ts": 2041, - "packages/cli/tests/repl-execution.test.ts": 5465, - "packages/cli/tests/repl-journey.test.ts": 20626, - "packages/cli/tests/repl-model.test.ts": 5262, - "packages/cli/tests/repl-route.test.ts": 4795, - "packages/cli/tests/repl-terminal.test.ts": 2918, - "packages/cli/tests/run-composition-deno.test.ts": 8040, - "packages/cli/tests/run-composition-nested.test.ts": 21027, - "packages/cli/tests/run-composition.test.ts": 7115, - "packages/cli/tests/run-deadline.test.ts": 8779, - "packages/cli/tests/run-profile.test.ts": 4429, - "packages/cli/tests/run-timeouts.test.ts": 13312, - "packages/cli/tests/secret-detection-cli.test.ts": 19647, - "packages/cli/tests/service-document.test.ts": 5296, - "packages/cli/tests/service-host.test.ts": 5176, - "packages/cli/tests/session-launch-cli.test.ts": 10798, - "packages/cli/tests/stdin-cli.test.ts": 37368, - "packages/cli/tests/stdout-delivery.test.ts": 1550, - "packages/cli/tests/syntax-cli.test.ts": 36390, - "packages/cli/tests/targets-cli.test.ts": 28754, - "packages/cli/tests/test-root-profile.test.ts": 7297, - "packages/cli/tests/test-target.test.ts": 37254, - "packages/cli/tests/testing-activation.test.ts": 4614, - "packages/cli/tests/testing-execution-host.test.ts": 57092, - "packages/cli/tests/upgrade-cli.test.ts": 21098, - "packages/cli/tests/upgrade-command-document.test.ts": 7460, - "packages/cli/tests/upgrade-output.test.ts": 4847, - "packages/cli/tests/value-root.test.ts": 17921, - "packages/cli/tests/verbose-component.test.ts": 5102, - "packages/cli/tests/workflow-agent-sessions.test.ts": 20280, - "packages/cli/tests/workflow-agent.test.ts": 16266, - "packages/cli/tests/workflow-cli.test.ts": 109626, - "packages/cli/tests/workflow-crash.test.ts": 81308, - "packages/cli/tests/workflow-declaration.test.ts": 4615, - "packages/cli/tests/workflow-export-publication.test.ts": 17473, - "packages/cli/tests/workflow-fetch.test.ts": 8201, - "packages/cli/tests/workflow-fork.test.ts": 85080, - "packages/cli/tests/workflow-host.test.ts": 14668, - "packages/cli/tests/workflow-inspection.test.ts": 115910, - "packages/cli/tests/workflow-installation.test.ts": 6162, - "packages/cli/tests/workflow-lifecycle-control.test.ts": 20775, - "packages/cli/tests/workflow-retention.test.ts": 23092, - "packages/cli/tests/workflow-suspension.test.ts": 8136, - "packages/code-review-agent/tests/doctor.test.ts": 1533, - "packages/code-review-agent/tests/parse-diagnostics.test.ts": 1611, - "packages/code-review-agent/tests/parse-diff.test.ts": 1601, - "packages/code-review-agent/tests/parse-doctor.test.ts": 4012, - "packages/code-review-agent/tests/parse-oxlint.test.ts": 1540, - "packages/code-review-agent/tests/policy.test.ts": 1551, - "packages/core/tests/agent-api.test.ts": 1567, - "packages/core/tests/agent-components.test.ts": 4627, - "packages/core/tests/agent-function-components.test.ts": 6345, - "packages/core/tests/agent-session-launch.test.ts": 6716, - "packages/core/tests/agent-session-placement.test.ts": 1612, - "packages/core/tests/agent-session-use-loaded-copy.test.ts": 1674, - "packages/core/tests/agent-session-use.test.ts": 1583, - "packages/core/tests/agent-stack.test.ts": 4198, - "packages/core/tests/all.test.ts": 5454, - "packages/core/tests/answer-identity.test.ts": 3695, - "packages/core/tests/answers-component.test.ts": 4788, - "packages/core/tests/answers-expansion-recursion.test.ts": 3892, - "packages/core/tests/capture-errors.test.ts": 4191, - "packages/core/tests/capture-props.test.ts": 3892, - "packages/core/tests/cli-journal.test.ts": 12094, - "packages/core/tests/code-block-component.test.ts": 5375, - "packages/core/tests/compiler-boundary.test.ts": 3850, - "packages/core/tests/component-api.test.ts": 3541, - "packages/core/tests/component-registration.test.ts": 4641, - "packages/core/tests/component-returns.test.ts": 5098, - "packages/core/tests/component-schema-conformance.test.ts": 3949, - "packages/core/tests/config-api.test.ts": 4047, - "packages/core/tests/construct-error-observation.test.ts": 4317, - "packages/core/tests/contextual-cwd.test.ts": 5151, - "packages/core/tests/daemon-integration.test.ts": 4696, - "packages/core/tests/daemon.test.ts": 3692, - "packages/core/tests/declared-markdown-component.test.ts": 5313, - "packages/core/tests/document-output-api.test.ts": 1535, - "packages/core/tests/document-target-execution.test.ts": 6755, - "packages/core/tests/document-targets.test.ts": 4030, - "packages/core/tests/document-validation.test.ts": 5348, - "packages/core/tests/documentation-index.test.ts": 3766, - "packages/core/tests/each.test.ts": 4056, - "packages/core/tests/elicit-component.test.ts": 4571, - "packages/core/tests/ephemeral-service.test.ts": 4359, - "packages/core/tests/eval-bindings.test.ts": 3914, - "packages/core/tests/eval-context.test.ts": 2152, - "packages/core/tests/eval-durable.test.ts": 3942, - "packages/core/tests/eval-error-mode.test.ts": 4324, - "packages/core/tests/eval-interpolate.test.ts": 1501, - "packages/core/tests/eval-middleware.test.ts": 3588, - "packages/core/tests/eval-persist.test.ts": 4073, - "packages/core/tests/eval-return.test.ts": 3935, - "packages/core/tests/eval-scope.test.ts": 3898, - "packages/core/tests/eval-timeout.test.ts": 4012, - "packages/core/tests/eval-transform.test.ts": 3660, - "packages/core/tests/evaluate-component.test.ts": 5827, - "packages/core/tests/evaluate-loaded-copy.test.ts": 3994, - "packages/core/tests/evaluate-provider-lifetime.test.ts": 4113, - "packages/core/tests/evaluation-profile.test.ts": 3779, - "packages/core/tests/exec-timeout.test.ts": 4022, - "packages/core/tests/execute.test.ts": 4458, - "packages/core/tests/execution-protocol.test.ts": 5301, - "packages/core/tests/expand.test.ts": 4673, - "packages/core/tests/expansion-identity.test.ts": 4548, - "packages/core/tests/expansion-metadata.test.ts": 3861, - "packages/core/tests/expression-props.test.ts": 4437, - "packages/core/tests/fail-component.test.ts": 4195, - "packages/core/tests/failure-printing.test.ts": 4156, - "packages/core/tests/fatal-cause.test.ts": 3759, - "packages/core/tests/fetch-component.test.ts": 5241, - "packages/core/tests/file-component.test.ts": 5891, - "packages/core/tests/file-delete-component.test.ts": 4132, - "packages/core/tests/files-fatal.test.ts": 4879, - "packages/core/tests/foreground-exec.test.ts": 4978, - "packages/core/tests/frontmatter.test.ts": 3609, - "packages/core/tests/function-components.test.ts": 4564, - "packages/core/tests/generated-composition.test.ts": 4502, - "packages/core/tests/generated-xmd.test.ts": 5865, - "packages/core/tests/glob-api.test.ts": 1870, - "packages/core/tests/glob-component.test.ts": 4612, - "packages/core/tests/guarded-journal.test.ts": 3953, - "packages/core/tests/has-content.test.ts": 3774, - "packages/core/tests/heal.test.ts": 3735, - "packages/core/tests/if.test.ts": 4420, - "packages/core/tests/inline-root.test.ts": 3999, - "packages/core/tests/invocation-failures.test.ts": 3708, - "packages/core/tests/invocation-identity.test.ts": 4385, - "packages/core/tests/invocation-scope.test.ts": 4060, - "packages/core/tests/journal-source-position.test.ts": 4210, - "packages/core/tests/json-component.test.ts": 4198, - "packages/core/tests/json.test.ts": 3686, - "packages/core/tests/let.test.ts": 4053, - "packages/core/tests/loaded-copy-files.test.ts": 3954, - "packages/core/tests/loop.test.ts": 4890, - "packages/core/tests/named-slots.test.ts": 4735, - "packages/core/tests/output-error-mode.test.ts": 5015, - "packages/core/tests/output-normalize.test.ts": 1529, - "packages/core/tests/output-terminal.test.ts": 1659, - "packages/core/tests/parse-components.test.ts": 4034, - "packages/core/tests/plan-response.test.ts": 1561, - "packages/core/tests/plugin-api.test.ts": 3606, - "packages/core/tests/plugin.test.ts": 3795, - "packages/core/tests/props-binding.test.ts": 4064, - "packages/core/tests/protected-content.test.ts": 3574, - "packages/core/tests/registered-printed-errors.test.ts": 3690, - "packages/core/tests/replay-stream.test.ts": 1506, - "packages/core/tests/retain.test.ts": 3951, - "packages/core/tests/root-composition.test.ts": 4078, - "packages/core/tests/root-props.test.ts": 4101, - "packages/core/tests/root-provider.test.ts": 4105, - "packages/core/tests/sample-component.test.ts": 4739, - "packages/core/tests/scanner.test.ts": 3611, - "packages/core/tests/scope-local.test.ts": 1522, - "packages/core/tests/secret-detection.test.ts": 4752, - "packages/core/tests/secret-files.test.ts": 2054, - "packages/core/tests/secret-rules.test.ts": 2065, - "packages/core/tests/secret-scanner-baseline.test.ts": 1712, - "packages/core/tests/secret-scanner.test.ts": 1894, - "packages/core/tests/source-position.test.ts": 3886, - "packages/core/tests/state-ownership.test.ts": 3992, - "packages/core/tests/streaming-emission.test.ts": 3859, - "packages/core/tests/switch.test.ts": 4486, - "packages/core/tests/syntax-catalog.test.ts": 4543, - "packages/core/tests/syntax-component.test.ts": 5165, - "packages/core/tests/syntax-loaded-copy.test.ts": 3999, - "packages/core/tests/temp-dir.test.ts": 6398, - "packages/core/tests/temp-file-compiler.test.ts": 2199, - "packages/core/tests/text-interpolation.test.ts": 3718, - "packages/core/tests/try-content.test.ts": 3770, - "packages/core/tests/unused-in-diff.test.ts": 4055, - "packages/core/tests/validate.test.ts": 3855, - "packages/core/tests/validation-integration.test.ts": 3878, - "packages/core/tests/workflow-component-bundle.test.ts": 4177, - "packages/durable-streams/tests/context.test.ts": 1758, - "packages/durable-streams/tests/deterministic-id.test.ts": 1837, - "packages/durable-streams/tests/divergence-api.test.ts": 1795, - "packages/durable-streams/tests/divergence.test.ts": 1842, - "packages/durable-streams/tests/durable-each.test.ts": 1825, - "packages/durable-streams/tests/durable-position.test.ts": 1826, - "packages/durable-streams/tests/durable-run.test.ts": 1865, - "packages/durable-streams/tests/ephemeral.test.ts": 1833, - "packages/durable-streams/tests/fail-stop.test.ts": 1867, - "packages/durable-streams/tests/guard-stream.test.ts": 1921, - "packages/durable-streams/tests/live-coordinator.test.ts": 1889, - "packages/durable-streams/tests/parse.test.ts": 1805, - "packages/durable-streams/tests/replay-guard.test.ts": 1904, - "packages/durable-streams/tests/replay-index.test.ts": 1648, - "packages/durable-streams/tests/retained.test.ts": 1659, - "packages/durable-streams/tests/serialize-event.test.ts": 1795, - "packages/durable-streams/tests/smoke.test.ts": 1755, - "packages/durable-streams/tests/structured-concurrency.test.ts": 1917, - "packages/durable-streams/tests/terminal-boundary.test.ts": 1849, - "packages/durable-streams/tests/types.test.ts": 1474, - "packages/git/tests/ambient-authentication.test.ts": 26920, - "packages/git/tests/api-entrypoint.test.ts": 5024, - "packages/git/tests/credential-helper.test.ts": 6150, - "packages/git/tests/git-add-crash.test.ts": 7101, - "packages/git/tests/git-add-durability.test.ts": 26659, - "packages/git/tests/git-add.test.ts": 36546, - "packages/git/tests/git-commit-crash.test.ts": 7525, - "packages/git/tests/git-commit-durability.test.ts": 35279, - "packages/git/tests/git-commit.test.ts": 41445, - "packages/git/tests/git-host-effect.test.ts": 8853, - "packages/git/tests/git-push-crash.test.ts": 10168, - "packages/git/tests/git-push-durability.test.ts": 44331, - "packages/git/tests/git-push.test.ts": 52468, - "packages/git/tests/git-switch-crash.test.ts": 7212, - "packages/git/tests/git-switch-durability.test.ts": 22671, - "packages/git/tests/git-switch.test.ts": 23633, - "packages/git/tests/git.test.ts": 4425, - "packages/git/tests/github-activation.test.ts": 5638, - "packages/git/tests/github-issues.test.ts": 4456, - "packages/git/tests/github-pull-requests.test.ts": 4028, - "packages/git/tests/github-workflow-activation.test.ts": 6072, - "packages/git/tests/issue-markdown.test.ts": 11597, - "packages/git/tests/issue-records.test.ts": 4345, - "packages/git/tests/materialization.test.ts": 5100, - "packages/git/tests/module-partition.test.ts": 5568, - "packages/git/tests/plugin.test.ts": 4922, - "packages/git/tests/provider-neutrality.test.ts": 3530, - "packages/git/tests/public-entrypoint.test.ts": 9609, - "packages/git/tests/pull-request-crash.test.ts": 8456, - "packages/git/tests/pull-request-durability.test.ts": 42879, - "packages/git/tests/pull-request-read.test.ts": 8248, - "packages/git/tests/pull-request-records.test.ts": 4297, - "packages/git/tests/pull-request.test.ts": 76370, - "packages/git/tests/repository-components.test.ts": 12989, - "packages/git/tests/repository-control-plane.test.ts": 11769, - "packages/git/tests/repository-materialization.test.ts": 8521, - "packages/git/tests/repository-replay.test.ts": 16218, - "packages/git/tests/repository-storage.test.ts": 19972, - "packages/git/tests/run-composition-ambient.test.ts": 13475, - "packages/git/tests/run-composition-lazy.test.ts": 5858, - "packages/git/tests/run-composition-managed.test.ts": 15234, - "packages/git/tests/run-composition-remote.test.ts": 15401, - "packages/git/tests/selection-authentication.test.ts": 1789, - "packages/git/tests/worktree-replay.test.ts": 6885, - "packages/runtime/tests/agent-session-coordinator.test.ts": 2147, - "packages/runtime/tests/duration.test.ts": 1526, - "packages/runtime/tests/executable-observer.test.ts": 1867, - "packages/runtime/tests/fetch.test.ts": 2341, - "packages/runtime/tests/fs.test.ts": 1744, - "packages/runtime/tests/host-files.test.ts": 2608, - "packages/runtime/tests/native-launcher.test.ts": 3932, - "packages/runtime/tests/process.test.ts": 1698, - "packages/runtime/tests/service.test.ts": 1878, - "packages/test-agent/tests/acp-server.test.ts": 1670, - "packages/test-agent/tests/behavior-engine.test.ts": 4244, - "packages/test-agent/tests/bridge.test.ts": 3685, - "packages/test-agent/tests/command-lazy.test.ts": 7878, - "packages/test-agent/tests/components.test.ts": 61379, - "packages/test-agent/tests/controller.test.ts": 2867, - "packages/test-agent/tests/cross-package-resolution.test.ts": 6126, - "packages/test-agent/tests/native-launch.test.ts": 30721, - "packages/test-agent/tests/net.test.ts": 1612, - "packages/test-agent/tests/profile.test.ts": 3973, - "packages/test-agent/tests/protocol.test.ts": 2381, - "packages/test-agent/tests/provider.test.ts": 4345, - "packages/test-agent/tests/public-api.test.ts": 5052, - "packages/test-agent/tests/route-slot.test.ts": 1545, - "packages/test-agent/tests/smoke.test.ts": 21015, - "packages/test-agent/tests/template.test.ts": 3946, - "packages/test-agent/tests/worker-lifecycle.test.ts": 13274, - "packages/test-support/tests/journal.test.ts": 1790, - "packages/testing/tests/agent-composition.test.ts": 4374, - "packages/testing/tests/assert-throws.test.ts": 5586, - "packages/testing/tests/assert.test.ts": 1535, - "packages/testing/tests/assertions.test.ts": 6102, - "packages/testing/tests/boundary-contract.test.ts": 4997, - "packages/testing/tests/cli.test.ts": 6740, - "packages/testing/tests/execution-harness.test.ts": 8986, - "packages/testing/tests/replay.test.ts": 5551, - "packages/testing/tests/smoke.test.ts": 5689, - "packages/testing/tests/test-component.test.ts": 6283, - "packages/testing/tests/testing-activation.test.ts": 5898, - "packages/testing/tests/testing-boundary.test.ts": 4710, - "packages/testing/tests/testing-mode.test.ts": 6244, - "packages/testing/tests/use-testing.test.ts": 5237, - "packages/web/tests/assets.test.ts": 1596, - "packages/web/tests/client-logic.test.ts": 1641, - "packages/web/tests/compile.test.ts": 4685, - "packages/web/tests/component.test.ts": 5161, - "packages/web/tests/declaration.test.ts": 4462, - "packages/web/tests/document.test.ts": 4704, - "packages/web/tests/elicitation.test.ts": 4446, - "packages/web/tests/live-form.test.ts": 4335, - "packages/web/tests/markdown.test.ts": 1739, - "packages/web/tests/opener.test.ts": 4387, - "packages/web/tests/page.test.ts": 1617, - "packages/web/tests/responder.test.ts": 4673, - "packages/web/tests/server-lifecycle.test.ts": 4609, - "packages/web/tests/server.test.ts": 4878, - "packages/workflow/tests/generated-agent-component.test.ts": 6951, - "packages/workflow/tests/generated-observations.test.ts": 4117, - "packages/workflow/tests/public-entrypoint.test.ts": 5937, - "packages/workflow/tests/retained-run.test.ts": 5062, - "packages/workflow/tests/service-denial.test.ts": 1840, - "packages/workflow/tests/workflow-agent-checkpoints.test.ts": 6196, - "packages/workflow/tests/workflow-agent-sessions.test.ts": 5043, - "packages/workflow/tests/workflow-bundle.test.ts": 4453, - "packages/workflow/tests/workflow-checkpoint.test.ts": 6455, - "packages/workflow/tests/workflow-definition.test.ts": 4303, - "packages/workflow/tests/workflow-export.test.ts": 8192, - "packages/workflow/tests/workflow-fork-source.test.ts": 5083, - "packages/workflow/tests/workflow-fork.test.ts": 4149, - "packages/workflow/tests/workflow-lifecycle-authority.test.ts": 6054, - "packages/workflow/tests/workflow-lifecycle-control.test.ts": 6620, - "packages/workflow/tests/workflow-lifecycle-inspection.test.ts": 11366, - "packages/workflow/tests/workflow-run-journal.test.ts": 7268, - "packages/workflow/tests/workflow-run-storage.test.ts": 8669, - "packages/workflow/tests/workflow-run.test.ts": 5002, - "packages/workflow/tests/workflow-suspension-answer.test.ts": 5957, - "packages/workflow/tests/workflow-suspension.test.ts": 5651, - "packages/workflow/tests/workspace-crash-recovery.test.ts": 7782, - "packages/workflow/tests/workspace-effect-loaded-copy.test.ts": 5088, - "packages/workflow/tests/workspace-effect-transaction.test.ts": 6058, - "packages/workflow/tests/workspace-effect.test.ts": 5885, - "packages/workflow/tests/workspace-files.test.ts": 12944, - "packages/workflow/tests/workspace-root-restoration.test.ts": 6768, - "packages/workflow/tests/workspace-root.test.ts": 5232, - "packages/workflow/tests/workspace-transaction.test.ts": 5253, - "packages/workflow/tests/xmd-artifact.test.ts": 7086, - "scripts/tests/acpx-vendor.test.ts": 2133, - "scripts/tests/adapter-distribution.test.ts": 4533, - "scripts/tests/adapter-npm-package.test.ts": 29375, - "scripts/tests/adapter-vendor.test.ts": 4164, - "scripts/tests/bootstrap-npm-package.test.ts": 11332, - "scripts/tests/build-npm.test.ts": 40632, - "scripts/tests/build-web-client.test.ts": 30546, - "scripts/tests/bump-version.test.ts": 1665, - "scripts/tests/changed-paths.test.ts": 1507, - "scripts/tests/ci-workflow.test.ts": 4789, - "scripts/tests/cli-npm-bin.test.ts": 254860, - "scripts/tests/cloudflare-dofs-vendor.test.ts": 4318, - "scripts/tests/consumer-cycle.test.ts": 1629, - "scripts/tests/documentation-validation.test.ts": 6250, - "scripts/tests/filesystem-contract-workflow.test.ts": 1587, - "scripts/tests/frozen-entry.test.ts": 8636, - "scripts/tests/jsr-consumer-documentation.test.ts": 18081, - "scripts/tests/main-green.test.ts": 1952, - "scripts/tests/main-health.test.ts": 1704, - "scripts/tests/measure-test-weights.test.ts": 1951, - "scripts/tests/no-module-scoped-registry.test.ts": 2386, - "scripts/tests/no-redundant-test-scope.test.ts": 6499, - "scripts/tests/no-sync-filesystem.test.ts": 6361, - "scripts/tests/no-yield-in-finally.test.ts": 2391, - "scripts/tests/oxlint-policy.test.ts": 12056, - "scripts/tests/packaged-document.test.ts": 1592, - "scripts/tests/prefer-effection-operation.test.ts": 7576, - "scripts/tests/prefer-effection-result.test.ts": 5674, - "scripts/tests/prepared-state.test.ts": 2741, - "scripts/tests/publish-workflow-generator.test.ts": 6106, - "scripts/tests/publish-workflow-membership.test.ts": 1724, - "scripts/tests/publishable-membership-agreement.test.ts": 4059, - "scripts/tests/readme-targets.test.ts": 18856, - "scripts/tests/release-targets.test.ts": 1661, - "scripts/tests/review-infrastructure.test.ts": 6247, - "scripts/tests/runtime-exclusions.test.ts": 2075, - "scripts/tests/runtime-tests.test.ts": 1699, - "scripts/tests/scope-bound-event-registration.test.ts": 6375, - "scripts/tests/shard-execution.test.ts": 11387, - "scripts/tests/staged-write.test.ts": 1543, - "scripts/tests/test-file-discovery.test.ts": 1781, - "scripts/tests/test-shards.test.ts": 1561, - "scripts/tests/test-weights.test.ts": 1863, - "scripts/tests/tracked.test.ts": 1526, - "scripts/tests/verify-adapter.test.ts": 7643, - "scripts/tests/verify-clean.test.ts": 1751, - "scripts/tests/verify-coordinator.test.ts": 1943, - "scripts/tests/version-lockstep.test.ts": 1743, - "scripts/tests/web-client-module.test.ts": 1573, - "scripts/tests/workspace.test.ts": 1528 + "packages/acp/tests/acceptance.test.ts": 4684, + "packages/acp/tests/acpx-checkpoint-meta.test.ts": 7911, + "packages/acp/tests/acpx-materialization.test.ts": 8409, + "packages/acp/tests/acpx-transient-env.test.ts": 1720, + "packages/acp/tests/adapter-materialization.test.ts": 74952, + "packages/acp/tests/adapter-protocol.test.ts": 57009, + "packages/acp/tests/codex-surfaces.test.ts": 1715, + "packages/acp/tests/loaded-copy.test.ts": 4391, + "packages/acp/tests/native-launch.test.ts": 5018, + "packages/acp/tests/permission-bridge.test.ts": 3855, + "packages/acp/tests/provider.test.ts": 5014, + "packages/acp/tests/serial-queue.test.ts": 1483, + "packages/acp/tests/session-key.test.ts": 3747, + "packages/acp/tests/session-route.test.ts": 3939, + "packages/acp/tests/terminal-screen.test.ts": 1635, + "packages/cli/tests/agent-adapters.test.ts": 5252, + "packages/cli/tests/agent-cli.test.ts": 37158, + "packages/cli/tests/agent-config.test.ts": 3425, + "packages/cli/tests/agent-options-cli.test.ts": 15138, + "packages/cli/tests/agent-options.test.ts": 3479, + "packages/cli/tests/agent-session-coordinator.test.ts": 4134, + "packages/cli/tests/cli-help.test.ts": 20086, + "packages/cli/tests/command.test.ts": 10167, + "packages/cli/tests/compiled-upgrade.test.ts": 10251, + "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 8468, + "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 117403, + "packages/cli/tests/document-suites/props/props-markdown.test.ts": 8020, + "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 11434, + "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 9716, + "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 14789, + "packages/cli/tests/evaluate-component.test.ts": 11221, + "packages/cli/tests/evaluate-workflow.test.ts": 5223, + "packages/cli/tests/fetch-cli.test.ts": 3667, + "packages/cli/tests/file-stream.test.ts": 1715, + "packages/cli/tests/github-zero-effect.test.ts": 6763, + "packages/cli/tests/inline-cli.test.ts": 32327, + "packages/cli/tests/launch-timeout.test.ts": 11567, + "packages/cli/tests/packaged-document.test.ts": 4625, + "packages/cli/tests/plan-args.test.ts": 1503, + "packages/cli/tests/plan-cli.test.ts": 96878, + "packages/cli/tests/plan-command-document.test.ts": 9231, + "packages/cli/tests/plan-component.test.ts": 11451, + "packages/cli/tests/plan-host-acts.test.ts": 5149, + "packages/cli/tests/plan.test.ts": 15116, + "packages/cli/tests/plugin-cli.test.ts": 39314, + "packages/cli/tests/plugin-host.test.ts": 5782, + "packages/cli/tests/plugin-modules.test.ts": 1498, + "packages/cli/tests/plugin-selection.test.ts": 1472, + "packages/cli/tests/process-retention.test.ts": 5280, + "packages/cli/tests/props-cli.test.ts": 29702, + "packages/cli/tests/props-schema.test.ts": 3892, + "packages/cli/tests/props-sources.test.ts": 2190, + "packages/cli/tests/pull-request-read-fork.test.ts": 6941, + "packages/cli/tests/repl-admission.test.ts": 1455, + "packages/cli/tests/repl-agent-execution.test.ts": 5949, + "packages/cli/tests/repl-boundaries.test.ts": 7194, + "packages/cli/tests/repl-composition.test.ts": 1972, + "packages/cli/tests/repl-execution.test.ts": 5186, + "packages/cli/tests/repl-journey.test.ts": 20413, + "packages/cli/tests/repl-model.test.ts": 5005, + "packages/cli/tests/repl-route.test.ts": 4579, + "packages/cli/tests/repl-terminal.test.ts": 2778, + "packages/cli/tests/run-composition-deno.test.ts": 7460, + "packages/cli/tests/run-composition-nested.test.ts": 20232, + "packages/cli/tests/run-composition.test.ts": 6624, + "packages/cli/tests/run-deadline.test.ts": 8368, + "packages/cli/tests/run-profile.test.ts": 4167, + "packages/cli/tests/run-timeouts.test.ts": 12915, + "packages/cli/tests/secret-detection-cli.test.ts": 18915, + "packages/cli/tests/service-document.test.ts": 5082, + "packages/cli/tests/service-host.test.ts": 4964, + "packages/cli/tests/session-launch-cli.test.ts": 10573, + "packages/cli/tests/stdin-cli.test.ts": 36225, + "packages/cli/tests/stdout-delivery.test.ts": 1502, + "packages/cli/tests/syntax-cli.test.ts": 35141, + "packages/cli/tests/targets-cli.test.ts": 28018, + "packages/cli/tests/test-root-profile.test.ts": 7134, + "packages/cli/tests/test-target.test.ts": 36226, + "packages/cli/tests/testing-activation.test.ts": 4268, + "packages/cli/tests/testing-execution-host.test.ts": 54817, + "packages/cli/tests/upgrade-cli.test.ts": 20574, + "packages/cli/tests/upgrade-command-document.test.ts": 6799, + "packages/cli/tests/upgrade-output.test.ts": 4280, + "packages/cli/tests/value-root.test.ts": 17005, + "packages/cli/tests/verbose-component.test.ts": 4757, + "packages/cli/tests/workflow-agent-sessions.test.ts": 18828, + "packages/cli/tests/workflow-agent.test.ts": 15123, + "packages/cli/tests/workflow-cli.test.ts": 106565, + "packages/cli/tests/workflow-crash.test.ts": 79447, + "packages/cli/tests/workflow-declaration.test.ts": 4306, + "packages/cli/tests/workflow-export-publication.test.ts": 17054, + "packages/cli/tests/workflow-fetch.test.ts": 7904, + "packages/cli/tests/workflow-fork.test.ts": 82695, + "packages/cli/tests/workflow-host.test.ts": 14229, + "packages/cli/tests/workflow-inspection.test.ts": 113974, + "packages/cli/tests/workflow-installation.test.ts": 5777, + "packages/cli/tests/workflow-lifecycle-control.test.ts": 20138, + "packages/cli/tests/workflow-retention.test.ts": 22437, + "packages/cli/tests/workflow-suspension.test.ts": 7422, + "packages/code-review-agent/tests/doctor.test.ts": 1455, + "packages/code-review-agent/tests/parse-diagnostics.test.ts": 1515, + "packages/code-review-agent/tests/parse-diff.test.ts": 1481, + "packages/code-review-agent/tests/parse-doctor.test.ts": 3841, + "packages/code-review-agent/tests/parse-oxlint.test.ts": 1483, + "packages/code-review-agent/tests/policy.test.ts": 1479, + "packages/core/tests/agent-api.test.ts": 1509, + "packages/core/tests/agent-components.test.ts": 4338, + "packages/core/tests/agent-function-components.test.ts": 6009, + "packages/core/tests/agent-session-launch.test.ts": 6290, + "packages/core/tests/agent-session-placement.test.ts": 1543, + "packages/core/tests/agent-session-use-loaded-copy.test.ts": 1600, + "packages/core/tests/agent-session-use.test.ts": 1508, + "packages/core/tests/agent-stack.test.ts": 3990, + "packages/core/tests/all.test.ts": 5105, + "packages/core/tests/answer-identity.test.ts": 3388, + "packages/core/tests/answers-component.test.ts": 4528, + "packages/core/tests/answers-expansion-recursion.test.ts": 3638, + "packages/core/tests/capture-errors.test.ts": 3844, + "packages/core/tests/capture-props.test.ts": 3638, + "packages/core/tests/cli-journal.test.ts": 11722, + "packages/core/tests/code-block-component.test.ts": 5039, + "packages/core/tests/compiler-boundary.test.ts": 3672, + "packages/core/tests/component-api.test.ts": 3414, + "packages/core/tests/component-registration.test.ts": 4422, + "packages/core/tests/component-returns.test.ts": 4804, + "packages/core/tests/component-schema-conformance.test.ts": 3785, + "packages/core/tests/config-api.test.ts": 3900, + "packages/core/tests/construct-error-observation.test.ts": 3894, + "packages/core/tests/contextual-cwd.test.ts": 4939, + "packages/core/tests/daemon-integration.test.ts": 4426, + "packages/core/tests/daemon.test.ts": 3403, + "packages/core/tests/declared-markdown-component.test.ts": 5142, + "packages/core/tests/document-output-api.test.ts": 1491, + "packages/core/tests/document-target-execution.test.ts": 6336, + "packages/core/tests/document-targets.test.ts": 3780, + "packages/core/tests/document-validation.test.ts": 5139, + "packages/core/tests/documentation-index.test.ts": 3501, + "packages/core/tests/each.test.ts": 3843, + "packages/core/tests/elicit-component.test.ts": 4286, + "packages/core/tests/ephemeral-service.test.ts": 4213, + "packages/core/tests/eval-bindings.test.ts": 3756, + "packages/core/tests/eval-context.test.ts": 2121, + "packages/core/tests/eval-durable.test.ts": 3778, + "packages/core/tests/eval-error-mode.test.ts": 3994, + "packages/core/tests/eval-interpolate.test.ts": 1438, + "packages/core/tests/eval-middleware.test.ts": 3431, + "packages/core/tests/eval-persist.test.ts": 3915, + "packages/core/tests/eval-return.test.ts": 3779, + "packages/core/tests/eval-scope.test.ts": 3676, + "packages/core/tests/eval-timeout.test.ts": 3738, + "packages/core/tests/eval-transform.test.ts": 3389, + "packages/core/tests/evaluate-component.test.ts": 5549, + "packages/core/tests/evaluate-loaded-copy.test.ts": 3805, + "packages/core/tests/evaluate-provider-lifetime.test.ts": 3871, + "packages/core/tests/evaluation-profile.test.ts": 3571, + "packages/core/tests/exec-timeout.test.ts": 3805, + "packages/core/tests/execute.test.ts": 4339, + "packages/core/tests/execution-protocol.test.ts": 4963, + "packages/core/tests/expand.test.ts": 4453, + "packages/core/tests/expansion-identity.test.ts": 4320, + "packages/core/tests/expansion-metadata.test.ts": 3661, + "packages/core/tests/expression-props.test.ts": 4243, + "packages/core/tests/fail-component.test.ts": 3961, + "packages/core/tests/failure-printing.test.ts": 3898, + "packages/core/tests/fatal-cause.test.ts": 3582, + "packages/core/tests/fetch-component.test.ts": 5041, + "packages/core/tests/file-component.test.ts": 5612, + "packages/core/tests/file-delete-component.test.ts": 3864, + "packages/core/tests/files-fatal.test.ts": 4678, + "packages/core/tests/foreground-exec.test.ts": 4690, + "packages/core/tests/frontmatter.test.ts": 3380, + "packages/core/tests/function-components.test.ts": 4235, + "packages/core/tests/generated-composition.test.ts": 4265, + "packages/core/tests/generated-xmd.test.ts": 5573, + "packages/core/tests/glob-api.test.ts": 1826, + "packages/core/tests/glob-component.test.ts": 4412, + "packages/core/tests/guarded-journal.test.ts": 3731, + "packages/core/tests/has-content.test.ts": 3612, + "packages/core/tests/heal.test.ts": 3595, + "packages/core/tests/if.test.ts": 4223, + "packages/core/tests/inline-root.test.ts": 3787, + "packages/core/tests/invocation-failures.test.ts": 3490, + "packages/core/tests/invocation-identity.test.ts": 4410, + "packages/core/tests/invocation-scope.test.ts": 3794, + "packages/core/tests/journal-source-position.test.ts": 3964, + "packages/core/tests/json-component.test.ts": 3942, + "packages/core/tests/json.test.ts": 3363, + "packages/core/tests/let.test.ts": 3748, + "packages/core/tests/loaded-copy-files.test.ts": 3734, + "packages/core/tests/loop.test.ts": 4549, + "packages/core/tests/named-slots.test.ts": 4465, + "packages/core/tests/output-error-mode.test.ts": 4632, + "packages/core/tests/output-normalize.test.ts": 1481, + "packages/core/tests/output-terminal.test.ts": 1626, + "packages/core/tests/parse-components.test.ts": 3795, + "packages/core/tests/plan-response.test.ts": 1517, + "packages/core/tests/plugin-api.test.ts": 3364, + "packages/core/tests/plugin.test.ts": 3569, + "packages/core/tests/props-binding.test.ts": 3873, + "packages/core/tests/protected-content.test.ts": 3384, + "packages/core/tests/registered-printed-errors.test.ts": 3579, + "packages/core/tests/replay-stream.test.ts": 1449, + "packages/core/tests/retain.test.ts": 3733, + "packages/core/tests/root-composition.test.ts": 3875, + "packages/core/tests/root-props.test.ts": 3829, + "packages/core/tests/root-provider.test.ts": 3905, + "packages/core/tests/sample-component.test.ts": 4519, + "packages/core/tests/scanner.test.ts": 3452, + "packages/core/tests/scope-local.test.ts": 1462, + "packages/core/tests/secret-detection.test.ts": 4509, + "packages/core/tests/secret-files.test.ts": 1949, + "packages/core/tests/secret-rules.test.ts": 1953, + "packages/core/tests/secret-scanner-baseline.test.ts": 1660, + "packages/core/tests/secret-scanner.test.ts": 1820, + "packages/core/tests/source-position.test.ts": 3663, + "packages/core/tests/state-ownership.test.ts": 3710, + "packages/core/tests/streaming-emission.test.ts": 3650, + "packages/core/tests/switch.test.ts": 4189, + "packages/core/tests/syntax-catalog.test.ts": 4327, + "packages/core/tests/syntax-component.test.ts": 4893, + "packages/core/tests/syntax-loaded-copy.test.ts": 3854, + "packages/core/tests/temp-dir.test.ts": 6169, + "packages/core/tests/temp-file-compiler.test.ts": 2138, + "packages/core/tests/text-interpolation.test.ts": 3549, + "packages/core/tests/try-content.test.ts": 3588, + "packages/core/tests/unused-in-diff.test.ts": 3836, + "packages/core/tests/validate.test.ts": 3611, + "packages/core/tests/validation-integration.test.ts": 3680, + "packages/core/tests/workflow-component-bundle.test.ts": 3963, + "packages/durable-streams/tests/context.test.ts": 1694, + "packages/durable-streams/tests/deterministic-id.test.ts": 1755, + "packages/durable-streams/tests/divergence-api.test.ts": 1710, + "packages/durable-streams/tests/divergence.test.ts": 1770, + "packages/durable-streams/tests/durable-each.test.ts": 1766, + "packages/durable-streams/tests/durable-position.test.ts": 1710, + "packages/durable-streams/tests/durable-run.test.ts": 1765, + "packages/durable-streams/tests/ephemeral.test.ts": 1734, + "packages/durable-streams/tests/fail-stop.test.ts": 1783, + "packages/durable-streams/tests/guard-stream.test.ts": 1804, + "packages/durable-streams/tests/live-coordinator.test.ts": 1771, + "packages/durable-streams/tests/parse.test.ts": 1753, + "packages/durable-streams/tests/replay-guard.test.ts": 1827, + "packages/durable-streams/tests/replay-index.test.ts": 1568, + "packages/durable-streams/tests/retained.test.ts": 1582, + "packages/durable-streams/tests/serialize-event.test.ts": 1744, + "packages/durable-streams/tests/smoke.test.ts": 1697, + "packages/durable-streams/tests/structured-concurrency.test.ts": 1853, + "packages/durable-streams/tests/terminal-boundary.test.ts": 1790, + "packages/durable-streams/tests/types.test.ts": 1428, + "packages/git/tests/ambient-authentication.test.ts": 26144, + "packages/git/tests/api-entrypoint.test.ts": 4875, + "packages/git/tests/credential-helper.test.ts": 5844, + "packages/git/tests/git-add-crash.test.ts": 6757, + "packages/git/tests/git-add-durability.test.ts": 25557, + "packages/git/tests/git-add.test.ts": 34045, + "packages/git/tests/git-commit-crash.test.ts": 7015, + "packages/git/tests/git-commit-durability.test.ts": 32762, + "packages/git/tests/git-commit.test.ts": 39336, + "packages/git/tests/git-host-effect.test.ts": 8358, + "packages/git/tests/git-push-crash.test.ts": 9603, + "packages/git/tests/git-push-durability.test.ts": 42141, + "packages/git/tests/git-push.test.ts": 50834, + "packages/git/tests/git-switch-crash.test.ts": 6753, + "packages/git/tests/git-switch-durability.test.ts": 20958, + "packages/git/tests/git-switch.test.ts": 22167, + "packages/git/tests/git.test.ts": 4145, + "packages/git/tests/github-activation.test.ts": 5322, + "packages/git/tests/github-issues.test.ts": 4160, + "packages/git/tests/github-pull-requests.test.ts": 3825, + "packages/git/tests/github-workflow-activation.test.ts": 5635, + "packages/git/tests/issue-markdown.test.ts": 10625, + "packages/git/tests/issue-records.test.ts": 4059, + "packages/git/tests/materialization.test.ts": 4785, + "packages/git/tests/module-partition.test.ts": 5028, + "packages/git/tests/plugin.test.ts": 4481, + "packages/git/tests/provider-neutrality.test.ts": 3228, + "packages/git/tests/public-entrypoint.test.ts": 9398, + "packages/git/tests/pull-request-crash.test.ts": 7962, + "packages/git/tests/pull-request-durability.test.ts": 40566, + "packages/git/tests/pull-request-read.test.ts": 7673, + "packages/git/tests/pull-request-records.test.ts": 4103, + "packages/git/tests/pull-request.test.ts": 72474, + "packages/git/tests/repository-components.test.ts": 12119, + "packages/git/tests/repository-control-plane.test.ts": 10929, + "packages/git/tests/repository-materialization.test.ts": 7982, + "packages/git/tests/repository-replay.test.ts": 15237, + "packages/git/tests/repository-storage.test.ts": 18670, + "packages/git/tests/run-composition-ambient.test.ts": 12652, + "packages/git/tests/run-composition-lazy.test.ts": 5383, + "packages/git/tests/run-composition-managed.test.ts": 14603, + "packages/git/tests/run-composition-remote.test.ts": 14980, + "packages/git/tests/selection-authentication.test.ts": 1760, + "packages/git/tests/worktree-replay.test.ts": 6498, + "packages/runtime/tests/agent-session-coordinator.test.ts": 2058, + "packages/runtime/tests/duration.test.ts": 1473, + "packages/runtime/tests/executable-observer.test.ts": 1811, + "packages/runtime/tests/fetch.test.ts": 2258, + "packages/runtime/tests/fs.test.ts": 1664, + "packages/runtime/tests/host-files.test.ts": 2479, + "packages/runtime/tests/native-launcher.test.ts": 3869, + "packages/runtime/tests/process.test.ts": 1638, + "packages/runtime/tests/service.test.ts": 1760, + "packages/test-agent/tests/acp-server.test.ts": 1593, + "packages/test-agent/tests/behavior-engine.test.ts": 4047, + "packages/test-agent/tests/bridge.test.ts": 3559, + "packages/test-agent/tests/command-lazy.test.ts": 7661, + "packages/test-agent/tests/components.test.ts": 60503, + "packages/test-agent/tests/controller.test.ts": 2761, + "packages/test-agent/tests/cross-package-resolution.test.ts": 5690, + "packages/test-agent/tests/native-launch.test.ts": 30027, + "packages/test-agent/tests/net.test.ts": 1573, + "packages/test-agent/tests/profile.test.ts": 3767, + "packages/test-agent/tests/protocol.test.ts": 2342, + "packages/test-agent/tests/provider.test.ts": 4074, + "packages/test-agent/tests/public-api.test.ts": 4740, + "packages/test-agent/tests/route-slot.test.ts": 1491, + "packages/test-agent/tests/smoke.test.ts": 20597, + "packages/test-agent/tests/template.test.ts": 3762, + "packages/test-agent/tests/worker-lifecycle.test.ts": 13052, + "packages/test-support/tests/journal.test.ts": 1736, + "packages/testing/tests/agent-composition.test.ts": 4095, + "packages/testing/tests/assert-throws.test.ts": 5231, + "packages/testing/tests/assert.test.ts": 1503, + "packages/testing/tests/assertions.test.ts": 5740, + "packages/testing/tests/boundary-contract.test.ts": 4733, + "packages/testing/tests/cli.test.ts": 6584, + "packages/testing/tests/execution-harness.test.ts": 8311, + "packages/testing/tests/replay.test.ts": 5216, + "packages/testing/tests/smoke.test.ts": 5357, + "packages/testing/tests/test-component.test.ts": 5846, + "packages/testing/tests/testing-activation.test.ts": 5508, + "packages/testing/tests/testing-boundary.test.ts": 4445, + "packages/testing/tests/testing-mode.test.ts": 5989, + "packages/testing/tests/use-testing.test.ts": 4997, + "packages/web/tests/assets.test.ts": 1595, + "packages/web/tests/client-logic.test.ts": 1629, + "packages/web/tests/compile.test.ts": 4428, + "packages/web/tests/component.test.ts": 4918, + "packages/web/tests/declaration.test.ts": 4203, + "packages/web/tests/document.test.ts": 4460, + "packages/web/tests/elicitation.test.ts": 4241, + "packages/web/tests/live-form.test.ts": 4147, + "packages/web/tests/markdown.test.ts": 1698, + "packages/web/tests/opener.test.ts": 4134, + "packages/web/tests/page.test.ts": 1508, + "packages/web/tests/responder.test.ts": 4388, + "packages/web/tests/server-lifecycle.test.ts": 4279, + "packages/web/tests/server.test.ts": 4605, + "packages/workflow/tests/generated-agent-component.test.ts": 6383, + "packages/workflow/tests/generated-observations.test.ts": 4037, + "packages/workflow/tests/public-entrypoint.test.ts": 5694, + "packages/workflow/tests/retained-run.test.ts": 4833, + "packages/workflow/tests/service-denial.test.ts": 1776, + "packages/workflow/tests/workflow-agent-checkpoints.test.ts": 5866, + "packages/workflow/tests/workflow-agent-sessions.test.ts": 4832, + "packages/workflow/tests/workflow-bundle.test.ts": 4386, + "packages/workflow/tests/workflow-checkpoint.test.ts": 5931, + "packages/workflow/tests/workflow-definition.test.ts": 4091, + "packages/workflow/tests/workflow-export.test.ts": 7740, + "packages/workflow/tests/workflow-fork-source.test.ts": 4901, + "packages/workflow/tests/workflow-fork.test.ts": 4055, + "packages/workflow/tests/workflow-lifecycle-authority.test.ts": 5859, + "packages/workflow/tests/workflow-lifecycle-control.test.ts": 6343, + "packages/workflow/tests/workflow-lifecycle-inspection.test.ts": 10830, + "packages/workflow/tests/workflow-run-journal.test.ts": 6900, + "packages/workflow/tests/workflow-run-storage.test.ts": 7788, + "packages/workflow/tests/workflow-run.test.ts": 4676, + "packages/workflow/tests/workflow-suspension-answer.test.ts": 5603, + "packages/workflow/tests/workflow-suspension.test.ts": 5319, + "packages/workflow/tests/workspace-crash-recovery.test.ts": 7519, + "packages/workflow/tests/workspace-effect-loaded-copy.test.ts": 4912, + "packages/workflow/tests/workspace-effect-transaction.test.ts": 5569, + "packages/workflow/tests/workspace-effect.test.ts": 5532, + "packages/workflow/tests/workspace-files.test.ts": 12183, + "packages/workflow/tests/workspace-root-restoration.test.ts": 6416, + "packages/workflow/tests/workspace-root.test.ts": 4845, + "packages/workflow/tests/workspace-transaction.test.ts": 4903, + "packages/workflow/tests/xmd-artifact.test.ts": 6615, + "scripts/tests/acpx-vendor.test.ts": 2057, + "scripts/tests/adapter-distribution.test.ts": 4404, + "scripts/tests/adapter-npm-package.test.ts": 31673, + "scripts/tests/adapter-vendor.test.ts": 3903, + "scripts/tests/bootstrap-npm-package.test.ts": 10858, + "scripts/tests/build-npm.test.ts": 38544, + "scripts/tests/build-web-client.test.ts": 28858, + "scripts/tests/bump-version.test.ts": 1601, + "scripts/tests/changed-paths.test.ts": 1487, + "scripts/tests/ci-workflow.test.ts": 4643, + "scripts/tests/cli-npm-bin.test.ts": 238191, + "scripts/tests/cloudflare-dofs-vendor.test.ts": 4194, + "scripts/tests/consumer-cycle.test.ts": 1614, + "scripts/tests/documentation-validation.test.ts": 6069, + "scripts/tests/filesystem-contract-workflow.test.ts": 1491, + "scripts/tests/frozen-entry.test.ts": 7857, + "scripts/tests/jsr-consumer-documentation.test.ts": 17583, + "scripts/tests/main-green.test.ts": 1907, + "scripts/tests/main-health.test.ts": 1646, + "scripts/tests/measure-test-weights.test.ts": 1938, + "scripts/tests/no-module-scoped-registry.test.ts": 2327, + "scripts/tests/no-redundant-test-scope.test.ts": 6439, + "scripts/tests/no-sync-filesystem.test.ts": 6219, + "scripts/tests/no-yield-in-finally.test.ts": 2345, + "scripts/tests/oxlint-policy.test.ts": 12080, + "scripts/tests/packaged-document.test.ts": 1551, + "scripts/tests/prefer-effection-operation.test.ts": 7392, + "scripts/tests/prefer-effection-result.test.ts": 5561, + "scripts/tests/prepared-state.test.ts": 2608, + "scripts/tests/publish-workflow-generator.test.ts": 6078, + "scripts/tests/publish-workflow-membership.test.ts": 1650, + "scripts/tests/publishable-membership-agreement.test.ts": 3926, + "scripts/tests/readme-targets.test.ts": 18513, + "scripts/tests/release-targets.test.ts": 1601, + "scripts/tests/review-infrastructure.test.ts": 5920, + "scripts/tests/runtime-exclusions.test.ts": 1980, + "scripts/tests/runtime-tests.test.ts": 1653, + "scripts/tests/scope-bound-event-registration.test.ts": 6208, + "scripts/tests/shard-execution.test.ts": 11115, + "scripts/tests/staged-write.test.ts": 1505, + "scripts/tests/test-file-discovery.test.ts": 1751, + "scripts/tests/test-shards.test.ts": 1516, + "scripts/tests/test-weights.test.ts": 1811, + "scripts/tests/tracked.test.ts": 1508, + "scripts/tests/verify-adapter.test.ts": 7750, + "scripts/tests/verify-clean.test.ts": 1712, + "scripts/tests/verify-coordinator.test.ts": 1871, + "scripts/tests/version-lockstep.test.ts": 1661, + "scripts/tests/web-client-module.test.ts": 1517, + "scripts/tests/workspace.test.ts": 1472 }, "node": { - "packages/acp/tests/acceptance.test.ts": 2110, - "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4869, - "packages/acp/tests/acpx-materialization.test.ts": 7089, - "packages/acp/tests/acpx-transient-env.test.ts": 828, - "packages/acp/tests/adapter-materialization.test.ts": 73589, - "packages/acp/tests/adapter-protocol.test.ts": 54177, - "packages/acp/tests/codex-surfaces.test.ts": 657, - "packages/acp/tests/native-launch.test.ts": 2222, - "packages/acp/tests/permission-bridge.test.ts": 1220, - "packages/acp/tests/provider.test.ts": 2161, + "packages/acp/tests/acceptance.test.ts": 2089, + "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4861, + "packages/acp/tests/acpx-materialization.test.ts": 7096, + "packages/acp/tests/acpx-transient-env.test.ts": 804, + "packages/acp/tests/adapter-materialization.test.ts": 73196, + "packages/acp/tests/adapter-protocol.test.ts": 52860, + "packages/acp/tests/codex-surfaces.test.ts": 673, + "packages/acp/tests/native-launch.test.ts": 2173, + "packages/acp/tests/permission-bridge.test.ts": 1225, + "packages/acp/tests/provider.test.ts": 2093, "packages/acp/tests/serial-queue.test.ts": 493, - "packages/acp/tests/session-key.test.ts": 549, - "packages/acp/tests/terminal-screen.test.ts": 611, - "packages/cli/tests/agent-adapters.test.ts": 2453, - "packages/cli/tests/agent-cli.test.ts": 43285, - "packages/cli/tests/agent-config.test.ts": 486, - "packages/cli/tests/agent-options-cli.test.ts": 16242, - "packages/cli/tests/agent-options.test.ts": 481, - "packages/cli/tests/agent-session-coordinator.test.ts": 1353, - "packages/cli/tests/cli-help.test.ts": 40747, - "packages/cli/tests/command.test.ts": 14935, - "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 4260, - "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 171781, - "packages/cli/tests/document-suites/props/props-markdown.test.ts": 3738, - "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 10122, - "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 6100, - "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 18177, - "packages/cli/tests/evaluate-component.test.ts": 15487, - "packages/cli/tests/fetch-cli.test.ts": 4949, - "packages/cli/tests/file-stream.test.ts": 533, - "packages/cli/tests/inline-cli.test.ts": 68083, - "packages/cli/tests/launch-timeout.test.ts": 20547, - "packages/cli/tests/packaged-document.test.ts": 1584, - "packages/cli/tests/plan-args.test.ts": 506, - "packages/cli/tests/plan-cli.test.ts": 119226, - "packages/cli/tests/plan-command-document.test.ts": 8084, - "packages/cli/tests/plan-component.test.ts": 11022, - "packages/cli/tests/plan-host-acts.test.ts": 2346, - "packages/cli/tests/plan.test.ts": 15829, - "packages/cli/tests/plugin-cli.test.ts": 82240, - "packages/cli/tests/plugin-host.test.ts": 3281, - "packages/cli/tests/plugin-modules.test.ts": 505, - "packages/cli/tests/plugin-selection.test.ts": 512, - "packages/cli/tests/process-retention.test.ts": 4997, - "packages/cli/tests/props-cli.test.ts": 63707, - "packages/cli/tests/props-schema.test.ts": 1350, - "packages/cli/tests/props-sources.test.ts": 683, - "packages/cli/tests/repl-boundaries.test.ts": 2529, - "packages/cli/tests/repl-composition.test.ts": 751, - "packages/cli/tests/repl-execution.test.ts": 2941, - "packages/cli/tests/repl-journey.test.ts": 11042, - "packages/cli/tests/repl-model.test.ts": 2778, - "packages/cli/tests/repl-route.test.ts": 2307, - "packages/cli/tests/repl-terminal.test.ts": 1256, - "packages/cli/tests/run-composition.test.ts": 4152, - "packages/cli/tests/run-deadline.test.ts": 3685, - "packages/cli/tests/run-profile.test.ts": 1253, - "packages/cli/tests/run-timeouts.test.ts": 20254, - "packages/cli/tests/secret-detection-cli.test.ts": 37931, - "packages/cli/tests/service-document.test.ts": 2565, - "packages/cli/tests/service-host.test.ts": 2347, - "packages/cli/tests/session-launch-cli.test.ts": 11044, - "packages/cli/tests/stdin-cli.test.ts": 63445, - "packages/cli/tests/stdout-delivery.test.ts": 493, - "packages/cli/tests/syntax-cli.test.ts": 62013, - "packages/cli/tests/targets-cli.test.ts": 51063, - "packages/cli/tests/test-root-profile.test.ts": 7125, - "packages/cli/tests/test-target.test.ts": 73776, - "packages/cli/tests/testing-activation.test.ts": 1497, - "packages/cli/tests/testing-execution-host.test.ts": 91050, - "packages/cli/tests/upgrade-cli.test.ts": 46673, - "packages/cli/tests/upgrade-command-document.test.ts": 5530, - "packages/cli/tests/upgrade-output.test.ts": 1845, - "packages/cli/tests/value-root.test.ts": 31600, - "packages/cli/tests/verbose-component.test.ts": 1940, - "packages/cli/tests/workflow-declaration.test.ts": 1449, - "packages/cli/tests/workflow-host.test.ts": 10859, - "packages/code-review-agent/tests/doctor.test.ts": 478, - "packages/code-review-agent/tests/parse-diagnostics.test.ts": 522, - "packages/code-review-agent/tests/parse-diff.test.ts": 517, - "packages/code-review-agent/tests/parse-doctor.test.ts": 1167, - "packages/code-review-agent/tests/parse-oxlint.test.ts": 482, - "packages/code-review-agent/tests/policy.test.ts": 484, - "packages/core/tests/agent-api.test.ts": 493, - "packages/core/tests/agent-components.test.ts": 2035, - "packages/core/tests/agent-function-components.test.ts": 4189, - "packages/core/tests/agent-session-launch.test.ts": 4584, - "packages/core/tests/agent-session-placement.test.ts": 495, - "packages/core/tests/agent-session-use.test.ts": 483, - "packages/core/tests/agent-stack.test.ts": 1530, - "packages/core/tests/all.test.ts": 3001, - "packages/core/tests/answer-identity.test.ts": 578, - "packages/core/tests/answers-component.test.ts": 2373, - "packages/core/tests/answers-expansion-recursion.test.ts": 970, - "packages/core/tests/capture-errors.test.ts": 1288, - "packages/core/tests/capture-props.test.ts": 993, - "packages/core/tests/cli-journal.test.ts": 21990, - "packages/core/tests/code-block-component.test.ts": 3154, - "packages/core/tests/compiler-boundary.test.ts": 1162, - "packages/core/tests/component-api.test.ts": 557, - "packages/core/tests/component-registration.test.ts": 2067, - "packages/core/tests/component-returns.test.ts": 2830, - "packages/core/tests/component-schema-conformance.test.ts": 1092, - "packages/core/tests/config-api.test.ts": 1242, - "packages/core/tests/construct-error-observation.test.ts": 1361, - "packages/core/tests/contextual-cwd.test.ts": 2435, - "packages/core/tests/daemon-integration.test.ts": 1891, - "packages/core/tests/daemon.test.ts": 578, - "packages/core/tests/declared-markdown-component.test.ts": 3168, - "packages/core/tests/document-output-api.test.ts": 495, - "packages/core/tests/document-target-execution.test.ts": 4535, - "packages/core/tests/document-targets.test.ts": 1191, - "packages/core/tests/document-validation.test.ts": 3100, - "packages/core/tests/documentation-index.test.ts": 819, - "packages/core/tests/each.test.ts": 1263, - "packages/core/tests/elicit-component.test.ts": 1996, - "packages/core/tests/ephemeral-service.test.ts": 1791, - "packages/core/tests/eval-bindings.test.ts": 1271, - "packages/core/tests/eval-context.test.ts": 572, - "packages/core/tests/eval-durable.test.ts": 1276, - "packages/core/tests/eval-error-mode.test.ts": 1635, - "packages/core/tests/eval-interpolate.test.ts": 502, - "packages/core/tests/eval-middleware.test.ts": 595, - "packages/core/tests/eval-persist.test.ts": 1380, - "packages/core/tests/eval-return.test.ts": 1313, - "packages/core/tests/eval-scope.test.ts": 1207, - "packages/core/tests/eval-timeout.test.ts": 1278, - "packages/core/tests/eval-transform.test.ts": 573, - "packages/core/tests/evaluate-component.test.ts": 3608, - "packages/core/tests/evaluate-loaded-copy.test.ts": 1339, - "packages/core/tests/evaluate-provider-lifetime.test.ts": 1438, - "packages/core/tests/evaluation-profile.test.ts": 861, - "packages/core/tests/exec-timeout.test.ts": 1308, - "packages/core/tests/execute.test.ts": 1969, - "packages/core/tests/execution-protocol.test.ts": 2822, - "packages/core/tests/expand.test.ts": 1968, - "packages/core/tests/expansion-identity.test.ts": 2011, - "packages/core/tests/expansion-metadata.test.ts": 1010, - "packages/core/tests/expression-props.test.ts": 1863, - "packages/core/tests/fail-component.test.ts": 1631, - "packages/core/tests/failure-printing.test.ts": 1468, - "packages/core/tests/fatal-cause.test.ts": 890, - "packages/core/tests/fetch-component.test.ts": 2900, - "packages/core/tests/file-component.test.ts": 3333, - "packages/core/tests/file-delete-component.test.ts": 1461, - "packages/core/tests/files-fatal.test.ts": 2432, - "packages/core/tests/foreground-exec.test.ts": 2309, - "packages/core/tests/frontmatter.test.ts": 546, - "packages/core/tests/function-components.test.ts": 1982, - "packages/core/tests/generated-composition.test.ts": 2014, - "packages/core/tests/generated-xmd.test.ts": 3536, - "packages/core/tests/glob-api.test.ts": 625, - "packages/core/tests/glob-component.test.ts": 2058, - "packages/core/tests/guarded-journal.test.ts": 1304, - "packages/core/tests/has-content.test.ts": 924, - "packages/core/tests/heal.test.ts": 939, - "packages/core/tests/if.test.ts": 1837, - "packages/core/tests/inline-root.test.ts": 1327, - "packages/core/tests/invocation-failures.test.ts": 597, - "packages/core/tests/invocation-identity.test.ts": 1761, - "packages/core/tests/invocation-scope.test.ts": 1193, - "packages/core/tests/journal-source-position.test.ts": 1461, - "packages/core/tests/json-component.test.ts": 1541, - "packages/core/tests/json.test.ts": 499, - "packages/core/tests/let.test.ts": 1290, - "packages/core/tests/loop.test.ts": 2310, - "packages/core/tests/named-slots.test.ts": 2304, - "packages/core/tests/output-error-mode.test.ts": 2652, - "packages/core/tests/output-normalize.test.ts": 552, - "packages/core/tests/output-terminal.test.ts": 704, - "packages/core/tests/parse-components.test.ts": 1449, - "packages/core/tests/plan-response.test.ts": 609, - "packages/core/tests/plugin-api.test.ts": 532, - "packages/core/tests/plugin.test.ts": 1004, - "packages/core/tests/props-binding.test.ts": 1505, - "packages/core/tests/protected-content.test.ts": 551, - "packages/core/tests/registered-printed-errors.test.ts": 854, - "packages/core/tests/replay-stream.test.ts": 495, - "packages/core/tests/retain.test.ts": 1153, - "packages/core/tests/root-composition.test.ts": 1451, - "packages/core/tests/root-props.test.ts": 1490, - "packages/core/tests/root-provider.test.ts": 1343, - "packages/core/tests/sample-component.test.ts": 2330, - "packages/core/tests/scanner.test.ts": 590, - "packages/core/tests/scope-local.test.ts": 489, - "packages/core/tests/secret-detection.test.ts": 2104, - "packages/core/tests/secret-files.test.ts": 742, - "packages/core/tests/secret-rules.test.ts": 831, - "packages/core/tests/secret-scanner-baseline.test.ts": 747, - "packages/core/tests/secret-scanner.test.ts": 701, - "packages/core/tests/source-position.test.ts": 1126, - "packages/core/tests/state-ownership.test.ts": 1193, - "packages/core/tests/streaming-emission.test.ts": 1174, - "packages/core/tests/switch.test.ts": 1868, - "packages/core/tests/syntax-catalog.test.ts": 1826, - "packages/core/tests/syntax-component.test.ts": 2697, - "packages/core/tests/temp-dir.test.ts": 3741, - "packages/core/tests/temp-file-compiler.test.ts": 555, - "packages/core/tests/text-interpolation.test.ts": 907, - "packages/core/tests/try-content.test.ts": 905, - "packages/core/tests/unused-in-diff.test.ts": 1388, - "packages/core/tests/validate.test.ts": 795, - "packages/core/tests/validation-integration.test.ts": 1219, - "packages/core/tests/workflow-component-bundle.test.ts": 1577, - "packages/durable-streams/tests/context.test.ts": 528, - "packages/durable-streams/tests/deterministic-id.test.ts": 556, - "packages/durable-streams/tests/divergence-api.test.ts": 536, - "packages/durable-streams/tests/divergence.test.ts": 571, - "packages/durable-streams/tests/durable-each.test.ts": 567, - "packages/durable-streams/tests/durable-position.test.ts": 582, - "packages/durable-streams/tests/durable-run.test.ts": 566, - "packages/durable-streams/tests/ephemeral.test.ts": 551, - "packages/durable-streams/tests/fail-stop.test.ts": 586, - "packages/durable-streams/tests/guard-stream.test.ts": 619, - "packages/durable-streams/tests/live-coordinator.test.ts": 558, - "packages/durable-streams/tests/parse.test.ts": 586, - "packages/durable-streams/tests/replay-guard.test.ts": 587, - "packages/durable-streams/tests/replay-index.test.ts": 534, - "packages/durable-streams/tests/retained.test.ts": 535, - "packages/durable-streams/tests/serialize-event.test.ts": 536, - "packages/durable-streams/tests/smoke.test.ts": 521, - "packages/durable-streams/tests/structured-concurrency.test.ts": 604, - "packages/durable-streams/tests/terminal-boundary.test.ts": 577, - "packages/durable-streams/tests/types.test.ts": 479, - "packages/git/tests/api-entrypoint.test.ts": 1319, - "packages/git/tests/git-host-effect.test.ts": 7178, - "packages/git/tests/git.test.ts": 1302, - "packages/git/tests/github-activation.test.ts": 1658, - "packages/git/tests/github-issues.test.ts": 1228, - "packages/git/tests/github-pull-requests.test.ts": 604, - "packages/git/tests/issue-markdown.test.ts": 9924, - "packages/git/tests/issue-records.test.ts": 1197, - "packages/git/tests/module-partition.test.ts": 3241, - "packages/git/tests/plugin.test.ts": 1731, - "packages/git/tests/provider-neutrality.test.ts": 2312, - "packages/git/tests/pull-request-records.test.ts": 1278, - "packages/git/tests/run-composition-lazy.test.ts": 1935, - "packages/git/tests/selection-authentication.test.ts": 499, - "packages/runtime/tests/duration.test.ts": 487, - "packages/runtime/tests/fetch.test.ts": 1112, - "packages/runtime/tests/fs.test.ts": 552, - "packages/runtime/tests/host-files.test.ts": 1286, - "packages/runtime/tests/native-launcher.test.ts": 2872, - "packages/runtime/tests/process.test.ts": 551, - "packages/runtime/tests/service.test.ts": 522, - "packages/test-agent/tests/acp-server.test.ts": 671, - "packages/test-agent/tests/behavior-engine.test.ts": 1586, - "packages/test-agent/tests/bridge.test.ts": 510, - "packages/test-agent/tests/command-lazy.test.ts": 6555, - "packages/test-agent/tests/components.test.ts": 107217, - "packages/test-agent/tests/controller.test.ts": 795, - "packages/test-agent/tests/cross-package-resolution.test.ts": 2908, - "packages/test-agent/tests/native-launch.test.ts": 54180, - "packages/test-agent/tests/net.test.ts": 588, - "packages/test-agent/tests/profile.test.ts": 1219, - "packages/test-agent/tests/protocol.test.ts": 577, - "packages/test-agent/tests/provider.test.ts": 1340, - "packages/test-agent/tests/public-api.test.ts": 1560, - "packages/test-agent/tests/route-slot.test.ts": 481, - "packages/test-agent/tests/smoke.test.ts": 28732, - "packages/test-agent/tests/template.test.ts": 1180, - "packages/test-agent/tests/worker-lifecycle.test.ts": 23743, - "packages/test-support/tests/journal.test.ts": 490, - "packages/testing/tests/agent-composition.test.ts": 1468, - "packages/testing/tests/assert-throws.test.ts": 3147, + "packages/acp/tests/session-key.test.ts": 538, + "packages/acp/tests/terminal-screen.test.ts": 622, + "packages/cli/tests/agent-adapters.test.ts": 2390, + "packages/cli/tests/agent-cli.test.ts": 42118, + "packages/cli/tests/agent-config.test.ts": 489, + "packages/cli/tests/agent-options-cli.test.ts": 15836, + "packages/cli/tests/agent-options.test.ts": 485, + "packages/cli/tests/agent-session-coordinator.test.ts": 1328, + "packages/cli/tests/cli-help.test.ts": 39802, + "packages/cli/tests/command.test.ts": 14546, + "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 4111, + "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 167498, + "packages/cli/tests/document-suites/props/props-markdown.test.ts": 3496, + "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 9531, + "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 5746, + "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 17402, + "packages/cli/tests/evaluate-component.test.ts": 14869, + "packages/cli/tests/fetch-cli.test.ts": 4805, + "packages/cli/tests/file-stream.test.ts": 534, + "packages/cli/tests/inline-cli.test.ts": 65480, + "packages/cli/tests/launch-timeout.test.ts": 20546, + "packages/cli/tests/packaged-document.test.ts": 1570, + "packages/cli/tests/plan-args.test.ts": 500, + "packages/cli/tests/plan-cli.test.ts": 114275, + "packages/cli/tests/plan-command-document.test.ts": 7627, + "packages/cli/tests/plan-component.test.ts": 10384, + "packages/cli/tests/plan-host-acts.test.ts": 2265, + "packages/cli/tests/plan.test.ts": 15081, + "packages/cli/tests/plugin-cli.test.ts": 78571, + "packages/cli/tests/plugin-host.test.ts": 3060, + "packages/cli/tests/plugin-modules.test.ts": 491, + "packages/cli/tests/plugin-selection.test.ts": 509, + "packages/cli/tests/process-retention.test.ts": 4709, + "packages/cli/tests/props-cli.test.ts": 60792, + "packages/cli/tests/props-schema.test.ts": 1313, + "packages/cli/tests/props-sources.test.ts": 663, + "packages/cli/tests/repl-admission.test.ts": 544, + "packages/cli/tests/repl-agent-execution.test.ts": 3673, + "packages/cli/tests/repl-boundaries.test.ts": 2424, + "packages/cli/tests/repl-composition.test.ts": 722, + "packages/cli/tests/repl-execution.test.ts": 2859, + "packages/cli/tests/repl-journey.test.ts": 10976, + "packages/cli/tests/repl-model.test.ts": 2634, + "packages/cli/tests/repl-route.test.ts": 2153, + "packages/cli/tests/repl-terminal.test.ts": 1226, + "packages/cli/tests/run-composition.test.ts": 3910, + "packages/cli/tests/run-deadline.test.ts": 3586, + "packages/cli/tests/run-profile.test.ts": 1218, + "packages/cli/tests/run-timeouts.test.ts": 19558, + "packages/cli/tests/secret-detection-cli.test.ts": 36194, + "packages/cli/tests/service-document.test.ts": 2530, + "packages/cli/tests/service-host.test.ts": 2332, + "packages/cli/tests/session-launch-cli.test.ts": 10548, + "packages/cli/tests/stdin-cli.test.ts": 60570, + "packages/cli/tests/stdout-delivery.test.ts": 487, + "packages/cli/tests/syntax-cli.test.ts": 59238, + "packages/cli/tests/targets-cli.test.ts": 48904, + "packages/cli/tests/test-root-profile.test.ts": 6743, + "packages/cli/tests/test-target.test.ts": 70089, + "packages/cli/tests/testing-activation.test.ts": 1432, + "packages/cli/tests/testing-execution-host.test.ts": 86909, + "packages/cli/tests/upgrade-cli.test.ts": 44611, + "packages/cli/tests/upgrade-command-document.test.ts": 5110, + "packages/cli/tests/upgrade-output.test.ts": 1778, + "packages/cli/tests/value-root.test.ts": 29764, + "packages/cli/tests/verbose-component.test.ts": 1853, + "packages/cli/tests/workflow-declaration.test.ts": 1425, + "packages/cli/tests/workflow-host.test.ts": 10417, + "packages/code-review-agent/tests/doctor.test.ts": 475, + "packages/code-review-agent/tests/parse-diagnostics.test.ts": 515, + "packages/code-review-agent/tests/parse-diff.test.ts": 479, + "packages/code-review-agent/tests/parse-doctor.test.ts": 1140, + "packages/code-review-agent/tests/parse-oxlint.test.ts": 497, + "packages/code-review-agent/tests/policy.test.ts": 480, + "packages/core/tests/agent-api.test.ts": 502, + "packages/core/tests/agent-components.test.ts": 1931, + "packages/core/tests/agent-function-components.test.ts": 4063, + "packages/core/tests/agent-session-launch.test.ts": 3960, + "packages/core/tests/agent-session-placement.test.ts": 479, + "packages/core/tests/agent-session-use.test.ts": 466, + "packages/core/tests/agent-stack.test.ts": 1431, + "packages/core/tests/all.test.ts": 3019, + "packages/core/tests/answer-identity.test.ts": 540, + "packages/core/tests/answers-component.test.ts": 2237, + "packages/core/tests/answers-expansion-recursion.test.ts": 931, + "packages/core/tests/capture-errors.test.ts": 1209, + "packages/core/tests/capture-props.test.ts": 978, + "packages/core/tests/cli-journal.test.ts": 21006, + "packages/core/tests/code-block-component.test.ts": 2919, + "packages/core/tests/compiler-boundary.test.ts": 1110, + "packages/core/tests/component-api.test.ts": 528, + "packages/core/tests/component-registration.test.ts": 2007, + "packages/core/tests/component-returns.test.ts": 2678, + "packages/core/tests/component-schema-conformance.test.ts": 1106, + "packages/core/tests/config-api.test.ts": 1227, + "packages/core/tests/construct-error-observation.test.ts": 1287, + "packages/core/tests/contextual-cwd.test.ts": 2418, + "packages/core/tests/daemon-integration.test.ts": 1863, + "packages/core/tests/daemon.test.ts": 557, + "packages/core/tests/declared-markdown-component.test.ts": 3080, + "packages/core/tests/document-output-api.test.ts": 490, + "packages/core/tests/document-target-execution.test.ts": 4282, + "packages/core/tests/document-targets.test.ts": 1134, + "packages/core/tests/document-validation.test.ts": 2809, + "packages/core/tests/documentation-index.test.ts": 824, + "packages/core/tests/each.test.ts": 1213, + "packages/core/tests/elicit-component.test.ts": 1893, + "packages/core/tests/ephemeral-service.test.ts": 1713, + "packages/core/tests/eval-bindings.test.ts": 1218, + "packages/core/tests/eval-context.test.ts": 570, + "packages/core/tests/eval-durable.test.ts": 1244, + "packages/core/tests/eval-error-mode.test.ts": 1573, + "packages/core/tests/eval-interpolate.test.ts": 494, + "packages/core/tests/eval-middleware.test.ts": 567, + "packages/core/tests/eval-persist.test.ts": 1367, + "packages/core/tests/eval-return.test.ts": 1276, + "packages/core/tests/eval-scope.test.ts": 1124, + "packages/core/tests/eval-timeout.test.ts": 1196, + "packages/core/tests/eval-transform.test.ts": 555, + "packages/core/tests/evaluate-component.test.ts": 3337, + "packages/core/tests/evaluate-loaded-copy.test.ts": 1290, + "packages/core/tests/evaluate-provider-lifetime.test.ts": 1385, + "packages/core/tests/evaluation-profile.test.ts": 848, + "packages/core/tests/exec-timeout.test.ts": 1287, + "packages/core/tests/execute.test.ts": 1847, + "packages/core/tests/execution-protocol.test.ts": 2693, + "packages/core/tests/expand.test.ts": 1884, + "packages/core/tests/expansion-identity.test.ts": 1918, + "packages/core/tests/expansion-metadata.test.ts": 983, + "packages/core/tests/expression-props.test.ts": 1780, + "packages/core/tests/fail-component.test.ts": 1544, + "packages/core/tests/failure-printing.test.ts": 1406, + "packages/core/tests/fatal-cause.test.ts": 862, + "packages/core/tests/fetch-component.test.ts": 2721, + "packages/core/tests/file-component.test.ts": 3220, + "packages/core/tests/file-delete-component.test.ts": 1426, + "packages/core/tests/files-fatal.test.ts": 2282, + "packages/core/tests/foreground-exec.test.ts": 2173, + "packages/core/tests/frontmatter.test.ts": 550, + "packages/core/tests/function-components.test.ts": 1897, + "packages/core/tests/generated-composition.test.ts": 1951, + "packages/core/tests/generated-xmd.test.ts": 3377, + "packages/core/tests/glob-api.test.ts": 605, + "packages/core/tests/glob-component.test.ts": 1985, + "packages/core/tests/guarded-journal.test.ts": 1284, + "packages/core/tests/has-content.test.ts": 897, + "packages/core/tests/heal.test.ts": 910, + "packages/core/tests/if.test.ts": 1732, + "packages/core/tests/inline-root.test.ts": 1290, + "packages/core/tests/invocation-failures.test.ts": 582, + "packages/core/tests/invocation-identity.test.ts": 2012, + "packages/core/tests/invocation-scope.test.ts": 1164, + "packages/core/tests/journal-source-position.test.ts": 1384, + "packages/core/tests/json-component.test.ts": 1499, + "packages/core/tests/json.test.ts": 486, + "packages/core/tests/let.test.ts": 1260, + "packages/core/tests/loop.test.ts": 2157, + "packages/core/tests/named-slots.test.ts": 2175, + "packages/core/tests/output-error-mode.test.ts": 2458, + "packages/core/tests/output-normalize.test.ts": 499, + "packages/core/tests/output-terminal.test.ts": 688, + "packages/core/tests/parse-components.test.ts": 1359, + "packages/core/tests/plan-response.test.ts": 589, + "packages/core/tests/plugin-api.test.ts": 506, + "packages/core/tests/plugin.test.ts": 979, + "packages/core/tests/props-binding.test.ts": 1367, + "packages/core/tests/protected-content.test.ts": 534, + "packages/core/tests/registered-printed-errors.test.ts": 859, + "packages/core/tests/replay-stream.test.ts": 483, + "packages/core/tests/retain.test.ts": 1109, + "packages/core/tests/root-composition.test.ts": 1324, + "packages/core/tests/root-props.test.ts": 1401, + "packages/core/tests/root-provider.test.ts": 1281, + "packages/core/tests/sample-component.test.ts": 2225, + "packages/core/tests/scanner.test.ts": 592, + "packages/core/tests/scope-local.test.ts": 490, + "packages/core/tests/secret-detection.test.ts": 2019, + "packages/core/tests/secret-files.test.ts": 739, + "packages/core/tests/secret-rules.test.ts": 830, + "packages/core/tests/secret-scanner-baseline.test.ts": 725, + "packages/core/tests/secret-scanner.test.ts": 684, + "packages/core/tests/source-position.test.ts": 1108, + "packages/core/tests/state-ownership.test.ts": 1191, + "packages/core/tests/streaming-emission.test.ts": 1124, + "packages/core/tests/switch.test.ts": 1819, + "packages/core/tests/syntax-catalog.test.ts": 1752, + "packages/core/tests/syntax-component.test.ts": 2548, + "packages/core/tests/temp-dir.test.ts": 3646, + "packages/core/tests/temp-file-compiler.test.ts": 542, + "packages/core/tests/text-interpolation.test.ts": 871, + "packages/core/tests/try-content.test.ts": 901, + "packages/core/tests/unused-in-diff.test.ts": 1302, + "packages/core/tests/validate.test.ts": 781, + "packages/core/tests/validation-integration.test.ts": 1147, + "packages/core/tests/workflow-component-bundle.test.ts": 1482, + "packages/durable-streams/tests/context.test.ts": 515, + "packages/durable-streams/tests/deterministic-id.test.ts": 541, + "packages/durable-streams/tests/divergence-api.test.ts": 523, + "packages/durable-streams/tests/divergence.test.ts": 552, + "packages/durable-streams/tests/durable-each.test.ts": 558, + "packages/durable-streams/tests/durable-position.test.ts": 526, + "packages/durable-streams/tests/durable-run.test.ts": 587, + "packages/durable-streams/tests/ephemeral.test.ts": 532, + "packages/durable-streams/tests/fail-stop.test.ts": 584, + "packages/durable-streams/tests/guard-stream.test.ts": 605, + "packages/durable-streams/tests/live-coordinator.test.ts": 538, + "packages/durable-streams/tests/parse.test.ts": 581, + "packages/durable-streams/tests/replay-guard.test.ts": 580, + "packages/durable-streams/tests/replay-index.test.ts": 530, + "packages/durable-streams/tests/retained.test.ts": 518, + "packages/durable-streams/tests/serialize-event.test.ts": 516, + "packages/durable-streams/tests/smoke.test.ts": 516, + "packages/durable-streams/tests/structured-concurrency.test.ts": 576, + "packages/durable-streams/tests/terminal-boundary.test.ts": 586, + "packages/durable-streams/tests/types.test.ts": 474, + "packages/git/tests/api-entrypoint.test.ts": 1354, + "packages/git/tests/git-host-effect.test.ts": 6680, + "packages/git/tests/git.test.ts": 1264, + "packages/git/tests/github-activation.test.ts": 1606, + "packages/git/tests/github-issues.test.ts": 1207, + "packages/git/tests/github-pull-requests.test.ts": 574, + "packages/git/tests/issue-markdown.test.ts": 9275, + "packages/git/tests/issue-records.test.ts": 1188, + "packages/git/tests/module-partition.test.ts": 2921, + "packages/git/tests/plugin.test.ts": 1674, + "packages/git/tests/provider-neutrality.test.ts": 2168, + "packages/git/tests/pull-request-records.test.ts": 1236, + "packages/git/tests/run-composition-lazy.test.ts": 1965, + "packages/git/tests/selection-authentication.test.ts": 482, + "packages/runtime/tests/duration.test.ts": 485, + "packages/runtime/tests/fetch.test.ts": 1101, + "packages/runtime/tests/fs.test.ts": 538, + "packages/runtime/tests/host-files.test.ts": 1252, + "packages/runtime/tests/native-launcher.test.ts": 2856, + "packages/runtime/tests/process.test.ts": 544, + "packages/runtime/tests/service.test.ts": 511, + "packages/test-agent/tests/acp-server.test.ts": 653, + "packages/test-agent/tests/behavior-engine.test.ts": 1514, + "packages/test-agent/tests/bridge.test.ts": 521, + "packages/test-agent/tests/command-lazy.test.ts": 6373, + "packages/test-agent/tests/components.test.ts": 102634, + "packages/test-agent/tests/controller.test.ts": 781, + "packages/test-agent/tests/cross-package-resolution.test.ts": 2750, + "packages/test-agent/tests/native-launch.test.ts": 52156, + "packages/test-agent/tests/net.test.ts": 592, + "packages/test-agent/tests/profile.test.ts": 1211, + "packages/test-agent/tests/protocol.test.ts": 553, + "packages/test-agent/tests/provider.test.ts": 1289, + "packages/test-agent/tests/public-api.test.ts": 1510, + "packages/test-agent/tests/route-slot.test.ts": 487, + "packages/test-agent/tests/smoke.test.ts": 27582, + "packages/test-agent/tests/template.test.ts": 1147, + "packages/test-agent/tests/worker-lifecycle.test.ts": 22671, + "packages/test-support/tests/journal.test.ts": 507, + "packages/testing/tests/agent-composition.test.ts": 1420, + "packages/testing/tests/assert-throws.test.ts": 2981, "packages/testing/tests/assert.test.ts": 500, - "packages/testing/tests/assertions.test.ts": 3776, - "packages/testing/tests/boundary-contract.test.ts": 2504, - "packages/testing/tests/cli.test.ts": 10930, - "packages/testing/tests/execution-harness.test.ts": 7583, - "packages/testing/tests/replay.test.ts": 3031, - "packages/testing/tests/smoke.test.ts": 3238, - "packages/testing/tests/test-component.test.ts": 3984, - "packages/testing/tests/testing-activation.test.ts": 3433, - "packages/testing/tests/testing-boundary.test.ts": 2039, - "packages/testing/tests/testing-mode.test.ts": 4040, - "packages/testing/tests/use-testing.test.ts": 2862, - "packages/web/tests/assets.test.ts": 478, - "packages/web/tests/client-logic.test.ts": 543, - "packages/web/tests/compile.test.ts": 2041, - "packages/web/tests/component.test.ts": 2472, - "packages/web/tests/declaration.test.ts": 1651, - "packages/web/tests/document.test.ts": 1811, - "packages/web/tests/elicitation.test.ts": 1657, - "packages/web/tests/live-form.test.ts": 1487, - "packages/web/tests/markdown.test.ts": 686, - "packages/web/tests/opener.test.ts": 1477, - "packages/web/tests/page.test.ts": 480, - "packages/web/tests/responder.test.ts": 1832, - "packages/web/tests/server-lifecycle.test.ts": 1727, - "packages/web/tests/server.test.ts": 2061, - "packages/workflow/tests/generated-observations.test.ts": 1458, - "packages/workflow/tests/retained-run.test.ts": 2195, - "packages/workflow/tests/service-denial.test.ts": 508, - "packages/workflow/tests/workflow-bundle.test.ts": 1638, - "packages/workflow/tests/workflow-definition.test.ts": 1319, - "packages/workflow/tests/workflow-fork.test.ts": 1209, - "packages/workflow/tests/workflow-run.test.ts": 2081, - "packages/workflow/tests/workspace-effect.test.ts": 3067, - "packages/workflow/tests/xmd-artifact.test.ts": 3662, - "scripts/tests/adapter-vendor.test.ts": 721, - "scripts/tests/bootstrap-npm-package.test.ts": 8295, - "scripts/tests/bump-version.test.ts": 611, - "scripts/tests/changed-paths.test.ts": 512, - "scripts/tests/ci-workflow.test.ts": 3638, - "scripts/tests/consumer-cycle.test.ts": 694, - "scripts/tests/filesystem-contract-workflow.test.ts": 587, - "scripts/tests/main-green.test.ts": 771, - "scripts/tests/main-health.test.ts": 619, - "scripts/tests/no-module-scoped-registry.test.ts": 1311, - "scripts/tests/no-redundant-test-scope.test.ts": 5343, - "scripts/tests/no-sync-filesystem.test.ts": 5664, - "scripts/tests/no-yield-in-finally.test.ts": 1317, - "scripts/tests/oxlint-policy.test.ts": 10912, - "scripts/tests/packaged-document.test.ts": 619, - "scripts/tests/prefer-effection-operation.test.ts": 6307, - "scripts/tests/prefer-effection-result.test.ts": 4550, - "scripts/tests/publish-workflow-membership.test.ts": 671, - "scripts/tests/review-infrastructure.test.ts": 3238, - "scripts/tests/runtime-exclusions.test.ts": 988, - "scripts/tests/runtime-tests.test.ts": 546, - "scripts/tests/scope-bound-event-registration.test.ts": 5495, - "scripts/tests/staged-write.test.ts": 537, - "scripts/tests/test-file-discovery.test.ts": 845, - "scripts/tests/test-shards.test.ts": 534, - "scripts/tests/test-weights.test.ts": 713, - "scripts/tests/tracked.test.ts": 542, - "scripts/tests/verify-coordinator.test.ts": 717, - "scripts/tests/version-lockstep.test.ts": 698, - "scripts/tests/web-client-module.test.ts": 530, - "scripts/tests/workspace.test.ts": 506 + "packages/testing/tests/assertions.test.ts": 3636, + "packages/testing/tests/boundary-contract.test.ts": 2406, + "packages/testing/tests/cli.test.ts": 10554, + "packages/testing/tests/execution-harness.test.ts": 7075, + "packages/testing/tests/replay.test.ts": 2871, + "packages/testing/tests/smoke.test.ts": 3096, + "packages/testing/tests/test-component.test.ts": 3648, + "packages/testing/tests/testing-activation.test.ts": 3235, + "packages/testing/tests/testing-boundary.test.ts": 1934, + "packages/testing/tests/testing-mode.test.ts": 3820, + "packages/testing/tests/use-testing.test.ts": 2676, + "packages/web/tests/assets.test.ts": 485, + "packages/web/tests/client-logic.test.ts": 530, + "packages/web/tests/compile.test.ts": 1889, + "packages/web/tests/component.test.ts": 2417, + "packages/web/tests/declaration.test.ts": 1633, + "packages/web/tests/document.test.ts": 1744, + "packages/web/tests/elicitation.test.ts": 1534, + "packages/web/tests/live-form.test.ts": 1433, + "packages/web/tests/markdown.test.ts": 672, + "packages/web/tests/opener.test.ts": 1487, + "packages/web/tests/page.test.ts": 489, + "packages/web/tests/responder.test.ts": 1728, + "packages/web/tests/server-lifecycle.test.ts": 1562, + "packages/web/tests/server.test.ts": 1935, + "packages/workflow/tests/generated-observations.test.ts": 1441, + "packages/workflow/tests/retained-run.test.ts": 2082, + "packages/workflow/tests/service-denial.test.ts": 516, + "packages/workflow/tests/workflow-bundle.test.ts": 1600, + "packages/workflow/tests/workflow-definition.test.ts": 1285, + "packages/workflow/tests/workflow-fork.test.ts": 1162, + "packages/workflow/tests/workflow-run.test.ts": 2045, + "packages/workflow/tests/workspace-effect.test.ts": 2882, + "packages/workflow/tests/xmd-artifact.test.ts": 3560, + "scripts/tests/adapter-vendor.test.ts": 698, + "scripts/tests/bootstrap-npm-package.test.ts": 8036, + "scripts/tests/bump-version.test.ts": 609, + "scripts/tests/changed-paths.test.ts": 517, + "scripts/tests/ci-workflow.test.ts": 3537, + "scripts/tests/consumer-cycle.test.ts": 665, + "scripts/tests/filesystem-contract-workflow.test.ts": 556, + "scripts/tests/main-green.test.ts": 769, + "scripts/tests/main-health.test.ts": 596, + "scripts/tests/no-module-scoped-registry.test.ts": 1334, + "scripts/tests/no-redundant-test-scope.test.ts": 5298, + "scripts/tests/no-sync-filesystem.test.ts": 5668, + "scripts/tests/no-yield-in-finally.test.ts": 1295, + "scripts/tests/oxlint-policy.test.ts": 10807, + "scripts/tests/packaged-document.test.ts": 595, + "scripts/tests/prefer-effection-operation.test.ts": 6357, + "scripts/tests/prefer-effection-result.test.ts": 4473, + "scripts/tests/publish-workflow-membership.test.ts": 660, + "scripts/tests/review-infrastructure.test.ts": 3047, + "scripts/tests/runtime-exclusions.test.ts": 957, + "scripts/tests/runtime-tests.test.ts": 534, + "scripts/tests/scope-bound-event-registration.test.ts": 5246, + "scripts/tests/staged-write.test.ts": 529, + "scripts/tests/test-file-discovery.test.ts": 796, + "scripts/tests/test-shards.test.ts": 536, + "scripts/tests/test-weights.test.ts": 679, + "scripts/tests/tracked.test.ts": 556, + "scripts/tests/verify-coordinator.test.ts": 706, + "scripts/tests/version-lockstep.test.ts": 677, + "scripts/tests/web-client-module.test.ts": 521, + "scripts/tests/workspace.test.ts": 497 }, "bun": { - "packages/acp/tests/acceptance.test.ts": 881, - "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4309, - "packages/acp/tests/acpx-materialization.test.ts": 6504, - "packages/acp/tests/acpx-transient-env.test.ts": 255, - "packages/acp/tests/adapter-materialization.test.ts": 70550, - "packages/acp/tests/adapter-protocol.test.ts": 53397, - "packages/acp/tests/codex-surfaces.test.ts": 124, - "packages/acp/tests/native-launch.test.ts": 1004, - "packages/acp/tests/permission-bridge.test.ts": 367, - "packages/acp/tests/provider.test.ts": 1110, - "packages/acp/tests/serial-queue.test.ts": 65, - "packages/acp/tests/session-key.test.ts": 70, + "packages/acp/tests/acceptance.test.ts": 764, + "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4310, + "packages/acp/tests/acpx-materialization.test.ts": 6501, + "packages/acp/tests/acpx-transient-env.test.ts": 256, + "packages/acp/tests/adapter-materialization.test.ts": 69597, + "packages/acp/tests/adapter-protocol.test.ts": 51387, + "packages/acp/tests/codex-surfaces.test.ts": 125, + "packages/acp/tests/native-launch.test.ts": 966, + "packages/acp/tests/permission-bridge.test.ts": 350, + "packages/acp/tests/provider.test.ts": 1095, + "packages/acp/tests/serial-queue.test.ts": 66, + "packages/acp/tests/session-key.test.ts": 63, "packages/acp/tests/terminal-screen.test.ts": 92, - "packages/cli/tests/agent-adapters.test.ts": 1152, - "packages/cli/tests/agent-cli.test.ts": 21790, + "packages/cli/tests/agent-adapters.test.ts": 1126, + "packages/cli/tests/agent-cli.test.ts": 19668, "packages/cli/tests/agent-config.test.ts": 30, - "packages/cli/tests/agent-options-cli.test.ts": 8164, - "packages/cli/tests/agent-options.test.ts": 33, - "packages/cli/tests/agent-session-coordinator.test.ts": 396, - "packages/cli/tests/cli-help.test.ts": 15775, - "packages/cli/tests/command.test.ts": 7938, - "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 2930, - "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 106383, - "packages/cli/tests/document-suites/props/props-markdown.test.ts": 2384, - "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 5311, - "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 5016, - "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 9107, - "packages/cli/tests/evaluate-component.test.ts": 8013, - "packages/cli/tests/fetch-cli.test.ts": 2379, - "packages/cli/tests/file-stream.test.ts": 56, - "packages/cli/tests/inline-cli.test.ts": 32942, - "packages/cli/tests/launch-timeout.test.ts": 10096, - "packages/cli/tests/packaged-document.test.ts": 465, - "packages/cli/tests/plan-args.test.ts": 39, - "packages/cli/tests/plan-cli.test.ts": 61713, - "packages/cli/tests/plan-command-document.test.ts": 5437, - "packages/cli/tests/plan-component.test.ts": 8825, - "packages/cli/tests/plan-host-acts.test.ts": 1167, - "packages/cli/tests/plan.test.ts": 12886, - "packages/cli/tests/plugin-cli.test.ts": 39744, - "packages/cli/tests/plugin-host.test.ts": 2006, - "packages/cli/tests/plugin-modules.test.ts": 36, - "packages/cli/tests/plugin-selection.test.ts": 36, - "packages/cli/tests/process-retention.test.ts": 2607, - "packages/cli/tests/props-cli.test.ts": 29136, - "packages/cli/tests/props-schema.test.ts": 422, - "packages/cli/tests/props-sources.test.ts": 128, - "packages/cli/tests/repl-boundaries.test.ts": 1229, - "packages/cli/tests/repl-composition.test.ts": 145, - "packages/cli/tests/repl-execution.test.ts": 1935, - "packages/cli/tests/repl-journey.test.ts": 10392, - "packages/cli/tests/repl-model.test.ts": 1784, - "packages/cli/tests/repl-route.test.ts": 1261, - "packages/cli/tests/repl-terminal.test.ts": 821, - "packages/cli/tests/run-composition.test.ts": 3038, - "packages/cli/tests/run-deadline.test.ts": 2382, - "packages/cli/tests/run-profile.test.ts": 312, - "packages/cli/tests/run-timeouts.test.ts": 11377, - "packages/cli/tests/secret-detection-cli.test.ts": 18381, - "packages/cli/tests/service-document.test.ts": 1532, - "packages/cli/tests/service-host.test.ts": 1390, - "packages/cli/tests/session-launch-cli.test.ts": 6066, - "packages/cli/tests/stdin-cli.test.ts": 31362, - "packages/cli/tests/stdout-delivery.test.ts": 52, - "packages/cli/tests/syntax-cli.test.ts": 33188, - "packages/cli/tests/targets-cli.test.ts": 21933, - "packages/cli/tests/test-root-profile.test.ts": 3814, - "packages/cli/tests/test-target.test.ts": 36435, - "packages/cli/tests/testing-activation.test.ts": 578, - "packages/cli/tests/testing-execution-host.test.ts": 49246, - "packages/cli/tests/upgrade-cli.test.ts": 18227, - "packages/cli/tests/upgrade-command-document.test.ts": 3569, - "packages/cli/tests/upgrade-output.test.ts": 831, - "packages/cli/tests/value-root.test.ts": 15949, - "packages/cli/tests/verbose-component.test.ts": 820, - "packages/cli/tests/workflow-declaration.test.ts": 538, - "packages/cli/tests/workflow-host.test.ts": 4406, - "packages/code-review-agent/tests/doctor.test.ts": 30, - "packages/code-review-agent/tests/parse-diagnostics.test.ts": 39, + "packages/cli/tests/agent-options-cli.test.ts": 7421, + "packages/cli/tests/agent-options.test.ts": 34, + "packages/cli/tests/agent-session-coordinator.test.ts": 388, + "packages/cli/tests/cli-help.test.ts": 15087, + "packages/cli/tests/command.test.ts": 7762, + "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 2793, + "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 103624, + "packages/cli/tests/document-suites/props/props-markdown.test.ts": 2168, + "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 4746, + "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 4632, + "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 8337, + "packages/cli/tests/evaluate-component.test.ts": 7487, + "packages/cli/tests/fetch-cli.test.ts": 2230, + "packages/cli/tests/file-stream.test.ts": 64, + "packages/cli/tests/inline-cli.test.ts": 30588, + "packages/cli/tests/launch-timeout.test.ts": 10086, + "packages/cli/tests/packaged-document.test.ts": 457, + "packages/cli/tests/plan-args.test.ts": 35, + "packages/cli/tests/plan-cli.test.ts": 55503, + "packages/cli/tests/plan-command-document.test.ts": 5230, + "packages/cli/tests/plan-component.test.ts": 8517, + "packages/cli/tests/plan-host-acts.test.ts": 1117, + "packages/cli/tests/plan.test.ts": 12156, + "packages/cli/tests/plugin-cli.test.ts": 37649, + "packages/cli/tests/plugin-host.test.ts": 1910, + "packages/cli/tests/plugin-modules.test.ts": 37, + "packages/cli/tests/plugin-selection.test.ts": 37, + "packages/cli/tests/process-retention.test.ts": 2310, + "packages/cli/tests/props-cli.test.ts": 27589, + "packages/cli/tests/props-schema.test.ts": 408, + "packages/cli/tests/props-sources.test.ts": 123, + "packages/cli/tests/repl-admission.test.ts": 48, + "packages/cli/tests/repl-agent-execution.test.ts": 3041, + "packages/cli/tests/repl-boundaries.test.ts": 1135, + "packages/cli/tests/repl-composition.test.ts": 140, + "packages/cli/tests/repl-execution.test.ts": 1875, + "packages/cli/tests/repl-journey.test.ts": 10206, + "packages/cli/tests/repl-model.test.ts": 1738, + "packages/cli/tests/repl-route.test.ts": 1196, + "packages/cli/tests/repl-terminal.test.ts": 812, + "packages/cli/tests/run-composition.test.ts": 2849, + "packages/cli/tests/run-deadline.test.ts": 2336, + "packages/cli/tests/run-profile.test.ts": 305, + "packages/cli/tests/run-timeouts.test.ts": 11057, + "packages/cli/tests/secret-detection-cli.test.ts": 17489, + "packages/cli/tests/service-document.test.ts": 1530, + "packages/cli/tests/service-host.test.ts": 1388, + "packages/cli/tests/session-launch-cli.test.ts": 5391, + "packages/cli/tests/stdin-cli.test.ts": 29849, + "packages/cli/tests/stdout-delivery.test.ts": 50, + "packages/cli/tests/syntax-cli.test.ts": 31280, + "packages/cli/tests/targets-cli.test.ts": 20986, + "packages/cli/tests/test-root-profile.test.ts": 3561, + "packages/cli/tests/test-target.test.ts": 34618, + "packages/cli/tests/testing-activation.test.ts": 557, + "packages/cli/tests/testing-execution-host.test.ts": 47781, + "packages/cli/tests/upgrade-cli.test.ts": 17837, + "packages/cli/tests/upgrade-command-document.test.ts": 3818, + "packages/cli/tests/upgrade-output.test.ts": 779, + "packages/cli/tests/value-root.test.ts": 15186, + "packages/cli/tests/verbose-component.test.ts": 785, + "packages/cli/tests/workflow-declaration.test.ts": 562, + "packages/cli/tests/workflow-host.test.ts": 4014, + "packages/code-review-agent/tests/doctor.test.ts": 31, + "packages/code-review-agent/tests/parse-diagnostics.test.ts": 40, "packages/code-review-agent/tests/parse-diff.test.ts": 36, - "packages/code-review-agent/tests/parse-doctor.test.ts": 297, + "packages/code-review-agent/tests/parse-doctor.test.ts": 293, "packages/code-review-agent/tests/parse-oxlint.test.ts": 30, - "packages/code-review-agent/tests/policy.test.ts": 34, + "packages/code-review-agent/tests/policy.test.ts": 32, "packages/core/tests/agent-api.test.ts": 38, - "packages/core/tests/agent-components.test.ts": 1287, - "packages/core/tests/agent-function-components.test.ts": 3532, - "packages/core/tests/agent-session-launch.test.ts": 3433, - "packages/core/tests/agent-session-placement.test.ts": 38, - "packages/core/tests/agent-session-use.test.ts": 47, - "packages/core/tests/agent-stack.test.ts": 785, - "packages/core/tests/all.test.ts": 2166, - "packages/core/tests/answer-identity.test.ts": 68, - "packages/core/tests/answers-component.test.ts": 1361, - "packages/core/tests/answers-expansion-recursion.test.ts": 327, - "packages/core/tests/capture-errors.test.ts": 569, - "packages/core/tests/capture-props.test.ts": 337, - "packages/core/tests/cli-journal.test.ts": 10559, - "packages/core/tests/code-block-component.test.ts": 2336, - "packages/core/tests/compiler-boundary.test.ts": 352, - "packages/core/tests/component-api.test.ts": 57, - "packages/core/tests/component-registration.test.ts": 1096, - "packages/core/tests/component-returns.test.ts": 1602, - "packages/core/tests/component-schema-conformance.test.ts": 553, - "packages/core/tests/config-api.test.ts": 339, - "packages/core/tests/construct-error-observation.test.ts": 710, - "packages/core/tests/contextual-cwd.test.ts": 1531, - "packages/core/tests/daemon-integration.test.ts": 1026, + "packages/core/tests/agent-components.test.ts": 1180, + "packages/core/tests/agent-function-components.test.ts": 3691, + "packages/core/tests/agent-session-launch.test.ts": 3158, + "packages/core/tests/agent-session-placement.test.ts": 37, + "packages/core/tests/agent-session-use.test.ts": 31, + "packages/core/tests/agent-stack.test.ts": 676, + "packages/core/tests/all.test.ts": 2322, + "packages/core/tests/answer-identity.test.ts": 47, + "packages/core/tests/answers-component.test.ts": 1381, + "packages/core/tests/answers-expansion-recursion.test.ts": 315, + "packages/core/tests/capture-errors.test.ts": 555, + "packages/core/tests/capture-props.test.ts": 315, + "packages/core/tests/cli-journal.test.ts": 9779, + "packages/core/tests/code-block-component.test.ts": 2152, + "packages/core/tests/compiler-boundary.test.ts": 326, + "packages/core/tests/component-api.test.ts": 53, + "packages/core/tests/component-registration.test.ts": 1062, + "packages/core/tests/component-returns.test.ts": 1573, + "packages/core/tests/component-schema-conformance.test.ts": 510, + "packages/core/tests/config-api.test.ts": 348, + "packages/core/tests/construct-error-observation.test.ts": 666, + "packages/core/tests/contextual-cwd.test.ts": 1518, + "packages/core/tests/daemon-integration.test.ts": 1028, "packages/core/tests/daemon.test.ts": 72, - "packages/core/tests/declared-markdown-component.test.ts": 1988, - "packages/core/tests/document-output-api.test.ts": 38, - "packages/core/tests/document-target-execution.test.ts": 3473, - "packages/core/tests/document-targets.test.ts": 407, - "packages/core/tests/document-validation.test.ts": 2083, - "packages/core/tests/documentation-index.test.ts": 167, - "packages/core/tests/each.test.ts": 421, - "packages/core/tests/elicit-component.test.ts": 1083, - "packages/core/tests/ephemeral-service.test.ts": 831, - "packages/core/tests/eval-bindings.test.ts": 401, - "packages/core/tests/eval-context.test.ts": 86, - "packages/core/tests/eval-durable.test.ts": 426, - "packages/core/tests/eval-error-mode.test.ts": 690, - "packages/core/tests/eval-interpolate.test.ts": 39, + "packages/core/tests/declared-markdown-component.test.ts": 2118, + "packages/core/tests/document-output-api.test.ts": 39, + "packages/core/tests/document-target-execution.test.ts": 3322, + "packages/core/tests/document-targets.test.ts": 396, + "packages/core/tests/document-validation.test.ts": 1987, + "packages/core/tests/documentation-index.test.ts": 168, + "packages/core/tests/each.test.ts": 390, + "packages/core/tests/elicit-component.test.ts": 1065, + "packages/core/tests/ephemeral-service.test.ts": 798, + "packages/core/tests/eval-bindings.test.ts": 374, + "packages/core/tests/eval-context.test.ts": 88, + "packages/core/tests/eval-durable.test.ts": 418, + "packages/core/tests/eval-error-mode.test.ts": 681, + "packages/core/tests/eval-interpolate.test.ts": 38, "packages/core/tests/eval-middleware.test.ts": 76, - "packages/core/tests/eval-persist.test.ts": 506, - "packages/core/tests/eval-return.test.ts": 424, - "packages/core/tests/eval-scope.test.ts": 334, - "packages/core/tests/eval-timeout.test.ts": 433, - "packages/core/tests/eval-transform.test.ts": 74, - "packages/core/tests/evaluate-component.test.ts": 2514, - "packages/core/tests/evaluate-loaded-copy.test.ts": 517, - "packages/core/tests/evaluate-provider-lifetime.test.ts": 576, - "packages/core/tests/evaluation-profile.test.ts": 193, - "packages/core/tests/exec-timeout.test.ts": 502, - "packages/core/tests/execute.test.ts": 993, - "packages/core/tests/execution-protocol.test.ts": 1607, - "packages/core/tests/expand.test.ts": 1247, - "packages/core/tests/expansion-identity.test.ts": 1237, - "packages/core/tests/expansion-metadata.test.ts": 333, - "packages/core/tests/expression-props.test.ts": 891, - "packages/core/tests/fail-component.test.ts": 754, - "packages/core/tests/failure-printing.test.ts": 683, - "packages/core/tests/fatal-cause.test.ts": 188, - "packages/core/tests/fetch-component.test.ts": 1772, - "packages/core/tests/file-component.test.ts": 2425, - "packages/core/tests/file-delete-component.test.ts": 620, - "packages/core/tests/files-fatal.test.ts": 1468, - "packages/core/tests/foreground-exec.test.ts": 1236, - "packages/core/tests/frontmatter.test.ts": 48, - "packages/core/tests/function-components.test.ts": 912, - "packages/core/tests/generated-composition.test.ts": 1135, - "packages/core/tests/generated-xmd.test.ts": 2338, - "packages/core/tests/glob-api.test.ts": 99, - "packages/core/tests/glob-component.test.ts": 1236, - "packages/core/tests/guarded-journal.test.ts": 445, - "packages/core/tests/has-content.test.ts": 270, - "packages/core/tests/heal.test.ts": 266, - "packages/core/tests/if.test.ts": 827, - "packages/core/tests/inline-root.test.ts": 493, - "packages/core/tests/invocation-failures.test.ts": 104, - "packages/core/tests/invocation-identity.test.ts": 825, - "packages/core/tests/invocation-scope.test.ts": 550, - "packages/core/tests/journal-source-position.test.ts": 599, - "packages/core/tests/json-component.test.ts": 2337, + "packages/core/tests/eval-persist.test.ts": 481, + "packages/core/tests/eval-return.test.ts": 412, + "packages/core/tests/eval-scope.test.ts": 321, + "packages/core/tests/eval-timeout.test.ts": 413, + "packages/core/tests/eval-transform.test.ts": 62, + "packages/core/tests/evaluate-component.test.ts": 2441, + "packages/core/tests/evaluate-loaded-copy.test.ts": 500, + "packages/core/tests/evaluate-provider-lifetime.test.ts": 560, + "packages/core/tests/evaluation-profile.test.ts": 185, + "packages/core/tests/exec-timeout.test.ts": 459, + "packages/core/tests/execute.test.ts": 974, + "packages/core/tests/execution-protocol.test.ts": 1543, + "packages/core/tests/expand.test.ts": 1235, + "packages/core/tests/expansion-identity.test.ts": 1134, + "packages/core/tests/expansion-metadata.test.ts": 329, + "packages/core/tests/expression-props.test.ts": 854, + "packages/core/tests/fail-component.test.ts": 716, + "packages/core/tests/failure-printing.test.ts": 649, + "packages/core/tests/fatal-cause.test.ts": 189, + "packages/core/tests/fetch-component.test.ts": 1695, + "packages/core/tests/file-component.test.ts": 2361, + "packages/core/tests/file-delete-component.test.ts": 589, + "packages/core/tests/files-fatal.test.ts": 1459, + "packages/core/tests/foreground-exec.test.ts": 1197, + "packages/core/tests/frontmatter.test.ts": 49, + "packages/core/tests/function-components.test.ts": 886, + "packages/core/tests/generated-composition.test.ts": 1168, + "packages/core/tests/generated-xmd.test.ts": 2246, + "packages/core/tests/glob-api.test.ts": 95, + "packages/core/tests/glob-component.test.ts": 1182, + "packages/core/tests/guarded-journal.test.ts": 440, + "packages/core/tests/has-content.test.ts": 260, + "packages/core/tests/heal.test.ts": 252, + "packages/core/tests/if.test.ts": 778, + "packages/core/tests/inline-root.test.ts": 480, + "packages/core/tests/invocation-failures.test.ts": 101, + "packages/core/tests/invocation-identity.test.ts": 1014, + "packages/core/tests/invocation-scope.test.ts": 533, + "packages/core/tests/journal-source-position.test.ts": 543, + "packages/core/tests/json-component.test.ts": 2448, "packages/core/tests/json.test.ts": 35, - "packages/core/tests/let.test.ts": 423, - "packages/core/tests/loop.test.ts": 1241, - "packages/core/tests/named-slots.test.ts": 1274, - "packages/core/tests/output-error-mode.test.ts": 1565, + "packages/core/tests/let.test.ts": 424, + "packages/core/tests/loop.test.ts": 1161, + "packages/core/tests/named-slots.test.ts": 1189, + "packages/core/tests/output-error-mode.test.ts": 1516, "packages/core/tests/output-normalize.test.ts": 40, - "packages/core/tests/output-terminal.test.ts": 161, - "packages/core/tests/parse-components.test.ts": 664, - "packages/core/tests/plan-response.test.ts": 96, - "packages/core/tests/plugin-api.test.ts": 47, - "packages/core/tests/plugin.test.ts": 191, - "packages/core/tests/props-binding.test.ts": 533, - "packages/core/tests/protected-content.test.ts": 49, - "packages/core/tests/registered-printed-errors.test.ts": 214, - "packages/core/tests/replay-stream.test.ts": 35, - "packages/core/tests/retain.test.ts": 483, - "packages/core/tests/root-composition.test.ts": 572, - "packages/core/tests/root-props.test.ts": 569, - "packages/core/tests/root-provider.test.ts": 580, - "packages/core/tests/sample-component.test.ts": 1104, - "packages/core/tests/scanner.test.ts": 77, - "packages/core/tests/scope-local.test.ts": 35, - "packages/core/tests/secret-detection.test.ts": 1148, + "packages/core/tests/output-terminal.test.ts": 165, + "packages/core/tests/parse-components.test.ts": 603, + "packages/core/tests/plan-response.test.ts": 84, + "packages/core/tests/plugin-api.test.ts": 46, + "packages/core/tests/plugin.test.ts": 189, + "packages/core/tests/props-binding.test.ts": 574, + "packages/core/tests/protected-content.test.ts": 48, + "packages/core/tests/registered-printed-errors.test.ts": 207, + "packages/core/tests/replay-stream.test.ts": 37, + "packages/core/tests/retain.test.ts": 485, + "packages/core/tests/root-composition.test.ts": 629, + "packages/core/tests/root-props.test.ts": 606, + "packages/core/tests/root-provider.test.ts": 593, + "packages/core/tests/sample-component.test.ts": 1175, + "packages/core/tests/scanner.test.ts": 78, + "packages/core/tests/scope-local.test.ts": 93, + "packages/core/tests/secret-detection.test.ts": 1131, "packages/core/tests/secret-files.test.ts": 128, - "packages/core/tests/secret-rules.test.ts": 150, - "packages/core/tests/secret-scanner-baseline.test.ts": 152, - "packages/core/tests/secret-scanner.test.ts": 103, - "packages/core/tests/source-position.test.ts": 376, - "packages/core/tests/state-ownership.test.ts": 404, - "packages/core/tests/streaming-emission.test.ts": 332, - "packages/core/tests/switch.test.ts": 817, - "packages/core/tests/syntax-catalog.test.ts": 894, - "packages/core/tests/syntax-component.test.ts": 1764, - "packages/core/tests/temp-dir.test.ts": 2800, - "packages/core/tests/temp-file-compiler.test.ts": 74, - "packages/core/tests/text-interpolation.test.ts": 229, - "packages/core/tests/try-content.test.ts": 270, - "packages/core/tests/unused-in-diff.test.ts": 502, - "packages/core/tests/validate.test.ts": 322, - "packages/core/tests/validation-integration.test.ts": 366, - "packages/core/tests/workflow-component-bundle.test.ts": 706, - "packages/durable-streams/tests/context.test.ts": 35, - "packages/durable-streams/tests/deterministic-id.test.ts": 68, + "packages/core/tests/secret-rules.test.ts": 147, + "packages/core/tests/secret-scanner-baseline.test.ts": 160, + "packages/core/tests/secret-scanner.test.ts": 96, + "packages/core/tests/source-position.test.ts": 334, + "packages/core/tests/state-ownership.test.ts": 379, + "packages/core/tests/streaming-emission.test.ts": 313, + "packages/core/tests/switch.test.ts": 808, + "packages/core/tests/syntax-catalog.test.ts": 853, + "packages/core/tests/syntax-component.test.ts": 1845, + "packages/core/tests/temp-dir.test.ts": 2820, + "packages/core/tests/temp-file-compiler.test.ts": 84, + "packages/core/tests/text-interpolation.test.ts": 222, + "packages/core/tests/try-content.test.ts": 261, + "packages/core/tests/unused-in-diff.test.ts": 492, + "packages/core/tests/validate.test.ts": 329, + "packages/core/tests/validation-integration.test.ts": 353, + "packages/core/tests/workflow-component-bundle.test.ts": 739, + "packages/durable-streams/tests/context.test.ts": 38, + "packages/durable-streams/tests/deterministic-id.test.ts": 73, "packages/durable-streams/tests/divergence-api.test.ts": 46, - "packages/durable-streams/tests/divergence.test.ts": 56, - "packages/durable-streams/tests/durable-each.test.ts": 69, + "packages/durable-streams/tests/divergence.test.ts": 57, + "packages/durable-streams/tests/durable-each.test.ts": 67, "packages/durable-streams/tests/durable-position.test.ts": 58, - "packages/durable-streams/tests/durable-run.test.ts": 65, - "packages/durable-streams/tests/ephemeral.test.ts": 60, - "packages/durable-streams/tests/fail-stop.test.ts": 73, - "packages/durable-streams/tests/guard-stream.test.ts": 109, + "packages/durable-streams/tests/durable-run.test.ts": 66, + "packages/durable-streams/tests/ephemeral.test.ts": 62, + "packages/durable-streams/tests/fail-stop.test.ts": 77, + "packages/durable-streams/tests/guard-stream.test.ts": 104, "packages/durable-streams/tests/live-coordinator.test.ts": 59, - "packages/durable-streams/tests/parse.test.ts": 51, - "packages/durable-streams/tests/replay-guard.test.ts": 70, - "packages/durable-streams/tests/replay-index.test.ts": 48, - "packages/durable-streams/tests/retained.test.ts": 44, - "packages/durable-streams/tests/serialize-event.test.ts": 41, - "packages/durable-streams/tests/smoke.test.ts": 33, - "packages/durable-streams/tests/structured-concurrency.test.ts": 94, - "packages/durable-streams/tests/terminal-boundary.test.ts": 77, + "packages/durable-streams/tests/parse.test.ts": 50, + "packages/durable-streams/tests/replay-guard.test.ts": 71, + "packages/durable-streams/tests/replay-index.test.ts": 49, + "packages/durable-streams/tests/retained.test.ts": 45, + "packages/durable-streams/tests/serialize-event.test.ts": 66, + "packages/durable-streams/tests/smoke.test.ts": 69, + "packages/durable-streams/tests/structured-concurrency.test.ts": 91, + "packages/durable-streams/tests/terminal-boundary.test.ts": 76, "packages/durable-streams/tests/types.test.ts": 30, - "packages/git/tests/api-entrypoint.test.ts": 306, - "packages/git/tests/git-host-effect.test.ts": 6749, - "packages/git/tests/git.test.ts": 376, - "packages/git/tests/github-activation.test.ts": 697, - "packages/git/tests/github-issues.test.ts": 317, - "packages/git/tests/github-pull-requests.test.ts": 100, - "packages/git/tests/issue-markdown.test.ts": 8692, - "packages/git/tests/issue-records.test.ts": 314, - "packages/git/tests/module-partition.test.ts": 1883, - "packages/git/tests/plugin.test.ts": 799, - "packages/git/tests/provider-neutrality.test.ts": 1090, - "packages/git/tests/pull-request-records.test.ts": 332, - "packages/git/tests/run-composition-lazy.test.ts": 785, - "packages/git/tests/selection-authentication.test.ts": 39, + "packages/git/tests/api-entrypoint.test.ts": 313, + "packages/git/tests/git-host-effect.test.ts": 6206, + "packages/git/tests/git.test.ts": 351, + "packages/git/tests/github-activation.test.ts": 643, + "packages/git/tests/github-issues.test.ts": 314, + "packages/git/tests/github-pull-requests.test.ts": 99, + "packages/git/tests/issue-markdown.test.ts": 8221, + "packages/git/tests/issue-records.test.ts": 302, + "packages/git/tests/module-partition.test.ts": 1718, + "packages/git/tests/plugin.test.ts": 811, + "packages/git/tests/provider-neutrality.test.ts": 1018, + "packages/git/tests/pull-request-records.test.ts": 317, + "packages/git/tests/run-composition-lazy.test.ts": 755, + "packages/git/tests/selection-authentication.test.ts": 40, "packages/runtime/tests/duration.test.ts": 30, - "packages/runtime/tests/fetch.test.ts": 620, - "packages/runtime/tests/fs.test.ts": 78, - "packages/runtime/tests/host-files.test.ts": 732, - "packages/runtime/tests/native-launcher.test.ts": 2404, - "packages/runtime/tests/process.test.ts": 82, - "packages/runtime/tests/service.test.ts": 66, - "packages/test-agent/tests/acp-server.test.ts": 102, - "packages/test-agent/tests/behavior-engine.test.ts": 662, + "packages/runtime/tests/fetch.test.ts": 617, + "packages/runtime/tests/fs.test.ts": 69, + "packages/runtime/tests/host-files.test.ts": 723, + "packages/runtime/tests/native-launcher.test.ts": 2430, + "packages/runtime/tests/process.test.ts": 89, + "packages/runtime/tests/service.test.ts": 59, + "packages/test-agent/tests/acp-server.test.ts": 105, + "packages/test-agent/tests/behavior-engine.test.ts": 626, "packages/test-agent/tests/bridge.test.ts": 68, - "packages/test-agent/tests/command-lazy.test.ts": 3306, - "packages/test-agent/tests/components.test.ts": 52683, + "packages/test-agent/tests/command-lazy.test.ts": 3224, + "packages/test-agent/tests/components.test.ts": 51236, "packages/test-agent/tests/controller.test.ts": 197, - "packages/test-agent/tests/cross-package-resolution.test.ts": 1798, - "packages/test-agent/tests/native-launch.test.ts": 24490, - "packages/test-agent/tests/net.test.ts": 110, - "packages/test-agent/tests/profile.test.ts": 321, - "packages/test-agent/tests/protocol.test.ts": 61, - "packages/test-agent/tests/provider.test.ts": 405, - "packages/test-agent/tests/public-api.test.ts": 476, - "packages/test-agent/tests/route-slot.test.ts": 59, - "packages/test-agent/tests/smoke.test.ts": 15508, - "packages/test-agent/tests/template.test.ts": 295, - "packages/test-agent/tests/worker-lifecycle.test.ts": 9251, + "packages/test-agent/tests/cross-package-resolution.test.ts": 1762, + "packages/test-agent/tests/native-launch.test.ts": 23529, + "packages/test-agent/tests/net.test.ts": 104, + "packages/test-agent/tests/profile.test.ts": 302, + "packages/test-agent/tests/protocol.test.ts": 57, + "packages/test-agent/tests/provider.test.ts": 383, + "packages/test-agent/tests/public-api.test.ts": 467, + "packages/test-agent/tests/route-slot.test.ts": 58, + "packages/test-agent/tests/smoke.test.ts": 15120, + "packages/test-agent/tests/template.test.ts": 298, + "packages/test-agent/tests/worker-lifecycle.test.ts": 8954, "packages/test-support/tests/journal.test.ts": 41, - "packages/testing/tests/agent-composition.test.ts": 610, - "packages/testing/tests/assert-throws.test.ts": 2263, + "packages/testing/tests/agent-composition.test.ts": 596, + "packages/testing/tests/assert-throws.test.ts": 2134, "packages/testing/tests/assert.test.ts": 56, - "packages/testing/tests/assertions.test.ts": 2965, - "packages/testing/tests/boundary-contract.test.ts": 1659, - "packages/testing/tests/cli.test.ts": 4908, - "packages/testing/tests/execution-harness.test.ts": 6397, - "packages/testing/tests/replay.test.ts": 2206, - "packages/testing/tests/smoke.test.ts": 1784, - "packages/testing/tests/test-component.test.ts": 2982, - "packages/testing/tests/testing-activation.test.ts": 2597, - "packages/testing/tests/testing-boundary.test.ts": 1088, - "packages/testing/tests/testing-mode.test.ts": 3165, - "packages/testing/tests/use-testing.test.ts": 1880, + "packages/testing/tests/assertions.test.ts": 2821, + "packages/testing/tests/boundary-contract.test.ts": 1475, + "packages/testing/tests/cli.test.ts": 4823, + "packages/testing/tests/execution-harness.test.ts": 6332, + "packages/testing/tests/replay.test.ts": 2094, + "packages/testing/tests/smoke.test.ts": 1859, + "packages/testing/tests/test-component.test.ts": 2875, + "packages/testing/tests/testing-activation.test.ts": 2519, + "packages/testing/tests/testing-boundary.test.ts": 1070, + "packages/testing/tests/testing-mode.test.ts": 3021, + "packages/testing/tests/use-testing.test.ts": 1767, "packages/web/tests/assets.test.ts": 30, - "packages/web/tests/client-logic.test.ts": 37, - "packages/web/tests/compile.test.ts": 997, - "packages/web/tests/component.test.ts": 1441, - "packages/web/tests/declaration.test.ts": 649, - "packages/web/tests/document.test.ts": 791, - "packages/web/tests/elicitation.test.ts": 559, - "packages/web/tests/live-form.test.ts": 510, - "packages/web/tests/markdown.test.ts": 137, - "packages/web/tests/opener.test.ts": 508, - "packages/web/tests/page.test.ts": 32, - "packages/web/tests/responder.test.ts": 832, - "packages/web/tests/server-lifecycle.test.ts": 632, - "packages/web/tests/server.test.ts": 1031, - "packages/workflow/tests/generated-observations.test.ts": 533, - "packages/workflow/tests/retained-run.test.ts": 1282, - "packages/workflow/tests/service-denial.test.ts": 58, - "packages/workflow/tests/workflow-bundle.test.ts": 695, - "packages/workflow/tests/workflow-definition.test.ts": 348, - "packages/workflow/tests/workflow-fork.test.ts": 301, - "packages/workflow/tests/workflow-run.test.ts": 1066, - "packages/workflow/tests/workspace-effect.test.ts": 1462, - "scripts/tests/adapter-vendor.test.ts": 157, - "scripts/tests/bootstrap-npm-package.test.ts": 5374, - "scripts/tests/bump-version.test.ts": 88, - "scripts/tests/changed-paths.test.ts": 39, - "scripts/tests/ci-workflow.test.ts": 1886, - "scripts/tests/consumer-cycle.test.ts": 105, - "scripts/tests/filesystem-contract-workflow.test.ts": 82, - "scripts/tests/main-green.test.ts": 292, - "scripts/tests/main-health.test.ts": 107, - "scripts/tests/no-module-scoped-registry.test.ts": 856, - "scripts/tests/no-redundant-test-scope.test.ts": 4891, - "scripts/tests/no-sync-filesystem.test.ts": 4519, - "scripts/tests/no-yield-in-finally.test.ts": 884, - "scripts/tests/oxlint-policy.test.ts": 10422, - "scripts/tests/packaged-document.test.ts": 75, - "scripts/tests/prefer-effection-operation.test.ts": 5919, - "scripts/tests/prefer-effection-result.test.ts": 4091, - "scripts/tests/publish-workflow-membership.test.ts": 94, - "scripts/tests/review-infrastructure.test.ts": 1854, - "scripts/tests/runtime-exclusions.test.ts": 326, - "scripts/tests/runtime-tests.test.ts": 80, - "scripts/tests/scope-bound-event-registration.test.ts": 4515, - "scripts/tests/staged-write.test.ts": 69, - "scripts/tests/test-file-discovery.test.ts": 248, - "scripts/tests/test-shards.test.ts": 47, - "scripts/tests/test-weights.test.ts": 206, - "scripts/tests/tracked.test.ts": 39, - "scripts/tests/verify-coordinator.test.ts": 225, - "scripts/tests/version-lockstep.test.ts": 125, - "scripts/tests/web-client-module.test.ts": 96, - "scripts/tests/workspace.test.ts": 70 + "packages/web/tests/client-logic.test.ts": 73, + "packages/web/tests/compile.test.ts": 899, + "packages/web/tests/component.test.ts": 1400, + "packages/web/tests/declaration.test.ts": 620, + "packages/web/tests/document.test.ts": 744, + "packages/web/tests/elicitation.test.ts": 526, + "packages/web/tests/live-form.test.ts": 479, + "packages/web/tests/markdown.test.ts": 135, + "packages/web/tests/opener.test.ts": 464, + "packages/web/tests/page.test.ts": 33, + "packages/web/tests/responder.test.ts": 806, + "packages/web/tests/server-lifecycle.test.ts": 615, + "packages/web/tests/server.test.ts": 974, + "packages/workflow/tests/generated-observations.test.ts": 515, + "packages/workflow/tests/retained-run.test.ts": 1176, + "packages/workflow/tests/service-denial.test.ts": 67, + "packages/workflow/tests/workflow-bundle.test.ts": 678, + "packages/workflow/tests/workflow-definition.test.ts": 334, + "packages/workflow/tests/workflow-fork.test.ts": 296, + "packages/workflow/tests/workflow-run.test.ts": 1058, + "packages/workflow/tests/workspace-effect.test.ts": 1372, + "scripts/tests/adapter-vendor.test.ts": 149, + "scripts/tests/bootstrap-npm-package.test.ts": 5126, + "scripts/tests/bump-version.test.ts": 84, + "scripts/tests/changed-paths.test.ts": 40, + "scripts/tests/ci-workflow.test.ts": 1841, + "scripts/tests/consumer-cycle.test.ts": 107, + "scripts/tests/filesystem-contract-workflow.test.ts": 78, + "scripts/tests/main-green.test.ts": 286, + "scripts/tests/main-health.test.ts": 97, + "scripts/tests/no-module-scoped-registry.test.ts": 853, + "scripts/tests/no-redundant-test-scope.test.ts": 4848, + "scripts/tests/no-sync-filesystem.test.ts": 4429, + "scripts/tests/no-yield-in-finally.test.ts": 865, + "scripts/tests/oxlint-policy.test.ts": 10340, + "scripts/tests/packaged-document.test.ts": 78, + "scripts/tests/prefer-effection-operation.test.ts": 5942, + "scripts/tests/prefer-effection-result.test.ts": 4046, + "scripts/tests/publish-workflow-membership.test.ts": 96, + "scripts/tests/review-infrastructure.test.ts": 1758, + "scripts/tests/runtime-exclusions.test.ts": 297, + "scripts/tests/runtime-tests.test.ts": 75, + "scripts/tests/scope-bound-event-registration.test.ts": 4413, + "scripts/tests/staged-write.test.ts": 65, + "scripts/tests/test-file-discovery.test.ts": 227, + "scripts/tests/test-shards.test.ts": 44, + "scripts/tests/test-weights.test.ts": 203, + "scripts/tests/tracked.test.ts": 38, + "scripts/tests/verify-coordinator.test.ts": 272, + "scripts/tests/version-lockstep.test.ts": 114, + "scripts/tests/web-client-module.test.ts": 67, + "scripts/tests/workspace.test.ts": 47 } } }