From 24551e83c7780d19eed3a36dc416d7b936b4976d Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Fri, 4 Sep 2026 22:25:33 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Launch=20Codex=20natively=20through?= =?UTF-8?q?=20one=20materialization=20turn=20(#755)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex CLI 0.153.2 writes a thread's rollout only once a turn has completed in it, so a launch that handed over the native UI without one would hand over a name `codex resume` disowns. XMD now spends exactly one turn of its own — `codex-materialization.v1`, fixed bytes, announced before it is spent — and then gets out of the way, which is what lets `codex` join `claude` as an advertised native-launch adapter. The turn is journaled as its own `materialized` phase so a second invocation can see the conversation is already openable and buy nothing. Adds two opt-in real-provider documents and the fixture behind them. The native proof decides whether Codex answered by reconstructing the terminal rather than searching the bytes it emitted. Codex addresses the cursor: it wraps, erases and repaints, so retained output holds text the screen no longer shows and an answer can be on screen in a shape no substring of the stream contains. `terminal-screen.ts` applies the pty's bytes to a screen of the pty's own size and reads that; a stall is now classified from the same screen. `composer-idle` stays what it always was — the terminal's state, never evidence that no answer appeared. What that screen is then asked lives in `codex-surfaces.ts`, because both questions the proof puts to it were wrong in ways only a paid run showed. Exhaustion is now a closed list of complete sentences rather than the substring `usage limit`, which Codex also says when it means the opposite: `You have 2 usage limit resets available` is an account with headroom to spare saying so, and reading it as a refusal blames an environment that never refused. Wording the list does not recognize is not exhaustion, so an unrecognized screen costs an investigation instead of a false claim about the product's environment — only a recognized refusal may report ENVIRONMENT_BLOCKED. Readiness is likewise a question about the screen. Codex draws its composer before it has a model, so a turn typed against arrived bytes can be queued rather than taken; the driver now waits for a composer with no trust dialog over it and no `model: loading` behind it, rechecks after the startup grace, and requires the typed turn to be present in the composer before Enter is reached. Every way that fails returns having typed at most, so no turn is spent and none is accounted for. Forty-four hand-written TUI streams decide what the observer and the classifier do with each shape offline, with no Codex process and no model turn. --- AGENTS.md | 4 +- architecture.md | 4 +- packages/acp/mod.ts | 5 +- packages/acp/src/CodexNativeLaunch.test.md | 601 ++ .../acp/src/CodexZeroNativeTurnExit.test.md | 430 ++ packages/acp/src/native-launch.ts | 146 +- packages/acp/src/provider.ts | 834 ++- packages/acp/src/session-route.ts | 80 +- packages/acp/tests/acceptance.test.ts | 18 +- packages/acp/tests/adapter-protocol.test.ts | 68 + packages/acp/tests/codex-surfaces.test.ts | 1175 ++++ .../fixtures/codex-native-launch-proof.ts | 2296 +++++++ .../fixtures/codex-native-to-acp-prompt.md | 16 + packages/acp/tests/fixtures/codex-surfaces.ts | 693 +++ .../tests/fixtures/fake-codex-app-server.cjs | 25 + .../acp/tests/fixtures/terminal-screen.ts | 1087 ++++ packages/acp/tests/native-launch.test.ts | 996 ++- packages/acp/tests/provider.test.ts | 114 +- packages/acp/tests/session-route.test.ts | 118 +- packages/acp/tests/terminal-screen.test.ts | 706 +++ packages/acp/vendor/adapters/MANIFEST.json | 8 +- packages/acp/vendor/adapters/PROVENANCE.md | 29 +- .../agentclientprotocol-codex-acp-1.6.2.tgz | Bin 208429 -> 208660 bytes .../vendor/adapters/generated/snapshots.ts | 5534 +++++++++-------- packages/core/mod.ts | 3 + packages/core/src/agent/checkpoint.ts | 21 + packages/core/src/agent/journal.ts | 44 +- packages/core/src/agent/launch-authority.ts | 101 +- packages/core/src/agent/launch-journal.ts | 396 +- packages/core/src/agent/launch-owner.ts | 22 +- packages/core/src/agent/launch.ts | 140 +- packages/core/src/expand.ts | 7 +- packages/core/src/terminal/pane-launcher.ts | 6 + .../core/tests/agent-session-launch.test.ts | 537 +- packages/runtime/launcher.ts | 36 + packages/runtime/mod.ts | 3 +- scripts/tests/adapter-vendor.test.ts | 18 +- specs/acp-client-spec.md | 38 +- specs/native-agent-session-launch-spec.md | 120 +- 39 files changed, 13348 insertions(+), 3131 deletions(-) create mode 100644 packages/acp/src/CodexNativeLaunch.test.md create mode 100644 packages/acp/src/CodexZeroNativeTurnExit.test.md create mode 100644 packages/acp/tests/codex-surfaces.test.ts create mode 100644 packages/acp/tests/fixtures/codex-native-launch-proof.ts create mode 100644 packages/acp/tests/fixtures/codex-native-to-acp-prompt.md create mode 100644 packages/acp/tests/fixtures/codex-surfaces.ts create mode 100644 packages/acp/tests/fixtures/terminal-screen.ts create mode 100644 packages/acp/tests/terminal-screen.test.ts diff --git a/AGENTS.md b/AGENTS.md index 5d324ab6..2118630d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -556,8 +556,8 @@ sees. Delivering an accepted plan as a focused, verified change. An Opus model acts in this role unless the task assigns another. Running this target prepares the -session from the contract below and hands you Claude's own interactive UI for -it. +session from the contract below and hands you the selected agent's own +interactive UI for it. diff --git a/architecture.md b/architecture.md index c484d854..9b62ba78 100644 --- a/architecture.md +++ b/architecture.md @@ -102,7 +102,7 @@ Existing documents and code get aligned to this section retroactively. | host profile | a named production assembly a trusted host offers a testing harness — `run` today — reused after argument parsing rather than restated; a host that cannot offer one refuses rather than approximating it | | execution outcome | what one nested document execution ended as, as the harness publishes it: a settlement carrying the exact completion `Result`, or, for a workflow attempt, a durable suspension carrying the run and suspension identities. A suspension is neither success nor failure | | trusted host | the code that decides what an execution is for — a CLI entrypoint or a workflow runner — as distinct from the document, the components it expands, and the middleware packages composed around it | -| native session launch | preparing one durable coding-agent session from a document and then handing its provider's own interactive UI the terminal for that exact session. It is session construction followed by an ownership handoff, not a chat frontend: XMD performs no model turn, proxies no native turn, and journals no native conversation | +| native session launch | preparing one durable coding-agent session from a document and then handing its provider's own interactive UI the terminal for that exact session. It is session construction followed by an ownership handoff, not a chat frontend: XMD proxies no native turn and journals no native conversation, and takes no model turn of its own beyond the one materialization turn an adapter may require to make a freshly created conversation resumable | | provider-native session identity | the identity an agent asserts for its own durable session state, distinct from the logical XMD session key and from the ACP/ACPX record identity. Only an asserted one crosses a handoff; XMD never infers that a string a native CLI would accept is one | | pending session placement | an exact provider-issued `Session` capability carrying where a logical session will live and which agent it is compatible with, and nothing more: no construction route, no provider handle, no resumable identity. A fresh `` produces one, and the first consuming operation chooses how the session is constructed | | session materialization | the transition that makes a placement's chosen route and its backend history resumable. ACP-first materialization happens only when the backend reports that it accepted the session's first turn; client-native materialization is the native launch's existing retained construction. Nothing else promotes a placement — not a returning ensure, a first output, a terminal result, a checkpoint token, an error code or a diagnostic | @@ -4158,7 +4158,7 @@ Status is measured against main. | nested run-profile Agent and elicitation declarations | lets one `` declare one child-scoped `` scenario set and one non-delegating `` matcher set; only frozen test data crosses the harness request, the trusted host constructs both providers inside the isolated child, siblings share no session or provider state, ordinary component shadowing remains in force, and the child journal retains only the selected Prompt and Elicit components' ordinary results | built on the #641 stack | | `Config` run deadline / exec default / Fetch default / verbosity | three independently owned contextual timeouts, absent unless configured, each read by exactly one consumer, and contextual verbosity — a boolean that is false unless configured, seeded by the command line and overridable for a lexical subtree, bounding nothing and owning no authority | built on this stack | | terminal grid (`` / ``) | replaces the root foreground terminal with one provider-neutral composite whose statically declared direct panes begin concurrently, stay independently interactive, preserve their final statuses until the reader closes the composite, and tear down completely before document execution continues. A paired pane expands isolated document flow; a self-closing pane runs the host's default shell. The grid owns one foreground-terminal lease, each pane owns a separate pane-terminal lease, and a pane-scoped native launcher lets `` use that pane without weakening the independent Agent session coordinator. The launcher terminates at the composite's required provider-neutral pane-execution operation; the authored ordinal stays in core's live closure, and the native request carries no pane identity. Core validates the complete row-major layout before provider contact, attaches only after every pane is ready, contains post-attach pane failures until close, and records the ordered provider-neutral outcomes. Completed replay contacts no terminal or Agent provider; partial replay rebuilds a fresh composite, restores completed panes as statuses, and continues incomplete pane effects under their existing durable identities. Provider commands, sockets, process topology and layout identifiers remain live-only inside the provider closure | defined for #717; #726 proves the persistent tmux pane-worker topology and its observable teardown boundary on macOS; structure and layout built in #729, provider-neutral execution and durability in #730, pane claim admission and native-launch middleware in #731; the required composite pane-execution endpoint is specified and implemented in #732, which is what gives a pane's `` that pane's terminal rather than the root's; the controlled non-tmux provider remains the authority for core lifecycle semantics; the tmux provider is built in #732 for the Deno and compiled foreground hosts — one invocation-private server per grid, authenticated persistent pane workers carrying exact argv, cwd and environment outside tmux parsing, explicit row-major layout imposed by pane swaps, a required composite `launch()` that gives a pane's `` its own terminal rather than the root's, and one ordered teardown that proves worker quiescence, channel closure and server disappearance before the document continues; its evidence uses a fake tmux with real workers and real sockets, and real tmux behaviour on macOS remains #726's; Node and Bun catalog and validate the same grids and install neither the provider nor the process observer, refusing before pane start | -| native session launch (`` / `launchAgentSession()`) | prepares one durable coding-agent session from the rendered body of `` and hands the provider's native UI the terminal for that exact session, then continues the document after it exits. The body renders completely first and only what it rendered crosses as the instruction layer; the launch performs no model turn; at the root it takes the run's foreground-terminal lease before an agent is resolved, while a launch inside `` takes that pane's lease through its pane-scoped native launcher. A host with no applicable terminal refuses without probing for an installed CLI. A session is constructed once, by one of two mechanisms, and its create-once construction route says which. Where the provider returns the identity, the ACPX provider creates the session, installs the layer at creation, releases ACP ownership before the spawn, and marks its handle stale so a later `` reattaches. Where the adapter names its own sessions, it allocates the identity inside ownership before any process exists, the native process creates the session under that name from a private mode-0600 instruction file, and ACP creates nothing — the instruction text reaches neither argv nor environment, and the file is removed on success, failure and cancellation alike while ownership is still held. Neither route converts into the other, and which one governs is chosen by the first operation that consumes the placement rather than by the `` that made it: a fresh `` publishes no route and establishes nothing, so a `` nested inside one constructs the session it placed, while a first subscribed `` publishes ACP-first before it ensures and keeps that account even if the turn that follows is never accepted. An established route is validated eagerly by a later ``, and a launch meeting a published ACP-first route refuses before an identity exists. A `` or `` meeting a bound client-allocated route attaches under the route's exact identity; a legacy unbound route or an unavailable attachment capability refuses before a turn and creates no substitute conversation. Phases are retained as `agent_session_launch` records under one expansion identity — `prepared` before ownership is released, then `detached`, then `exited` — so a completed replay launches nothing, a replay holding only `prepared` proves the handoff never began and may still create under the retained identity, and one holding `detached` resumes and never falls back. The public route carries an opaque one-use launch request and answers nothing; authority to run and retain a phase is delivered to the installed provider directly, so neither a returned completion nor a rebuilt request authors a launch. Every operation that can act on an advertised session takes exclusive ownership under one natural key first, through a coordinator the host built and passed in; contention refuses instead of queueing, and an owner that never proved it stopped leaves a recovery tombstone. A host that cannot say who owns a session refuses every advertised operation, and one that cannot say how a session was constructed additionally refuses an agent that names its own — before any provider effect. Every private setup or child-creation failure is normalized to `process-creation-failed` with fixed provider-owned text, carrying no path, argv, environment or host message. No launch path discards persistent provider state. A client-allocated session is bound to one executable build: the build is observed inside ownership before an identity is allocated, the binding is published with the V2 route and retained beside the prepared record, the native child runs the exact observed path in place of the launcher name, and every later create, resume, attachment and incomplete replay reobserves and compares before a process, an ensure or a turn. A `` or `` meeting a bound client-native route attaches to it: it reobserves the build, requires any retained provider arrangement to assert that same conversation, calls ensure with the route identity as `resumeSessionId`, and requires the provider to report that identity before a turn — refusing on missing capability, build drift, missing history or a differing assertion without creating a substitute conversation. ACP runtimes are partitioned by resolved agent command and binding, each handle is closed by the partition that created it, and a bound partition is torn down when its last handle closes. A legacy V1 client-native route keeps exactly the released native-only behavior and never attaches | built on the #517 stack, extended by the #519 and #561 stacks; Deno and the compiled binary assemble the host — coordinator, route store and executable observer — and Node and Bun keep the same advertised names while assembling none of it, so every advertised operation refuses before provider work; `claude` is advertised for native launch after passing the client-allocated gate at Claude Code 2.1.241 on macOS arm64 (#520) and separately for client-native attachment after passing the native-to-ACP marker gate (#561), and Codex remains unadvertised because nothing has run its provider-returned claims against an installed Codex; `Agent.AddDir` is unbuilt | +| native session launch (`` / `launchAgentSession()`) | prepares one durable coding-agent session from the rendered body of `` and hands the provider's native UI the terminal for that exact session, then continues the document after it exits. The body renders completely first and only what it rendered crosses as the instruction layer; the launch performs no model turn except an adapter's declared materialization turn, which runs at most once, only for a conversation the launch just created, and only with that adapter's fixed prompt; at the root it takes the run's foreground-terminal lease before an agent is resolved, while a launch inside `` takes that pane's lease through its pane-scoped native launcher. A host with no applicable terminal refuses without probing for an installed CLI. A session is constructed once, by one of two mechanisms, and its create-once construction route says which. Where the provider returns the identity, the ACPX provider creates the session, installs the layer at creation, releases ACP ownership before the spawn, and marks its handle stale so a later `` reattaches. Where the adapter names its own sessions, it allocates the identity inside ownership before any process exists, the native process creates the session under that name from a private mode-0600 instruction file, and ACP creates nothing — the instruction text reaches neither argv nor environment, and the file is removed on success, failure and cancellation alike while ownership is still held. Neither route converts into the other, and which one governs is chosen by the first operation that consumes the placement rather than by the `` that made it: a fresh `` publishes no route and establishes nothing, so a `` nested inside one constructs the session it placed, while a first subscribed `` publishes ACP-first before it ensures and keeps that account even if the turn that follows is never accepted. An established route is validated eagerly by a later ``, and a launch meeting a published ACP-first route refuses before an identity exists. A `` or `` meeting a bound client-allocated route attaches under the route's exact identity; a legacy unbound route or an unavailable attachment capability refuses before a turn and creates no substitute conversation. Phases are retained as `agent_session_launch` records under one expansion identity — `prepared` before ownership is released, then `materialized` where an adapter required a materialization turn, then `detached`, then `exited` — so a completed replay launches nothing, a replay holding only `prepared` proves the handoff never began and may still create under the retained identity, and one holding `detached` resumes and never falls back. The public route carries an opaque one-use launch request and answers nothing; authority to run and retain a phase is delivered to the installed provider directly, so neither a returned completion nor a rebuilt request authors a launch. Every operation that can act on an advertised session takes exclusive ownership under one natural key first, through a coordinator the host built and passed in; contention refuses instead of queueing, and an owner that never proved it stopped leaves a recovery tombstone. A host that cannot say who owns a session refuses every advertised operation, and one that cannot say how a session was constructed additionally refuses an agent that names its own — before any provider effect. Every private setup or child-creation failure is normalized to `process-creation-failed` with fixed provider-owned text, carrying no path, argv, environment or host message. No launch path discards persistent provider state. A session whose adapter pins a build is bound to that one build, whichever side named it: the build is observed inside ownership before an identity is allocated or a provider session is created, the binding is published with the bound route — V2 for client-native, V3 for ACP-first — and retained beside the prepared record, the native child runs the exact observed path in place of the launcher name, and every later create, resume, attachment and incomplete replay reobserves and compares before a process, an ensure or a turn. A `` or `` meeting a bound client-native route attaches to it: it reobserves the build, requires any retained provider arrangement to assert that same conversation, calls ensure with the route identity as `resumeSessionId`, and requires the provider to report that identity before a turn — refusing on missing capability, build drift, missing history or a differing assertion without creating a substitute conversation. ACP runtimes are partitioned by resolved agent command and binding, each handle is closed by the partition that created it, and a bound partition is torn down when its last handle closes. A legacy V1 client-native route keeps exactly the released native-only behavior and never attaches, and a legacy V1 ACP-first route refuses an agent whose adapter binds a build rather than gaining the current executable | built on the #517 stack, extended by the #519 and #561 stacks; Deno and the compiled binary assemble the host — coordinator, route store and executable observer — and Node and Bun keep the same advertised names while assembling none of it, so every advertised operation refuses before provider work; `claude` is advertised for native launch after passing the client-allocated gate at Claude Code 2.1.241 on macOS arm64 (#520) and separately for client-native attachment after passing the native-to-ACP marker gate (#561); `codex` is advertised for native launch after passing the provider-returned gate at `codex-cli 0.153.2` on macOS arm64 with the vendored `@agentclientprotocol/codex-acp` 1.6.2 snapshot that asserts the App Server thread on the response `_meta` (#755), which costs one fixed `codex-materialization.v1` turn because Codex writes the rollout `codex resume ` reads at a thread's first turn — a launch outside that tuple refuses before an identity exists, and reading the ACP session id as the native identity stays forbidden; `Agent.AddDir` is unbuilt | | `` | performs one XMD-mediated HTTP read through contextual `API.Fetch`, admitting the whole request before transport, and retains the normalized request and the detached response as one `fetch` durable observation; capture decides whether a status is data or a failure, and the trusted host's destination ceiling sits below the component | built on the #456 stack; a generated fragment may name the pinned identity only for a request the trusted host stated exactly, on the #369 stack | | `API.Files` | routes every document filesystem operation to the installed provider, with no host default and structural failure data. Its mandatory semantic operations include `ensureDirectory`, which recursively creates or adopts one directory and returns Unit; separately loaded copies compose through the stable Api name | built on the #227 stack; directory ensure added by #643 | | `` | removes one file the document names, inside the contextual working directory. An ordinary overridable core default with a closed schema of one required non-empty `path`, **self-closing only** — a paired spelling never enters its body, because the component declares its one form and canonical invocation-form dispatch enters that body only for the form the scan recorded, before `Env.cwd` is read and before the provider is reached. Neither the composable `Component.hasContent()` chain nor a method on whatever object a caller handed over takes part. It renders the empty string, declares no `returns` and hands back no receipt, so an ordinary `as` captures that empty string; absence is the same success, so deleting a path twice succeeds twice. One regular file or one final symbolic link goes — the link rather than its target, inside or outside — and every directory is refused, an empty one included. Empty, absolute, lexically escaping and parent-link-escaping paths are refused before any removal, and a printed error names only the path the document wrote. One semantic `API.Files.deleteFile` call and no filesystem access of its own; under a workflow run it is one `workspace_file` effect retaining `{ kind: "deleted" }`. The standard Deno workflow profile admits it to generated XMD as the exact self-closing identity `@executablemd/core#File.Delete`, third in the write table, where it performs that same ordinary effect and contributes no evaluator result | built on the #567 stack | diff --git a/packages/acp/mod.ts b/packages/acp/mod.ts index c39c348b..5e46f60a 100644 --- a/packages/acp/mod.ts +++ b/packages/acp/mod.ts @@ -43,8 +43,10 @@ export { knownNativeAdapters, nativeAdapterFor, } from "./src/native-launch.ts"; -export { allocatesIdentity } from "./src/native-launch.ts"; +export { allocatesIdentity, bindsBuild } from "./src/native-launch.ts"; export type { + BoundProviderReturnedAdapter, + BuildBoundAdapter, ClientAllocatedAdapter, NativeAdapter, NativeBinding, @@ -109,4 +111,5 @@ export type { AgentSessionRouteStore, AgentSessionRouteV1, AgentSessionRouteV2, + AgentSessionRouteV3, } from "./src/session-route.ts"; diff --git a/packages/acp/src/CodexNativeLaunch.test.md b/packages/acp/src/CodexNativeLaunch.test.md new file mode 100644 index 00000000..fee0096e --- /dev/null +++ b/packages/acp/src/CodexNativeLaunch.test.md @@ -0,0 +1,601 @@ +# A real Codex session, prepared, materialized and then rejoined + +`xmd run AGENTS.md#Implementor --default-agent codex` is the whole product for +Codex: it prepares one Codex conversation from this repository's own Implementor +contract and hands you Codex's own interactive UI for that exact conversation. +This document runs that command — the literal one, through the built binary — +and asks the two questions that decide whether the conversation is real. + +**Did the prepared contract govern the first thing you say?** The first native +turn asks for the opening sentence of the role contract without ever sending +that sentence. A conversation that did not receive the prepared layer cannot +produce it, and neither can an agreeable model, because nothing in the question +says what the sentence is. + +**Is what you said natively part of the same conversation?** That first native +turn also plants a one-time marker. A second, independent invocation then +rejoins the same conversation over ACP — from an authored document that has +never seen the marker — and asks for it. Recovering it is what makes the native +UI and the ACP session one conversation rather than two wearing the same name. + +Getting there costs one turn nobody asked for. Codex CLI 0.153.2 does not write +a conversation anywhere `codex resume` can find it until a turn has completed in +it, so XMD spends exactly one of its own first: `codex-materialization.v1`, +whose only job is to make the conversation openable. That the operator can then +leave without ever speaking — and come back without buying a second one — is the +subject of `CodexZeroNativeTurnExit.test.md`, so that question can be corrected +without respending the three turns this document is authorized to spend. + +## What this costs, and what it touches + +Three real model turns against the operator's own Codex credentials: XMD's own +materialization turn, one native turn that answers from the prepared layer and +plants the marker, and one ACP turn that rejoins and recalls it. Nothing else is +sent. The materialization turn's bytes are fixed by the product, are shown to +the operator before they are spent, and carry no path, identity, environment or +authored content. + +It is opt-in twice over. Without `XMD_CODEX_NATIVE_PROOF=1` the fixture refuses +before starting any Codex process; without a separate +`XMD_CODEX_MODEL_TURNS_AUTHORIZED=3` it refuses before spending a turn. It also +refuses on a machine outside the frozen compatibility tuple below, because the +finding this feature rests on is a fact about one Codex build. + +Everything sent besides those three turns is terminal control: Codex's own +directory trust dialog is answered at most once with the choice it pre-selects, +and the exit is two Ctrl-C bytes. The command runs with an operator's +environment rather than this process's, so anything an enclosing agent session +exported is dropped. + +The command runs in a fresh temporary directory holding a byte-for-byte copy of +this repository's own `AGENTS.md` and `.agents/implementor.md`, so the literal +production target resolves without creating Codex state for the repository. +Afterwards the conversation is removed through Codex's own +`codex delete --force `, naming the exact identity this run created. +`CODEX_HOME` is left alone — relocating it de-authenticates Codex — and nothing +beneath Codex's configuration, history or rollout files is ever opened. + +Run it with: + +```sh +XMD_CODEX_NATIVE_PROOF=1 XMD_CODEX_MODEL_TURNS_AUTHORIZED=3 \ + deno task xmd test packages/acp/src/CodexNativeLaunch.test.md --raw +``` + +## What a verdict may say + +The schema is the disclosure boundary, not a convenience. It accepts versions +and digests of public executables, the session identity and who chose it, +booleans, counts, phase and failure classes, usage field *names*, and cleanup +outcomes. The role contract's text, the history marker, the materialization +reply, raw terminal output, argument vectors, the environment and private paths +have nowhere to go in it, so a fixture that tried to report one would fail this +document. + + + +```json +{ + "type": "object", + "additionalProperties": false, + "required": [ + "mode", "verdict", "authorized", "turnsAuthorized", "authorizedTurnBudget", + "ran", "refusal", "detail", + "codexVersion", "codexDigest", "platform", "architecture", + "adapterPackage", "adapterVersion", "adapterDigest", "compatibilityTupleFrozen", + "target", "projectCopyVerified", "implementorMarkerRendered", "siblingMarkersRendered", + "nativeSessionId", "identityProvenance", "reentryNativeSessionId", + "substitutedIdentity", "routeConverted", + "firstXmdExitCode", "secondXmdExitCode", "instructionChannel", + "modelTurns", "materializationTurns", "nativeUserTurns", "acpReattachTurns", + "answerObserverInvocations", "conversationInputByteCount", + "consentInputBytes", "consentSurfaces", "exitControlBytes", + "reentryConsentInputBytes", "reentryConsentSurfaces", "reentryExitControlBytes", + "inheritedAgentMarkersRemoved", + "noticedBeforeSpending", "noticedAfterSpending", + "openingSentenceExact", "markerRecovered", "acpDocumentCarriesMarker", + "answerSurface", "outcome", + "materialization", "route", "journal", "cleanup", "privateStateInspected" + ], + "properties": { + "mode": { "type": "string" }, + "verdict": { "enum": ["PASS", "REFUSED", "ENVIRONMENT_BLOCKED", "PRODUCT_FAILED", "HARNESS_FAILED"] }, + "authorized": { "type": "boolean" }, + "turnsAuthorized": { "type": "boolean" }, + "authorizedTurnBudget": { "type": "integer" }, + "ran": { "type": "boolean" }, + "refusal": { "type": "string" }, + "detail": { "type": "string" }, + "codexVersion": { "type": "string" }, + "codexDigest": { "type": "string" }, + "platform": { "type": "string" }, + "architecture": { "type": "string" }, + "adapterPackage": { "type": "string" }, + "adapterVersion": { "type": "string" }, + "adapterDigest": { "type": "string" }, + "compatibilityTupleFrozen": { "type": "boolean" }, + "target": { "type": "string" }, + "projectCopyVerified": { "type": "boolean" }, + "implementorMarkerRendered": { "type": "boolean" }, + "siblingMarkersRendered": { "type": "integer" }, + "nativeSessionId": { "type": "string" }, + "identityProvenance": { "type": "string" }, + "reentryNativeSessionId": { "type": "string" }, + "substitutedIdentity": { "type": "boolean" }, + "routeConverted": { "type": "boolean" }, + "firstXmdExitCode": { "type": "integer" }, + "secondXmdExitCode": { "type": "integer" }, + "instructionChannel": { "type": "string" }, + "modelTurns": { "type": "integer" }, + "materializationTurns": { "type": "integer" }, + "nativeUserTurns": { "type": "integer" }, + "acpReattachTurns": { "type": "integer" }, + "answerObserverInvocations": { "type": "integer" }, + "conversationInputByteCount": { "type": "integer" }, + "consentInputBytes": { "type": "string" }, + "consentSurfaces": { "type": "array", "items": { "type": "string" } }, + "exitControlBytes": { "type": "string" }, + "reentryConsentInputBytes": { "type": "string" }, + "reentryConsentSurfaces": { "type": "array", "items": { "type": "string" } }, + "reentryExitControlBytes": { "type": "string" }, + "inheritedAgentMarkersRemoved": { "type": "integer" }, + "noticedBeforeSpending": { "type": "boolean" }, + "noticedAfterSpending": { "type": "boolean" }, + "openingSentenceExact": { "type": "boolean" }, + "markerRecovered": { "type": "boolean" }, + "acpDocumentCarriesMarker": { "type": "boolean" }, + "answerSurface": { "type": "string" }, + "outcome": { "enum": ["same-identity", "no-session", "unresolved"] }, + "privateStateInspected": { "type": "boolean" }, + "materialization": { + "type": "object", + "additionalProperties": false, + "required": [ + "promptVersion", "requestIdStable", "promptExact", "turnNamed", "turnProvider", + "durationReported", "responsePresent", "stopReason", + "reportedUsageFields", "unreportedUsageFields", "failureClasses" + ], + "properties": { + "promptVersion": { "type": "string" }, + "requestIdStable": { "type": "boolean" }, + "promptExact": { "type": "boolean" }, + "turnNamed": { "type": "boolean" }, + "turnProvider": { "type": "string" }, + "durationReported": { "type": "boolean" }, + "responsePresent": { "type": "boolean" }, + "stopReason": { "type": "string" }, + "reportedUsageFields": { "type": "array", "items": { "type": "string" } }, + "unreportedUsageFields": { "type": "array", "items": { "type": "string" } }, + "failureClasses": { "type": "array", "items": { "type": "string" } } + } + }, + "route": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "provider", "buildVersion", "buildDigest"], + "properties": { + "kind": { "type": "string" }, + "provider": { "type": "string" }, + "buildVersion": { "type": "string" }, + "buildDigest": { "type": "string" } + } + }, + "journal": { + "type": "object", + "additionalProperties": false, + "required": [ + "provider", "agent", "launcher", "provenance", "nativeSessionId", "cwdIsProject", + "sessionState", "instructionsDigestPresent", + "firstPhases", "secondPhases", "failureClasses" + ], + "properties": { + "provider": { "type": "string" }, + "agent": { "type": "string" }, + "launcher": { "type": "string" }, + "provenance": { "type": "string" }, + "nativeSessionId": { "type": "string" }, + "cwdIsProject": { "type": "boolean" }, + "sessionState": { "type": "array", "items": { "type": "string" } }, + "instructionsDigestPresent": { "type": "boolean" }, + "firstPhases": { "type": "array", "items": { "type": "string" } }, + "secondPhases": { "type": "array", "items": { "type": "string" } }, + "failureClasses": { "type": "array", "items": { "type": "string" } } + } + }, + "cleanup": { + "type": "object", + "additionalProperties": false, + "required": [ + "liveChildren", "journalsRemoved", "routeRecordsRemoved", + "sessionDeleteOutcome", "temporaryRootRemoved" + ], + "properties": { + "liveChildren": { "type": "integer" }, + "journalsRemoved": { "type": "boolean" }, + "routeRecordsRemoved": { "type": "boolean" }, + "sessionDeleteOutcome": { "enum": ["deleted", "nothing-to-delete", "failed"] }, + "temporaryRootRemoved": { "type": "boolean" } + } + } + } +} +``` + + + + +```sh timeout=40min exec as="run" +deno run --allow-all --frozen packages/acp/tests/fixtures/codex-native-launch-proof.ts native-launch +``` + +The fixture returns a structured verdict even when the product fails one of the +two questions, so a nonzero exit means the harness broke rather than a question +being answered. + + + + +{run.stdout} + + +The whole result is shown before anything is judged. It carries no contract +text, no marker and no transcript content — `openingSentenceExact` and +`markerRecovered` are the only things about the conversation that crossed the +fixture boundary. + +```json +{run.stdout} +``` + +## Nothing private was read + +True whether or not the proof was opted into. Codex's rollout files are the one +place that could answer this document cheaply, and they are exactly what a proof +of the public contract may not open. + + + +## What the verdict decides + +The fixture classifies its own outcome, and that classification — not this +document's arithmetic — chooses what is worth asserting. A run its environment +blocked may have reached the first question and even spent the turn that asks +it, but it establishes neither answer, so demanding both would convict the +product of the environment's failure. Each branch below asserts only what its +verdict makes true, and every authorized verdict other than `PASS` ends by +stopping the document with the fixture's own reason, so a proof that did not +pass says why in one sentence instead of failing on an accounting mismatch that +was never the point. The one outcome that passes without proving anything is the +unarmed refusal: with the opt-ins absent nothing is spent and nothing is +observed, and that zero-turn skip is how this document runs on an ordinary +machine and in CI. + + + + +A pass is a claim about a journey that happened, so the first thing it owes is +that one did. + + + +## The frozen compatibility point + +The materialization turn exists because of a fact about one Codex build. This +records which one, so a later build that stopped needing it — or started needing +something else — cannot pass as this one. + + + + + + + + + + +## Three turns, and only one of them was XMD's + +The authorized budget is three, and the accounts name each one: one +materialization the product spent, one native turn the operator typed, one ACP +turn the second invocation asked. There is no fourth, and there is no second +materialization. + + + + + + + 0} /> + + +Reading an answer off a terminal is something the harness does, and it does it +for the one invocation that submits that native turn and for no other. The +invocations that type nothing reconstruct nothing, so the paths this document +does not test are the paths they already were. + + + +## The operator was told before it was spent + +A billable turn nobody was warned about is a bill, not a feature. The notice +appears on the terminal that acquired the launch before the turn starts, and the +completion summary appears before the native UI opens. + + + + +## The turn was the exact one the contract names + +Its version, its bytes and its request identity are fixed by the product, not +composed at the call site, and the retained preparation named the same request +the retained turn later reported against. + + + + + + + + + + + +Usage is recorded as the provider reported it. Which fields arrived is a fact +about Codex, not about this document, so both lists are shown and only their sum +is fixed: every field is either reported or explicitly unreported, and none is +quietly inferred to be zero. + + + +## The prepared contract governed the first native turn + +The question named neither the sentence nor any file, and the materialization +turn before it said nothing about either. The answer carried the opening +sentence of `.agents/implementor.md` exactly, which only the prepared layer +could have supplied — which also settles that materialization did not consume +the layer on its way past. + + + +## ACP rejoined the conversation the native UI was in + +The marker existed only in that first native turn — never in the instruction +layer, the argument vector, the environment or the session key — and the +document that asked for it does not contain it. It came back behind a prefix +that appeared in no earlier turn, so only an answer could have produced it. + + + + + + + + + +## The identity is the one Codex named + +Codex chose it and told the adapter through its own metadata. XMD never parsed +it out of anything, and never substituted a fresh one when the first was +inconvenient. + + + + +## The production target ran, and its siblings did not + + + + + + +## Route and journal agree + +The route names the construction and the Codex build the tuple above froze, and +nothing else: an ACP-first route deliberately records no conversation identity, +launcher or instruction layer, so those are read from the journal instead. A +route that had converted to anything but `acp-first` would mean the launch +stopped being the one this document describes. + + + + + + + + + + + + + + +One launch, one materialization, one detachment, one exit. The ACP reattachment +is not a launch, so it adds no phases of its own. + + + + + + +## Nothing was left behind + + + + + + + + + + +## The environment stopped it + +Something outside the product prevented an answer. Whether that happened before +the journey started or partway through it decides what may still be believed, so +the two are judged apart rather than together. + + + + +Nothing was launched. No conversation exists, no turn was bought, and the +accounts are all still zero — which is the only thing worth checking about a run +that never began. + + + + + + + + + + + + + + +The journey began and turns were spent, so what the accounts say about them has +to hold even though the questions went unanswered. Every turn is attributable to +one of the three the budget names, and no more were bought than were authorized. +What this branch may not do is demand the three the budget allows, the second +invocation, or either answer: a bound reached is the harness giving up, and +reading that as a product result would convict the product of the environment's +failure. + + + + +A conversation was created, so it still has to be cleaned up. This is the part +of a blocked run that costs money if it is wrong. + + + + + + + + + + + + + + + +## The product answered, and the answer was wrong + +The journey ran and the fixture reached a finding, so the accounts and the +cleanup are the two things that decide whether the finding can be trusted. They +are checked and nothing else is: the finding itself is the fixture's to state. + + + + + + + + + + + + + + +## The harness broke before the product could be judged + +Nothing is asserted here on purpose. A harness that stopped may have left its +own accounts half-written, and an assertion about them would fail first and +report the wrong thing — burying the sentence that says what actually broke. + + + + + + +## It refused before any conversation existed + +The refusal happens before any Codex conversation exists, so a refused run +spends no turn and observes nothing. + + + + + + + + + + + + + + + +Both opt-ins were given, so this run asked to spend turns and was turned away +anyway. That is a refusal the operator has to resolve, not a suite that may go +green around it. + + + + + + +Without both opt-ins this is the ordinary way the document runs: on a developer +machine and in CI, where it must cost nothing and still be read. Reaching here +is the pass. + + + + + + + + + + + + + diff --git a/packages/acp/src/CodexZeroNativeTurnExit.test.md b/packages/acp/src/CodexZeroNativeTurnExit.test.md new file mode 100644 index 00000000..73b706ba --- /dev/null +++ b/packages/acp/src/CodexZeroNativeTurnExit.test.md @@ -0,0 +1,430 @@ +# Leaving a prepared Codex session without saying anything + +A prepared session you never speak in should still be there tomorrow. For Claude +that costs nothing, and `ClaudeZeroTurnExit.test.md` proves it: the session the +launch constructs is openable the moment it exists. + +Codex is not like that. Codex CLI 0.153.2 does not write a conversation anywhere +`codex resume` can find it until a turn has completed in it, so a launch that +handed you the native UI without one would hand you a name Codex disowns. XMD +therefore spends exactly one model turn of its own — `codex-materialization.v1`, +whose only job is to make the conversation openable — and then gets out of the +way. + +That turn is the whole subject of this document, and it asks the two questions +that decide whether it was worth spending. + +**Did one turn actually make the conversation openable?** The launch opens the +native UI, and the operator leaves without typing anything at all. Reaching +Codex's own composer rather than `No saved session found with ID` is the answer. + +**Is it still openable without buying another one?** A second, independent +invocation of the same command re-enters. If it materialized again, the reader +paid twice for something already done, so the durable accounts must retain +exactly one materialization across both invocations. + +Whether the prepared contract governs what you say once you are in there is a +different question, and it lives in `CodexNativeLaunch.test.md` so that this one +can be corrected without ever respending its turns. + +## What this costs, and what it touches + +One real model turn against the operator's own Codex credentials: XMD's own +materialization turn, in the session the first launch constructed. Nothing is +typed into either native UI. The exact bytes of that turn are fixed by the +product, are shown to the operator before they are spent, and carry no path, +identity, environment or authored content. + +It is opt-in twice over. Without `XMD_CODEX_NATIVE_PROOF=1` the fixture refuses +before starting any Codex process; without a separate +`XMD_CODEX_MODEL_TURNS_AUTHORIZED=1` it refuses before spending the turn. It +also refuses on a machine outside the frozen compatibility tuple below, because +the finding this feature rests on is a fact about one Codex build. + +Everything sent besides that turn is terminal control: Codex's own directory +trust dialog is answered at most once with the choice it pre-selects, and the +exit is two Ctrl-C bytes. The command runs with an operator's environment rather +than this process's, so anything an enclosing agent session exported is dropped. + +The command runs in a fresh temporary directory holding a byte-for-byte copy of +this repository's own `AGENTS.md` and `.agents/implementor.md`. Afterwards both +conversations are removed through Codex's own `codex delete --force `, each +named by the exact identity this run created. `CODEX_HOME` +is left alone — relocating it de-authenticates Codex — and nothing beneath +Codex's configuration, history or rollout files is ever opened. + +Run it with: + +```sh +XMD_CODEX_NATIVE_PROOF=1 XMD_CODEX_MODEL_TURNS_AUTHORIZED=1 \ + deno task xmd test packages/acp/src/CodexZeroNativeTurnExit.test.md --raw +``` + +## What a verdict may say + +The schema is the disclosure boundary, not a convenience. It accepts versions +and digests of public executables, the session identity and who chose it, +booleans, counts, phase and failure classes, usage field *names*, and cleanup +outcomes. The role contract's text, the materialization turn's reply, raw +terminal output, argument vectors, the environment and private paths have +nowhere to go in it, so a fixture that tried to report one would fail this +document. + + + +```json +{ + "type": "object", + "additionalProperties": false, + "required": [ + "mode", "verdict", "authorized", "turnsAuthorized", "authorizedTurnBudget", + "ran", "refusal", "detail", + "codexVersion", "codexDigest", "platform", "architecture", + "adapterPackage", "adapterVersion", "adapterDigest", "compatibilityTupleFrozen", + "target", "projectCopyVerified", "implementorMarkerRendered", "siblingMarkersRendered", + "nativeSessionId", "identityProvenance", "reentryNativeSessionId", + "substitutedIdentity", "routeConverted", + "firstXmdExitCode", "secondXmdExitCode", "instructionChannel", + "modelTurns", "materializationTurns", "nativeUserTurns", "acpReattachTurns", + "answerObserverInvocations", "conversationInputByteCount", + "consentInputBytes", "consentSurfaces", "exitControlBytes", + "reentryConsentInputBytes", "reentryConsentSurfaces", "reentryExitControlBytes", + "inheritedAgentMarkersRemoved", + "noticedBeforeSpending", "noticedAfterSpending", + "openingSentenceExact", "markerRecovered", "acpDocumentCarriesMarker", + "answerSurface", "outcome", + "materialization", "route", "journal", "cleanup", "privateStateInspected" + ], + "properties": { + "mode": { "type": "string" }, + "verdict": { "enum": ["PASS", "REFUSED", "ENVIRONMENT_BLOCKED", "PRODUCT_FAILED", "HARNESS_FAILED"] }, + "authorized": { "type": "boolean" }, + "turnsAuthorized": { "type": "boolean" }, + "authorizedTurnBudget": { "type": "integer" }, + "ran": { "type": "boolean" }, + "refusal": { "type": "string" }, + "detail": { "type": "string" }, + "codexVersion": { "type": "string" }, + "codexDigest": { "type": "string" }, + "platform": { "type": "string" }, + "architecture": { "type": "string" }, + "adapterPackage": { "type": "string" }, + "adapterVersion": { "type": "string" }, + "adapterDigest": { "type": "string" }, + "compatibilityTupleFrozen": { "type": "boolean" }, + "target": { "type": "string" }, + "projectCopyVerified": { "type": "boolean" }, + "implementorMarkerRendered": { "type": "boolean" }, + "siblingMarkersRendered": { "type": "integer" }, + "nativeSessionId": { "type": "string" }, + "identityProvenance": { "type": "string" }, + "reentryNativeSessionId": { "type": "string" }, + "substitutedIdentity": { "type": "boolean" }, + "routeConverted": { "type": "boolean" }, + "firstXmdExitCode": { "type": "integer" }, + "secondXmdExitCode": { "type": "integer" }, + "instructionChannel": { "type": "string" }, + "modelTurns": { "type": "integer" }, + "materializationTurns": { "type": "integer" }, + "nativeUserTurns": { "type": "integer" }, + "acpReattachTurns": { "type": "integer" }, + "answerObserverInvocations": { "type": "integer" }, + "conversationInputByteCount": { "type": "integer" }, + "consentInputBytes": { "type": "string" }, + "consentSurfaces": { "type": "array", "items": { "type": "string" } }, + "exitControlBytes": { "type": "string" }, + "reentryConsentInputBytes": { "type": "string" }, + "reentryConsentSurfaces": { "type": "array", "items": { "type": "string" } }, + "reentryExitControlBytes": { "type": "string" }, + "inheritedAgentMarkersRemoved": { "type": "integer" }, + "noticedBeforeSpending": { "type": "boolean" }, + "noticedAfterSpending": { "type": "boolean" }, + "openingSentenceExact": { "type": "boolean" }, + "markerRecovered": { "type": "boolean" }, + "acpDocumentCarriesMarker": { "type": "boolean" }, + "answerSurface": { "type": "string" }, + "outcome": { "enum": ["same-identity", "no-session", "unresolved"] }, + "privateStateInspected": { "type": "boolean" }, + "materialization": { + "type": "object", + "additionalProperties": false, + "required": [ + "promptVersion", "requestIdStable", "promptExact", "turnNamed", "turnProvider", + "durationReported", "responsePresent", "stopReason", + "reportedUsageFields", "unreportedUsageFields", "failureClasses" + ], + "properties": { + "promptVersion": { "type": "string" }, + "requestIdStable": { "type": "boolean" }, + "promptExact": { "type": "boolean" }, + "turnNamed": { "type": "boolean" }, + "turnProvider": { "type": "string" }, + "durationReported": { "type": "boolean" }, + "responsePresent": { "type": "boolean" }, + "stopReason": { "type": "string" }, + "reportedUsageFields": { "type": "array", "items": { "type": "string" } }, + "unreportedUsageFields": { "type": "array", "items": { "type": "string" } }, + "failureClasses": { "type": "array", "items": { "type": "string" } } + } + }, + "route": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "provider", "buildVersion", "buildDigest"], + "properties": { + "kind": { "type": "string" }, + "provider": { "type": "string" }, + "buildVersion": { "type": "string" }, + "buildDigest": { "type": "string" } + } + }, + "journal": { + "type": "object", + "additionalProperties": false, + "required": [ + "provider", "agent", "launcher", "provenance", "nativeSessionId", "cwdIsProject", + "sessionState", "instructionsDigestPresent", + "firstPhases", "secondPhases", "failureClasses" + ], + "properties": { + "provider": { "type": "string" }, + "agent": { "type": "string" }, + "launcher": { "type": "string" }, + "provenance": { "type": "string" }, + "nativeSessionId": { "type": "string" }, + "cwdIsProject": { "type": "boolean" }, + "sessionState": { "type": "array", "items": { "type": "string" } }, + "instructionsDigestPresent": { "type": "boolean" }, + "firstPhases": { "type": "array", "items": { "type": "string" } }, + "secondPhases": { "type": "array", "items": { "type": "string" } }, + "failureClasses": { "type": "array", "items": { "type": "string" } } + } + }, + "cleanup": { + "type": "object", + "additionalProperties": false, + "required": [ + "liveChildren", "journalsRemoved", "routeRecordsRemoved", + "sessionDeleteOutcome", "temporaryRootRemoved" + ], + "properties": { + "liveChildren": { "type": "integer" }, + "journalsRemoved": { "type": "boolean" }, + "routeRecordsRemoved": { "type": "boolean" }, + "sessionDeleteOutcome": { "enum": ["deleted", "nothing-to-delete", "failed"] }, + "temporaryRootRemoved": { "type": "boolean" } + } + } + } +} +``` + + + + +```sh timeout=40min exec as="run" +deno run --allow-all --frozen packages/acp/tests/fixtures/codex-native-launch-proof.ts zero-native-turn +``` + +The fixture returns a structured verdict even when the product fails one of the +two questions, so a nonzero exit means the harness broke rather than a question +being answered. + + + + +{run.stdout} + + +The whole result is shown before anything is judged. It carries no contract +text, no transcript content and no reply — `promptExact` and `responsePresent` +are the only things about the turn that crossed the fixture boundary. + +```json +{run.stdout} +``` + +## Nothing private was read + +True whether or not the proof was opted into. Codex's rollout files are the one +place that could answer this document cheaply, and they are exactly what a proof +of the public contract may not open. + + + + + +## The frozen compatibility point + +The materialization turn exists because of a fact about one Codex build. This +records which one, so a later build that stopped needing it — or started needing +something else — cannot pass as this one. + + + + + + + + + + +## Exactly one model turn, and it was XMD's + +The authorized budget is one, and one is what the durable accounts retain across +both invocations. Nothing was typed into either native UI, so every byte the +conversation received was the product's own. + + + + + + + + + + +Nothing was typed, so nothing was watched for. The harness reconstructs a +terminal only for an invocation that submits a turn and waits for its answer, +and neither of these two invocations does. + + + +## The operator was told before it was spent + +A billable turn nobody was warned about is a bill, not a feature. The notice +appears on the terminal that acquired the launch before the turn starts, and the +completion summary appears before the native UI opens. + + + + +## The turn was the exact one the contract names + +Its version, its bytes and its request identity are fixed by the product, not +composed at the call site, and the retained preparation named the same request +the retained turn later reported against. + + + + + + + + + + + +Usage is recorded as the provider reported it. Which fields arrived is a fact +about Codex, not about this document, so both lists are shown and only their +sum is fixed: every field is either reported or explicitly unreported, and none +is quietly inferred to be zero. + + + +## The session was openable, and stayed openable + +The first launch left Codex's own composer rather than its refusal. The second +invocation reached the same conversation, under the same identity, without a +second materialization — which the count above already settled. + + + + + + + + +## The identity is the one Codex named + +Codex chose it and told the adapter through its own metadata. XMD never parsed +it out of anything, and never substituted a fresh one when the first was +inconvenient. + + + + +## The production target ran, and its siblings did not + + + + + + +## Route and journal agree + +The route names the construction and the Codex build the tuple above froze, and +nothing else: an ACP-first route deliberately records no conversation identity, +launcher or instruction layer, so those are read from the journal instead. A +route that had converted to anything but `acp-first` would mean the launch +stopped being the one this document describes. + + + + + + + + + + + + + + +The phases are the whole story. The first invocation materializes; the second, +finding a conversation that is already openable, does not. + + + + + + +## Nothing was left behind + + + + + + + + + + + +## Without the authorization, or the build, nothing started + +The refusal happens before any Codex conversation exists, so an unauthorized run +spends no turn and observes nothing. This is the branch that runs on an ordinary +machine, and the branch that runs on a machine outside the frozen tuple. + + + + + + + + + + + + + + diff --git a/packages/acp/src/native-launch.ts b/packages/acp/src/native-launch.ts index d77b7a58..29756090 100644 --- a/packages/acp/src/native-launch.ts +++ b/packages/acp/src/native-launch.ts @@ -22,12 +22,15 @@ * client-allocated one allocates an identity or writes a private file. That is * the failure the contract asks for rather than a hopeful spawn. * - * Adapters differ in one structural way, and it is discriminated rather than - * inferred: who chooses the provider-native session identity. A - * `provider-returned` adapter can only resume a session something else created - * and named. A `client-allocated` adapter names the session first and hands - * that name to the native process, which is what lets a launch construct a - * conversation instead of merely reattaching to one. + * Adapters differ along two structural axes, and both are discriminated rather + * than inferred. The first is who chooses the provider-native session identity: + * a `provider-returned` adapter can only resume a session something else + * created and named, while a `client-allocated` adapter names the session first + * and hands that name to the native process, which is what lets a launch + * construct a conversation instead of merely reattaching to one. The second is + * whether the adapter pins the build behind its executable, and it is + * independent of the first: a name means one conversation only beside the build + * that issued or accepted it, whichever side chose the name. */ import { randomUUID } from "node:crypto"; @@ -36,9 +39,10 @@ import type { IdentityProvenance } from "@executablemd/core"; /** * What an adapter knows about the build behind its executable. * - * A session whose identity XMD chose only means something while the build that - * accepted it can be recognized later: two builds of one provider accept the - * same identity and disagree silently about what it names. Everything here is + * A provider-native identity only means something while the build behind it can + * be recognized later: two builds of one provider accept the same identity and + * disagree silently about what it names, whether XMD chose that identity or the + * provider issued it. Everything here is * that adapter's private dialect — which command to observe, what its version * output looks like, and what the ACP adapter child needs in order to run the * same build. None of it reaches a document. @@ -104,6 +108,41 @@ function claudeVersion(output: string): string | undefined { return canonical.length === 1 ? canonical[0] : undefined; } +/** + * Codex reports `codex-cli 0.153.2`. + * + * The product word is retained with the number for the same reason Claude's + * whole line is: a bare semver from another tool compares equal. The one-line + * rule is the same as well, and so is the silence about what did not match. + */ +function codexVersion(output: string): string | undefined { + const canonical = output + .split("\n") + .map((line) => line.trim()) + .filter((line) => /^codex-cli \d+\.\d+\.\d+$/.test(line)); + return canonical.length === 1 ? canonical[0] : undefined; +} + +/** + * The one XMD-owned model turn a freshly created conversation needs before the + * native UI can open it. + * + * Declared by the adapter that needs it and by no other, because needing one is + * a fact about a provider's persistence rather than about launching. An adapter + * without this member owes no turn, and a launch on it may not take one. + * + * The prompt is a constant of this build. It interpolates nothing, carries no + * authored content, no path, no identity and no environment, and it is the same + * bytes every time so a reader of a journal can recognize an XMD-owned turn in + * their own conversation. + */ +export interface MaterializationContract { + /** Which exact prompt this is — versioned, because the bytes are the contract. */ + readonly promptVersion: string; + /** The prompt itself, sent as one text block. */ + readonly prompt: string; +} + interface AdapterCommands { /** Stable adapter identity — `claude`, `codex`. Never an executable path. */ launcher: string; @@ -111,6 +150,8 @@ interface AdapterCommands { identity: IdentityProvenance; /** The argv that resumes this exact provider-native session. */ resume(nativeSessionId: string): string[]; + /** The turn a session this adapter newly creates owes, when it owes one. */ + materialization?: MaterializationContract; } /** @@ -146,13 +187,48 @@ export interface ProviderReturnedAdapter extends AdapterCommands { identity: "provider-returned"; } -export type NativeAdapter = ProviderReturnedAdapter | ClientAllocatedAdapter; +/** + * A provider-returned adapter whose sessions are pinned to one observed build. + * + * The provider still names the session, so nothing here chooses an identity. + * What the binding adds is the other half of what makes that name mean + * something: an identity a provider issued is only resumable by the build that + * issued it, and two builds of one provider accept the same string while + * disagreeing about which conversation it is. + * + * It is a separate type rather than an optional member on the one above, + * because an adapter a host supplies for a provider that does not pin builds + * must not be able to reach a binding at all. Narrowing is the only way in. + */ +export interface BoundProviderReturnedAdapter extends ProviderReturnedAdapter { + binding: NativeBinding; +} + +export type NativeAdapter = + | ProviderReturnedAdapter + | BoundProviderReturnedAdapter + | ClientAllocatedAdapter; + +/** An adapter whose sessions are bound to the build that accepted them. */ +export type BuildBoundAdapter = BoundProviderReturnedAdapter | ClientAllocatedAdapter; /** Whether this adapter names its own sessions. */ export function allocatesIdentity(adapter: NativeAdapter): adapter is ClientAllocatedAdapter { return adapter.identity === "client-allocated"; } +/** + * Whether this adapter's sessions are bound to one observed executable build. + * + * Independent of who names the session: `claude` allocates its identities and + * `codex` is handed them, and both are meaningless beside a build this run + * cannot recognize. An adapter without a binding keeps the released + * native-only behavior — it observes nothing and gains nothing. + */ +export function bindsBuild(adapter: NativeAdapter): adapter is BuildBoundAdapter { + return "binding" in adapter; +} + const ADAPTERS: Readonly> = { claude: { launcher: "claude", @@ -183,7 +259,33 @@ const ADAPTERS: Readonly> = { }, codex: { launcher: "codex", + // Codex creates the conversation through ACP and reports what it is called. + // XMD supplies nothing here and accepts only that assertion. identity: "provider-returned", + binding: { + command: "codex", + version: codexVersion, + // No `adapterCommand`: the host registry already resolves `codex` to the + // vendored patched adapter this build carries, and a command pinned here + // would replace that exact snapshot with whatever a fetch produced. + // + // The first thing the Codex ACP adapter consults when deciding which + // Codex to run, so the build that creates the session through ACP is the + // build the native UI then resumes it with. + environment: (livePath) => ({ CODEX_PATH: livePath }), + }, + // The App Server writes a thread's rollout at its first turn, and `codex + // resume ` reads rollouts — so a thread ACP created and nothing has + // spoken in is refused by name. One turn closes exactly that gap and + // nothing else, which is why the prompt asks for an acknowledgement and + // forbids the work the session was prepared for. + materialization: { + promptVersion: "codex-materialization.v1", + prompt: + "This turn only makes the Codex conversation resumable. Do not perform the prepared " + + "task, inspect or modify files, call tools, or take any external action. Reply with a " + + "brief acknowledgement only.", + }, resume: (nativeSessionId) => ["codex", "resume", nativeSessionId], }, }; @@ -201,12 +303,26 @@ const ADAPTERS: Readonly> = { * independent invocation resumed the same identity — including a session left * without a word said in it. * - * `codex` is absent. Its command shape is known and its adapter contract tests - * pass, and neither is the proof: nothing has run it against an installed - * Codex. A host may still advertise an adapter itself by passing its name - * through `AcpxProviderDependencies.advertiseNativeLaunch`. + * `codex` is here because `packages/acp/src/CodexNativeLaunch.test.md` and + * `packages/acp/src/CodexZeroNativeTurnExit.test.md` ran the production command + * through the built binary against `codex-cli 0.153.2` on macOS arm64 and showed + * the same contract, reached differently: Codex allocates the identity and + * reports it on the response `_meta`, the layer governs the first native user + * turn, and a second independent invocation reaches that same conversation. + * + * Codex costs one model turn to get there. The App Server writes a thread's + * rollout at its first turn and `codex resume ` reads rollouts, so a thread + * ACP created and nothing has spoken in is refused by name: `session/resume` + * answers "no rollout found for thread id ", and the native UI answers "No + * saved session found with ID ". `codex-materialization.v1` is the one + * XMD-owned turn that closes exactly that gap, and it runs only for a freshly + * created conversation. It is not a bootstrap turn: it carries no authored + * content, and providers that need no materialization keep spending none. + * + * A host may still advertise an adapter itself by passing its name through + * `AcpxProviderDependencies.advertiseNativeLaunch`. */ -export const ADVERTISED_NATIVE_LAUNCH: readonly string[] = ["claude"]; +export const ADVERTISED_NATIVE_LAUNCH: readonly string[] = ["claude", "codex"]; /** * The adapters whose client-native ACP attachment has been proven against the diff --git a/packages/acp/src/provider.ts b/packages/acp/src/provider.ts index e6e39d70..0d8c90cb 100644 --- a/packages/acp/src/provider.ts +++ b/packages/acp/src/provider.ts @@ -23,11 +23,13 @@ import { createChannel, createScope, + each, ensure, Err, Ok, scoped, spawn, + stream, suspend, until, useScope, @@ -51,16 +53,30 @@ import type { InstructionReconciliation, ExitedLaunchRecord, LaunchFailure, + LaunchFailureClass, LaunchOptions, + MaterializationPlan, + MaterializationUsage, + MaterializedLaunchRecord, PreparedLaunchRecord, PromptOptions, Session, SessionLaunchResult, } from "@executablemd/core"; -import { allocatesIdentity } from "./native-launch.ts"; -import type { ClientAllocatedAdapter, NativeBinding } from "./native-launch.ts"; +import { allocatesIdentity, bindsBuild } from "./native-launch.ts"; +import type { + BoundProviderReturnedAdapter, + BuildBoundAdapter, + ClientAllocatedAdapter, + NativeAdapter, + NativeBinding, +} from "./native-launch.ts"; import { AgentSessionRouteError } from "./session-route.ts"; -import type { AgentSessionRoute, AgentSessionRouteStore } from "./session-route.ts"; +import type { + AgentSessionRoute, + AgentSessionRouteStore, + AgentSessionRouteV3, +} from "./session-route.ts"; import { createAcpRuntime, createAgentRegistry, createRuntimeStore } from "./acpx-runtime.ts"; import type { AcpAgentRegistry, @@ -71,6 +87,9 @@ import type { AcpRuntimeMaterialization, AcpRuntimeOptions, AcpRuntimeTurn, + AcpRuntimeTurnResult, + AcpRuntimeUsageBreakdown, + AcpRuntimeUsageCost, AcpSessionRecord, AcpSessionStore, SessionAgentOptions, @@ -81,6 +100,7 @@ import { strictPermissions, } from "./permission-bridge.ts"; import { consumeTurn } from "./events.ts"; +import { checkpointFromResult } from "./checkpoint.ts"; import { resolveSessionPlacement } from "./session-key.ts"; import { useSerialQueues } from "./serial-queue.ts"; import { @@ -89,6 +109,7 @@ import { cwd, ExecutableObservationError, nativeLaunch, + notifyTerminal, } from "@executablemd/runtime"; import type { AgentSessionCoordinator, @@ -103,7 +124,6 @@ import { knownNativeAdapters, nativeAdapterFor, } from "./native-launch.ts"; -import type { NativeAdapter } from "./native-launch.ts"; /** * One MCP server as ACPX configures them. @@ -500,7 +520,7 @@ interface LaunchInvocation { * path is only true for the run that observed it. A later replay reobserves * rather than finding one lying about. */ - readonly bound: Map; + readonly bound: Map; /** Sessions whose detach phase ran live in this invocation. */ readonly detachedLive: Set; /** @@ -1171,14 +1191,16 @@ function* useAcpxProviderState( if (prepareAgent !== undefined) { yield* prepareAgent(selected); } - // Resolution is read-only for an agent whose sessions XMD names. Probing - // spawns an ACP child, and that is provider work on a session whose + // Resolution is read-only for an agent whose sessions are bound to a build. + // Probing spawns an ACP child, and that is provider work on a session whose // construction has not been settled yet — it would run before the route is - // published, before a client-native route can refuse this surface, and - // before a host missing either capability has said so. Nothing on that path - // needs the answer: the session is created by a native process, and where - // ACP does serve one, the establishment itself reports being unable to. - if (!validatedAgents.has(selected) && !namesOwnSessions(selected)) { + // published, before a route this surface cannot serve can refuse it, and + // before a host missing either capability has said so. It would also spawn a + // child of a build nothing has observed, which is the one thing a bound + // session may not talk to. Nothing on that path needs the answer: a + // client-native session is created by a native process, and where ACP does + // serve one, the establishment itself reports being unable to. + if (!validatedAgents.has(selected) && !boundSessions(selected)) { const base = yield* runtimeOptions(); const probe = createRuntime({ ...base, probeAgent: selected }); const report = yield* until(probe.doctor()); @@ -1359,7 +1381,16 @@ function* useAcpxProviderState( /** What one ensure is for, beyond the placement it is for. */ interface EnsureIntent { - attachment?: { build: BoundBuild; resumeSessionId: string }; + /** + * The observed build this ensure's ACP child must be. + * + * Separate from `attachment`, because the two answer different questions + * and not every session that has one has the other: a session the provider + * named is bound to a build without any identity for ACP to be told to + * reopen. + */ + build?: BoundBuild; + attachment?: { resumeSessionId: string }; /** The placement's state, as the caller resolved it. */ state: "pending" | "established"; /** @@ -1395,7 +1426,7 @@ function* useAcpxProviderState( let managedEntry: ManagedSession; try { managedEntry = yield* ensureThrough( - attachment?.build, + intent.build, { sessionKey: prepared.placement.sessionKey, agent: agentName, @@ -1559,14 +1590,29 @@ function* useAcpxProviderState( return ownable(agentName) && adapter !== undefined && allocatesIdentity(adapter); } + /** + * Whether this agent's sessions carry a construction route and one build. + * + * The two facts arrive together and for the same reason. A route says how the + * session was first constructed, and for an adapter that pins a build the + * build is part of that account: whichever side chose the identity, the name + * resolves to one conversation only beside the build that issued or accepted + * it. An agent whose adapter binds nothing keeps its merged behavior, on a + * host that installs neither. + */ + function boundSessions(agentName: string): boolean { + const adapter = adapterFor(agentName); + return ownable(agentName) && adapter !== undefined && bindsBuild(adapter); + } + /** * Refuse an advertised agent this host is not assembled to serve. * * Fail-closed, and closed means what the agent actually needs: every - * advertised session needs a coordinator to say who owns it, and one whose - * sessions XMD names also needs a route store to say how it was constructed. - * A host that can answer one question but not the other cannot act on a - * session a native UI may be in. + * advertised session needs a coordinator to say who owns it, and one bound to + * a build also needs a route store to say how it was constructed and an + * observer to say which build that was. A host that can answer one question + * but not the other cannot act on a session a native UI may be in. */ function requireAssembly(agentName: string, sessionKey: string): void { if (!ownable(agentName)) { @@ -1576,7 +1622,7 @@ function* useAcpxProviderState( if (!coordinator) { missing.push("exclusive ownership"); } - if (namesOwnSessions(agentName)) { + if (boundSessions(agentName)) { if (!routeStore) { missing.push("construction routes"); } @@ -1650,6 +1696,22 @@ function* useAcpxProviderState( }; } + /** The same route, for a session whose build was observed as it was bound. */ + function boundAcpFirstRoute( + agentCommand: string, + sessionKey: string, + executableBinding: AgentSessionRouteV3["executableBinding"], + ): AgentSessionRouteV3 { + return { + schema: "session-route.v3", + route: "acp-first", + provider: ACPX_PROVIDER, + agent: agentCommand, + sessionKey, + executableBinding, + }; + } + /** * Publish or adopt `acp-first` for an agent whose sessions XMD would * otherwise name, and refuse if this session already has an identity. @@ -1762,24 +1824,121 @@ function* useAcpxProviderState( }); } + /** + * Observe the build behind a session the provider names, and settle its route. + * + * The order is the contract. The build is observed before the route is read, + * because for this construction the build is part of what the route says: an + * identity the provider issues resolves to one conversation only beside the + * build that issued it, so a route published without one would describe a + * session no later run could confirm. What comes back is the build every + * provider effect for this session then goes through. + * + * A session that already has provider state but no route was constructed + * before this contract. Its route is published as the unbound one it is — + * this run's observation says which build is installed today, not which one + * issued that identity — and it refuses here rather than being bound after + * the fact. + */ + function* bindProviderNamed( + agentName: string, + agentCommand: string, + adapter: BoundProviderReturnedAdapter, + sessionKey: string, + hasProviderState: boolean, + ): Operation { + const build = yield* observeBuild(agentName, agentCommand, adapter.binding); + let route: AgentSessionRoute; + try { + route = yield* reconcileRoute( + agentCommand, + sessionKey, + // deno-lint-ignore require-yield + function* () { + return boundAcpFirstRoute(agentCommand, sessionKey, build.binding); + }, + hasProviderState, + ); + } catch (error) { + throw new AttachmentRefused({ class: "identity-unavailable", message: routeMessage(error) }); + } + if (route.route !== "acp-first") { + throw new AttachmentRefused({ + class: "identity-unavailable", + message: + `session "${sessionKey}" was constructed under an identity XMD chose, and ` + + `"${agentName}" names its own sessions, so neither account describes the other`, + }); + } + if (route.schema !== "session-route.v3") { + // Constructed before any build was recorded. A build observed now says + // which build is installed today, not which one issued this identity, so + // there is nothing to compare and nothing to continue. + throw new AttachmentRefused({ + class: "executable-binding-refused", + message: + `session "${sessionKey}" was constructed before XMD recorded which build issued its ` + + `identity, so this run cannot show it is talking to that build. Name a different ` + + `.`, + }); + } + if (!sameExecutableBuild(build.binding, route.executableBinding)) { + throw new AttachmentRefused(buildDrift(sessionKey, route.executableBinding, build.binding)); + } + return build; + } + + /** What a settled construction route lets one ACP operation do. */ + interface SessionConstruction { + /** The observed build every provider effect for this session goes through. */ + build: BoundBuild; + /** + * The exact conversation ACP must open rather than create. + * + * Present only for a session a native process constructed under an identity + * XMD chose. A session the provider named has its own history to reopen, and + * a name supplied here would be XMD choosing one after the fact. + */ + resumeSessionId?: string; + } + /** * Reconcile this session's construction route, and say what an ACP operation * may do with it. * * `` and `` are eager, so a session nobody constructed is - * constructed here as `acp-first` before ensure. A session a native process - * constructed is attached to — never converted, never republished — and only - * when this host has proven that capability for this adapter and can still - * show it is talking to the build that created it. + * constructed here — as `acp-first`, bound to the observed build when the + * adapter pins one — before ensure. A session a native process constructed is + * attached to — never converted, never republished — and only when this host + * has proven that capability for this adapter and can still show it is talking + * to the build that created it. */ function* constructRoute( agentName: string, prepared: Prepared, - ): Operation<{ build: BoundBuild; resumeSessionId: string } | undefined> { - if (!namesOwnSessions(agentName)) { + ): Operation { + const adapter = adapterFor(agentName); + if (!ownable(agentName) || adapter === undefined) { return undefined; } const agentCommand = agentCommandOf(prepared); + // An existing managed entry, or a durable record ACPX already kept, is + // provider state — and existing history is never reclassified. + const constructed = + prepared.kind === "existing" || (yield* until(store.load(prepared.sessionKey))) !== undefined; + if (!allocatesIdentity(adapter)) { + if (!bindsBuild(adapter)) { + return undefined; + } + const build = yield* bindProviderNamed( + agentName, + agentCommand, + adapter, + prepared.sessionKey, + constructed, + ); + return { build }; + } const route = yield* reconcileRoute( agentCommand, prepared.sessionKey, @@ -1787,9 +1946,7 @@ function* useAcpxProviderState( function* () { return acpFirstRoute(agentCommand, prepared.sessionKey); }, - // An existing managed entry, or a durable record ACPX already kept, is - // provider state — and existing history is never reclassified. - prepared.kind === "existing" || (yield* until(store.load(prepared.sessionKey))) !== undefined, + constructed, ); if (route.route !== "client-native") { return undefined; @@ -1815,8 +1972,7 @@ function* useAcpxProviderState( `.`, }); } - const binding = (adapterFor(agentName) as ClientAllocatedAdapter).binding; - const build = yield* observeBuild(agentName, agentCommand, binding); + const build = yield* observeBuild(agentName, agentCommand, adapter.binding); if (!sameExecutableBuild(build.binding, route.executableBinding)) { throw new AttachmentRefused( buildDrift(prepared.sessionKey, route.executableBinding, build.binding), @@ -2081,14 +2237,18 @@ function* useAcpxProviderState( // first Prompt constructs this session through ACP, so that is what // its construction route says — and a session a native process // constructed is attached to under the identity it already has. - const attachment = yield* constructRoute(agentName, prepared); + const construction = yield* constructRoute(agentName, prepared); + const resumeSessionId = construction?.resumeSessionId; // The pending ACP-first branch, and the only one that defers: an // attachment resumes an identity that already exists, and an - // established placement has one of its own. + // established placement has one of its own. A build alone does not + // defer anything — a session the provider names is still constructed + // by this ensure, bound to the build that names it. const constructing = - state === "pending" && attachment === undefined && prepared.kind === "placement"; + state === "pending" && resumeSessionId === undefined && prepared.kind === "placement"; const entry = yield* ensureFromPrepared(agentName, prepared, { - ...(attachment === undefined ? {} : { attachment }), + ...(construction === undefined ? {} : { build: construction.build }), + ...(resumeSessionId === undefined ? {} : { attachment: { resumeSessionId } }), state, materialization: constructing, deferEstablished: constructing, @@ -2508,9 +2668,18 @@ function* useAcpxProviderState( // return — a store read that fails, an instruction layer this provider will // not replace — and a claim taken before them is one those exits would have // to remember to give back. - const existing = yield* until(store.load(sessionKey)); - let sessionState: "created" | "resumed" = existing ? "resumed" : "created"; - let reconciliation: InstructionReconciliation = existing ? "resumed" : "installed"; + const stored = yield* until(store.load(sessionKey)); + // A pending record is occupancy an earlier attempt left behind: ACPX will + // not reuse it, no backend ever accepted a turn in it, and treating it as a + // conversation would skip the very turn that would make one openable. So + // this launch is creating, whatever is on disk under the same key. + const existing = + stored !== undefined && stored.sessionMaterialization?.state !== "pending" + ? stored + : undefined; + const sessionState: "created" | "resumed" = existing ? "resumed" : "created"; + const reconciliation: InstructionReconciliation = existing ? "resumed" : "installed"; + const known = { agent: agentName, sessionKey, cwd: sessionCwd, launcher: adapter.launcher }; if (existing && storedSystemPrompt(existing) !== instructions) { // ACPX fixes a session's instruction layer when its ACP session is @@ -2528,44 +2697,96 @@ function* useAcpxProviderState( `session "${sessionKey}" already carries a different XMD instruction layer, and ` + `this provider does not replace one. Launch a differently named , or ` + `launch the same prepared instructions again.`, - { agent: agentName, sessionKey, cwd: sessionCwd, launcher: adapter.launcher }, + known, ); } + // Which build this session belongs to, settled before ACP is asked for + // anything. The provider names the session, and the name it issues resolves + // to one conversation only beside the build that issued it — so the build is + // observed, the route recording it is published or adopted, and the two are + // compared, all under ownership and before the first provider effect. + let build: BoundBuild | undefined; + if (bindsBuild(adapter)) { + try { + build = yield* bindProviderNamed( + agentName, + agentCommand, + adapter, + sessionKey, + existing !== undefined, + ); + } catch (error) { + if (error instanceof AttachmentRefused) { + return refusal(error.failure.class, error.failure.message, known); + } + throw error; + } + invocation.bound.set(sessionKey, { build, adapter }); + // The check a replay performs at its own first live phase, performed here + // against a live observation. Recording it is what keeps the detach or the + // spawn below from reading the same two accounts a second time. + invocation.reconciled.add(sessionKey); + } + + // Planned before the ensure it decides the shape of, and only for the + // conversation this launch creates through an adapter that says one is + // owed. A resumed session has already been spoken in — whatever made it + // openable happened before this run — so taking a turn in it would be + // spending someone's model turn to learn nothing. The request id is minted + // here rather than at the turn so a replay reading the retained record is + // looking for the turn the first attempt committed to. + const plan: MaterializationPlan | undefined = + sessionState === "created" && adapter.materialization + ? { + promptVersion: adapter.materialization.promptVersion, + requestId: randomUUID(), + prompt: adapter.materialization.prompt, + } + : undefined; + + const ensureInput: AcpRuntimeEnsureInput = { + sessionKey, + agent: agentName, + mode: "persistent", + cwd: sessionCwd, + sessionOptions: { systemPrompt: instructions }, + }; + if (plan !== undefined) { + // A conversation this adapter cannot open until something has been said + // in it is not one the record may assert. ACPX holds it as occupancy + // instead, and only the adapter's own acceptance of the turn below + // promotes it — which is what makes that acceptance decide whether this + // launch reaches a native UI at all. + ensureInput.materialization = "first-turn-acceptance"; + } + // Claimed here, one line before the ensure it is for, and settled by the // same scope-owned cleanup an attachment uses. The handle enters the ledger // the moment it exists, bound to the runtime that made it: everything below // can refuse, and a handle only the managed map knew about is one teardown // could not close through its creator. - const managedEntry = yield* ensureThrough( - undefined, - { - sessionKey, - agent: agentName, - mode: "persistent", + const managedEntry = yield* ensureThrough(build, ensureInput, (handle, entry) => { + const session: Session = { sessionKey, cwd: sessionCwd }; + if (handle.agentSessionId !== undefined) { + session.agentSessionId = handle.agentSessionId; + } + // Established by construction when nothing is owed: this adapter's + // provider returns the identity, so the session exists the moment the + // ensure answers. A launch that owes a turn holds a placement instead, + // and the accepted turn is what establishes it. + return { + handle, + runtime: entry, + agentCommand, cwd: sessionCwd, - sessionOptions: { systemPrompt: instructions }, - }, - (handle, entry) => { - const session: Session = { sessionKey, cwd: sessionCwd }; - if (handle.agentSessionId !== undefined) { - session.agentSessionId = handle.agentSessionId; - } - // Established by construction: this adapter's provider returns the - // identity, so the session exists the moment the ensure answers. - return { - handle, - runtime: entry, - agentCommand, - cwd: sessionCwd, - session, - state: "established" as const, - }; - }, - ); + session, + state: plan === undefined ? "established" : "pending", + }; + }); managed.set(sessionKey, managedEntry); - const nativeSessionId = managedEntry.handle.agentSessionId; + const nativeSessionId = plan === undefined ? managedEntry.handle.agentSessionId : ""; if (nativeSessionId === undefined) { // An ACP session id and an ACPX record id are not native identities, and // neither is a string that merely looks like one. @@ -2578,7 +2799,7 @@ function* useAcpxProviderState( "identity-unavailable", `agent "${agentName}" created a session but asserted no provider-native ` + `session identity, so there is nothing ${adapter.launcher} can resume`, - { agent: agentName, sessionKey, cwd: sessionCwd, launcher: adapter.launcher }, + known, ); } @@ -2600,6 +2821,14 @@ function* useAcpxProviderState( permissionMode: providerOptions.permissionMode, launcher: adapter.launcher, }; + if (build) { + // Exactly the binding the route carries, so the journal and the route + // agree by construction rather than by comparison. + record.executableBinding = build.binding; + } + if (plan !== undefined) { + record.materialization = plan; + } let model: string | undefined; try { model = yield* effectiveModel(managedEntry.runtime.runtime, managedEntry.handle); @@ -2626,6 +2855,310 @@ function* useAcpxProviderState( return status.models?.currentModelId; } + /** + * What the provider said this turn cost, and nothing else. + * + * A member the provider did not report is left absent rather than copied as + * `undefined`, because these are merged over what earlier events reported: an + * explicit `undefined` would overwrite a figure the provider did give with the + * claim that it gave none. Zero is a figure; saying nothing is not. + */ + function reportedUsage( + breakdown: AcpRuntimeUsageBreakdown | undefined, + cost: AcpRuntimeUsageCost | undefined, + ): MaterializationUsage { + const usage: { + -readonly [K in keyof MaterializationUsage]: MaterializationUsage[K]; + } = {}; + if (breakdown?.inputTokens !== undefined) { + usage.inputTokens = breakdown.inputTokens; + } + if (breakdown?.outputTokens !== undefined) { + usage.outputTokens = breakdown.outputTokens; + } + if (breakdown?.cachedReadTokens !== undefined) { + usage.cachedReadTokens = breakdown.cachedReadTokens; + } + if (breakdown?.cachedWriteTokens !== undefined) { + usage.cachedWriteTokens = breakdown.cachedWriteTokens; + } + if (breakdown?.thoughtTokens !== undefined) { + usage.thoughtTokens = breakdown.thoughtTokens; + } + if (breakdown?.totalTokens !== undefined) { + usage.totalTokens = breakdown.totalTokens; + } + if (cost?.amount !== undefined) { + usage.costAmount = cost.amount; + } + if (cost?.currency !== undefined) { + usage.costCurrency = cost.currency; + } + return usage; + } + + /** One line per member, saying `provider did not report` where it did not. */ + function usageLines(usage: MaterializationUsage): string { + const figure = (value: number | undefined): string => + value === undefined ? "provider did not report" : String(value); + const cost = + usage.costAmount === undefined + ? "provider did not report" + : `${usage.costAmount}${usage.costCurrency === undefined ? "" : ` ${usage.costCurrency}`}`; + return [ + ` input tokens: ${figure(usage.inputTokens)}`, + ` output tokens: ${figure(usage.outputTokens)}`, + ` cached read tokens: ${figure(usage.cachedReadTokens)}`, + ` cached write tokens: ${figure(usage.cachedWriteTokens)}`, + ` thought tokens: ${figure(usage.thoughtTokens)}`, + ` total tokens: ${figure(usage.totalTokens)}`, + ` cost: ${cost}`, + ].join("\n"); + } + + /** + * Spend the one turn this launch planned, and report what it cost. + * + * Reached only for a preparation that planned one, which is only ever a + * conversation this launch created through an adapter that says a fresh one is + * not yet openable. Everything about it is fixed before it runs: the exact + * prompt and the request id come from the retained plan, so a replay is + * looking for this turn rather than authorizing another. + * + * It is not a Prompt. It publishes no event stream, reaches no authored + * middleware, and is deliberately not registered with the permission bridge — + * an unregistered turn's permission request is refused by the bridge's own + * fail-closed answer, so there is no composed handler that could grant this + * turn the tool authority its prompt already forbids. A tool call that arrives + * anyway fails materialization, and the session is never handed over. + * + * It is also what settles the session's identity. The preparation held + * occupancy and asserted nothing, so the conversation this launch hands over + * is named here — by the backend's own acceptance of this turn, and by + * nothing else. + */ + function materializeSession( + placed: Prepared, + prepared: PreparedLaunchRecord, + plan: MaterializationPlan, + ): Operation { + return scoped(function* (): Operation { + const base: Pick = { + phase: "materialized", + promptVersion: plan.promptVersion, + requestId: plan.requestId, + }; + const refused = (message: string): MaterializedLaunchRecord => ({ + ...base, + usage: {}, + response: "", + failure: { class: "materialization-failed", message }, + }); + + const entry = managed.get(prepared.sessionKey); + if (!entry || !isLive(entry)) { + return refused( + `session "${prepared.sessionKey}" is no longer held by this provider, so the turn ` + + `that would make it resumable cannot be taken here`, + ); + } + // Occupancy, and only occupancy. The preparation asserted no identity + // because no backend had accepted a turn here, so a handle that already + // names a conversation is not the placement this launch prepared — and + // spending a turn in it would be spending one in someone else's. + if (entry.handle.agentSessionId !== undefined) { + return refused( + `session "${prepared.sessionKey}" is held under a provider-native identity this ` + + `launch never prepared`, + ); + } + + // Before the turn, never after: what is about to be spent is the reader's + // model turn, and telling them once it is gone is telling them too late. + yield* notifyTerminal( + `${prepared.agent}: spending one model turn in session "${prepared.sessionKey}" so ` + + `${prepared.launcher} can open it (${plan.promptVersion}).`, + ); + + // Registered before the turn exists, and reaching it through a slot the + // start fills. Registering afterwards puts a suspension point between a + // live turn and the only thing that would stop it, and a cancellation + // landing there — a reader closing the grid this launch is running in is + // one — leaves a model turn running in their conversation that nothing + // is waiting for and nothing cancels until this whole provider comes + // down. There is no such gap this way round: `startTurn` answers without + // suspending, so the slot is filled in the step that creates the turn. + let turn: AcpRuntimeTurn | undefined; + let settled = false; + yield* ensure(function* () { + if (!turn || settled) { + return; + } + activeTurns.delete(turn); + try { + yield* until(turn.cancel()); + } catch (error) { + cleanupErrors.push(toError(error)); + } + }); + turn = entry.runtime.runtime.startTurn({ + handle: entry.handle, + text: plan.prompt, + mode: "prompt", + requestId: plan.requestId, + }); + activeTurns.add(turn); + + const started = Date.now(); + let response = ""; + let usage: MaterializationUsage = {}; + let acted = false; + let outcome: Result; + try { + for (const event of yield* each(stream(turn.events))) { + if (event.type === "text_delta" && (event.stream ?? "output") === "output") { + response += event.text; + } else if (event.type === "tool_call") { + // The prompt forbids this, so an arriving tool call means the turn + // did something other than acknowledge. Recorded rather than acted + // on: the turn is already running, and what this decides is that no + // native UI opens on what it left behind. + acted = true; + } else if (event.type === "status") { + usage = { ...usage, ...reportedUsage(event.breakdown, event.cost) }; + } + yield* each.next(); + } + outcome = Ok(yield* until(turn.result)); + } catch (error) { + outcome = Err(toError(error)); + } + settled = true; + const durationMs = Date.now() - started; + + const stopped = ( + message: string, + stopReason?: string, + failureClass: LaunchFailureClass = "materialization-failed", + ): MaterializedLaunchRecord => { + const record: MaterializedLaunchRecord = { + ...base, + durationMs, + usage, + response, + failure: { class: failureClass, message }, + }; + if (stopReason !== undefined) { + record.stopReason = stopReason; + } + return record; + }; + + if (!outcome.ok) { + return stopped(outcome.error.message); + } + const result = outcome.value; + if (result.status === "cancelled") { + return stopped("the materialization turn was cancelled", result.stopReason); + } + if (result.status === "failed") { + return stopped(result.error.message); + } + // ACP defines end_turn as the only successful stop reason, and an adapter + // that omits it on a normal completion means that one. + const stopReason = result.stopReason ?? "end_turn"; + if (stopReason !== "end_turn") { + return stopped( + `the materialization turn ended with stop reason "${stopReason}"`, + stopReason, + ); + } + if (acted) { + return stopped( + "the materialization turn called a tool, which its prompt forbids, so this launch " + + "will not hand the session to a native UI", + stopReason, + ); + } + // The backend's own acceptance, asked for after the turn completed. A + // turn that failed, was cancelled, or simply ended without the adapter + // saying so rejects here, and a turn nothing accepted materialized + // nothing — so there is no identity, and nothing to hand over. + const accepted: Result = yield* until( + turn.materialized.then( + (value): Result => Ok(value), + (error: unknown): Result => Err(toError(error)), + ), + ); + if (!accepted.ok) { + return stopped(accepted.error.message, stopReason); + } + const nativeSessionId = accepted.value.agentSessionId; + if (nativeSessionId === undefined || nativeSessionId.length === 0) { + // The turn itself reached the backend and was accepted; what is absent + // is the conversation's name, which is the same thing an adapter that + // asserts no identity leaves absent before any turn. Classed as that + // rather than as a failed turn, because the turn did not fail. + return stopped( + "the backend accepted the materialization turn without naming the session it made " + + "openable, so there is nothing a native UI could resume", + stopReason, + "identity-unavailable", + ); + } + // The provider's own name for the turn, read from the response metadata + // this package recognizes and from nothing else. Without it there is no + // evidence the exchange reached the backend rather than a socket. + const named = checkpointFromResult(result); + if (named === undefined) { + return stopped( + "the materialization turn completed without naming the provider turn it was", + stopReason, + ); + } + if (response.length === 0) { + return stopped( + "the materialization turn produced no assistant response, so nothing was said in " + + "the conversation this launch was making openable", + stopReason, + ); + } + + // The durable order the prompt path uses, for the same reason: ACPX's own + // record asserted the identity as it promoted this session, the host's + // mapping commits second, and the placement becomes established last. A + // host that refuses to retain it leaves a session nobody may prompt + // through, so the handle is given up rather than held under a mapping + // that does not exist. + const identity: AcpxSessionIdentity = { + acpxRecordId: accepted.value.acpxRecordId, + agentSessionId: nativeSessionId, + }; + if (sessions?.established && placed.kind === "placement") { + try { + yield* sessions.established(placed.placement, identity); + } catch (error) { + yield* abandonHandle(entry, "session retention refused"); + if (!holding(prepared.sessionKey)) { + detachPlacement(prepared.sessionKey, entry); + } + return stopped(toError(error).message, stopReason); + } + } + entry.session.agentSessionId = nativeSessionId; + entry.state = "established"; + + yield* notifyTerminal( + `${prepared.agent}: materialization turn completed in ${durationMs}ms ` + + `(${named.provider} ${named.kind} ${named.value}).\n` + + `${response}\n` + + `${usageLines(usage)}`, + ); + + return { ...base, nativeSessionId, turn: named, durationMs, usage, response, stopReason }; + }); + } + /** * Give up this provider's ACP handle for `sessionKey`, keeping its record. * @@ -2671,12 +3204,14 @@ function* useAcpxProviderState( if (invocation.fresh.has(sessionKey)) { return { phase: "detached" }; } - // Nothing was prepared live, so this is a replay — and for a session XMD - // named, this detach is its first live phase. The two durable accounts are - // checked here rather than at the spawn, because retaining a detach is - // itself advancing the launch: a journal that says the handoff began is not - // something to write about a session this run cannot confirm. - if (prepared.identityProvenance === "client-allocated") { + // For a session bound to a build, this detach may be the first live phase of + // a replay. The durable accounts are checked here rather than at the spawn, + // because retaining a detach is itself advancing the launch: a journal that + // says the handoff began is not something to write about a session this run + // cannot confirm. A launch that prepared live has already checked them, and + // this reads them once per invocation. + const bound = adapterFor(prepared.agent); + if (bound && bindsBuild(bound)) { const refused = yield* reconcile(invocation, prepared, agentCommand); if (refused) { return { phase: "detached", failure: refused }; @@ -2803,6 +3338,7 @@ function* useAcpxProviderState( placement, ), ), + materialize: (prepared, plan) => materializeSession(placement, prepared, plan), detach: (prepared) => detachSession(invocation, prepared, agentCommandOf(placement)), exit: (prepared) => runNativeUi(invocation, prepared, agentCommandOf(placement)), @@ -2837,11 +3373,11 @@ function* useAcpxProviderState( function* nativeCommand( invocation: LaunchInvocation, prepared: PreparedLaunchRecord, - adapter: ClientAllocatedAdapter, + adapter: NativeAdapter, agentCommand: string, ): Operation> { const publishedHere = invocation.fresh.get(prepared.sessionKey); - if (publishedHere === undefined) { + if (publishedHere === undefined && bindsBuild(adapter)) { // A replay. Whichever suffix it holds, the two durable accounts are // checked before this run does anything a native process could act on — // a detached replay reaches here as its first live phase, and a @@ -2851,13 +3387,21 @@ function* useAcpxProviderState( return Err(new RetainedRefusal(refused)); } } - const creating = publishedHere ?? invocation.detachedLive.has(prepared.sessionKey); - const argv = creating - ? adapter.create( - prepared.nativeSessionId, - yield* privateInstructionFile(prepared.instructions), - ) - : adapter.resume(prepared.nativeSessionId); + // Creation is a question only for an identity XMD chose. A session the + // provider named already exists — ACP made it — so there is nothing here to + // create and no instruction layer to install through argv. + let argv: string[]; + if ( + allocatesIdentity(adapter) && + (publishedHere ?? invocation.detachedLive.has(prepared.sessionKey)) + ) { + argv = adapter.create( + prepared.nativeSessionId, + yield* privateInstructionFile(prepared.instructions), + ); + } else { + argv = adapter.resume(prepared.nativeSessionId); + } // The exact file this run observed, in place of the launcher name the // adapter writes. The name is what durable records carry; the path is what // this invocation spawns, and it is live only. A legacy session observed no @@ -2898,6 +3442,21 @@ function* useAcpxProviderState( agentCommand: string, ): Operation { const stop = (message: string): LaunchFailure => ({ class: "identity-unavailable", message }); + const disagree = (): LaunchFailure => + stop( + `session "${prepared.sessionKey}" is described differently by its journal and its ` + + `construction route, and neither account repairs the other`, + ); + // A launch that never got as far as the native process, prepared under a + // contract that recorded no build. Nothing here can show which build has + // this session's history, and resuming anyway would be answering the + // question by ignoring it. A completed launch never reaches this code. + const unrecorded = (): LaunchFailure => ({ + class: "executable-binding-refused", + message: + `session "${prepared.sessionKey}" was prepared before XMD recorded which build its ` + + `identity belongs to, so this run cannot confirm the conversation it names`, + }); // Which provider retained this preparation, before anything is compared // against it. A record another provider wrote describes a session this one // does not own, and an ACPX route that happens to agree about a UUID is not @@ -2908,57 +3467,76 @@ function* useAcpxProviderState( `cannot confirm the conversation it names`, ); } + // The live half's adapter first, because which construction this journal can + // be checked against is the adapter's fact rather than the route's: a route + // agreeing with a record about one string is not evidence that the same side + // named the session both times. + const adapter = adapterFor(prepared.agent); + if (!adapter || !bindsBuild(adapter)) { + return { + class: "executable-binding-refused", + message: + `session "${prepared.sessionKey}" names a launcher this build has no way to observe, ` + + `so the conversation it prepared cannot be confirmed`, + }; + } let route; try { route = yield* routeStore!.read(sessionKeyOf(agentCommand, prepared.sessionKey)); } catch (error) { return stop(routeMessage(error)); } - if (!route || route.route !== "client-native") { - return stop( - `session "${prepared.sessionKey}" has no client-allocated construction route, so the ` + - `conversation this launch prepared cannot be confirmed`, - ); - } - if ( - route.nativeSessionId !== prepared.nativeSessionId || - route.identityProvenance !== prepared.identityProvenance || - route.instructionsDigest !== prepared.instructionsDigest || - route.launcher !== prepared.launcher - ) { - return stop( - `session "${prepared.sessionKey}" is described differently by its journal and its ` + - `construction route, and neither account repairs the other`, - ); + let retainedBinding: ExecutableBuildBindingV1; + if (allocatesIdentity(adapter)) { + if (!route || route.route !== "client-native") { + return stop( + `session "${prepared.sessionKey}" has no client-allocated construction route, so the ` + + `conversation this launch prepared cannot be confirmed`, + ); + } + if ( + route.nativeSessionId !== prepared.nativeSessionId || + route.identityProvenance !== prepared.identityProvenance || + route.instructionsDigest !== prepared.instructionsDigest || + route.launcher !== prepared.launcher + ) { + return disagree(); + } + if (route.schema === "session-route.v1") { + return unrecorded(); + } + retainedBinding = route.executableBinding; + } else { + if (!route || route.route !== "acp-first") { + return stop( + `session "${prepared.sessionKey}" has no provider-named construction route, so the ` + + `conversation this launch prepared cannot be confirmed`, + ); + } + if (route.schema !== "session-route.v3") { + return unrecorded(); + } + retainedBinding = route.executableBinding; + // ACPX's own record, which is what any later ACP operation would reopen + // this session through. A record naming another conversation, or none, + // means the identity the native process is about to be handed is not the + // one this provider would come back to. + try { + yield* retainedAssertion(prepared.sessionKey, prepared.nativeSessionId); + } catch (error) { + if (error instanceof AttachmentRefused) { + return error.failure; + } + throw error; + } } - // A launch that never got as far as the native process, prepared under a - // contract that recorded no build. Nothing here can show which build has - // this session's history, and resuming anyway would be answering the - // question by ignoring it. A completed launch never reaches this code. - if (route.schema === "session-route.v1" || prepared.executableBinding === undefined) { - return { - class: "executable-binding-refused", - message: - `session "${prepared.sessionKey}" was prepared before XMD recorded which build ` + - `accepted its identity, so this run cannot confirm the conversation it names`, - }; + if (prepared.executableBinding === undefined) { + return unrecorded(); } - if (!sameExecutableBuild(route.executableBinding, prepared.executableBinding)) { - return stop( - `session "${prepared.sessionKey}" is described differently by its journal and its ` + - `construction route, and neither account repairs the other`, - ); + if (!sameExecutableBuild(retainedBinding, prepared.executableBinding)) { + return disagree(); } // The live half, before the first effect a native process could act on. - const adapter = adapterFor(prepared.agent); - if (!adapter || !allocatesIdentity(adapter)) { - return { - class: "executable-binding-refused", - message: - `session "${prepared.sessionKey}" names a launcher this build has no way to observe, ` + - `so the conversation it prepared cannot be confirmed`, - }; - } let build: BoundBuild; try { build = yield* observeBuild(prepared.agent, agentCommand, adapter.binding); @@ -2968,8 +3546,8 @@ function* useAcpxProviderState( } throw error; } - if (!sameExecutableBuild(build.binding, route.executableBinding)) { - return buildDrift(prepared.sessionKey, route.executableBinding, build.binding); + if (!sameExecutableBuild(build.binding, retainedBinding)) { + return buildDrift(prepared.sessionKey, retainedBinding, build.binding); } invocation.bound.set(prepared.sessionKey, { build, adapter }); return undefined; @@ -2993,9 +3571,7 @@ function* useAcpxProviderState( } let resolved: Result; try { - resolved = allocatesIdentity(adapter) - ? yield* nativeCommand(invocation, prepared, adapter, agentCommand) - : Ok(adapter.resume(prepared.nativeSessionId)); + resolved = yield* nativeCommand(invocation, prepared, adapter, agentCommand); } catch { // Anything raised here came from private setup — an adapter's own code, a // temporary directory, a write. Whatever shape it has, it says nothing @@ -3115,11 +3691,13 @@ function* useAcpxProviderState( // before the caller saw the failure, and preserving the route is what // stops that uncertainty from later being reclassified as // client-native. - const attachment = yield* constructRoute(agentName, prepared); + const construction = yield* constructRoute(agentName, prepared); + const resumeSessionId = construction?.resumeSessionId; const session = yield* turns.withSlot(prepared.sessionKey, () => withSessionRoute(context, function* () { const entry = yield* ensureFromPrepared(agentName, prepared, { - ...(attachment === undefined ? {} : { attachment }), + ...(construction === undefined ? {} : { build: construction.build }), + ...(resumeSessionId === undefined ? {} : { attachment: { resumeSessionId } }), state, }); return entry.session; diff --git a/packages/acp/src/session-route.ts b/packages/acp/src/session-route.ts index 128d0b27..b37a0754 100644 --- a/packages/acp/src/session-route.ts +++ b/packages/acp/src/session-route.ts @@ -68,9 +68,6 @@ export type AgentSessionRouteV1 = * attachment know it is talking to the same build the native UI is in, so it * is required rather than optional here. * - * There is no V2 `acp-first`. ACP-first construction gained no fact, so a - * second schema for it would be a version number with nothing behind it. - * * V1 `client-native` is not upgraded into this. A build observed later says * which build is installed now, not which one established the session, and * writing it into an old record would claim knowledge XMD never had. @@ -88,8 +85,34 @@ export interface AgentSessionRouteV2 { executableBinding: ExecutableBuildBindingV1; } +/** + * The exact V3 record, which exists only for `acp-first`. + * + * It carries the same fact V2 added, for the other construction. An ACP-first + * session is named by the provider, and a name the provider issued is resumable + * only by the build that issued it: the native UI resuming that conversation + * has to be the same build ACP created it through, and nothing in the identity + * itself says which one that was. + * + * It still says nothing about the conversation. There remains no provider + * identity, instruction layer or process fact here — an ACP-first route names + * the construction and the build, and a session ACP created has nothing else to + * report at this boundary. + * + * A V1 `acp-first` route is not upgraded into this, for the same reason a V1 + * `client-native` one is not upgraded into V2. + */ +export interface AgentSessionRouteV3 { + schema: "session-route.v3"; + route: "acp-first"; + provider: string; + agent: string; + sessionKey: string; + executableBinding: ExecutableBuildBindingV1; +} + /** Every route form this build accepts. */ -export type AgentSessionRoute = AgentSessionRouteV1 | AgentSessionRouteV2; +export type AgentSessionRoute = AgentSessionRouteV1 | AgentSessionRouteV2 | AgentSessionRouteV3; /** Why a route could not be used. Never carries a path or provider-private state. */ export class AgentSessionRouteError extends Error { @@ -104,6 +127,7 @@ const CLIENT_NATIVE_MEMBERS = [ "instructionsDigest", "launcher", ]; +const BOUND_ACP_FIRST_MEMBERS = [...ACP_FIRST_MEMBERS, "executableBinding"]; const BOUND_CLIENT_NATIVE_MEMBERS = [...CLIENT_NATIVE_MEMBERS, "executableBinding"]; const BINDING_MEMBERS = ["schema", "reportedVersion", "executableDigest"]; const DIGEST_MEMBERS = ["algorithm", "value"]; @@ -163,11 +187,14 @@ export function parseAgentSessionRoute(value: unknown): AgentSessionRoute | unde if (!isRecord(value)) { return undefined; } - const bound = value.schema === "session-route.v2"; - if (!bound && value.schema !== "session-route.v1") { + const { schema, provider, agent, sessionKey } = value; + if ( + schema !== "session-route.v1" && + schema !== "session-route.v2" && + schema !== "session-route.v3" + ) { return undefined; } - const { provider, agent, sessionKey } = value; if (typeof provider !== "string" || provider.length === 0) { return undefined; } @@ -177,13 +204,40 @@ export function parseAgentSessionRoute(value: unknown): AgentSessionRoute | unde if (typeof sessionKey !== "string" || sessionKey.length === 0) { return undefined; } + // Each schema admits exactly the constructions it was minted for. V2 is + // client-native and V3 is ACP-first, so a record pairing one with the other + // is state this build cannot account for rather than a route with a spare + // member — which is also what keeps a V1 record from being read as a bound + // one, and a bound one from being read past its binding. if (value.route === "acp-first") { - // There is no bound ACP-first form, so a V2 record claiming one is state - // this build cannot account for rather than a route with a spare member. - return !bound && exactly(value, ACP_FIRST_MEMBERS) - ? { schema: "session-route.v1", route: "acp-first", provider, agent, sessionKey } - : undefined; + if (schema === "session-route.v2") { + return undefined; + } + if (schema === "session-route.v1") { + return exactly(value, ACP_FIRST_MEMBERS) + ? { schema: "session-route.v1", route: "acp-first", provider, agent, sessionKey } + : undefined; + } + if (!exactly(value, BOUND_ACP_FIRST_MEMBERS)) { + return undefined; + } + const executableBinding = parseExecutableBinding(value.executableBinding); + if (!executableBinding) { + return undefined; + } + return { + schema: "session-route.v3", + route: "acp-first", + provider, + agent, + sessionKey, + executableBinding, + }; + } + if (schema === "session-route.v3") { + return undefined; } + const bound = schema === "session-route.v2"; const members = bound ? BOUND_CLIENT_NATIVE_MEMBERS : CLIENT_NATIVE_MEMBERS; if (value.route !== "client-native" || !exactly(value, members)) { return undefined; @@ -247,7 +301,7 @@ export function serializeAgentSessionRoute(route: AgentSessionRoute): string { payload.instructionsDigest = route.instructionsDigest; payload.launcher = route.launcher; } - if (route.schema === "session-route.v2") { + if (route.schema === "session-route.v2" || route.schema === "session-route.v3") { payload.executableBinding = { schema: route.executableBinding.schema, reportedVersion: route.executableBinding.reportedVersion, diff --git a/packages/acp/tests/acceptance.test.ts b/packages/acp/tests/acceptance.test.ts index 83efa70a..cdfede11 100644 --- a/packages/acp/tests/acceptance.test.ts +++ b/packages/acp/tests/acceptance.test.ts @@ -19,7 +19,7 @@ import { createFakeRuntime, makeRegistry, makeStore, useFlatWorld } from "./help const CWD = "/work"; const DOC = [ - '', + '', '', '', "", @@ -44,9 +44,9 @@ describe("Tier XA — ACPX provider through the rootProvider seam", () => { factory: createAcpxProvider({ createRuntime: harness.create, sessionStore: makeStore(), - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ gemini: "gemini-cmd" }), }), - options: { defaultAgent: "codex", permissionMode: "deny-all" }, + options: { defaultAgent: "gemini", permissionMode: "deny-all" }, }, }); @@ -102,7 +102,7 @@ describe("Tier WAP — same-named sibling Sessions", () => { yield* writeTextFile( docPath, [ - '', + '', '', "first", "", @@ -122,7 +122,7 @@ describe("Tier WAP — same-named sibling Sessions", () => { factory: createAcpxProvider({ createRuntime: harness.create, sessionStore: makeStore(), - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ gemini: "gemini-cmd" }), sessions: { // deno-lint-ignore require-yield *place(context) { @@ -140,7 +140,7 @@ describe("Tier WAP — same-named sibling Sessions", () => { }, }, }), - options: { defaultAgent: "codex", permissionMode: "deny-all" }, + options: { defaultAgent: "gemini", permissionMode: "deny-all" }, }, }); // Middleware that rewrites the descriptive name on its way through, which @@ -194,7 +194,7 @@ describe("Tier WAP — same-named sibling Sessions", () => { yield* writeTextFile( docPath, [ - '', + '', '', "first", "", @@ -214,7 +214,7 @@ describe("Tier WAP — same-named sibling Sessions", () => { factory: createAcpxProvider({ createRuntime: harness.create, sessionStore: makeStore(), - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ gemini: "gemini-cmd" }), sessions: { // deno-lint-ignore require-yield *place(context) { @@ -227,7 +227,7 @@ describe("Tier WAP — same-named sibling Sessions", () => { }, }, }), - options: { defaultAgent: "codex", permissionMode: "deny-all" }, + options: { defaultAgent: "gemini", permissionMode: "deny-all" }, }, }); diff --git a/packages/acp/tests/adapter-protocol.test.ts b/packages/acp/tests/adapter-protocol.test.ts index 461e539e..f3674eac 100644 --- a/packages/acp/tests/adapter-protocol.test.ts +++ b/packages/acp/tests/adapter-protocol.test.ts @@ -330,4 +330,72 @@ describe("Tier EA — the embedded adapters' prompt-response metadata", () => { expect(metaOf(first, "codex")).toEqual({ turnId: `turn:${sessionId}:1` }); expect(metaOf(second, "codex")).toEqual({ turnId: `turn:${sessionId}:2` }); }); + + it("EA9: interleaved Codex sessions each report the thread they opened", function* () { + const adapter = yield* useAdapter("codex", CODEX_ENV); + yield* initialize(adapter); + + const alpha = yield* effectionSpawn(() => + adapter.request("session/new", { cwd: process.cwd(), mcpServers: [] }), + ); + const beta = yield* effectionSpawn(() => + adapter.request("session/new", { cwd: process.cwd(), mcpServers: [] }), + ); + const [one, two] = yield* all([alpha, beta]); + + // The App Server named each thread, so this is the identity that surface + // emitted compared with what the adapter reported on `_meta`. A top-level + // session id cannot stand in: a client reading that cannot tell an identity + // the provider allocated from one it invented itself. + expect(metaOf(one, "agentSessionId")).toBe(one["sessionId"]); + expect(metaOf(two, "agentSessionId")).toBe(two["sessionId"]); + // Two at once, so an adapter reporting anything session-global — the last + // thread it opened, say — hands at least one of them the other's answer. + expect(metaOf(one, "agentSessionId")).not.toBe(metaOf(two, "agentSessionId")); + }); + + it("EA10: reopening a Codex session reports the thread the App Server reopened", function* () { + const adapter = yield* useAdapter("codex", CODEX_ENV); + yield* initialize(adapter); + // A thread opened first, so the reopened answers below are distinguishable + // from the most recent thread this adapter process created. + const opened = yield* openSession(adapter); + + const resumed = yield* adapter.request("session/resume", { + sessionId: "thread-resumed", + cwd: process.cwd(), + mcpServers: [], + }); + const loaded = yield* adapter.request("session/load", { + sessionId: "thread-loaded", + cwd: process.cwd(), + mcpServers: [], + }); + + // Continuing a conversation needs its identity as much as starting one + // does, and reopening is the route a run takes on every turn after the + // first. + expect(metaOf(resumed, "agentSessionId")).toBe("thread-resumed"); + expect(metaOf(loaded, "agentSessionId")).toBe("thread-loaded"); + expect(metaOf(resumed, "agentSessionId")).not.toBe(opened); + expect(metaOf(loaded, "agentSessionId")).not.toBe(opened); + }); + + it("EA11: the Claude adapter reports no native session identity", function* () { + const adapter = yield* useAdapter("claude", CLAUDE_ENV); + yield* initialize(adapter); + + const session = yield* adapter.request("session/new", { + cwd: process.cwd(), + mcpServers: [], + }); + + // Claude is handed the identity it uses rather than allocating one, so + // there is nothing for its adapter to report back. This is the absence the + // client-allocated route depends on: were a value to appear here, a client + // choosing a route by whether the provider named the session would start + // taking the wrong one. + expect(typeof session["sessionId"]).toBe("string"); + expect(metaOf(session, "agentSessionId")).toBeUndefined(); + }); }); diff --git a/packages/acp/tests/codex-surfaces.test.ts b/packages/acp/tests/codex-surfaces.test.ts new file mode 100644 index 00000000..553cd62d --- /dev/null +++ b/packages/acp/tests/codex-surfaces.test.ts @@ -0,0 +1,1175 @@ +/** + * Tier CS — Codex surface classification and submission readiness + * (specs/native-agent-session-launch-spec.md §Provider-returned adapters). + * + * Two decisions in the Codex native proof are worth more than the turns they + * guard. One reads a screen that produced no answer and says whether the + * environment refused; the other decides whether a composer will take a turn at + * all, and so whether a model turn is spent. Both were wrong in ways only a paid + * run could show: + * + * - A bare `usage limit` substring matched Codex's own benign banner — + * `You have 2 usage limit resets available` — which an account with headroom + * to spare shows. Two authorized runs were reported as blocked by a quota that + * a `/status` on the same account said was 42% unspent. + * - Readiness was read off the byte stream, so a composer whose bytes had + * arrived counted as ready even while `model: loading` was still painted + * behind it, and the confirmation that the turn had been typed swallowed its + * own timeout and pressed Enter anyway. + * + * These cases drive the exact predicates and the exact driver the fixture + * consumes, against hand-written frames. No Codex process starts and no model + * turn is spent. + */ +import { describe, it } from "@executablemd/test-support/bdd"; +import { expect } from "@executablemd/test-support/expect"; +import type { Operation } from "effection"; +import { + presentedDuring, + TerminalReader, + TerminalScreen, + waitForPresented, +} from "./fixtures/terminal-screen.ts"; +import type { ScreenSnapshot } from "./fixtures/terminal-screen.ts"; +import { + classifyInitialSurface, + classifyStall, + COMPOSER, + composerReady, + exhaustedUsage, + MISSING_SESSION, + MODEL_LOADING, + PROMPT_TAIL, + promptPresented, + reachComposer, + submitWhenReady, + TRUST_DIALOG, +} from "./fixtures/codex-surfaces.ts"; +import type { + SubmissionBounds, + SubmissionPort, + SurfaceBounds, + SurfacePort, +} from "./fixtures/codex-surfaces.ts"; + +/** The pty the proof opens, so these frames wrap where that one wraps. */ +const ROWS = 40; +const COLUMNS = 120; + +const ESC = String.fromCharCode(0x1b); +const csi = (body: string): string => `${ESC}[${body}`; +const at = (row: number, column: number): string => csi(`${row};${column}H`); +const ALT_ON = csi("?1049h"); +const CLEAR = csi("2J"); +/** Hold the drawing back, and put the finished frame on show. */ +const SYNC_ON = csi("?2026h"); +const SYNC_OFF = csi("?2026l"); + +function paint(rows: readonly string[]): string { + return rows.map((row, index) => `${at(index + 1, 1)}${csi("2K")}${row}`).join(""); +} + +/** A bordered pane row, padded to its right border as a TUI pads one. */ +function pane(text: string, width = 80): string { + return `│ ${text.padEnd(width)} │`; +} + +/** A whole frame, drawn over whatever was there. */ +function frame(rows: readonly string[]): string { + return `${CLEAR}${paint(rows)}`; +} + +/** + * One read of a pty, carrying several finished frames. + * + * Each is committed in its own right, so every one of them is a frame a terminal + * put on show — and all of them arrive together, because a `data` event is + * however many bytes happened to be waiting rather than however much of a + * drawing was finished. + */ +function oneRead(...frames: readonly (readonly string[])[]): string { + return frames.map((rows) => `${SYNC_ON}${frame(rows)}${SYNC_OFF}`).join(""); +} + +function showing(rows: readonly string[]): ScreenSnapshot { + const terminal = new TerminalScreen({ rows: ROWS, columns: COLUMNS }); + terminal.write(`${ALT_ON}${frame(rows)}`); + return terminal.presented(); +} + +/** Codex's own banner, offering the resets an account still has in hand. */ +const BENIGN_RESETS = "You have 2 usage limit resets available. Run /usage to use one."; +/** What `/status` reports on the same account: headroom, in the same words. */ +const BENIGN_STATUS = "Usage limit: 42% left this week (resets Monday)"; +/** Help text about the refusal, which is not the refusal. */ +const CONDITIONAL_HELP = "If you've hit your usage limit, run /usage to see when it resets."; +/** What an account with nothing left is actually told. */ +const GENUINE_REFUSAL = "You've hit your usage limit."; + +/** + * The wording the previous round accepted, none of which anybody has witnessed. + * + * Kept as cases rather than deleted quietly: each one was a way for a screen + * that is not the refusal to reach `ENVIRONMENT_BLOCKED`, and a list that names + * them fails loudly if one is ever put back. + */ +/** + * The refusal's own words, inside a sentence that is somebody else's. + * + * What separates these from the refusal is where the sentence begins, and + * nothing else — the wording, the punctuation and the whole of the phrase are + * all present. They are the cases the run-prefix rule exists for; a containment + * test over the same run accepts every one of them. + */ +const EMBEDDED: readonly string[] = [ + "Note: You've hit your usage limit.", + "We don't think you've hit your usage limit.", + "It looks like you've hit your usage limit.", +]; + +const UNWITNESSED: readonly string[] = [ + "You've hit your weekly limit.", + "You've reached your usage limit.", + "You've reached your weekly limit.", + "You have hit your usage limit.", + "You have reached your usage limit.", + "Usage limit reached", + // The sentence's own opening, carrying on into a limit that is not the one + // this proof was refused by. Where it ends is part of what was witnessed. + "You've hit your usage limit for images.", +]; + +const READY = [pane("OpenAI Codex (v0.153.2)"), "", pane(COMPOSER), pane("model: gpt-5.6-sol")]; +const LOADING = [pane("OpenAI Codex (v0.153.2)"), "", pane(COMPOSER), pane(MODEL_LOADING)]; +const TRUSTING = [pane(TRUST_DIALOG), pane("1. Yes, continue"), "", pane(COMPOSER)]; + +describe("Tier CS — Codex surface classification", () => { + it("CS1: the resets banner is not exhaustion, composer or no composer", function* () { + // Bare, as it lands over a session that is about to work perfectly well. + expect(exhaustedUsage(showing([pane(BENIGN_RESETS)]))).toBe(false); + expect(classifyStall(showing([pane(BENIGN_RESETS)]))).toEqual({ + tag: "unknown", + verdict: "HARNESS_FAILED", + }); + + // And where it actually appears: above a composer waiting for a turn. The + // classification is the composer's resting state, which explains nothing. + const withComposer = showing([pane(BENIGN_RESETS), "", pane(COMPOSER)]); + expect(exhaustedUsage(withComposer)).toBe(false); + expect(classifyStall(withComposer)).toEqual({ + tag: "composer-idle", + verdict: "HARNESS_FAILED", + }); + }); + + it("CS2: status and headroom text is not exhaustion", function* () { + const status = showing([pane(BENIGN_STATUS), pane("Weekly limit: 42% left")]); + expect(exhaustedUsage(status)).toBe(false); + expect(classifyStall(status).verdict).toBe("HARNESS_FAILED"); + + // The words are on the screen. It is the sentence that decides, not them. + const terminal = new TerminalScreen({ rows: ROWS, columns: COLUMNS }); + terminal.write(`${ALT_ON}${frame([pane(BENIGN_STATUS)])}`); + expect(terminal.presented().rows.join("")).toContain("Usage limit"); + }); + + it("CS3: the exhausted-limit sentence is exhaustion", function* () { + const refused = showing([pane(GENUINE_REFUSAL), "", pane(COMPOSER)]); + expect(exhaustedUsage(refused)).toBe(true); + // Ahead of the idle composer drawn under it, which is the surface a refusal + // leaves behind and would otherwise be all this reported. + expect(classifyStall(refused)).toEqual({ + tag: "usage-limited", + verdict: "ENVIRONMENT_BLOCKED", + }); + + // Case and typographic apostrophes are the terminal's, not the sentence's. + expect(exhaustedUsage(showing([pane("you’ve hit your usage limit.")]))).toBe(true); + // Wrapped across two rows by a 40-column pane, which is where a substring + // search on a single row would lose it. + expect(exhaustedUsage(showing([pane("You've hit your usage", 20), pane("limit.", 20)]))).toBe( + true, + ); + }); + + it("CS4: a refusal inside an uncommitted frame is not classified until shown", function* () { + const terminal = new TerminalScreen({ rows: ROWS, columns: COLUMNS }); + terminal.write(`${ALT_ON}${frame([pane("Working"), pane("esc to interrupt")])}`); + expect(classifyStall(terminal.presented()).tag).toBe("still-working"); + + // The refusal is drawn inside a hold: cells this screen has and no terminal + // ever put on show. Reading them would blame an environment for a frame + // nobody was looking at. + terminal.write(`${SYNC_ON}${frame([pane(GENUINE_REFUSAL), "", pane(COMPOSER)])}`); + expect(exhaustedUsage(terminal.presented())).toBe(false); + expect(classifyStall(terminal.presented())).toEqual({ + tag: "still-working", + verdict: "HARNESS_FAILED", + }); + + // It counts from the commit, and not before. + terminal.write(SYNC_OFF); + expect(classifyStall(terminal.presented())).toEqual({ + tag: "usage-limited", + verdict: "ENVIRONMENT_BLOCKED", + }); + }); + + it("CS5: only the witnessed refusal may blame the environment", function* () { + // Reading a screen is the only way to get here. There is no exported + // tag-to-verdict step, so no caller can hand this proof a `usage-limited` + // it decided on for itself — the refusal has to be on a screen. + expect(classifyStall(showing([pane(GENUINE_REFUSAL)])).verdict).toBe("ENVIRONMENT_BLOCKED"); + + // Everything else describes where the terminal got to, and a proof that read + // any of them as an explanation would name a cause the screen never gave. + for (const rows of [ + [pane(BENIGN_RESETS), "", pane(COMPOSER)], + [pane(BENIGN_STATUS)], + [pane(CONDITIONAL_HELP), "", pane(COMPOSER)], + [pane("Working"), pane("esc to interrupt")], + [pane("Allow command"), pane("codex wants to run something")], + [pane(COMPOSER)], + [pane("something no version of this proof has ever seen")], + ]) { + expect(exhaustedUsage(showing(rows))).toBe(false); + expect(classifyStall(showing(rows)).verdict).toBe("HARNESS_FAILED"); + } + + // The refusal is the sentence, not the words in it. A run that begins + // somewhere else began somewhere else. + for (const sentence of EMBEDDED) { + expect(exhaustedUsage(showing([pane(sentence)]))).toBe(false); + expect(classifyStall(showing([pane(sentence)])).verdict).toBe("HARNESS_FAILED"); + } + + // Wording nobody has seen stays wording nobody has seen. An unrecognized + // screen costs an investigation; a wrongly recognized one costs the truth. + for (const wording of UNWITNESSED) { + expect(exhaustedUsage(showing([pane(wording)]))).toBe(false); + expect(classifyStall(showing([pane(wording)])).verdict).toBe("HARNESS_FAILED"); + } + }); +}); + +/** + * A port over scripted *reads*, built out of the pieces the pty is built out of. + * + * The same {@link TerminalReader} and the same {@link waitForPresented} the live + * path uses, for the same reason {@link surfaceFake} uses them: one entry of the + * script is one read, and a read can finish several frames. Submission needs + * that as much as reaching a composer does — Codex draws its refusal above a + * composer, so the refusal and the ready composer it must not be typed into can + * arrive in one read, with only the last of them left on screen. + */ +function fakePort(script: readonly string[]): { + port: SubmissionPort; + log: { typed: string[]; sent: string[]; waited: string[]; paused: number[]; charged: number }; +} { + const reader = new TerminalReader({ rows: ROWS, columns: COLUMNS }, false); + reader.write(ALT_ON); + const log = { typed: [], sent: [], waited: [], paused: [], charged: 0 } as { + typed: string[]; + sent: string[]; + waited: string[]; + paused: number[]; + charged: number; + }; + let next = 0; + const advance = (): boolean => { + const chunk = script[next]; + if (chunk === undefined) { + return false; + } + next += 1; + reader.write(chunk); + return true; + }; + return { + log, + port: { + screen: () => reader.snapshot(), + waitForScreen(name, _ms, predicate) { + log.waited.push(name); + return waitForPresented(reader, predicate, function* (wait): Operation { + // A script that runs out is a bound that ran out: no further byte is + // ever going to arrive, which is what a spent bound means. + while (!wait.settled() && advance()) { + // Reads, until one of them answers or there are none left. + } + }); + }, + type(text) { + log.typed.push(text); + }, + send(typed) { + // Ordered as the pty orders it: the turn is charged at the keystroke, + // ahead of the byte, so a run that reaches here has spent one either way. + log.charged += 1; + log.sent.push(typed); + }, + pause(ms, predicate) { + log.paused.push(ms); + // Time passing is frames arriving, which is how an overlay or a model + // that is still loading gets its chance to show up — and how a refusal + // gets its chance to be drawn and painted back over with nobody waiting. + return presentedDuring( + reader, + predicate, + (function* (): Operation { + advance(); + })(), + ); + }, + }, + }; +} + +const BOUNDS: SubmissionBounds = { ready: 1_000, startupGrace: 10, presented: 1_000 }; + +/** Minted for one run, and so the only text on the screen unique to that run. */ +const MARKER = "MK4A17BE0C9D31"; + +/** Shaped like the proof's turn: long, and ending in a marker minted per run. */ +const TURN = + "Do not use any tools. Reply with exactly two lines. First line: the first " + + "sentence of the body text under the 'Implementor' heading of your contract, " + + "copied verbatim. Second line: remember this marker for later, MK4A17BE0C9D31"; + +/** The composer with the turn in it — placeholder gone, as a real one is. */ +const TYPED_IN = [pane("OpenAI Codex (v0.153.2)"), "", pane(TURN.slice(-40)), pane("Send with ⏎")]; + +/** + * The refusal where Codex draws it: above a composer, rather than instead of one. + * + * The shape that makes this path dangerous, and the same one CS15 pins for the + * surface classifier. A screen can carry the refusal and a composer at once, so + * every predicate about the composer is satisfied by a screen that has already + * said the session does not exist. + */ +const REFUSED_READY = [ + pane("OpenAI Codex (v0.153.2)"), + pane(`${MISSING_SESSION} 019a-…`), + "", + pane(COMPOSER), +]; + +/** + * The refusal once the composer has gone with it. + * + * The other half of why a batch is not the whole answer. This screen fails + * {@link composerReady}, so a wait collecting on readiness can never be holding + * it — it can only be the screen a read ended on, which nothing but a read of + * the screen itself will find. + */ +const REFUSED_ALONE = [pane("OpenAI Codex (v0.153.2)"), pane(`${MISSING_SESSION} 019a-…`)]; + +/** The same refusal, over a composer holding the turn this run typed. */ +const REFUSED_TYPED = [ + pane("OpenAI Codex (v0.153.2)"), + pane(`${MISSING_SESSION} 019a-…`), + "", + pane(TURN.slice(-40)), + pane("Send with ⏎"), +]; + +describe("Tier CS — submission readiness", () => { + it("CS6: a model still loading takes no input at all", function* () { + expect(composerReady(showing(LOADING))).toBe(false); + // The composer is drawn and its bytes have arrived, which is exactly what + // the stream-reading version accepted. + expect(showing(LOADING).rows.join("")).toContain("Ask Codex to do anything"); + + const { port, log } = fakePort([frame(LOADING), frame(LOADING)]); + const outcome = yield* submitWhenReady(port, TURN, BOUNDS); + + expect(outcome).toEqual({ submitted: false, reason: "composer-never-ready" }); + // Nothing was typed, so nothing was queued into a composer that could take + // it later, and Enter was never reached. + expect(log.typed).toEqual([]); + expect(log.sent).toEqual([]); + }); + + it("CS7: a later ready frame permits exactly one submission", function* () { + const { port, log } = fakePort([ + frame(LOADING), + frame(READY), + frame(READY), + frame(TYPED_IN), + frame(TYPED_IN), + ]); + const outcome = yield* submitWhenReady(port, TURN, BOUNDS); + + expect(outcome).toEqual({ submitted: true }); + expect(log.typed).toEqual([TURN]); + expect(log.sent).toEqual([TURN]); + // The grace was taken, and the readiness read on both sides of it. + expect(log.paused).toEqual([BOUNDS.startupGrace]); + expect(log.waited).toEqual(["composer-ready", "prompt-presented"]); + }); + + it("CS8: a turn that never appears is never sent", function* () { + const { port, log } = fakePort([frame(READY), frame(READY), frame(READY)]); + const outcome = yield* submitWhenReady(port, TURN, BOUNDS); + + expect(outcome).toEqual({ submitted: false, reason: "prompt-never-presented" }); + // Typed, and that is all: the keystroke that spends the turn is the one + // thing this path may not reach. + expect(log.typed).toEqual([TURN]); + expect(log.sent).toEqual([]); + }); + + it("CS9: a composer under a dialog is not ready, and is waited for again", function* () { + expect(composerReady(showing(TRUSTING))).toBe(false); + + // Ready, then a dialog over it during the grace — which is the window the + // recheck exists for — then ready again. + const { port, log } = fakePort([frame(READY), frame(TRUSTING), frame(READY), frame(TYPED_IN)]); + expect(yield* submitWhenReady(port, TURN, BOUNDS)).toEqual({ submitted: true }); + expect(log.waited).toEqual(["composer-ready", "composer-ready-again", "prompt-presented"]); + expect(log.sent).toEqual([TURN]); + + // And when it does not come back, nothing is typed and nothing is sent. + const stuck = fakePort([frame(READY), frame(TRUSTING), frame(TRUSTING)]); + expect(yield* submitWhenReady(stuck.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "composer-unready-after-grace", + }); + expect(stuck.log.typed).toEqual([]); + expect(stuck.log.sent).toEqual([]); + }); + + it("CS10: readiness is read off the presented screen, not the held one", function* () { + // The whole ready frame is drawn inside a hold. Its bytes have arrived; no + // terminal has shown them. + const { port, log } = fakePort([ + `${SYNC_ON}${frame(READY)}`, + SYNC_OFF, + frame(READY), + frame(TYPED_IN), + ]); + expect(composerReady(port.screen())).toBe(false); + + expect(yield* submitWhenReady(port, TURN, BOUNDS)).toEqual({ submitted: true }); + expect(log.sent).toEqual([TURN]); + + // The same is true of the turn's own appearance: a composer showing it only + // in cells that were never committed has not presented it. + const held = new TerminalScreen({ rows: ROWS, columns: COLUMNS }); + held.write(`${ALT_ON}${frame(READY)}${SYNC_ON}${frame(TYPED_IN)}`); + expect(promptPresented(held.presented(), TURN)).toBe(false); + held.write(SYNC_OFF); + expect(promptPresented(held.presented(), TURN)).toBe(true); + }); + + it("CS11: every refusal on this path charges no turn at all", function* () { + // The two ways a submission can be refused, and the accounting each owes. + // `sent` is the byte; `charged` is what the run reports having spent, and + // the pty counts it before writing the byte so neither can outrun the other. + const unready = fakePort([frame(LOADING), frame(LOADING)]); + expect(yield* submitWhenReady(unready.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "composer-never-ready", + }); + expect(unready.log.charged).toBe(0); + + const unpresented = fakePort([frame(READY), frame(READY), frame(READY)]); + expect(yield* submitWhenReady(unpresented.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "prompt-never-presented", + }); + expect(unpresented.log.charged).toBe(0); + + // And the one path that does spend a turn spends exactly one. + const sending = fakePort([frame(READY), frame(READY), frame(TYPED_IN)]); + expect(yield* submitWhenReady(sending.port, TURN, BOUNDS)).toEqual({ submitted: true }); + expect(sending.log.charged).toBe(1); + }); + + it("CS12: the marker is absent before typing and presented before Enter", function* () { + // The tail is what `promptPresented` looks for, so the marker has to fit + // inside it or the thing being recognized is shared with every other run. + expect(TURN.slice(-PROMPT_TAIL)).toContain(MARKER); + + // Before typing, the composer is empty and there is nowhere else on screen + // for the marker to be: nothing has been sent, so no transcript carries it. + const ready = showing(READY); + expect(composerReady(ready)).toBe(true); + expect(ready.rows.join("")).not.toContain(MARKER); + expect(promptPresented(ready, TURN)).toBe(false); + + // Between typing and Enter it is on the screen, in the composer, which is + // what makes the turn about to be charged the turn this run prepared. + const composed = showing(TYPED_IN); + expect(composed.rows.join("")).toContain(MARKER); + expect(promptPresented(composed, TURN)).toBe(true); + + // A composer holding some other run's turn is not this run's turn presented. + const stale = showing([pane("OpenAI Codex (v0.153.2)"), "", pane("MK0000000000000")]); + expect(promptPresented(stale, TURN)).toBe(false); + }); + + it("CS22: a refusal reached while readying the turn charges nothing", function* () { + // Reaching a composer establishes the session was not refused, but only for + // as long as its grace was willing to wait — and Codex draws the composer + // before it resolves the thread. A refusal slower than that grace lands + // here, where the turn is already being readied and Enter is next. + // + // What makes it dangerous is that it is invisible to every predicate on this + // path. Codex draws `No saved session found with ID` *above* a composer + // rather than in place of one, so a refused screen is one the readiness test + // calls ready and the presentation test calls presented. + expect(composerReady(showing(REFUSED_READY))).toBe(true); + expect(promptPresented(showing(REFUSED_TYPED), TURN)).toBe(true); + // Only the surface classifier orders the refusal above them, which is why + // it, and not readiness, is what the gates below ask. + expect(classifyInitialSurface(showing(REFUSED_READY))).toBe("missing-session"); + expect(classifyInitialSurface(showing(REFUSED_TYPED))).toBe("missing-session"); + + // Gate one: the refusal is in the batch that satisfied the readiness wait, + // and the read ends on an ordinary composer, so the screen afterwards shows + // nothing wrong. + const atReady = fakePort([oneRead(REFUSED_READY, READY)]); + expect(yield* submitWhenReady(atReady.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "session-refused", + }); + expect(atReady.log.charged).toBe(0); + expect(atReady.log.sent).toEqual([]); + // Nothing was even typed: the refusal was read before there was any reason to. + expect(atReady.log.typed).toEqual([]); + + // Gate two: the composer came undone over the grace, and the refusal rides + // in the batch that answers the second readiness wait. + const atReadyAgain = fakePort([frame(READY), frame(LOADING), oneRead(REFUSED_READY, READY)]); + expect(yield* submitWhenReady(atReadyAgain.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "session-refused", + }); + expect(atReadyAgain.log.charged).toBe(0); + expect(atReadyAgain.log.waited).toEqual(["composer-ready", "composer-ready-again"]); + + // Gate three: the turn is typed, and the read that confirms it on screen is + // the read carrying the refusal. Typing spends nothing, so this still costs + // no turn — but it is one Enter away from costing one. + const atPresented = fakePort([frame(READY), frame(READY), oneRead(REFUSED_TYPED, TYPED_IN)]); + expect(yield* submitWhenReady(atPresented.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "session-refused", + }); + expect(atPresented.log.charged).toBe(0); + expect(atPresented.log.sent).toEqual([]); + expect(atPresented.log.typed).toEqual([TURN]); + + // Gate four: the refusal is in no batch at all. It arrives in the same read + // as the confirmed turn but *after* it, so it satisfies no predicate on this + // path and is collected by nothing — it is only there to be seen by reading + // the screen as it stands, which is what the last thing before Enter does. + const atEnter = fakePort([frame(READY), frame(READY), oneRead(TYPED_IN, REFUSED_READY)]); + expect(yield* submitWhenReady(atEnter.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "session-refused", + }); + expect(atEnter.log.charged).toBe(0); + expect(atEnter.log.sent).toEqual([]); + + // And the priority is the refusal's alone. The same shape with nothing + // refused still submits, so what was added is a finding and not a new way + // for an ordinary run to lose its turn. + const clean = fakePort([frame(READY), frame(READY), frame(TYPED_IN)]); + expect(yield* submitWhenReady(clean.port, TURN, BOUNDS)).toEqual({ submitted: true }); + expect(clean.log.charged).toBe(1); + expect(clean.log.sent).toEqual([TURN]); + }); + + it("CS23: a refusal left on the screen stops the turn at either decision taken against it", function* () { + // Every check CS22 added reads a batch, and a batch is the frames that + // answered a wait rather than the screen the read ended on. Two decisions + // here are taken against the screen instead — whether the composer needs a + // second wait, and whether it survived one — and both are followed by + // typing. Neither had anything collecting a refusal for it. + + // After the startup grace. The refusal drawn during the pause leaves + // `composerReady` true, which is the shape this whole tier turns on, so the + // second wait is not taken, no batch exists to be checked, and the read that + // confirms the turn repaints a clean composer over the refusal. + const duringGrace = fakePort([frame(READY), frame(REFUSED_READY), frame(TYPED_IN)]); + expect(yield* submitWhenReady(duringGrace.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "session-refused", + }); + expect(duringGrace.log.charged).toBe(0); + expect(duringGrace.log.sent).toEqual([]); + // Nothing was typed either: this stops before the composer is touched, not + // at the last moment before Enter. + expect(duringGrace.log.typed).toEqual([]); + expect(duringGrace.log.waited).toEqual(["composer-ready"]); + + // After a `composer-ready-again` batch. Here the refusal is what the read + // ended on and it carries no composer — which is what sent this run to a + // second wait to begin with, and is exactly what keeps it out of a batch + // collected on readiness. An earlier frame in the same read satisfied the + // wait, so the batch is honestly ready and honestly not refused. + const afterAgain = fakePort([ + frame(READY), + frame(LOADING), + oneRead(READY, REFUSED_ALONE), + frame(TYPED_IN), + ]); + expect(yield* submitWhenReady(afterAgain.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "session-refused", + }); + expect(afterAgain.log.charged).toBe(0); + expect(afterAgain.log.typed).toEqual([]); + expect(afterAgain.log.waited).toEqual(["composer-ready", "composer-ready-again"]); + + // The two refusals are opposite shapes, and that is the point: one is caught + // because it passes for ready, the other because it cannot be collected by a + // wait that asks for ready. A batch check answers for neither. + expect(composerReady(showing(REFUSED_READY))).toBe(true); + expect(composerReady(showing(REFUSED_ALONE))).toBe(false); + + // A composer that simply came back after the grace still types and still + // submits, so the second wait keeps its ordinary ending. + const recovered = fakePort([ + frame(READY), + frame(LOADING), + oneRead(LOADING, READY), + frame(TYPED_IN), + ]); + expect(yield* submitWhenReady(recovered.port, TURN, BOUNDS)).toEqual({ submitted: true }); + expect(recovered.log.charged).toBe(1); + expect(recovered.log.sent).toEqual([TURN]); + expect(recovered.log.waited).toEqual([ + "composer-ready", + "composer-ready-again", + "prompt-presented", + ]); + }); + + it("CS24: a refusal a wait was not watching for still ends that wait", function* () { + // The third place a refusal can hide, and the one neither a batch nor the + // screen afterwards reaches. A wait ends at the read that satisfied it, so a + // frame its predicate says nothing about does not merely go uncollected — it + // does not stop the wait at all, and the next read repaints over it. This + // refusal carries no composer, so `composerReady` and `promptPresented` are + // both false on it, and both of these waits ran straight past it. + expect(composerReady(showing(REFUSED_ALONE))).toBe(false); + expect(promptPresented(showing(REFUSED_ALONE), TURN)).toBe(false); + expect(classifyInitialSurface(showing(REFUSED_ALONE))).toBe("missing-session"); + + // Waiting for the composer to come back. The refusal is presented, then a + // composer is drawn over it in a read of its own, and the batch that answers + // is honestly ready while the screen afterwards is honestly clean. + const whileWaitingReady = fakePort([ + frame(READY), + frame(LOADING), + frame(REFUSED_ALONE), + frame(READY), + frame(TYPED_IN), + ]); + expect(yield* submitWhenReady(whileWaitingReady.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "session-refused", + }); + // Nothing typed: this wait is upstream of the composer being touched, so the + // refusal that ends it ends the run before there is a turn in the box. + expect(whileWaitingReady.log.typed).toEqual([]); + expect(whileWaitingReady.log.sent).toEqual([]); + expect(whileWaitingReady.log.charged).toBe(0); + + // Waiting for the typed turn to appear. Here the turn is already in the + // composer, so what is left to protect is Enter — and the refusal is again + // presented in a read the wait had no reason to stop at. + const whileWaitingPresented = fakePort([ + frame(READY), + frame(READY), + frame(REFUSED_ALONE), + frame(TYPED_IN), + ]); + expect(yield* submitWhenReady(whileWaitingPresented.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "session-refused", + }); + // The typing stands — it happened before anything was refused, and typing + // into a composer costs nothing. Enter is what must not follow it. + expect(whileWaitingPresented.log.typed).toEqual([TURN]); + expect(whileWaitingPresented.log.sent).toEqual([]); + expect(whileWaitingPresented.log.charged).toBe(0); + + // Neither wait is now a wait for anything that happens to end it: a run + // where nothing is refused still waits out its full script and submits. + const patient = fakePort([ + frame(READY), + frame(LOADING), + frame(LOADING), + frame(READY), + frame(TYPED_IN), + ]); + expect(yield* submitWhenReady(patient.port, TURN, BOUNDS)).toEqual({ submitted: true }); + expect(patient.log.charged).toBe(1); + expect(patient.log.sent).toEqual([TURN]); + }); + + it("CS26: a refusal presented during the startup grace is seen, and charges nothing", function* () { + // The grace exists because Codex draws its composer before it has finished + // deciding anything, so it is the window a late refusal is most likely to + // land in — and it was the one window nothing was watching. Presented and + // painted over inside a single read, the refusal leaves a ready composer + // behind it, which is what every question asked after the grace sees. + const refusedDuringGrace = fakePort([ + frame(READY), + oneRead(REFUSED_READY, READY), + frame(TYPED_IN), + ]); + expect(yield* submitWhenReady(refusedDuringGrace.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "session-refused", + }); + expect(refusedDuringGrace.log.typed).toEqual([]); + expect(refusedDuringGrace.log.sent).toEqual([]); + expect(refusedDuringGrace.log.charged).toBe(0); + // Decided against something the screen no longer shows: afterwards it is an + // ordinary ready composer, and asking it would have typed a turn. + expect(composerReady(refusedDuringGrace.port.screen())).toBe(true); + + // The refusal need not be repainted to count, and a grace that ends on one + // is still the same finding rather than a composer that came undone. + const refusedAndLeft = fakePort([frame(READY), frame(REFUSED_ALONE), frame(TYPED_IN)]); + expect(yield* submitWhenReady(refusedAndLeft.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "session-refused", + }); + expect(refusedAndLeft.log.charged).toBe(0); + + // An ordinary repaint during the grace still submits, so what the grace now + // watches for is the refusal and not change in general. + const repaintedDuringGrace = fakePort([frame(READY), oneRead(LOADING, READY), frame(TYPED_IN)]); + expect(yield* submitWhenReady(repaintedDuringGrace.port, TURN, BOUNDS)).toEqual({ + submitted: true, + }); + expect(repaintedDuringGrace.log.charged).toBe(1); + expect(repaintedDuringGrace.log.sent).toEqual([TURN]); + + // A refusal drawn during the grace and held back was never on show, so the + // grace has nothing to report and the turn is typed. The run then ends + // waiting for a composer this hold never lets it see — not as a refusal, + // which is what a hold counting would have made it — and sends nothing. + const heldDuringGrace = fakePort([ + frame(READY), + `${SYNC_ON}${frame(REFUSED_ALONE)}`, + frame(TYPED_IN), + ]); + expect(yield* submitWhenReady(heldDuringGrace.port, TURN, BOUNDS)).toEqual({ + submitted: false, + reason: "prompt-never-presented", + }); + expect(heldDuringGrace.log.typed).toEqual([TURN]); + expect(heldDuringGrace.log.charged).toBe(0); + }); +}); + +/** + * A port over scripted *reads*, built out of the pieces the pty is built out of. + * + * Deliberately not a screen with a predicate checked beside it. The live path is + * a {@link TerminalReader} fed one read at a time, with waits woken from inside + * it by {@link waitForPresented}; this is the same reader and the same wait, and + * differs only in what makes a read arrive. That is what lets one entry of the + * script carry several finished frames — as a pty's `data` event routinely does + * — and have this driver see each of them, rather than only the one the read + * ended on. + */ +function surfaceFake(script: readonly string[]): { + port: SurfacePort; + log: { consents: number; waited: string[]; paused: number[] }; +} { + const reader = new TerminalReader({ rows: ROWS, columns: COLUMNS }, false); + reader.write(ALT_ON); + const log = { consents: 0, waited: [], paused: [] } as { + consents: number; + waited: string[]; + paused: number[]; + }; + let next = 0; + const advance = (): boolean => { + const chunk = script[next]; + if (chunk === undefined) { + return false; + } + next += 1; + reader.write(chunk); + return true; + }; + return { + log, + port: { + screen: () => reader.snapshot(), + waitForScreen(name, _ms, predicate) { + log.waited.push(name); + return waitForPresented(reader, predicate, function* (wait): Operation { + // Nothing happens between reads here, as nothing happens between a + // pty's. A script that runs out is a bound that ran out: no further + // byte is ever going to arrive, which is what a spent bound means. + while (!wait.settled() && advance()) { + // Reads, until one of them answers or there are none left. + } + }); + }, + consent() { + log.consents += 1; + }, + pause(ms, predicate) { + log.paused.push(ms); + return presentedDuring( + reader, + predicate, + (function* (): Operation { + advance(); + })(), + ); + }, + }, + }; +} + +const SURFACE: SurfaceBounds = { surface: 1_000, settle: 10, grace: 10 }; + +const MISSING = [pane("OpenAI Codex (v0.153.2)"), "", pane(`${MISSING_SESSION} 019a-…`)]; +const STARTING = [pane("OpenAI Codex (v0.153.2)"), pane(MODEL_LOADING)]; + +describe("Tier CS — reaching a composer", () => { + it("CS13: the trust dialog is answered once, and only while it is up", function* () { + const { port, log } = surfaceFake([frame(TRUSTING), frame(TRUSTING), frame(READY)]); + expect(yield* reachComposer(port, SURFACE)).toBe("composer-ready"); + expect(log.consents).toBe(1); + // The settle came before the Enter, and the answered dialog was waited out + // rather than read again as a second question. + expect(log.paused).toEqual([SURFACE.settle]); + expect(log.waited).toEqual(["surface", "trust-answered", "surface", "readiness-grace"]); + + // A dialog that goes away during the settle is not answered at all: the + // Enter would land in whatever replaced it. + const vanishing = surfaceFake([frame(TRUSTING), frame(READY), frame(READY)]); + expect(yield* reachComposer(vanishing.port, SURFACE)).toBe("composer-ready"); + expect(vanishing.log.consents).toBe(0); + }); + + it("CS14: a directory Codex already trusts is consented to zero times", function* () { + const { port, log } = surfaceFake([frame(READY), frame(READY)]); + expect(yield* reachComposer(port, SURFACE)).toBe("composer-ready"); + expect(log.consents).toBe(0); + // The grace was still spent watching for the readiness to come undone. + expect(log.waited).toEqual(["surface", "readiness-grace"]); + + // And when one does arrive over the composer during that grace, it is + // answered — which is the ordering Codex actually draws. + const late = surfaceFake([frame(READY), frame(TRUSTING), frame(TRUSTING), frame(READY)]); + expect(yield* reachComposer(late.port, SURFACE)).toBe("composer-ready"); + expect(late.log.consents).toBe(1); + }); + + it("CS15: a refused identity is a finding, not something to press Enter at", function* () { + const { port, log } = surfaceFake([frame(MISSING)]); + expect(yield* reachComposer(port, SURFACE)).toBe("missing-session"); + expect(log.consents).toBe(0); + + // Codex draws its composer while still opening the thread, so the refusal + // and a ready-looking composer share a screen. The refusal is what it is. + expect(classifyInitialSurface(showing([...MISSING, "", pane(COMPOSER)]))).toBe( + "missing-session", + ); + }); + + it("CS16: a held frame decides nothing until the terminal commits it", function* () { + for (const [held, committed] of [ + [TRUSTING, "trust-dialog"], + [MISSING, "missing-session"], + [READY, "composer-ready"], + // A loading model is not a surface either way. What it must never do is + // become readiness, and cells nobody has been shown are the one place + // that could happen without anybody seeing it. + [LOADING, "starting"], + ] as const) { + const terminal = new TerminalScreen({ rows: ROWS, columns: COLUMNS }); + terminal.write(`${ALT_ON}${frame(STARTING)}`); + terminal.write(`${SYNC_ON}${frame(held)}`); + // The bytes are in. Nothing is on show but the frame from before them. + expect(classifyInitialSurface(terminal.presented())).toBe("starting"); + expect(composerReady(terminal.presented())).toBe(false); + terminal.write(SYNC_OFF); + expect(classifyInitialSurface(terminal.presented())).toBe(committed); + } + + // Driven end to end: a dialog that is only ever held is a dialog this never + // answers, and the same dialog committed is answered exactly once. + const uncommitted = surfaceFake([`${SYNC_ON}${frame(TRUSTING)}`]); + expect(yield* reachComposer(uncommitted.port, SURFACE)).toBe("unresolved"); + expect(uncommitted.log.consents).toBe(0); + + const shown = surfaceFake([ + `${SYNC_ON}${frame(TRUSTING)}`, + SYNC_OFF, + frame(TRUSTING), + frame(READY), + ]); + expect(yield* reachComposer(shown.port, SURFACE)).toBe("composer-ready"); + expect(shown.log.consents).toBe(1); + }); + + it("CS17: readiness that comes undone during the grace is not readiness", function* () { + // Codex draws its composer before it reports that it holds no such thread, + // so the refusal can land on a screen this driver has already read as ready. + // A grace watching only for the trust dialog runs out in front of it and + // returns the composer — sending a turn at a session that does not exist. + const refused = surfaceFake([frame(READY), frame(MISSING)]); + expect(yield* reachComposer(refused.port, SURFACE)).toBe("missing-session"); + expect(refused.log.consents).toBe(0); + + // The grace ends at the first screen that is no longer ready, so a refusal + // it sees is the answer even where a composer is drawn over it afterwards. + // A grace that ran on past it would report the composer and lose the + // finding this whole feature exists to establish. + const repainted = surfaceFake([frame(READY), frame(MISSING), frame(READY)]); + expect(yield* reachComposer(repainted.port, SURFACE)).toBe("missing-session"); + + // The same for a model that goes back to loading: input typed there may be + // queued rather than taken, so this is not a composer to submit into. + const relapsed = surfaceFake([frame(READY), frame(LOADING)]); + expect(yield* reachComposer(relapsed.port, SURFACE)).toBe("unresolved"); + expect(relapsed.log.consents).toBe(0); + + // Unless the model finishes loading, at which point it is ready again and + // this says so. The grace refuses a screen, not a session. + const recovered = surfaceFake([frame(READY), frame(LOADING), frame(READY)]); + expect(yield* reachComposer(recovered.port, SURFACE)).toBe("composer-ready"); + expect(recovered.log.consents).toBe(0); + + // And a composer that simply stays ready is still reached, which is what + // every accepted run of this proof has done. + const stable = surfaceFake([frame(READY), frame(READY), frame(READY)]); + expect(yield* reachComposer(stable.port, SURFACE)).toBe("composer-ready"); + expect(stable.log.consents).toBe(0); + }); + + it("CS18: a transition held back during the grace decides at its commit", function* () { + // The refusal is drawn inside a hold that never commits. Its bytes arrived + // during the grace; no terminal showed them, so the screen is still the + // ready one and that is what this returns. + const held = surfaceFake([frame(READY), `${SYNC_ON}${frame(MISSING)}`]); + expect(yield* reachComposer(held.port, SURFACE)).toBe("composer-ready"); + + // Commit the same frame and the grace has something to see. + const committed = surfaceFake([frame(READY), `${SYNC_ON}${frame(MISSING)}`, SYNC_OFF]); + expect(yield* reachComposer(committed.port, SURFACE)).toBe("missing-session"); + expect(committed.log.consents).toBe(0); + + // A loading model held back and then committed reads the same way round. + const loading = surfaceFake([frame(READY), `${SYNC_ON}${frame(LOADING)}`, SYNC_OFF]); + expect(yield* reachComposer(loading.port, SURFACE)).toBe("unresolved"); + expect(loading.log.consents).toBe(0); + }); + + it("CS19: a surface presented and repainted inside one read is still presented", function* () { + // Codex commits its refusal and draws the composer over it within + // milliseconds of each other, and a pty hands over however many bytes were + // waiting — so both frames routinely arrive in one read, with nothing in + // between for a waiter outside the reader to be woken by. Everything above + // scripts a read per frame, which is the one arrangement that cannot show + // this. + const refusedThenDrawn = surfaceFake([oneRead(MISSING, READY)]); + expect(yield* reachComposer(refusedThenDrawn.port, SURFACE)).toBe("missing-session"); + expect(refusedThenDrawn.log.consents).toBe(0); + // And the screen the read ended on is a ready composer — which is what a + // driver reading only the current screen would have found, and typed into. + expect(classifyInitialSurface(refusedThenDrawn.port.screen())).toBe("composer-ready"); + + // The same read arriving during the grace, which is the other place a + // refusal lands: after this driver has already read a composer as ready. + const refusedDuringGrace = surfaceFake([frame(READY), oneRead(MISSING, READY)]); + expect(yield* reachComposer(refusedDuringGrace.port, SURFACE)).toBe("missing-session"); + expect(classifyInitialSurface(refusedDuringGrace.port.screen())).toBe("composer-ready"); + + // A model that goes back to loading and finishes inside one read is the + // mirror of that, and settles the other way. The loading frame ends the + // grace, and then readiness is read off the screen as it stands — because + // the composer about to be typed into is this one, not the frame that woke + // the wait. + const relapsedThenLoaded = surfaceFake([frame(READY), oneRead(LOADING, READY)]); + expect(yield* reachComposer(relapsedThenLoaded.port, SURFACE)).toBe("composer-ready"); + expect(relapsedThenLoaded.log.consents).toBe(0); + + // And a trust dialog that comes and goes inside one read is never answered. + // It was presented, so the grace ends at it; but Enter is an act against + // whatever is on screen when it is pressed, and by then the dialog is gone. + const askedThenGone = surfaceFake([frame(READY), oneRead(TRUSTING, READY)]); + expect(yield* reachComposer(askedThenGone.port, SURFACE)).toBe("composer-ready"); + expect(askedThenGone.log.consents).toBe(0); + + // A dialog that is still up when the read ends is answered exactly once, + // so the case above turns on the dialog having gone and not on the shape. + const stillAsking = surfaceFake([ + frame(READY), + oneRead(READY, TRUSTING), + frame(TRUSTING), + frame(READY), + ]); + expect(yield* reachComposer(stillAsking.port, SURFACE)).toBe("composer-ready"); + expect(stillAsking.log.consents).toBe(1); + + // The same dialog inside the *first* read, before any composer has been read + // as ready. The dialog is what the wait matched, and the driver never goes to + // answer it at all: it waits out no settle, because a settle is time given to + // a dialog that is still finishing drawing, and this one is already gone. + const goneBeforeLookedAt = surfaceFake([oneRead(TRUSTING, READY)]); + expect(yield* reachComposer(goneBeforeLookedAt.port, SURFACE)).toBe("composer-ready"); + expect(goneBeforeLookedAt.log.consents).toBe(0); + expect(goneBeforeLookedAt.log.paused).toEqual([]); + expect(goneBeforeLookedAt.log.waited).toEqual(["surface", "readiness-grace"]); + + // A read carrying three frames, with the refusal in the middle of it. Neither + // end of the read shows it: the model was still loading when the read began + // and a composer was up by the time it ended, so this is only found by + // examining every commit rather than the first or the last. + const refusedMidRead = surfaceFake([oneRead(LOADING, MISSING, READY)]); + expect(yield* reachComposer(refusedMidRead.port, SURFACE)).toBe("missing-session"); + expect(refusedMidRead.log.consents).toBe(0); + expect(classifyInitialSurface(refusedMidRead.port.screen())).toBe("composer-ready"); + }); + + it("CS20: a refusal behind an earlier match in the same read is still the finding", function* () { + // The case above finds the refusal because nothing ahead of it in the read + // satisfied the wait. That is not the general shape. Both waits here ask a + // question a benign frame can also answer — the grace watches for the + // composer becoming *anything* else, and the initial wait for any surface + // worth acting on — so a read can satisfy one at its first frame and carry + // the refusal behind it. A wait that stopped looking at the frame that woke + // it would report the benign one and never see the refusal at all. + + // Codex reloading a model, refusing the identity, and drawing its composer + // back: three commits, one read, arriving after this driver has already read + // a composer as ready. The grace matches at `loading`, and the refusal is + // two frames further in. + const refusedBehindLoading = surfaceFake([frame(READY), oneRead(LOADING, MISSING, READY)]); + expect(yield* reachComposer(refusedBehindLoading.port, SURFACE)).toBe("missing-session"); + expect(refusedBehindLoading.log.consents).toBe(0); + // The screen the read ended on is a ready composer, which is what this + // driver would otherwise have typed a turn into. + expect(classifyInitialSurface(refusedBehindLoading.port.screen())).toBe("composer-ready"); + + // The same ordering on the initial wait, where the trust dialog is the + // actionable frame that answers it first and the refusal follows. + const refusedBehindTrust = surfaceFake([oneRead(TRUSTING, MISSING, READY)]); + expect(yield* reachComposer(refusedBehindTrust.port, SURFACE)).toBe("missing-session"); + // Never consented to: a refused session is a finding, not a directory to + // take a standing permission on. + expect(refusedBehindTrust.log.consents).toBe(0); + expect(classifyInitialSurface(refusedBehindTrust.port.screen())).toBe("composer-ready"); + + // And the priority is the refusal's alone. A benign frame behind another + // benign frame decides nothing extra: the grace still ends at the first, and + // readiness is still read off the screen as it stands. + const loadedBehindTrust = surfaceFake([frame(READY), oneRead(TRUSTING, LOADING, READY)]); + expect(yield* reachComposer(loadedBehindTrust.port, SURFACE)).toBe("composer-ready"); + expect(loadedBehindTrust.log.consents).toBe(0); + }); + + it("CS21: a refusal presented after the consent is the finding, and no turn follows", function* () { + // The third wait this driver consumes a batch from, and the one CS20 never + // reaches. Answering the dialog is not the end of the startup: Codex goes on + // to load a model and to report what it holds, and this wait is what it is + // watched through. The wait asks only that the dialog be gone, which the + // model reloading already satisfies — so the refusal arrives behind a frame + // that answered the wait, in the read that answered it. + const refusedAfterConsent = surfaceFake([ + frame(TRUSTING), + frame(TRUSTING), + oneRead(LOADING, MISSING, READY), + ]); + expect(yield* reachComposer(refusedAfterConsent.port, SURFACE)).toBe("missing-session"); + // The consent stands, and is neither repeated nor taken back: it was pressed + // at a dialog that was up and asking, about a directory, before anything had + // been refused. What must not follow it is a turn, and the finding is what + // stops one — this returns instead of going round to type into the composer + // the same read drew. + expect(refusedAfterConsent.log.consents).toBe(1); + expect(refusedAfterConsent.log.waited).toEqual(["surface", "trust-answered"]); + expect(classifyInitialSurface(refusedAfterConsent.port.screen())).toBe("composer-ready"); + + // A benign batch here still continues, so what was added is the refusal's + // priority and not a general distrust of whatever follows a consent. + const startedAfterConsent = surfaceFake([ + frame(TRUSTING), + frame(TRUSTING), + oneRead(LOADING, READY), + frame(READY), + ]); + expect(yield* reachComposer(startedAfterConsent.port, SURFACE)).toBe("composer-ready"); + expect(startedAfterConsent.log.consents).toBe(1); + expect(startedAfterConsent.log.waited).toEqual([ + "surface", + "trust-answered", + "surface", + "readiness-grace", + ]); + }); + + it("CS25: a refusal presented during the settle is seen, and nothing is consented to", function* () { + // The settle is a window like any other — the terminal goes on drawing for + // its whole duration — but nothing was watching it. A refusal presented and + // painted over inside one read during the settle leaves the dialog on screen + // afterwards, and the recheck before Enter is a question about the screen, + // which by then says the dialog is still up and still asking. + const refusedDuringSettle = surfaceFake([ + frame(TRUSTING), + oneRead(MISSING, TRUSTING), + frame(READY), + frame(READY), + ]); + expect(yield* reachComposer(refusedDuringSettle.port, SURFACE)).toBe("missing-session"); + // Not consented to at all. This refusal was presented *before* the Enter, + // unlike CS21's, so there is no question of a consent standing: the + // directory was never answered for, because the session it was being + // answered for had already been refused. + expect(refusedDuringSettle.log.consents).toBe(0); + // And the screen it was decided against still shows the dialog, which is + // exactly why the screen alone could not decide it. + expect(classifyInitialSurface(refusedDuringSettle.port.screen())).toBe("trust-dialog"); + + // An ordinary repaint during the settle still consents. The dialog redrawing + // itself, or a model loading behind it, is a settle doing its job. + const redrawnDuringSettle = surfaceFake([ + frame(TRUSTING), + oneRead(STARTING, TRUSTING), + frame(READY), + frame(READY), + ]); + expect(yield* reachComposer(redrawnDuringSettle.port, SURFACE)).toBe("composer-ready"); + expect(redrawnDuringSettle.log.consents).toBe(1); + + // And a dialog that simply goes away during the settle is still not answered, + // which is the behaviour the settle existed for in the first place. + const departedDuringSettle = surfaceFake([ + frame(TRUSTING), + frame(READY), + frame(READY), + frame(READY), + ]); + expect(yield* reachComposer(departedDuringSettle.port, SURFACE)).toBe("composer-ready"); + expect(departedDuringSettle.log.consents).toBe(0); + + // A refusal drawn during the settle and never committed is not something the + // terminal ever put on show, and observing the settle does not change that. + // The consent goes ahead — the dialog is what is presented — and the run ends + // waiting for an answer that this held drawing never lets arrive, which is a + // different outcome from the refusal it would be if a hold counted. + const heldDuringSettle = surfaceFake([ + frame(TRUSTING), + `${SYNC_ON}${frame(MISSING)}`, + frame(READY), + ]); + expect(yield* reachComposer(heldDuringSettle.port, SURFACE)).toBe("unresolved"); + expect(heldDuringSettle.log.consents).toBe(1); + }); +}); diff --git a/packages/acp/tests/fixtures/codex-native-launch-proof.ts b/packages/acp/tests/fixtures/codex-native-launch-proof.ts new file mode 100644 index 00000000..8758200b --- /dev/null +++ b/packages/acp/tests/fixtures/codex-native-launch-proof.ts @@ -0,0 +1,2296 @@ +/** + * Issue #755 — the parts of a real Codex launch Markdown cannot observe. + * + * `CodexNativeLaunch.test.md` and `CodexZeroNativeTurnExit.test.md` own the + * sequencing, the gating, the schemas, the assertions and everything an + * operator reads. This fixture owns only what an authored document cannot + * reach: a sized pseudo-terminal, child lifecycle, structured route and journal + * reads, the vendored adapter's own snapshot identity, and exact-path cleanup. + * + * What it does not own is the product. The thing under test is the ordinary + * production command: + * + * dist/xmd run AGENTS.md#Implementor --default-agent codex --journal … --raw + * + * run inside a byte-for-byte copy of the repository's own `AGENTS.md` and + * `.agents/implementor.md`. No role Markdown is built, interpolated or + * rewritten here — a proof that assembled the document in TypeScript would be + * proving something nobody runs. + * + * Three boundaries keep the evidence honest: + * + * - **Nothing provider-private is read.** Every observation comes from a value + * this fixture supplied, a process outcome, XMD's own diagnostic journal, the + * exact XMD route record for this run's natural key, or the terminal output + * of the production command. Nothing beneath `~/.codex` is opened — not + * rollouts, not history, not configuration — and `CODEX_HOME` is left exactly + * as the operator has it, because relocating it de-authenticates Codex. + * - **No shim stands in front of the executable.** The Claude proofs put a + * recording wrapper on `PATH` to read the native argv. Doing that here would + * defeat the thing #755 freezes: the observer canonicalizes and hashes what it + * resolves, so a wrapper would bind the session to the wrapper's digest rather + * than to the Codex build in the compatibility tuple. The native identity is + * established from Codex's own terminal output instead — it prints + * `Session ID: ` as it leaves — which is stronger than an argv anyway: a + * picker or `--last` would print a different one. + * - **Nothing sensitive is rendered.** One filtered JSON verdict reaches stdout: + * versions, digests this repository already checks in, the exact identity and + * its provenance, booleans, counts, phase and failure classes, usage figures, + * and cleanup outcomes. Raw terminal buffers, argv, environment and prepared + * instruction text never leave this file. + * + * Model turns are the one number nobody may be wrong about, so every one of them + * is counted at the keystroke or at the record that proves it happened, never + * summarized on the way out. + */ + +import { ensure, main, race, scoped, sleep, until, withResolvers } from "effection"; +import type { Operation } from "effection"; +import { exists, rm } from "@effectionx/fs"; +import { spawn as spawnChild } from "node:child_process"; +import type { ChildProcess } from "node:child_process"; +import { createHash, randomUUID } from "node:crypto"; +import { chmod, copyFile, mkdir, mkdtemp, readdir, readFile, realpath } from "node:fs/promises"; +import { homedir, tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import process from "node:process"; +import { createEmbeddedAdapters, embeddedAdapterIdentities } from "../../src/adapter-snapshots.ts"; +import { ADVERTISED_NATIVE_LAUNCH } from "../../src/native-launch.ts"; +import { agentSessionKeyDigest } from "@executablemd/runtime"; +import type { AnswerQuestion, ScreenSnapshot, TerminalReader } from "./terminal-screen.ts"; +import { + presentedDuring, + terminalChannels, + terminalReader, + waitForPresented, +} from "./terminal-screen.ts"; +import type { StallClassification, SubmissionPort, SurfacePort } from "./codex-surfaces.ts"; +import type { SubmissionRefusal } from "./codex-surfaces.ts"; +import { + classifyInitialSurface, + classifyStall, + reachComposer, + refusalDetail, + SCREEN_CHROME, + submitWhenReady, +} from "./codex-surfaces.ts"; + +/** Opting in at all. Absent, every mode refuses before a Codex process. */ +const PROOF_ENV = "XMD_CODEX_NATIVE_PROOF"; +/** The separate, exact grant a mode that spends model turns needs. */ +const TURNS_ENV = "XMD_CODEX_MODEL_TURNS_AUTHORIZED"; + +/** The production target, exactly as an operator would type it. */ +const TARGET = "AGENTS.md#Implementor"; + +/** The document that reattaches to the launched session through ACP. */ +const ACP_DOCUMENT = fileURLToPath(new URL("./codex-native-to-acp-prompt.md", import.meta.url)); + +/** + * The compatibility point these journeys are only meaningful against + * (`issue-755-codex-materialization-turn-implementor-handoff.md`). + * + * Frozen rather than read from the machine, because the whole claim is that one + * exact build was proven. A run against another build is not a weaker pass; it + * is a different question, and it says so. + */ +const REQUIRED_PLATFORM = "darwin"; +const REQUIRED_ARCHITECTURE = "arm64"; +const REQUIRED_CODEX_VERSION = "codex-cli 0.153.2"; +const REQUIRED_CODEX_DIGEST = "195ace4100a634a9df39147f493e730e666b5bd87795f3c9f3251d8542400424"; +const REQUIRED_ADAPTER_PACKAGE = "@agentclientprotocol/codex-acp"; +const REQUIRED_ADAPTER_VERSION = "1.6.2"; +const REQUIRED_ADAPTER_DIGEST = "3ee22bc6b1649d02fcef80b352516f395fe774e63b459193195a41c42930dd8b"; + +/** The one prompt version a Codex launch may spend a turn on. */ +const PROMPT_VERSION = "codex-materialization.v1"; + +/** + * The role contract's opening sentence. + * + * Never sent to the model. It reaches the session only as the prepared + * instruction layer the launch installed, so a native answer carrying it is + * evidence the layer governed that turn — evidence that needs no test-only token + * planted in a production role document. + */ +const OPENING_SENTENCE = "The Implementor delivers an accepted plan as a focused, verified change."; + +/** What a recovered marker has to be wrapped in, so a repaint cannot supply it. */ +const RECALL_PREFIX = "RECOVERED-"; + +/** Rendered-output markers: one per role section, each unique to its section. */ +const IMPLEMENTOR_MARKER = "Running this target prepares the session"; +const SIBLING_MARKERS: readonly string[] = [ + ".agents/planner.md", + ".agents/architect.md", + "It has no contract document of its own", +]; + +const ENTER = "\r"; +const INTERRUPT = "\u0003"; +/** Two interrupts: the first asks the TUI to leave, the second insists. */ +const EXIT_CONTROL_BYTES = "0303"; + +/** + * How the one dialog Codex may put in front of a session is answered. + * + * It is not a conversation. It asks the person at the terminal for a standing + * permission — may Codex load project-local configuration from this directory — + * reaches no model and creates no user turn, so answering it is terminal control + * and is reported on its own channel. The answer is the pre-selected + * `1. Yes, continue` the dialog itself offers. + */ +const TRUST_SURFACE = "directory-trust"; +const TRUST_BYTES = "0d"; + +/** What Codex prints as it leaves, naming the conversation it was in. */ +const SESSION_ID_LINE = /Session ID:\s*([0-9a-fA-F-]{36})/u; + +/** What XMD says on the terminal before it spends the reader's model turn. */ +const NOTICE_BEFORE = "spending one model turn in session"; +/** And what it says once that turn has been spent. */ +const NOTICE_AFTER = "materialization turn completed in"; + +/** + * Environment names an enclosing coding-agent session exports. + * + * A proof that runs inside one inherits them, and a provider behaves + * differently when it does. `CODEX_PATH` in particular is the variable XMD's own + * binding sets for the adapter child, so inheriting one would let this process + * choose the build the product is supposed to resolve for itself. + */ +function inheritedAgentMarker(name: string): boolean { + return ( + name === "CLAUDECODE" || + name === "CLAUDE_PID" || + name === "CLAUDE_EFFORT" || + name.startsWith("CLAUDE_CODE_") || + name.startsWith("CODEX_") + ); +} + +/** Every phase is bounded on its own, well inside the document's block timeout. */ +const VERSION_MS = 30_000; +const SURFACE_MS = 240_000; +/** + * How long one native answer is waited for. + * + * Twice the Claude proof's bound, because two Codex answers reached that one and + * the second was still showing a working state when the classifier read the + * screen afterwards. A bound is not a claim about the product: reaching it says + * this harness stopped waiting, and `answerSurface` says what was on screen. + */ +const ANSWER_MS = 600_000; +const SETTLE_MS = 120_000; +const DELETE_MS = 60_000; +const ADAPTER_MS = 120_000; +/** How long a surface is given to settle before a key is sent into it. */ +const SETTLE_DELAY_MS = 3_000; + +/** + * How long the composer is given to turn out to have a dialog over it. + * + * Codex draws the composer first and the trust dialog over it, so a reader that + * stops at the composer stops in front of a question nobody answered. This is + * the window in which that turns out to be what happened. + */ +const TRUST_GRACE_MS = 20_000; + +/** The pseudo-terminal's size. A zero-column terminal draws no TUI at all. */ +const PTY_ROWS = 40; +const PTY_COLUMNS = 120; + +type Mode = "preflight" | "zero-native-turn" | "native-launch"; + +/** + * The exact model-turn budget each mode may spend, and the value its grant must + * carry. + * + * `zero-native-turn` spends the one XMD-owned materialization turn and no native + * user turn — which is what "zero native turn" names. `native-launch` spends + * that one, plus the first native user turn, plus the ACP turn that asks the + * reattached session what the native turn said. + */ +const AUTHORIZED_TURNS: Readonly> = { + preflight: "0", + "zero-native-turn": "1", + "native-launch": "3", +}; + +/** + * `UNDECIDED` is the state a journey starts in and never ends in: it says the + * body classified nothing, which is the one state a decider may speak from. It + * is a distinct value rather than a reused failure so that a body which does + * classify — including one reporting that this harness stopped waiting — is not + * mistaken for one that stayed silent and then overruled. + */ +type Verdict = + | "UNDECIDED" + | "PASS" + | "REFUSED" + | "ENVIRONMENT_BLOCKED" + | "PRODUCT_FAILED" + | "HARNESS_FAILED"; + +class PhaseTimeout extends Error { + override name = "PhaseTimeout"; + constructor(readonly phase: string) { + super(`phase "${phase}" exceeded its bound`); + } +} + +/** Run `op` under a bound. The loser is halted with the surrounding scope. */ +function* bounded(phase: string, ms: number, op: () => Operation): Operation { + const outcome = yield* race([ + (function* (): Operation<{ done: true; value: T }> { + return { done: true, value: yield* op() }; + })(), + (function* (): Operation<{ done: false }> { + yield* sleep(ms); + return { done: false }; + })(), + ]); + if (!outcome.done) { + throw new PhaseTimeout(phase); + } + return outcome.value; +} + +/** A fixed-category description carrying no path, credential or transcript. */ +function classify(error: unknown): string { + if (error instanceof PhaseTimeout) { + return `timeout in ${error.phase}`; + } + if (error instanceof Error) { + return error.name; + } + return "unknown"; +} + +function isReachable(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } +} + +function killed(pid: number): void { + try { + process.kill(pid, "SIGKILL"); + } catch { + // Already gone, which is the state this was asking for. + } +} + +function interrupted(pid: number): void { + try { + process.kill(pid, "SIGINT"); + } catch { + // Already gone. + } +} + +const TEARDOWN_GRACE_MS = 8_000; + +function settledWithin(pid: number, ms: number): Promise { + return new Promise((resolve) => { + const started = Date.now(); + const poll = () => { + if (!isReachable(pid) || Date.now() - started >= ms) { + resolve(); + return; + } + setTimeout(poll, 100); + }; + poll(); + }); +} + +/** Collapse every run of whitespace, so a TUI's own layout cannot hide a word. */ +function squeeze(text: string): string { + return text.replaceAll(/\s+/gu, ""); +} + +const CONTROL_SEQUENCE = + // deno-lint-ignore no-control-regex + /\x1B\[[0-9;?]*[ -\/]*[@-~]|\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)|\x1B[()][B0]|\x1B[<>=]|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/gu; + +/** + * The glyphs a TUI draws its frame out of. + * + * Codex wraps a long answer inside a bordered pane, so a sentence can arrive + * with a border between two of its words. No model emits these mid-sentence, + * so removing them recovers the text without letting anything else through. + */ +const FRAME_GLYPH = /[─-╿▀-▟]/gu; + +function readable(text: string): string { + return text.replaceAll(CONTROL_SEQUENCE, "").replaceAll(FRAME_GLYPH, ""); +} + +/** Whether `text` shows `marker`, ignoring the layout a TUI drew around it. */ +function shows(text: string, marker: string): boolean { + return squeeze(readable(text)).includes(squeeze(marker)); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +interface ChildOutcome { + code: number | null; + signal: string | null; + stdout: string; + stderr: string; +} + +/** Every child a mode started, so cleanup can prove none outlived it. */ +type LiveSet = Set; + +/** Run one captured child to settlement. Never a TUI. */ +function runChild( + command: string, + args: string[], + options: { cwd: string; input?: string; live: LiveSet; env?: Record }, +): Operation { + return (function* (): Operation { + const settled = withResolvers(); + const failed = withResolvers(); + let child: ChildProcess | undefined; + + // Registered before the spawn: a halt between acquiring a process and + // registering its cleanup leaks the process. + yield* ensure(() => { + const running = child; + if (!running?.pid) { + return; + } + if (running.exitCode === null && running.signalCode === null) { + killed(running.pid); + } + options.live.delete(running.pid); + }); + + child = spawnChild(command, args, { + cwd: options.cwd, + stdio: ["pipe", "pipe", "pipe"], + ...(options.env === undefined ? {} : { env: options.env }), + }); + if (child.pid) { + options.live.add(child.pid); + } + let stdout = ""; + let stderr = ""; + child.stdout?.on("data", (chunk: Buffer) => { + stdout += chunk.toString(); + }); + child.stderr?.on("data", (chunk: Buffer) => { + stderr += chunk.toString(); + }); + child.once("error", (error: Error) => failed.reject(error)); + child.once("close", (code: number | null, signal: string | null) => { + if (child?.pid) { + options.live.delete(child.pid); + } + settled.resolve({ code, signal, stdout, stderr }); + }); + + if (options.input !== undefined) { + child.stdin?.write(options.input); + } + child.stdin?.end(); + + return yield* race([settled.operation, failed.operation]); + })(); +} + +/** + * A path this fixture may write into a generated shell command. + * + * Everything here is built from `tmpdir()`, the repository root and a UUID, so + * this can only fail if one of them holds a quote or a newline. Refusing is the + * only safe answer: a command assembled around one would run something else. + */ +function literal(path: string): string { + if (/['\n\r]/u.test(path)) { + throw new Error("a fixture path holds a character a shell command cannot carry safely"); + } + return `'${path}'`; +} + +/** + * One production command running under a sized pseudo-terminal. + * + * `` takes the run's foreground-terminal lease and refuses a + * host with no terminal, which is what a real operator gets and the reason this + * cannot be a piped child. `/usr/bin/script` is the terminal boundary, so no PTY + * dependency is added for a test. + * + * The size is set inside the pty and not by an environment variable, because a + * TUI asks the terminal rather than the environment. `script` leaves a pty at + * zero columns, and Codex draws nothing at all into one — a proof reading that + * empty screen would conclude the surface never appeared. + */ +interface Pty { + /** + * Wait until output arriving *since the last handled surface* satisfies + * `predicate`, then mark it handled. + * + * Windowed on purpose: a dialog that has already been answered stays in the + * buffer, and re-reading it would answer it twice. + */ + waitForAny( + name: string, + ms: number, + tags: readonly { tag: string; marker: string }[], + ): Operation; + /** + * Wait until the reconstructed screen is showing the expected answer. + * + * Separate from {@link waitForAny} because an answer is not a surface + * appearing in the byte stream. Codex addresses the cursor: it wraps, erases + * and redraws what it has already drawn, so the expectation can arrive split + * across rows, or be moved somewhere else by a later repaint, and the bytes + * retain text that is no longer on the screen at all. The bytes are applied to + * a terminal here and the terminal is what gets read. + * + * Available only to a run that submits a turn. A run that spoke none has no + * reply to attribute, and its reader refuses to be asked for one. + */ + waitForAnswer(ms: number, question: AnswerQuestion): Operation; + /** + * Wait until what the terminal is *showing* satisfies `predicate`, and answer + * with the frame that did. + * + * The stream answers a different question than the screen does, and every + * surface decision is a question about the screen: bytes that drew a composer + * are retained after a dialog was drawn over it, bytes that drew a refusal are + * retained after a repaint wiped it, and a frame held back by `CSI ?2026h` has + * arrived without having been shown to anybody. + * + * The frames rather than a flag, because one `data` event from the pty applies + * every frame its bytes finished before anybody out here is woken. A caller + * told only that a surface arrived would look at the screen and find whatever + * that read ended on, which for a refusal Codex repaints over is a composer. + * + * Every frame that matched rather than the first, because a wait is answered + * by a read and not by a frame: what satisfied it first is one of the things + * that read presented, and the caller is the only thing that knows which of + * them it was actually asking about. + * + * Empty when the bound is spent, because a surface that never arrived is + * something to report rather than an error. + */ + waitForScreen( + name: string, + ms: number, + predicate: (snapshot: ScreenSnapshot) => boolean, + ): Operation; + /** + * Wait out a fixed delay, and answer with what was presented during it. + * + * Nothing shortens it — that is what makes it a delay rather than a wait — but + * the terminal does not stop drawing because a driver has stopped looking, so + * the same answer a wait gives is owed here too. A caller reading the screen + * once the delay is out is asking what survived it, which is a different + * question from what happened during it. + */ + pause( + ms: number, + predicate: (snapshot: ScreenSnapshot) => boolean, + ): Operation; + /** Whether everything seen so far contains `marker`, layout ignored. */ + saw(marker: string): boolean; + /** The first match of `pattern` anywhere in what has been drawn. */ + captured(pattern: RegExp): string; + /** What the terminal last presented. Never a frame still being drawn. */ + screen(): ScreenSnapshot; + /** Write terminal-control bytes — never conversation. */ + control(bytes: string): void; + /** + * Type one conversation turn, without sending it. + * + * Separate from `send()` because a TUI reads text and a newline arriving + * together as a pasted line rather than as a submitted one — the newline lands + * in the box and nothing is sent. + */ + type(text: string): void; + /** Press Enter. Charged here: this is the keystroke that spends the turn. */ + send(typed: string): void; + /** Wait for the process to leave. */ + settle(ms: number): Operation; + exitCode(): number; +} + +/** + * The three input channels, kept apart because they mean different things — + * and each recorded at the moment it is written. + * + * These are callbacks rather than counters a caller reads afterwards, because + * the phase that sends a turn is also the phase that can time out. A run that + * spent a turn and then hung must still report the turn. + */ +interface PtyChannels { + /** One conversation turn was submitted: this many bytes, and one turn. */ + charged(bytes: number): void; + /** A consent dialog was answered with these bytes. Never a conversation. */ + consented(surface: string, bytes: string): void; + /** Exit control was sent. */ + exited(bytes: string): void; +} + +function ptyRun( + command: string, + args: string[], + options: { + cwd: string; + env: Record; + live: LiveSet; + channels: PtyChannels; + /** + * The terminal this run draws on, supplied by its caller. + * + * Every run gets one, because every run decides which surface it is looking + * at and that is a fact about the screen rather than about the bytes. + * Whether it also watches for an answer is stated at the call site, so a + * reply can only be attributed by a run that submitted a turn. + */ + reader: TerminalReader; + }, + body: (pty: Pty) => Operation, +): Operation { + return scoped(function* (): Operation { + const settled = withResolvers(); + const failed = withResolvers(); + let child: ChildProcess | undefined; + let text = ""; + let consumed = 0; + let code = -1; + // The waiter reports which tag it saw rather than only that it saw one, so + // the tag it answers with is the one the screen actually showed. + let pending: + | { match: (fresh: string) => string | undefined; resolve: (tag: string) => void } + | undefined; + let answered: (() => void) | undefined; + + // Interrupt, then insist. A harness that reaches straight for SIGKILL takes + // away the product's chance to retain its own exit and then reports what it + // stranded as a product failure. + yield* ensure(function* () { + const running = child; + if (!running?.pid) { + return; + } + if (running.exitCode === null && running.signalCode === null) { + interrupted(running.pid); + yield* until(settledWithin(running.pid, TEARDOWN_GRACE_MS)); + if (isReachable(running.pid)) { + killed(running.pid); + } + } + options.live.delete(running.pid); + }); + + const line = [ + `stty rows ${PTY_ROWS} columns ${PTY_COLUMNS}`, + `exec ${[command, ...args].map(literal).join(" ")}`, + ].join("; "); + child = spawnChild("/usr/bin/script", ["-q", "/dev/null", "/bin/sh", "-c", line], { + cwd: options.cwd, + env: options.env, + stdio: ["pipe", "pipe", "pipe"], + }); + if (child.pid) { + options.live.add(child.pid); + } + + // Everything the run said, whichever channel said it. Surface detection + // reads this, because a surface arriving is a thing the stream says. + const record = (chunk: string | Uint8Array) => { + text += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"); + const waiter = pending; + if (!waiter) { + return; + } + const tag = waiter.match(text.slice(consumed)); + if (tag === undefined) { + return; + } + pending = undefined; + consumed = text.length; + waiter.resolve(tag); + }; + const channels = terminalChannels(record, options.reader); + + // Only stdout went through the pty, so only stdout is what the terminal + // showed. `script` and the shell beneath it report their own troubles on + // stderr, which reached this process without ever being displayed — + // evidence about the run, and not a thing anybody could have read on screen. + // Screen waits are woken from inside the reader, at the commit that answered + // them, rather than out here once a whole read has been applied. Where a + // read carried several finished frames, out here is too late to tell them + // apart: only the last one is still on the screen. + child.stdout?.on("data", (chunk: Buffer) => { + if (channels.display(chunk) && answered !== undefined) { + const resolve = answered; + answered = undefined; + resolve(); + } + }); + child.stderr?.on("data", (chunk: Buffer) => { + channels.diagnostic(chunk); + }); + child.once("error", (error: Error) => failed.reject(error)); + child.once("close", (status: number | null) => { + code = status ?? -1; + if (child?.pid) { + options.live.delete(child.pid); + } + settled.resolve(); + }); + + const write = (bytes: string) => { + // Everything already on screen belongs to the surface being answered, so + // the next wait reads only what this write provoked. + consumed = text.length; + child?.stdin?.write(bytes); + }; + + const pty: Pty = { + waitForAny(name, ms, tags) { + return bounded(name, ms, function* (): Operation { + const match = (fresh: string): string | undefined => + tags.find((entry) => shows(fresh, entry.marker))?.tag; + const already = match(text.slice(consumed)); + if (already !== undefined) { + consumed = text.length; + return already; + } + const waiter = withResolvers(); + pending = { match, resolve: waiter.resolve }; + return yield* race([waiter.operation, failed.operation]); + }); + }, + waitForAnswer(ms, question) { + return bounded("answer", ms, function* (): Operation { + // Arming answers against what is already showing, because the reply + // can be complete before this is reached and no further byte arrive. + // A reader that is not watching refuses the question outright. + if (options.reader.ask(question)) { + return; + } + const waiter = withResolvers(); + answered = waiter.resolve; + yield* race([waiter.operation, failed.operation]); + }); + }, + waitForScreen(name, ms, predicate) { + return waitForPresented(options.reader, predicate, function* (wait): Operation { + try { + yield* bounded(name, ms, () => race([wait.matched, failed.operation])); + } catch (error) { + if (!(error instanceof PhaseTimeout)) { + throw error; + } + // A bound that ran out is a surface that never arrived, which the + // caller reports rather than raises. + } + }); + }, + pause(ms, predicate) { + return presentedDuring(options.reader, predicate, sleep(ms)); + }, + saw(marker) { + return shows(text, marker); + }, + captured(pattern) { + return readable(text).match(pattern)?.[1] ?? ""; + }, + screen() { + return options.reader.snapshot(); + }, + control(bytes) { + write(bytes); + }, + type(spoken) { + write(spoken); + }, + send(typed) { + // Charged before the write returns, so an answer that never arrives + // cannot make this look like a turn nobody spent. + options.channels.charged(Buffer.byteLength(`${typed}${ENTER}`, "utf8")); + write(ENTER); + }, + settle(ms) { + return bounded("settle", ms, () => race([settled.operation, failed.operation])); + }, + exitCode: () => code, + }; + + return yield* body(pty); + }); +} + +/** One journey's owned paths. Every removal below names an exact one. */ +interface Journey { + root: string; + /** Canonical, because a launch records the directory it resolved. */ + project: string; + journals: [string, string, string]; + live: LiveSet; + codex: string; + /** Every native identity this journey caused to exist, for exact deletion. */ + created: Set; +} + +interface CleanupReport { + liveChildren: number; + journalsRemoved: boolean; + routeRecordsRemoved: boolean; + sessionDeleteOutcome: string; + temporaryRootRemoved: boolean; +} + +/** What one production invocation established. */ +interface InvocationOutcome { + exitCode: number; + refused: boolean; + reachedSurface: boolean; + answered: boolean; + /** + * What a turn without an answer was showing, and what that makes the run. + * + * Read off the presented screen by {@link classifyStall} and kept whole. The + * verdict travels with the tag rather than being derived from it later, + * because a tag alone is a label this fixture could have written down itself. + */ + answerStall: StallClassification | undefined; + /** + * Why no turn was submitted, when none was. + * + * Set only by a run that reached the composer and then declined to type into + * it or to press Enter. It is the state that keeps a readiness failure from + * being read as an answer that never came: nothing was asked, so nothing + * about the product was established and nothing was charged. + */ + submissionRefusal: SubmissionRefusal | undefined; + /** The identity Codex named as it left, from its own output. */ + leftSessionId: string; + noticedBeforeSpending: boolean; + noticedAfterSpending: boolean; +} + +interface TurnPlan { + /** What to say once the composer is ready, or nothing at all. */ + say?: string; + /** What proves the answer arrived. */ + expect?: string; +} + +/** The one verdict shape every mode renders, exactly. */ +interface JourneyVerdict { + mode: string; + verdict: Verdict; + authorized: boolean; + turnsAuthorized: boolean; + authorizedTurnBudget: number; + ran: boolean; + refusal: string; + detail: string; + + codexVersion: string; + codexDigest: string; + platform: string; + architecture: string; + adapterPackage: string; + adapterVersion: string; + adapterDigest: string; + compatibilityTupleFrozen: boolean; + + target: string; + projectCopyVerified: boolean; + implementorMarkerRendered: boolean; + siblingMarkersRendered: number; + + nativeSessionId: string; + identityProvenance: string; + reentryNativeSessionId: string; + substitutedIdentity: boolean; + routeConverted: boolean; + + firstXmdExitCode: number; + secondXmdExitCode: number; + instructionChannel: string; + + /** Every model turn this journey caused, however it was caused. */ + modelTurns: number; + materializationTurns: number; + nativeUserTurns: number; + acpReattachTurns: number; + /** How many of this journey's invocations reconstructed a terminal at all. */ + answerObserverInvocations: number; + conversationInputByteCount: number; + consentInputBytes: string; + consentSurfaces: string[]; + exitControlBytes: string; + reentryConsentInputBytes: string; + reentryConsentSurfaces: string[]; + reentryExitControlBytes: string; + inheritedAgentMarkersRemoved: number; + + noticedBeforeSpending: boolean; + noticedAfterSpending: boolean; + openingSentenceExact: boolean; + markerRecovered: boolean; + acpDocumentCarriesMarker: boolean; + answerSurface: string; + outcome: string; + + materialization: { + promptVersion: string; + requestIdStable: boolean; + promptExact: boolean; + turnNamed: boolean; + turnProvider: string; + durationReported: boolean; + responsePresent: boolean; + stopReason: string; + reportedUsageFields: string[]; + unreportedUsageFields: string[]; + failureClasses: string[]; + }; + route: { + kind: string; + provider: string; + buildVersion: string; + buildDigest: string; + }; + journal: { + provider: string; + agent: string; + launcher: string; + provenance: string; + nativeSessionId: string; + cwdIsProject: boolean; + sessionState: string[]; + instructionsDigestPresent: boolean; + firstPhases: string[]; + secondPhases: string[]; + failureClasses: string[]; + }; + cleanup: CleanupReport; + privateStateInspected: boolean; +} + +function blankJourney(mode: Mode): JourneyVerdict { + return { + mode, + verdict: "UNDECIDED", + authorized: false, + turnsAuthorized: false, + authorizedTurnBudget: Number(AUTHORIZED_TURNS[mode]), + ran: false, + refusal: "", + detail: "the journey did not reach a verdict", + codexVersion: "", + codexDigest: "", + platform: process.platform, + architecture: process.arch, + adapterPackage: "", + adapterVersion: "", + adapterDigest: "", + compatibilityTupleFrozen: false, + target: TARGET, + projectCopyVerified: false, + implementorMarkerRendered: false, + siblingMarkersRendered: 0, + nativeSessionId: "", + identityProvenance: "", + reentryNativeSessionId: "", + substitutedIdentity: false, + routeConverted: false, + firstXmdExitCode: -1, + secondXmdExitCode: -1, + instructionChannel: "", + modelTurns: 0, + materializationTurns: 0, + nativeUserTurns: 0, + acpReattachTurns: 0, + answerObserverInvocations: 0, + conversationInputByteCount: 0, + consentInputBytes: "", + consentSurfaces: [], + exitControlBytes: "", + reentryConsentInputBytes: "", + reentryConsentSurfaces: [], + reentryExitControlBytes: "", + inheritedAgentMarkersRemoved: 0, + noticedBeforeSpending: false, + noticedAfterSpending: false, + openingSentenceExact: false, + markerRecovered: false, + acpDocumentCarriesMarker: false, + answerSurface: "", + outcome: "unresolved", + materialization: { + promptVersion: "", + requestIdStable: false, + promptExact: false, + turnNamed: false, + turnProvider: "", + durationReported: false, + responsePresent: false, + stopReason: "", + reportedUsageFields: [], + unreportedUsageFields: [], + failureClasses: [], + }, + route: { + kind: "", + provider: "", + buildVersion: "", + buildDigest: "", + }, + journal: { + provider: "", + agent: "", + launcher: "", + provenance: "", + nativeSessionId: "", + cwdIsProject: false, + sessionState: [], + instructionsDigestPresent: false, + firstPhases: [], + secondPhases: [], + failureClasses: [], + }, + cleanup: { + liveChildren: 0, + journalsRemoved: false, + routeRecordsRemoved: false, + sessionDeleteOutcome: "nothing-to-delete", + temporaryRootRemoved: false, + }, + privateStateInspected: false, + }; +} + +const REPO_ROOT = fileURLToPath(new URL("../../../..", import.meta.url)); +const XMD_BINARY = join(REPO_ROOT, "dist", "xmd"); +const SESSION_COORDINATOR_ROOT = join(homedir(), ".acpx", "xmd-native-sessions", "v1"); +/** Where the binary under proof materializes the adapters it carries. */ +const ADAPTER_ROOT = join(homedir(), ".xmd", "adapters"); + +/** + * The exact absolute paths one natural key owns, and nothing else. + * + * The natural key includes the agent command, and for a launch through this + * build's own adapter that is the embedded snapshot's command line — settled by + * the bytes the build carries, beneath the root the binary uses, so it is + * knowable here without opening anything. Deriving it through the product's own + * digest rather than searching for a record that matches means this reads only + * the three files this journey's key owns, and never another key's. + */ +function keyPaths(sessionKey: string): string[] { + const agent = createEmbeddedAdapters(ADAPTER_ROOT).command("codex"); + const digest = agentSessionKeyDigest({ provider: "acpx", agent, sessionKey }); + return [ + join(SESSION_COORDINATOR_ROOT, "routes", `${digest}.json`), + join(SESSION_COORDINATOR_ROOT, "ownership", `${digest}.json`), + join(SESSION_COORDINATOR_ROOT, "leases", `${digest}.lease`), + ]; +} + +interface LaunchRecord { + phase: string; + agent?: string; + sessionKey?: string; + provider?: string; + nativeSessionId?: string; + sessionState?: string; + instructionChannel?: string; + identityProvenance?: string; + instructionsDigest?: string; + instructions?: string; + cwd?: string; + launcher?: string; + executableBinding?: { + reportedVersion?: string; + executableDigest?: { value?: string }; + }; + materialization?: { promptVersion?: string; requestId?: string; prompt?: string }; + promptVersion?: string; + requestId?: string; + turn?: { provider?: string; kind?: string; value?: string }; + durationMs?: number; + usage?: Record; + response?: string; + stopReason?: string; + failure?: { class?: string }; +} + +/** A member of an untrusted record, kept only if it is already text. */ +function textOf(source: Record, member: string): string | undefined { + const value = source[member]; + return typeof value === "string" ? value : undefined; +} + +/** The same for a member that is itself read through its own members. */ +function recordOf( + source: Record, + member: string, +): Record | undefined { + const value = source[member]; + return isRecord(value) ? value : undefined; +} + +/** + * One launch record, read out of a journal this fixture did not write. + * + * Every member is taken at the type a verdict reads it at and dropped + * otherwise, so a journal line that carries the wrong shape reaches a verdict + * as an absent member rather than as a value of a type it never held. + */ +function parseLaunchRecord(source: Record): LaunchRecord | undefined { + const phase = textOf(source, "phase"); + if (phase === undefined) { + return undefined; + } + const materialization = recordOf(source, "materialization"); + const turn = recordOf(source, "turn"); + const failure = recordOf(source, "failure"); + const durationMs = source["durationMs"]; + return { + phase, + agent: textOf(source, "agent"), + sessionKey: textOf(source, "sessionKey"), + provider: textOf(source, "provider"), + nativeSessionId: textOf(source, "nativeSessionId"), + sessionState: textOf(source, "sessionState"), + instructionChannel: textOf(source, "instructionChannel"), + identityProvenance: textOf(source, "identityProvenance"), + instructionsDigest: textOf(source, "instructionsDigest"), + instructions: textOf(source, "instructions"), + cwd: textOf(source, "cwd"), + launcher: textOf(source, "launcher"), + promptVersion: textOf(source, "promptVersion"), + requestId: textOf(source, "requestId"), + response: textOf(source, "response"), + stopReason: textOf(source, "stopReason"), + durationMs: typeof durationMs === "number" ? durationMs : undefined, + usage: recordOf(source, "usage"), + materialization: materialization && { + promptVersion: textOf(materialization, "promptVersion"), + requestId: textOf(materialization, "requestId"), + prompt: textOf(materialization, "prompt"), + }, + turn: turn && { + provider: textOf(turn, "provider"), + kind: textOf(turn, "kind"), + value: textOf(turn, "value"), + }, + failure: failure && { class: textOf(failure, "class") }, + }; +} + +/** + * The launch records one diagnostic journal holds, in order. + * + * A retained preparation carries the prepared instruction text, so this reads it + * and nothing that reaches a verdict repeats it: what crosses out of here is + * whether a digest exists, never the text or the digest's own bytes. + */ +function* readLaunchRecords(path: string): Operation { + if (!(yield* exists(path))) { + return []; + } + const text = yield* until(readFile(path, "utf8")); + const records: LaunchRecord[] = []; + for (const line of text.split("\n")) { + if (line.trim().length === 0) { + continue; + } + let event: unknown; + try { + event = JSON.parse(line); + } catch { + continue; + } + if (!isRecord(event) || event.type !== "yield" || !isRecord(event.description)) { + continue; + } + if (event.description.type !== "agent_session_launch") { + continue; + } + const result = event.result; + if (!isRecord(result) || result.status !== "ok" || !isRecord(result.value)) { + continue; + } + const record = parseLaunchRecord(result.value); + if (record) { + records.push(record); + } + } + return records; +} + +/** + * Establish one journey's root and byte-for-byte project copy. + * + * Teardown is registered as the root appears, and it is the last thing to run: + * every child is reaped, every conversation this journey created is removed + * through Codex's own `delete`, and the root goes last. The durable accounts are + * read by a finalizer the caller registers afterwards, which LIFO puts *before* + * this one. + */ +function* useJourney(label: string, cleanup: CleanupReport): Operation { + const root = join(tmpdir(), `xmd-755-${label}-${randomUUID()}`); + yield* until(mkdir(root, { recursive: true, mode: 0o700 })); + yield* until(chmod(root, 0o700)); + + const project = join(root, "project"); + yield* until(mkdir(join(project, ".agents"), { recursive: true, mode: 0o700 })); + + const journey: Journey = { + root, + // A launch records the directory it resolved, and on macOS a temporary root + // reaches it through a symlink. Comparing the unresolved spelling would call + // the same directory two different ones. + project: yield* until(realpath(project)), + journals: [ + join(root, "journal-1.jsonl"), + join(root, "journal-2.jsonl"), + join(root, "journal-3.jsonl"), + ], + live: new Set(), + codex: "", + created: new Set(), + }; + + yield* ensure(function* () { + for (const pid of journey.live) { + killed(pid); + } + cleanup.liveChildren = [...journey.live].filter(isReachable).length; + + // Codex's own delete, never manual removal of provider state, and only for + // the exact identities this journey caused to exist. `--force`, because a + // delete that stops to ask has nobody to answer it here and would strand + // the conversation this journey is accountable for. + let outcome = journey.created.size === 0 ? "nothing-to-delete" : "deleted"; + for (const id of journey.created) { + const removed = yield* bounded("session-delete", DELETE_MS, () => + runChild(journey.codex, ["delete", "--force", id], { + cwd: root, + live: journey.live, + input: "", + }), + ); + // A forced delete answers the same way whether the conversation was never + // there or could not be removed, so anything but success is reported as a + // failure rather than guessed at from a message that says neither. + if (removed.code !== 0) { + outcome = "failed"; + } + } + cleanup.sessionDeleteOutcome = outcome; + + yield* rm(journey.root, { recursive: true, force: true }); + cleanup.temporaryRootRemoved = !(yield* exists(journey.root)); + }); + + return journey; +} + +/** Copy the checked-in role documents, and prove the copy is the original. */ +function* copyProject(journey: Journey): Operation { + let identical = true; + for (const relative of ["AGENTS.md", join(".agents", "implementor.md")]) { + const from = join(REPO_ROOT, relative); + const to = join(journey.project, relative); + yield* until(copyFile(from, to)); + const before = createHash("sha256") + .update(yield* until(readFile(from))) + .digest("hex"); + const after = createHash("sha256") + .update(yield* until(readFile(to))) + .digest("hex"); + identical &&= before === after; + } + return identical; +} + +/** + * The environment the production command runs under: an operator's, not this + * process's. + * + * Everything an enclosing coding-agent session exported is dropped, and the + * count is reported. + */ +/** The host environment, at the string-valued shape a child is given. */ +function hostEnvironment(): Record { + const inherited: Record = {}; + for (const [name, value] of Object.entries(process.env)) { + if (typeof value === "string") { + inherited[name] = value; + } + } + return inherited; +} + +function environmentFor(verdict: JourneyVerdict): Record { + const inherited: Record = {}; + let removed = 0; + for (const [name, value] of Object.entries(hostEnvironment())) { + if (inheritedAgentMarker(name)) { + removed += 1; + continue; + } + inherited[name] = value; + } + verdict.inheritedAgentMarkersRemoved = removed; + return inherited; +} + +function xmdArguments(document: string, journal: string): string[] { + return ["run", document, "--default-agent", "codex", "--journal", journal, "--raw"]; +} + +/** + * The pty, as the shared surface driver reads and answers it. + * + * `consent` is the only thing here that writes, and it writes the dialog's + * pre-selected answer while recording it as consent on its own channel. There + * is no way through this port to type or to spend a conversation turn. + */ +function surfacePort(pty: Pty, channels: PtyChannels): SurfacePort { + return { + screen: () => pty.screen(), + waitForScreen: (name, ms, predicate) => pty.waitForScreen(name, ms, predicate), + consent() { + channels.consented(TRUST_SURFACE, TRUST_BYTES); + pty.control(ENTER); + }, + pause: (ms, predicate) => pty.pause(ms, predicate), + }; +} + +/** The bounds every surface reader in this proof is given. */ +const SURFACE_BOUNDS = { + surface: SURFACE_MS, + settle: SETTLE_DELAY_MS, + grace: TRUST_GRACE_MS, +}; + +/** + * Answer the trust dialog if it is presented, then establish the composer. + * + * Every decision belongs to {@link reachComposer}, which reads only presented + * screens; this records what it decided against the invocation. + */ +function* reachSurface( + pty: Pty, + channels: PtyChannels, + outcome: InvocationOutcome, +): Operation { + const reached = yield* reachComposer(surfacePort(pty, channels), SURFACE_BOUNDS); + // Codex was handed an identity it has no saved session for. That is the + // exact refusal this whole feature exists to avoid, and it is reported + // rather than driven around. + outcome.refused = reached === "missing-session"; + outcome.reachedSurface = reached === "composer-ready"; +} + +/** + * The pty, at the width submitting a turn needs. + * + * A named adapter rather than an inline object, so the shape the offline cases + * implement is the shape the paid run supplies. + */ +function submissionPort(pty: Pty): SubmissionPort { + return { + screen: () => pty.screen(), + // Handed on whole. Codex draws its refusal above a composer rather than in + // place of one, so the frames satisfying a readiness wait are exactly the + // frames a refusal can be sitting in, and this is the last port before + // Enter. + waitForScreen: (name, ms, predicate) => pty.waitForScreen(name, ms, predicate), + type: (text) => pty.type(text), + send: (typed) => pty.send(typed), + pause: (ms, predicate) => pty.pause(ms, predicate), + }; +} + +/** Drive one process from its opening surface to its exit. */ +function* drive( + pty: Pty, + plan: TurnPlan, + channels: PtyChannels, + outcome: InvocationOutcome, +): Operation { + // Read before the surface, because the notice is printed while XMD still owns + // the terminal and the TUI draws over it afterwards. + yield* reachSurface(pty, channels, outcome); + outcome.noticedBeforeSpending = pty.saw(NOTICE_BEFORE); + outcome.noticedAfterSpending = pty.saw(NOTICE_AFTER); + + if (outcome.refused) { + yield* pty.settle(SETTLE_MS); + outcome.leftSessionId = pty.captured(SESSION_ID_LINE); + return; + } + + if (plan.say !== undefined && plan.expect !== undefined && outcome.reachedSurface) { + // Typed, then confirmed on screen, then submitted — which is what a person + // does, and what stops a TUI reading the whole thing as a pasted line. Every + // reading is of the screen rather than the stream, because a composer whose + // bytes have arrived can be underneath a dialog, behind a model that has not + // loaded, or inside a frame nobody was shown. + const submission = yield* submitWhenReady(submissionPort(pty), plan.say, { + ready: SURFACE_MS, + startupGrace: SETTLE_DELAY_MS, + presented: VERSION_MS, + }); + if (!submission.submitted) { + outcome.submissionRefusal = submission.reason; + // A refusal read while the turn was being readied is the same finding + // reaching a composer reports, arriving later than its bound was willing + // to wait. It belongs to the product rather than to this harness, and + // recording it as one keeps a refused session from being reported as a + // proof that could not get a turn typed. + outcome.refused ||= submission.reason === "session-refused"; + } else { + try { + yield* pty.waitForAnswer(ANSWER_MS, { + expected: plan.expect, + typed: plan.say, + chrome: SCREEN_CHROME, + }); + outcome.answered = true; + } catch (error) { + if (!(error instanceof PhaseTimeout)) { + throw error; + } + outcome.answerStall = classifyStall(pty.screen()); + } + } + } + + yield* sleep(SETTLE_DELAY_MS); + channels.exited(EXIT_CONTROL_BYTES); + pty.control(INTERRUPT); + yield* sleep(SETTLE_DELAY_MS); + pty.control(INTERRUPT); + try { + yield* pty.settle(SETTLE_MS); + } catch (error) { + if (!(error instanceof PhaseTimeout)) { + throw error; + } + } + outcome.leftSessionId = pty.captured(SESSION_ID_LINE); +} + +function* invoke( + journey: Journey, + verdict: JourneyVerdict, + index: 0 | 1, + plan: TurnPlan, +): Operation { + const first = index === 0; + const channels: PtyChannels = { + charged(bytes) { + verdict.conversationInputByteCount += bytes; + verdict.nativeUserTurns += 1; + verdict.modelTurns += 1; + }, + consented(surface, bytes) { + if (first) { + verdict.consentSurfaces = [...verdict.consentSurfaces, surface]; + verdict.consentInputBytes += bytes; + return; + } + verdict.reentryConsentSurfaces = [...verdict.reentryConsentSurfaces, surface]; + verdict.reentryConsentInputBytes += bytes; + }, + exited(bytes) { + if (first) { + verdict.exitControlBytes = bytes; + return; + } + verdict.reentryExitControlBytes = bytes; + }, + }; + const outcome: InvocationOutcome = { + exitCode: -1, + refused: false, + reachedSurface: false, + answered: false, + answerStall: undefined, + submissionRefusal: undefined, + leftSessionId: "", + noticedBeforeSpending: false, + noticedAfterSpending: false, + }; + + // Built from what this invocation does. Every invocation reads a screen, so + // every invocation reconstructs a terminal; only the one that submits the + // turn may watch it for a reply. Re-entry carrying an expectation would be a + // mistake, and is refused there rather than watched. + const reader = terminalReader({ rows: PTY_ROWS, columns: PTY_COLUMNS }, { index, ...plan }); + if (reader.watching) { + verdict.answerObserverInvocations += 1; + } + + yield* ptyRun( + XMD_BINARY, + xmdArguments(TARGET, journey.journals[index]), + { + cwd: journey.project, + env: environmentFor(verdict), + live: journey.live, + channels, + reader, + }, + function* (pty): Operation { + yield* drive(pty, plan, channels, outcome); + verdict.implementorMarkerRendered ||= pty.saw(IMPLEMENTOR_MARKER); + verdict.siblingMarkersRendered += SIBLING_MARKERS.filter((marker) => pty.saw(marker)).length; + outcome.exitCode = pty.exitCode(); + }, + ); + return outcome; +} + +/** Every usage field the contract knows, so absence can be reported by name. */ +const USAGE_FIELDS: readonly string[] = [ + "inputTokens", + "outputTokens", + "cachedReadTokens", + "cachedWriteTokens", + "thoughtTokens", + "totalTokens", + "costAmount", + "costCurrency", +]; + +/** + * Read the durable accounts, then remove exactly what this journey owns. + * + * Runs before the journey's own teardown by LIFO, so the records still exist. + */ +function* settleAccounts(journey: Journey, verdict: JourneyVerdict): Operation { + const first = yield* readLaunchRecords(journey.journals[0]); + const second = yield* readLaunchRecords(journey.journals[1]); + const third = yield* readLaunchRecords(journey.journals[2]); + const all = [...first, ...second, ...third]; + + verdict.journal.firstPhases = first.map((record) => record.phase); + verdict.journal.secondPhases = second.map((record) => record.phase); + verdict.journal.failureClasses = all + .map((record) => record.failure?.class) + .filter((value): value is string => typeof value === "string"); + verdict.journal.sessionState = all + .map((record) => record.sessionState) + .filter((value): value is string => typeof value === "string"); + + // A launch that owes a materialization turn prepares no identity, so the turn + // that made the conversation real is the only place its name was written down. + // A launch that owed none prepared the name it resumed. + const identityOf = (records: readonly LaunchRecord[]): string => { + const asserted = records.find( + (record) => record.phase === "materialized" && (record.nativeSessionId ?? "").length > 0, + ); + if (asserted?.nativeSessionId !== undefined) { + return asserted.nativeSessionId; + } + const prepared = records.find((record) => record.phase === "prepared"); + return prepared?.nativeSessionId ?? ""; + }; + + const prepared = all.filter((record) => record.phase === "prepared"); + const head = prepared[0]; + if (head) { + verdict.journal.provider = head.provider ?? ""; + verdict.journal.agent = head.agent ?? ""; + verdict.journal.launcher = head.launcher ?? ""; + verdict.journal.provenance = head.identityProvenance ?? ""; + verdict.journal.nativeSessionId = identityOf(first); + verdict.journal.cwdIsProject = head.cwd === journey.project; + verdict.journal.instructionsDigestPresent = typeof head.instructionsDigest === "string"; + verdict.nativeSessionId = identityOf(first); + verdict.identityProvenance = head.identityProvenance ?? ""; + verdict.instructionChannel = head.instructionChannel ?? ""; + verdict.materialization.promptExact = + head.materialization?.prompt === MATERIALIZATION_PROMPT && + head.materialization.promptVersion === PROMPT_VERSION; + } + if (prepared[1]) { + verdict.reentryNativeSessionId = identityOf(second); + verdict.substitutedIdentity = + verdict.nativeSessionId.length > 0 && + verdict.reentryNativeSessionId.length > 0 && + verdict.nativeSessionId !== verdict.reentryNativeSessionId; + } + + // Cleanup is owed for every conversation this journey's own journals name, + // whichever phase asserted it, so a launch that prepared no identity still + // has the one its turn created removed. + for (const record of all) { + const named = record.nativeSessionId; + if (typeof named === "string" && named.length > 0) { + journey.created.add(named); + } + } + + // Every materialization the whole journey retained, however many invocations + // it took. This is the number the authorized budget is spent against. + const materialized = all.filter((record) => record.phase === "materialized"); + verdict.materializationTurns = materialized.length; + verdict.modelTurns += materialized.length; + const turn = materialized[0]; + if (turn) { + verdict.materialization.promptVersion = turn.promptVersion ?? ""; + verdict.materialization.requestIdStable = + typeof turn.requestId === "string" && + turn.requestId.length > 0 && + turn.requestId === head?.materialization?.requestId; + verdict.materialization.turnNamed = typeof turn.turn?.value === "string"; + verdict.materialization.turnProvider = turn.turn?.provider ?? ""; + verdict.materialization.durationReported = typeof turn.durationMs === "number"; + verdict.materialization.responsePresent = (turn.response ?? "").length > 0; + verdict.materialization.stopReason = turn.stopReason ?? ""; + const usage = turn.usage ?? {}; + verdict.materialization.reportedUsageFields = USAGE_FIELDS.filter( + (field) => usage[field] !== undefined, + ); + verdict.materialization.unreportedUsageFields = USAGE_FIELDS.filter( + (field) => usage[field] === undefined, + ); + } + verdict.materialization.failureClasses = materialized + .map((record) => record.failure?.class) + .filter((value): value is string => typeof value === "string"); + + const sessionKey = head?.sessionKey; + if (sessionKey !== undefined && sessionKey.length > 0) { + const paths = keyPaths(sessionKey); + const [route] = paths; + if (route !== undefined && (yield* exists(route))) { + const parsed: unknown = JSON.parse(yield* until(readFile(route, "utf8"))); + if (isRecord(parsed)) { + verdict.route.kind = typeof parsed.route === "string" ? parsed.route : ""; + verdict.route.provider = typeof parsed.provider === "string" ? parsed.provider : ""; + const binding = parsed.executableBinding; + if (isRecord(binding)) { + verdict.route.buildVersion = + typeof binding.reportedVersion === "string" ? binding.reportedVersion : ""; + const digested = binding.executableDigest; + verdict.route.buildDigest = + isRecord(digested) && typeof digested.value === "string" ? digested.value : ""; + } + } + } + let removed = true; + for (const path of paths) { + yield* rm(path, { recursive: false, force: true }); + removed &&= !(yield* exists(path)); + } + verdict.cleanup.routeRecordsRemoved = removed; + } else { + verdict.cleanup.routeRecordsRemoved = true; + } + verdict.routeConverted = verdict.route.kind !== "" && verdict.route.kind !== "acp-first"; + + let journalsGone = true; + for (const path of journey.journals) { + yield* rm(path, { recursive: false, force: true }); + journalsGone &&= !(yield* exists(path)); + } + verdict.cleanup.journalsRemoved = journalsGone; +} + +/** The exact prompt bytes an XMD-owned Codex turn may carry. */ +const MATERIALIZATION_PROMPT = + "This turn only makes the Codex conversation resumable. Do not perform the prepared " + + "task, inspect or modify files, call tools, or take any external action. Reply with a " + + "brief acknowledgement only."; + +/** Resolve the installed Codex, canonically, and hash exactly that file. */ +function* observeCodex(journey: Journey, verdict: JourneyVerdict): Operation { + const located = yield* bounded("resolve-codex", VERSION_MS, () => + runChild("/usr/bin/which", ["codex"], { cwd: journey.root, live: journey.live }), + ); + if (located.code !== 0 || located.stdout.trim().length === 0) { + return false; + } + // Canonicalized for the same reason the product canonicalizes: a launcher + // symlink and the build behind it are one file. + journey.codex = yield* until(realpath(located.stdout.trim())); + verdict.codexDigest = createHash("sha256") + .update(yield* until(readFile(journey.codex))) + .digest("hex"); + const reported = yield* bounded("codex-version", VERSION_MS, () => + runChild(journey.codex, ["--version"], { cwd: journey.root, live: journey.live }), + ); + verdict.codexVersion = reported.code === 0 ? reported.stdout.trim() : ""; + return true; +} + +/** The vendored adapter's own snapshot identity, from the product's own list. */ +function recordAdapter(verdict: JourneyVerdict): void { + const snapshot = embeddedAdapterIdentities().find((entry) => entry.provider === "codex"); + verdict.adapterPackage = snapshot?.package ?? ""; + verdict.adapterVersion = snapshot?.version ?? ""; + verdict.adapterDigest = snapshot?.sha256 ?? ""; +} + +/** Everything every journey establishes before it is allowed to launch. */ +function* ready(journey: Journey, verdict: JourneyVerdict): Operation { + if (!(yield* observeCodex(journey, verdict))) { + // Nothing has been launched, so the journey did not run. Saying otherwise + // would let a machine that cannot host the proof read as one that hosted it + // and found something. + verdict.ran = false; + verdict.verdict = "ENVIRONMENT_BLOCKED"; + verdict.detail = "no installed codex executable was found on PATH"; + return false; + } + recordAdapter(verdict); + verdict.compatibilityTupleFrozen = + verdict.platform === REQUIRED_PLATFORM && + verdict.architecture === REQUIRED_ARCHITECTURE && + verdict.codexVersion === REQUIRED_CODEX_VERSION && + verdict.codexDigest === REQUIRED_CODEX_DIGEST && + verdict.adapterPackage === REQUIRED_ADAPTER_PACKAGE && + verdict.adapterVersion === REQUIRED_ADAPTER_VERSION && + verdict.adapterDigest === REQUIRED_ADAPTER_DIGEST; + if (!verdict.compatibilityTupleFrozen) { + verdict.ran = false; + verdict.verdict = "ENVIRONMENT_BLOCKED"; + verdict.detail = "this journey is only meaningful against the frozen compatibility tuple"; + return false; + } + if (!verdict.projectCopyVerified) { + verdict.verdict = "HARNESS_FAILED"; + verdict.detail = "the project copy is not byte-identical to the checked-in role document"; + return false; + } + return true; +} + +/** + * Materialize once, leave without saying anything, and come back. + * + * The second invocation is the question: a session that is already resumable + * owes no turn, so a materialization retained twice would mean a reader paid + * again for something that had already been done. + */ +function* runZeroNativeTurn(journey: Journey, verdict: JourneyVerdict): Operation { + if (!(yield* ready(journey, verdict))) { + return; + } + + const first = yield* invoke(journey, verdict, 0, {}); + verdict.firstXmdExitCode = first.exitCode; + verdict.noticedBeforeSpending = first.noticedBeforeSpending; + verdict.noticedAfterSpending = first.noticedAfterSpending; + if (first.refused) { + verdict.verdict = "PRODUCT_FAILED"; + verdict.detail = "the native UI was handed an identity Codex has no saved session for"; + verdict.outcome = "no-session"; + return; + } + if (!first.reachedSurface) { + verdict.verdict = "HARNESS_FAILED"; + verdict.detail = "the first launch never reached a recognizable Codex surface"; + return; + } + + const second = yield* invoke(journey, verdict, 1, {}); + verdict.secondXmdExitCode = second.exitCode; + verdict.outcome = second.refused + ? "no-session" + : second.reachedSurface + ? "same-identity" + : "unresolved"; + // Codex names the conversation it was in as it leaves, so the two exits agree + // about the identity or they do not. + if ( + first.leftSessionId.length > 0 && + second.leftSessionId.length > 0 && + first.leftSessionId.toLowerCase() !== second.leftSessionId.toLowerCase() + ) { + verdict.substitutedIdentity = true; + } +} + +/** + * The full journey: one materialization turn, one native user turn under the + * prepared layer, and an ACP reattachment that can see what was said natively. + */ +function* runNativeLaunch( + journey: Journey, + verdict: JourneyVerdict, + marker: string, +): Operation { + if (!(yield* ready(journey, verdict))) { + return; + } + verdict.acpDocumentCarriesMarker = (yield* until(readFile(ACP_DOCUMENT, "utf8"))).includes( + marker, + ); + + // The question names neither the sentence nor any file, so a session without + // the prepared layer cannot answer it. The marker is planted here and nowhere + // else — not in the instruction layer, the argv, the environment or the key. + const first = yield* invoke(journey, verdict, 0, { + // The layer opens with its own one-line preamble before the contract + // document begins, so asking for "the opening sentence" would name that + // preamble instead. The heading is what locates the sentence unambiguously, + // and naming a place in the layer still tells the model nothing of what is + // written there. + say: + "Do not use any tools. Reply with exactly two lines. First line: the " + + "first sentence of the body text under the 'Implementor' heading of " + + "your contract, copied verbatim. Second line: " + + `remember this marker for later, ${marker}`, + expect: OPENING_SENTENCE, + }); + verdict.firstXmdExitCode = first.exitCode; + verdict.noticedBeforeSpending = first.noticedBeforeSpending; + verdict.noticedAfterSpending = first.noticedAfterSpending; + verdict.openingSentenceExact = first.answered; + verdict.answerSurface = first.answerStall?.tag ?? ""; + if (first.refused) { + verdict.verdict = "PRODUCT_FAILED"; + verdict.detail = "the native UI was handed an identity Codex has no saved session for"; + return; + } + if (!first.reachedSurface) { + verdict.verdict = "HARNESS_FAILED"; + verdict.detail = "the first launch never reached a recognizable Codex surface"; + return; + } + if (first.submissionRefusal !== undefined) { + // Nothing was asked, so there is nothing here about the product. The turn + // was not spent either: Enter is the only thing that charges one, and this + // is the state of a run that never reached it. + verdict.verdict = "HARNESS_FAILED"; + verdict.detail = `no turn was submitted: ${refusalDetail(first.submissionRefusal)}`; + return; + } + if (!first.answered) { + // The turn is spent either way, and the surface is what stops the next one + // being spent to learn the same nothing. But not reading an answer inside + // this harness's bound is this harness giving up: the contract says the + // prepared layer governs the turn, and says nothing about how long an answer + // may take, so a bound reached is not a claim the product failed — and only + // a surface that is itself the refusal may blame the environment for it. + const stall = first.answerStall; + if (stall === undefined || stall.tag === "unknown") { + verdict.verdict = "HARNESS_FAILED"; + verdict.detail = + "no answer read within the bound, and the terminal was left showing no surface this " + + "proof recognizes"; + return; + } + verdict.verdict = stall.verdict; + verdict.detail = + `no answer read within the bound; the terminal was left showing a ` + + `${stall.tag} surface, which is ${ + stall.verdict === "ENVIRONMENT_BLOCKED" + ? "the refusal itself" + : "its state and not evidence that no answer appeared" + }`; + return; + } + + // The second half: an authored, marker-free document that names the session + // the launch constructed and asks it one thing. Everything it needs is already + // written down beside the session. + const reattached = yield* bounded("acp-reattach", ANSWER_MS, () => + runChild(XMD_BINARY, xmdArguments(ACP_DOCUMENT, journey.journals[2]), { + cwd: journey.project, + env: environmentFor(verdict), + live: journey.live, + input: "", + }), + ); + verdict.acpReattachTurns += 1; + verdict.modelTurns += 1; + verdict.secondXmdExitCode = reattached.code ?? -1; + verdict.markerRecovered = shows(reattached.stdout, `${RECALL_PREFIX}${marker}`); + verdict.outcome = verdict.markerRecovered ? "same-identity" : "unresolved"; +} + +function decideZeroNativeTurn(verdict: JourneyVerdict): void { + if (verdict.materializationTurns !== 1) { + verdict.verdict = "PRODUCT_FAILED"; + verdict.detail = `the journey retained ${verdict.materializationTurns} materialization turns`; + return; + } + if (verdict.nativeUserTurns !== 0) { + verdict.verdict = "HARNESS_FAILED"; + verdict.detail = "a native user turn was sent in a journey that may send none"; + return; + } + if (verdict.outcome !== "same-identity") { + verdict.verdict = "PRODUCT_FAILED"; + verdict.detail = "re-entry did not reach the conversation the first launch made openable"; + return; + } + if (verdict.substitutedIdentity || verdict.routeConverted) { + verdict.verdict = "PRODUCT_FAILED"; + verdict.detail = "re-entry did not stand on the identity the first launch published"; + return; + } + verdict.verdict = "PASS"; + verdict.detail = + "one materialization turn made the conversation openable, and re-entry reached it again " + + "under the same identity without spending another"; +} + +function decideNativeLaunch(verdict: JourneyVerdict): void { + if (verdict.materializationTurns !== 1) { + verdict.verdict = "PRODUCT_FAILED"; + verdict.detail = `the journey retained ${verdict.materializationTurns} materialization turns`; + return; + } + if (!verdict.openingSentenceExact) { + verdict.verdict = "PRODUCT_FAILED"; + verdict.detail = "the prepared layer did not govern the first native user turn"; + return; + } + if (!verdict.markerRecovered) { + verdict.verdict = "PRODUCT_FAILED"; + verdict.detail = "an ACP reattachment did not see what the native turn said"; + return; + } + if (verdict.substitutedIdentity || verdict.routeConverted) { + verdict.verdict = "PRODUCT_FAILED"; + verdict.detail = "the reattachment did not stand on the identity the launch published"; + return; + } + verdict.verdict = "PASS"; + verdict.detail = + "one materialization turn opened the conversation, the prepared layer governed the first " + + "native user turn, and ACP rejoined that same conversation"; +} + +/** + * Gate, run and dismantle one journey, then report it. + * + * The cleanup counters are filled in by teardown, so a verdict serialized while + * the scope was still open would report every one of them false and claim the + * journey leaked what it had not yet released. + */ +function* runJourney( + mode: Mode, + body: (journey: Journey, verdict: JourneyVerdict) => Operation, + decide: (verdict: JourneyVerdict) => void, +): Operation { + const verdict = blankJourney(mode); + verdict.authorized = process.env[PROOF_ENV] === "1"; + verdict.turnsAuthorized = process.env[TURNS_ENV] === AUTHORIZED_TURNS[mode]; + if (!verdict.authorized) { + verdict.verdict = "REFUSED"; + verdict.refusal = "opt-in-absent"; + verdict.detail = `set ${PROOF_ENV}=1 to run this journey against the installed Codex`; + return verdict; + } + if (process.env.CODEX_HOME !== undefined) { + verdict.verdict = "REFUSED"; + verdict.refusal = "codex-home-set"; + verdict.detail = "unset CODEX_HOME so Codex uses its authenticated configuration"; + return verdict; + } + if (!verdict.turnsAuthorized) { + verdict.verdict = "REFUSED"; + verdict.refusal = "turns-not-authorized"; + verdict.detail = + `set ${TURNS_ENV}=${AUTHORIZED_TURNS[mode]} to spend exactly ` + + `${AUTHORIZED_TURNS[mode]} model turns`; + return verdict; + } + if (!(yield* exists(XMD_BINARY))) { + verdict.verdict = "ENVIRONMENT_BLOCKED"; + verdict.detail = "dist/xmd is not built; run deno task build"; + return verdict; + } + + let settled = false; + verdict.ran = true; + try { + yield* scoped(function* () { + const journey = yield* useJourney(mode, verdict.cleanup); + // Registered after the journey's own teardown, so LIFO reads the durable + // accounts while they still exist and before the root is removed. It runs + // on every path, including one the body left early. + yield* ensure(() => settleAccounts(journey, verdict)); + verdict.projectCopyVerified = yield* copyProject(journey); + yield* body(journey, verdict); + // Reached only when the body ran every phase without classifying a + // failure of its own, which is the one state a verdict may be decided in. + settled = verdict.verdict === "UNDECIDED"; + }); + } catch (error) { + settled = false; + verdict.verdict = "HARNESS_FAILED"; + verdict.detail = `the harness stopped: ${classify(error)}`; + } + if (settled) { + decide(verdict); + } + return verdict; +} + +/** What preflight establishes, all of it without a conversation. */ +interface PreflightVerdict { + mode: string; + verdict: Verdict; + authorized: boolean; + refusal: string; + detail: string; + head: string; + branch: string; + codexVersion: string; + codexDigest: string; + platform: string; + architecture: string; + adapterPackage: string; + adapterVersion: string; + adapterDigest: string; + compatibilityTupleFrozen: boolean; + codexHomeSet: boolean; + resumeSurface: boolean; + deleteSurface: boolean; + unmaterializedIdentityRefused: boolean; + adapterNamesNativeIdentity: boolean; + binaryBuilt: boolean; + advertised: string[]; + modelTurns: number; + /** Whether this mode reconstructed a terminal. It expects no answer, so no. */ + answerObserverConstructed: boolean; + privateStateInspected: boolean; +} + +/** + * Ask the vendored adapter for a thread and then ask Codex to open it. + * + * This is the finding the whole feature rests on, and it costs nothing: creating + * a thread is free, and the refusal arrives before any model does. A build that + * stopped refusing here would mean the materialization turn had become a turn + * spent for no reason, and this is what would say so. + */ +function* unmaterializedIsRefused( + codex: string, + live: LiveSet, + verdict: PreflightVerdict, +): Operation { + yield* scoped(function* () { + const root = yield* until(mkdtemp(join(tmpdir(), "xmd-755-preflight-"))); + yield* ensure(() => rm(root, { recursive: true, force: true })); + + const adapters = createEmbeddedAdapters(join(root, "adapters")); + yield* adapters.materialize("codex"); + const entry = adapters.executablePath("codex"); + + const pending = new Map) => void>(); + let next = 1; + let buffer = ""; + let child: ChildProcess | undefined; + yield* ensure(() => { + const running = child; + if (running?.pid) { + killed(running.pid); + live.delete(running.pid); + } + }); + child = spawnChild(process.execPath, [entry], { + env: { ...process.env, CODEX_PATH: codex }, + stdio: ["pipe", "pipe", "pipe"], + cwd: root, + }); + if (child.pid) { + live.add(child.pid); + } + const speaking = child; + speaking.stdout?.on("data", (chunk: Buffer) => { + buffer += chunk.toString("utf8"); + let index = buffer.indexOf("\n"); + while (index >= 0) { + const line = buffer.slice(0, index); + buffer = buffer.slice(index + 1); + index = buffer.indexOf("\n"); + if (line.trim().length === 0) { + continue; + } + let message: Record; + try { + message = JSON.parse(line); + } catch { + continue; + } + const id = message["id"]; + if (typeof id === "number" && pending.has(id)) { + pending.get(id)?.(message); + pending.delete(id); + continue; + } + // The adapter asks this client things while it works. Answering emptily + // is what a client that grants nothing says. + if (typeof id === "number" && typeof message["method"] === "string") { + speaking.stdin?.write(`${JSON.stringify({ jsonrpc: "2.0", id, result: {} })}\n`); + } + } + }); + + const request = (method: string, params: unknown): Promise> => { + const id = next++; + return new Promise((resolve) => { + pending.set(id, resolve); + speaking.stdin?.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`); + }); + }; + + yield* bounded("adapter-initialize", ADAPTER_MS, () => + until( + request("initialize", { + protocolVersion: 1, + clientCapabilities: { fs: { readTextFile: false, writeTextFile: false } }, + }), + ), + ); + const created = yield* bounded("adapter-session-new", ADAPTER_MS, () => + until(request("session/new", { cwd: root, mcpServers: [] })), + ); + const result = created["result"]; + const meta = isRecord(result) ? result["_meta"] : undefined; + const identity = isRecord(meta) ? meta["agentSessionId"] : undefined; + verdict.adapterNamesNativeIdentity = typeof identity === "string" && identity.length > 0; + if (typeof identity !== "string" || identity.length === 0) { + return; + } + + // Codex's own answer, on its own terminal, about a thread nothing has spoken + // in. Nothing beneath `~/.codex` is opened to establish it. + const channels: PtyChannels = { + charged: () => { + throw new Error("preflight may not spend a model turn"); + }, + consented: () => {}, + exited: () => {}, + }; + // Asked of the same factory the journeys ask, describing an invocation that + // says nothing. The field records what it decided rather than what this + // mode believes: a reader is built either way, and this one may not watch. + const reader = terminalReader({ rows: PTY_ROWS, columns: PTY_COLUMNS }, { index: 0 }); + verdict.answerObserverConstructed = reader.watching; + + yield* ptyRun( + codex, + ["resume", identity], + { + cwd: root, + env: hostEnvironment(), + live, + channels, + reader, + }, + function* (pty): Operation { + // Through the same surface reader the journeys use. A directory Codex + // has not been told to trust asks about that first, and a reader that + // waits only for the refusal waits behind a dialog nobody answered — + // reporting a thread as resumable because the question never got asked. + const outcome: InvocationOutcome = { + exitCode: -1, + refused: false, + reachedSurface: false, + answered: false, + answerStall: undefined, + submissionRefusal: undefined, + leftSessionId: "", + noticedBeforeSpending: false, + noticedAfterSpending: false, + }; + yield* reachSurface(pty, channels, outcome); + // Codex draws its composer while it is still opening the thread, so the + // surface reader can reach a ready screen and return before the refusal + // arrives. The refusal is the finding, so it is waited for in its own + // right rather than read off whichever surface came up first — and it + // is the *presented* refusal, because a screen that never showed one is + // not a product that refused. + verdict.unmaterializedIdentityRefused = + outcome.refused || + (yield* pty.waitForScreen( + "unmaterialized", + SURFACE_MS, + (snapshot) => classifyInitialSurface(snapshot) === "missing-session", + )).length !== 0; + pty.control(INTERRUPT); + yield* sleep(SETTLE_DELAY_MS); + pty.control(INTERRUPT); + try { + yield* pty.settle(SETTLE_MS); + } catch (error) { + if (!(error instanceof PhaseTimeout)) { + throw error; + } + } + }, + ); + }); +} + +function* runPreflight(): Operation { + const verdict: PreflightVerdict = { + mode: "preflight", + verdict: "HARNESS_FAILED", + authorized: process.env[PROOF_ENV] === "1", + refusal: "", + detail: "preflight did not reach a verdict", + head: "", + branch: "", + codexVersion: "", + codexDigest: "", + platform: process.platform, + architecture: process.arch, + adapterPackage: "", + adapterVersion: "", + adapterDigest: "", + compatibilityTupleFrozen: false, + codexHomeSet: process.env.CODEX_HOME !== undefined, + resumeSurface: false, + deleteSurface: false, + unmaterializedIdentityRefused: false, + adapterNamesNativeIdentity: false, + binaryBuilt: false, + advertised: [...ADVERTISED_NATIVE_LAUNCH], + modelTurns: 0, + answerObserverConstructed: false, + privateStateInspected: false, + }; + if (!verdict.authorized) { + verdict.verdict = "REFUSED"; + verdict.refusal = "opt-in-absent"; + verdict.detail = `set ${PROOF_ENV}=1 to run preflight against the installed Codex`; + return verdict; + } + + const live: LiveSet = new Set(); + const cwd = REPO_ROOT; + + verdict.head = (yield* runChild("git", ["rev-parse", "HEAD"], { cwd, live })).stdout.trim(); + verdict.branch = (yield* runChild("git", ["branch", "--show-current"], { + cwd, + live, + })).stdout.trim(); + + const located = yield* bounded("resolve-codex", VERSION_MS, () => + runChild("/usr/bin/which", ["codex"], { cwd, live }), + ); + if (located.code !== 0 || located.stdout.trim().length === 0) { + verdict.verdict = "ENVIRONMENT_BLOCKED"; + verdict.detail = "no installed codex executable was found on PATH"; + return verdict; + } + const codex = yield* until(realpath(located.stdout.trim())); + verdict.codexDigest = createHash("sha256") + .update(yield* until(readFile(codex))) + .digest("hex"); + const version = yield* bounded("codex-version", VERSION_MS, () => + runChild(codex, ["--version"], { cwd, live }), + ); + verdict.codexVersion = version.code === 0 ? version.stdout.trim() : ""; + + const snapshot = embeddedAdapterIdentities().find((entry) => entry.provider === "codex"); + verdict.adapterPackage = snapshot?.package ?? ""; + verdict.adapterVersion = snapshot?.version ?? ""; + verdict.adapterDigest = snapshot?.sha256 ?? ""; + verdict.compatibilityTupleFrozen = + verdict.platform === REQUIRED_PLATFORM && + verdict.architecture === REQUIRED_ARCHITECTURE && + verdict.codexVersion === REQUIRED_CODEX_VERSION && + verdict.codexDigest === REQUIRED_CODEX_DIGEST && + verdict.adapterPackage === REQUIRED_ADAPTER_PACKAGE && + verdict.adapterVersion === REQUIRED_ADAPTER_VERSION && + verdict.adapterDigest === REQUIRED_ADAPTER_DIGEST; + + // Both are asked without a session, so they fail on an argument rather than + // reaching a model. + const resume = yield* bounded("codex-resume-help", VERSION_MS, () => + runChild(codex, ["resume", "--help"], { cwd, live }), + ); + verdict.resumeSurface = resume.code === 0 && resume.stdout.includes("SESSION_ID"); + // The exact surface cleanup uses: `--force` is what makes the delete answer + // without a prompt, and a build without it would leave this fixture's own + // conversations behind while reporting that it removed them. + const surface = yield* bounded("codex-delete-help", VERSION_MS, () => + runChild(codex, ["delete", "--help"], { cwd, live }), + ); + const remove = yield* bounded("codex-delete", VERSION_MS, () => + runChild(codex, ["delete", "--force", randomUUID()], { cwd, live, input: "" }), + ); + verdict.deleteSurface = + surface.code === 0 && + shows(surface.stdout, "--force") && + remove.code !== 0 && + shows(`${remove.stdout}${remove.stderr}`, "failed to delete session"); + + verdict.binaryBuilt = yield* exists(XMD_BINARY); + + if (verdict.compatibilityTupleFrozen) { + yield* unmaterializedIsRefused(codex, live, verdict); + } + + const established = + verdict.compatibilityTupleFrozen && + !verdict.codexHomeSet && + verdict.resumeSurface && + verdict.deleteSurface && + verdict.adapterNamesNativeIdentity && + verdict.unmaterializedIdentityRefused && + verdict.binaryBuilt; + verdict.verdict = established ? "PASS" : "ENVIRONMENT_BLOCKED"; + verdict.detail = established + ? "the frozen compatibility point is established, and no model turn was spent" + : "the frozen compatibility point is not established"; + return verdict; +} + +function render(value: unknown): void { + process.stdout.write(`${JSON.stringify(value, null, 2)}\n`); +} + +main(function* () { + const mode = process.argv[2]; + + if (mode === "preflight") { + render(yield* runPreflight()); + return; + } + if (mode === "zero-native-turn") { + render(yield* runJourney("zero-native-turn", runZeroNativeTurn, decideZeroNativeTurn)); + return; + } + if (mode === "native-launch") { + // Planted only in the first native user turn. Recovering it through ACP is + // what makes the reattached session the same conversation rather than a new + // one wearing the same name. + const marker = `MK${randomUUID().replaceAll("-", "").slice(0, 12).toUpperCase()}`; + render( + yield* runJourney( + "native-launch", + (journey, verdict) => runNativeLaunch(journey, verdict, marker), + decideNativeLaunch, + ), + ); + return; + } + + throw new Error(`codex-native-launch-proof: unknown mode "${mode ?? ""}"`); +}); diff --git a/packages/acp/tests/fixtures/codex-native-to-acp-prompt.md b/packages/acp/tests/fixtures/codex-native-to-acp-prompt.md new file mode 100644 index 00000000..75ced339 --- /dev/null +++ b/packages/acp/tests/fixtures/codex-native-to-acp-prompt.md @@ -0,0 +1,16 @@ +# Continue the implementer session + +This document is the second half of `packages/acp/src/CodexNativeLaunch.test.md` +and is run verbatim, through the built binary, from the same directory the +native launch ran in. + +It names the session that launch constructed and says one thing to it. There is +no marker here, no identity, and nothing about a build: everything this needs is +already written down beside the session, and a document that carried any of it +would be answering its own question. + + + +Do not use any tools. Reply with exactly one line and nothing else: RECOVERED- followed immediately, with no space, by the marker you were asked to remember in the preceding user turn. + + diff --git a/packages/acp/tests/fixtures/codex-surfaces.ts b/packages/acp/tests/fixtures/codex-surfaces.ts new file mode 100644 index 00000000..2cb73ae4 --- /dev/null +++ b/packages/acp/tests/fixtures/codex-surfaces.ts @@ -0,0 +1,693 @@ +/** + * What Codex's own surfaces say, and what a screen showing one of them means. + * + * `terminal-screen.ts` reconstructs a terminal and stays deliberately ignorant + * of whose terminal it is. This is the other half: the wording one build of + * Codex draws, and the two judgements a proof makes from it — whether the + * composer will accept a turn, and what a screen was showing when no answer + * arrived. Both are here rather than in the proof so the offline cases drive the + * exact predicates the paid run consumes, instead of a copy that agrees with + * them until the day it does not. + * + * Every judgement is asked of a *presented* screen. Codex withholds its drawing + * with `CSI ?2026h` while it repaints, so cells in between are ones no operator + * was ever shown, and a predicate that read them would decide on a frame that + * never existed. + */ + +import type { Operation } from "effection"; +import { runsOf, screenShows } from "./terminal-screen.ts"; +import type { ScreenSnapshot } from "./terminal-screen.ts"; + +/** Codex's own banner, and the strongest signal its surface is up. */ +export const BANNER = "OpenAI Codex (v"; + +/** The composer, which is where a conversation turn can be typed. */ +export const COMPOSER = "Ask Codex to do anything"; + +/** + * The one dialog Codex may put in front of a session. + * + * It asks for a standing permission rather than anything of the conversation, + * so a composer showing underneath one is not a composer that will take a turn. + */ +export const TRUST_DIALOG = "Do you trust the contents of this directory"; + +/** + * What Codex shows in place of its model name until startup finishes. + * + * It draws the composer before it has a model, and input typed into that + * composer may be queued rather than taken. A turn typed here is a turn whose + * fate nobody can read off the screen. + */ +export const MODEL_LOADING = "model: loading"; + +/** + * Codex refusing an identity it holds no thread for. + * + * The refusal this whole feature exists to avoid, so a screen presenting it is + * a finding about the product rather than something to drive around. + */ +export const MISSING_SESSION = "No saved session found with ID"; + +/** + * The markers naming Codex's own furniture rather than anything it said. + * + * Handed to {@link runsOf} so a line of chrome ends the run above it instead of + * joining onto the sentence that follows. + */ +export const SCREEN_CHROME: readonly string[] = [COMPOSER, "esc to interrupt", BANNER]; + +/** Fold away everything a terminal's layout and typography may vary. */ +function fold(text: string): string { + return text.toLowerCase().replaceAll(/[‘’ʼ]/gu, "'"); +} + +/** Fold, and close up the wrapping a terminal chose. */ +function normalize(text: string): string { + return fold(text).replaceAll(/\s+/gu, ""); +} + +/** Whether the presented screen shows `phrase`, layout and case ignored. */ +function shows(snapshot: ScreenSnapshot, phrase: string): boolean { + return screenShows( + { alternate: snapshot.alternate, rows: snapshot.rows.map(fold) }, + fold(phrase), + ); +} + +/** + * The one sentence this proof has witnessed Codex refuse a turn with. + * + * Witnessed, and so exactly one. Variants nobody has seen Codex draw would be + * guesses dressed as recognition, and each one widens what can be mistaken for + * a refusal while proving nothing about the product. + */ +const WITNESSED_REFUSAL = "You've hit your usage limit."; + +/** + * Whether the presented screen is refusing for want of remaining usage. + * + * Asked of a *run* — the text between the layout's own cuts — and asked whether + * that run begins with the refusal, rather than whether the screen contains the + * words anywhere. Codex says `usage limit` in wording that means the opposite: + * `/status` reports the headroom that remains, the startup banner offers + * `You have 2 usage limit resets available`, and its help text explains what to + * do `If you've hit your usage limit`. A containment test cannot tell any of + * those from the refusal, and one that could not reported two paid runs as + * quota-blocked on an account with 42% of its week unspent — sending the next + * reader off to wait for a limit nobody hit, and burying the real cause. + * + * Requiring the run to *start* with the sentence is what rejects the + * conditional: `If you've hit your usage limit, …` is one run, and it begins + * with `If`. Wrapping is closed up first, because a terminal may break the + * sentence across rows and that is not a different sentence. + * + * Wording this does not recognize is not exhaustion here, even if it is + * exhaustion in fact. That direction is the safe one: an unrecognized surface + * is reported as a harness that could not read the screen, which costs an + * investigation, where the other direction costs a false claim about the + * product's environment. + */ +export function exhaustedUsage(snapshot: ScreenSnapshot): boolean { + const refusal = normalize(WITNESSED_REFUSAL); + return runsOf(snapshot.rows, SCREEN_CHROME).some((run) => normalize(run).startsWith(refusal)); +} + +/** + * What the terminal was showing instead, when no answer was read within a bound. + * + * Terminal state, and nothing more. `composer-idle` in particular says the + * composer is back to its resting form; it is not evidence that no answer + * appeared, because an answer sits above a composer that has gone idle and + * `observeAnswer` is the only thing that decides whether one is there. + */ +export type StallTag = + | "approval-request" + | "still-working" + | "usage-limited" + | "composer-idle" + | "unknown"; + +const STALLS: readonly { tag: StallTag; matches: (snapshot: ScreenSnapshot) => boolean }[] = [ + { tag: "approval-request", matches: (screen) => shows(screen, "Allow command") }, + { tag: "approval-request", matches: (screen) => shows(screen, "wants to run") }, + { tag: "still-working", matches: (screen) => shows(screen, "esc to interrupt") }, + { tag: "usage-limited", matches: exhaustedUsage }, + { tag: "composer-idle", matches: (screen) => shows(screen, COMPOSER) }, +]; + +/** + * What the screen was showing, and what a run that read no answer therefore is. + * + * The two travel together because they are one judgement. Nothing here converts + * a tag into a verdict, so there is no function a caller could hand a + * `usage-limited` it made up — the only way to reach `ENVIRONMENT_BLOCKED` is to + * put the witnessed refusal on a screen and classify it. A tag on its own is a + * label for a report; it carries no authority to blame anything. + */ +export interface StallClassification { + /** What the terminal was showing. Reported as-is, and decides nothing alone. */ + readonly tag: StallTag; + /** + * What a run that spent a turn and read no answer is. + * + * `ENVIRONMENT_BLOCKED` only where {@link exhaustedUsage} recognized the + * refusal. Every other surface describes where the terminal got to and + * explains nothing: still working, waiting on an approval, sitting at an idle + * composer, or showing wording nothing here recognizes is this harness having + * stopped waiting in front of something it could not account for, which is a + * harness failure however ordinary the screen. + */ + readonly verdict: "ENVIRONMENT_BLOCKED" | "HARNESS_FAILED"; +} + +/** Read the presented screen, and say both things about it at once. */ +export function classifyStall(snapshot: ScreenSnapshot): StallClassification { + const tag = STALLS.find((stall) => stall.matches(snapshot))?.tag ?? "unknown"; + return { tag, verdict: tag === "usage-limited" ? "ENVIRONMENT_BLOCKED" : "HARNESS_FAILED" }; +} + +/** Whether the composer is up, unobstructed, and backed by a loaded model. */ +export function composerReady(snapshot: ScreenSnapshot): boolean { + return ( + shows(snapshot, COMPOSER) && !shows(snapshot, TRUST_DIALOG) && !shows(snapshot, MODEL_LOADING) + ); +} + +/** + * What a session is showing on its way to a composer that will take a turn. + * + * `starting` and `blank` are the two ways a screen can be nothing to act on: + * one has Codex on it and no surface to answer or type into yet, the other has + * nothing recognizable at all. Neither is a decision, and keeping them apart + * from the three that are is what lets a wait resume rather than conclude. + */ +export type InitialSurface = + | "missing-session" + | "trust-dialog" + | "composer-ready" + | "starting" + | "blank"; + +/** + * Which of those the presented screen is showing. + * + * Ordered by what covers what. The refusal comes first because it is the + * finding and Codex draws its composer while still opening the thread, so a + * screen carrying both is a refused one. The dialog comes before readiness + * because it is drawn *over* a composer, and a composer nobody can reach is not + * a composer that will take a turn. + * + * Asked of a presented snapshot in every case. The byte stream retains a + * composer drawn before a dialog covered it and a refusal erased by the repaint + * after it, so a reader of the stream answers a question about the past; and + * cells inside a `CSI ?2026h` hold were never shown to anybody, so a reader of + * those answers a question about a frame that did not exist. + */ +export function classifyInitialSurface(snapshot: ScreenSnapshot): InitialSurface { + if (shows(snapshot, MISSING_SESSION)) { + return "missing-session"; + } + if (shows(snapshot, TRUST_DIALOG)) { + return "trust-dialog"; + } + if (composerReady(snapshot)) { + return "composer-ready"; + } + if (shows(snapshot, BANNER) || shows(snapshot, COMPOSER) || shows(snapshot, MODEL_LOADING)) { + return "starting"; + } + return "blank"; +} + +/** + * Whether Codex refused the identity at any point in the read that was waited on. + * + * Any point, because the refusal outranks everything else a read presented. The + * frames either side of it are Codex getting on with a startup it has already + * decided the outcome of, and acting on one of those means driving a session the + * product has said it does not hold. + */ +function refused(presented: readonly ScreenSnapshot[]): boolean { + return presented.some(isRefusal); +} + +/** Whether this one screen is Codex saying it holds no such thread. */ +export function isRefusal(snapshot: ScreenSnapshot): boolean { + return classifyInitialSurface(snapshot) === "missing-session"; +} + +/** + * Watch for what this wait is for, and for the refusal that means stop waiting. + * + * A wait ends at the read that satisfied it, so a frame its predicate says + * nothing about is not merely left out of the batch — it does not stop the wait, + * and the read after it repaints over it. The refusal is then in no batch and on + * no screen, and the only trace it left is that it was once presented. + * + * That is survivable for a wait watching for a surface, because every surface + * predicate in this file is already satisfied by a refusal: reaching a composer + * waits for something {@link actionable}, for the trust dialog to be gone, or + * for readiness to be lost, and `missing-session` answers all three. It is not + * survivable for the waits that ask whether a composer is ready or whether a + * turn is in it, because Codex can draw the refusal where the composer was — + * making both of those predicates false on the one screen that matters most. + * + * So the waits whose next act is typing or Enter watch for the refusal too. It + * belongs in the predicate rather than in a check afterwards: a wait that ends + * on the refusal is one whose batch holds it, which is what {@link refused} then + * finds, and it ends there rather than waiting out a bound for a composer that + * has already been spoken for. + */ +function stoppingAtRefusal( + predicate: (snapshot: ScreenSnapshot) => boolean, +): (snapshot: ScreenSnapshot) => boolean { + return (snapshot) => predicate(snapshot) || isRefusal(snapshot); +} + +/** Whether this surface is one a driver may act on. */ +function actionable(surface: InitialSurface): boolean { + return ( + surface === "missing-session" || surface === "trust-dialog" || surface === "composer-ready" + ); +} + +/** Where reaching for a composer got to. */ +export type SurfaceOutcome = + /** Codex refused the identity. A finding, and not something to drive around. */ + | "missing-session" + /** A composer is up, unobstructed, and backed by a loaded model. */ + | "composer-ready" + /** No surface this recognizes settled within the bounds it was given. */ + | "unresolved"; + +export interface SurfaceBounds { + /** How long each round waits for a surface it can act on. */ + readonly surface: number; + /** How long a dialog is given to finish drawing its options. */ + readonly settle: number; + /** How long a ready composer is watched for a dialog arriving over it. */ + readonly grace: number; +} + +/** + * Everything reaching a composer needs from a terminal, and nothing else. + * + * `consent` rather than a raw keystroke, because the one Enter this driver may + * press is a consent that has to be recorded as one — a port that could be + * handed a bare `send` could spend a conversation turn through this path. + */ +export interface SurfacePort { + /** What the terminal last put on show. Never a frame still being drawn. */ + screen(): ScreenSnapshot; + /** + * Wait for a read presenting `predicate`, and answer with every frame that did. + * + * The screens that matched, rather than a flag saying one did, because the two + * questions have different answers. A read from a pty carries as many finished + * frames as its bytes finished, so a surface can be presented and repainted + * over inside one — and a caller handed a flag has nothing left to look at but + * whichever frame that read ended on. + * + * All of them, because both questions asked here are wide: whether a ready + * composer stopped being one, and whether any surface worth acting on is up. + * A refusal satisfies either, and so does a model reloading or a dialog going + * up — so the read that carries the refusal often answers the wait a frame or + * two before it. + * + * Empty where the bound was spent, because a surface that never arrived is + * something to report rather than an error. + */ + waitForScreen( + name: string, + ms: number, + predicate: (snapshot: ScreenSnapshot) => boolean, + ): Operation; + /** Answer the trust dialog: record the consent, and press Enter. */ + consent(): void; + /** + * Wait out a fixed delay, and answer with what was presented during it. + * + * A delay rather than a wait — nothing shortens it — but the same answer a + * wait gives, because the terminal does not stop drawing while a driver is + * deliberately not looking. The screen afterwards says what survived the + * delay, which is a different question from what happened during it, and a + * refusal is routinely only in the second. + */ + pause( + ms: number, + predicate: (snapshot: ScreenSnapshot) => boolean, + ): Operation; +} + +/** + * How many surfaces one session is followed through. + * + * A composer that goes back to loading, the dialog that follows it, the composer + * that dialog was covering, and one spare. A session that needs more than that + * is one this driver does not understand, and saying so is better than pressing + * Enter into it until something happens. + */ +const SURFACE_ROUNDS = 4; + +/** + * Answer the trust dialog if it is presented, then establish the composer. + * + * Whether the dialog appears belongs to the machine — a directory Codex already + * trusts shows none — so this reacts to what is on screen rather than following + * a script, and consents at most once. + * + * Every Enter is preceded by reading the presented screen again. The dialog is + * still drawing its options as it announces itself, so a settle is waited out + * first; and if the screen is no longer showing the dialog after it, no Enter is + * pressed. That is the difference between consenting to a question and pressing + * Enter into whatever replaced it. + */ +export function* reachComposer( + port: SurfacePort, + bounds: SurfaceBounds, +): Operation { + let consented = false; + for (let round = 0; round < SURFACE_ROUNDS; round++) { + const arrived = yield* port.waitForScreen("surface", bounds.surface, (snapshot) => + actionable(classifyInitialSurface(snapshot)), + ); + if (arrived.length === 0) { + return "unresolved"; + } + // A refusal the terminal presented is the finding, whatever it drew next and + // whatever it drew before. Codex commits the refusal and repaints a composer + // over it, and one read from a pty can carry both — so the screen as it now + // stands is not enough to see it, and a driver that only looked there would + // type a turn into a composer for a session the product had already said it + // does not hold. Asked of the whole read because the dialog Codex may draw + // ahead of the refusal answers this wait too, and the refusal outranks it. + if (refused(arrived)) { + return "missing-session"; + } + // Otherwise read again rather than trusting the wait: what satisfied the + // predicate may have been repainted over, and consenting or typing is an act + // against whatever is on show now. + const surface = classifyInitialSurface(port.screen()); + if (surface === "missing-session") { + return "missing-session"; + } + if (surface === "trust-dialog") { + if (consented) { + // Asked twice about one directory. Answering again would be consenting + // twice to a question that was only asked once. + return "unresolved"; + } + // Settling lets a dialog drawn in pieces finish being drawn, and a session + // Codex is still resolving refuse. The screen afterwards cannot tell those + // apart from a refusal that was shown and painted back over, so the settle + // is asked what it saw: consenting to a directory on behalf of a session + // that no longer exists answers a question nobody is asking any more. + const duringSettle = yield* port.pause(bounds.settle, isRefusal); + if (refused(duringSettle)) { + return "missing-session"; + } + if (classifyInitialSurface(port.screen()) !== "trust-dialog") { + continue; + } + consented = true; + port.consent(); + // The dialog stays on screen until Codex repaints over it. Waiting for it + // to go is what stops the next round reading an answered question as a + // second one and giving up in front of a session that is coming up fine. + const answered = yield* port.waitForScreen( + "trust-answered", + bounds.surface, + (snapshot) => classifyInitialSurface(snapshot) !== "trust-dialog", + ); + // Answering the dialog is not the end of the startup, so this is a third + // read a refusal can be presented in — and this wait asks only that the + // dialog be gone, which the model Codex loads next already satisfies. The + // refusal then arrives behind a frame that has answered this wait, inside + // the read that answered it, and going round without looking would find + // only the composer that read ended on. The consent already pressed + // stands: it answered a question about a directory that was up and asking + // at the time. What it must not be followed by is a turn. + if (refused(answered)) { + return "missing-session"; + } + continue; + } + if (surface === "composer-ready") { + // Codex draws its composer early — before it asks about the directory, + // before a model has finished loading, and before it reports that it holds + // no such thread. So a first ready screen is a screen that may still be + // about to become one of those, and the grace watches for it becoming + // *anything* else rather than for the dialog alone. Watching only for the + // dialog is how a refusal or a loading model arriving during the grace + // used to run out the bound unnoticed and be returned as readiness. + const departed = yield* port.waitForScreen( + "readiness-grace", + bounds.grace, + (snapshot) => classifyInitialSurface(snapshot) !== "composer-ready", + ); + // The refusal ends it here too, and for the same reason: it was presented, + // and a repaint in the same read does not take that back. This is the wait + // it most often arrives behind — Codex puts its model back to loading + // before it says it holds no such thread — and a loading frame satisfies + // this predicate as readily as the refusal two frames later does. + if (refused(departed)) { + return "missing-session"; + } + // Otherwise the decision is the screen as it now stands: a surface that + // arrived inside a hold counts from its commit, one that came and went + // never displaced what is on show, and readiness is a claim about a + // composer this driver is about to type into. + const settled = classifyInitialSurface(port.screen()); + if (settled === "missing-session") { + return "missing-session"; + } + if (settled !== "composer-ready") { + continue; + } + return "composer-ready"; + } + } + return "unresolved"; +} + +/** + * How much of the typed turn has to be on screen for it to count as presented. + * + * The tail, because a composer scrolls a long turn and shows its end. The + * proof's turn ends in a marker minted for that one run, so the tail is unique + * to it and no earlier repaint can supply one — provided the whole marker fits + * inside it, which is a thing about the two lengths together and so is proven + * rather than assumed. + */ +export const PROMPT_TAIL = 24; + +/** + * Whether the composer is showing the turn this harness typed into it. + * + * Asked of the typed text alone, and deliberately not of {@link COMPOSER} as + * well. That string is the composer's placeholder, which is what an *empty* + * composer shows — the thing {@link composerReady} reads — and it is gone the + * moment there is a turn in the box. Requiring both would be requiring the + * composer to be simultaneously empty and full, and every run would report a + * turn it never managed to type. + * + * Before submission there is nowhere else on the screen for this text to be: + * nothing has been sent, so no transcript carries it back. + */ +export function promptPresented(snapshot: ScreenSnapshot, typed: string): boolean { + const tail = typed.slice(-PROMPT_TAIL); + return tail.length > 0 && shows(snapshot, tail); +} + +/** The bounds a submission is given, supplied by whoever owns the run. */ +export interface SubmissionBounds { + /** How long the composer is waited for, before and after the grace. */ + readonly ready: number; + /** + * How long a ready-looking composer is given to turn out to have something + * over it, or a model still loading behind it. + * + * Its length matters less than the recheck it precedes: a composer that stops + * being ready during the grace is waited for again rather than typed into. + */ + readonly startupGrace: number; + /** How long the typed turn is given to appear in the composer. */ + readonly presented: number; +} + +/** + * Everything submitting a turn needs from a terminal, and nothing else. + * + * Narrow on purpose: the offline cases implement this against scripted frames, + * so what they decide is what the pty path does. + */ +export interface SubmissionPort { + /** What the terminal last put on show. Never a frame still being drawn. */ + screen(): ScreenSnapshot; + /** + * Wait for a read presenting `predicate`, and answer with every frame that did. + * + * The frames rather than a flag, and for a sharper reason than reaching a + * composer had. Codex draws its refusal *above* a composer rather than in + * place of one, so a screen carrying `No saved session found with ID` and an + * empty composer both is a screen {@link composerReady} calls ready and + * {@link promptPresented} calls presented. A refusal therefore does not merely + * ride along in these batches — it satisfies them. Answering `true` here would + * hand back the one bit that cannot distinguish the composer this driver is + * waiting for from the refusal that means it must not type into it, and the + * next thing down that path is Enter. + * + * Empty where the bound was spent, because a surface that never arrived is + * something to report rather than an error. + */ + waitForScreen( + name: string, + ms: number, + predicate: (snapshot: ScreenSnapshot) => boolean, + ): Operation; + /** Type one turn into the composer, without sending it. */ + type(text: string): void; + /** Press Enter. This is the keystroke that spends the turn. */ + send(typed: string): void; + /** Wait out a fixed delay, and answer with what was presented during it. */ + pause( + ms: number, + predicate: (snapshot: ScreenSnapshot) => boolean, + ): Operation; +} + +export type SubmissionRefusal = + | "session-refused" + | "composer-never-ready" + | "composer-unready-after-grace" + | "prompt-never-presented"; + +export type SubmissionOutcome = + | { readonly submitted: true } + | { readonly submitted: false; readonly reason: SubmissionRefusal }; + +const REFUSALS: Readonly> = { + "session-refused": + "Codex refused the identity while the turn was being readied, so the composer on screen " + + "belonged to a session it had already said it does not hold", + "composer-never-ready": + "the composer never came up ready — it was absent, covered by the trust dialog, or still " + + "showing a model that had not loaded", + "composer-unready-after-grace": + "the composer stopped being ready during the startup grace and did not come back", + "prompt-never-presented": + "the turn was typed but never appeared in the composer, so what Codex would have received " + + "could not be established", +}; + +/** The sentence naming why nothing was submitted. Carries no typed content. */ +export function refusalDetail(reason: SubmissionRefusal): string { + return REFUSALS[reason]; +} + +/** + * Type one turn and submit it, or establish that it must not be submitted. + * + * Enter is reached from exactly one place, past three screen readings that each + * have to hold: the composer is ready, it is still ready after the grace in + * which an overlay or a loading model would have shown itself, and the turn is + * on screen in the composer. A run that fails any of them returns having typed + * at most — no Enter, and so no turn to account for. + * + * None of those three is a question about a refusal, though, and `No saved + * session found with ID` can be drawn either above the composer or where the + * composer was. The first shape passes every test here — ready, and holding the + * turn that was typed. The second fails them all, which is worse: a wait + * watching only for readiness does not stop at it, and the next read paints over + * it. So the refusal is watched for by each wait as well as asked of the batch + * that answers it, and asked again of the screen as it stands wherever this + * decides something without a wait: after the startup grace, after a composer is + * waited for a second time, and immediately before Enter. + * + * Reaching a composer has already established that the session was not refused, + * but only up to the bound it was willing to wait — and Codex draws its composer + * before it resolves the thread, so a refusal slower than that bound arrives + * here, with the turn already typed and Enter next. + */ +export function* submitWhenReady( + port: SubmissionPort, + typed: string, + bounds: SubmissionBounds, +): Operation { + const ready = yield* port.waitForScreen( + "composer-ready", + bounds.ready, + stoppingAtRefusal(composerReady), + ); + if (refused(ready)) { + return { submitted: false, reason: "session-refused" }; + } + if (ready.length === 0) { + return { submitted: false, reason: "composer-never-ready" }; + } + + // The grace is a window a refusal can be drawn in, and what it leaves behind + // is a screen rather than a batch: nothing else is watching it. Asked for + // readiness alone the screen afterwards answers yes, because the refusal is + // drawn above a composer that is otherwise up — so the second wait would not + // even be taken, and the read that later confirms the turn repaints a clean + // composer over the only frame that ever said the session does not exist. A + // refusal repainted before the grace is out leaves no trace on that screen at + // all, so the grace is asked what it saw as well as what it left. + const duringGrace = yield* port.pause(bounds.startupGrace, isRefusal); + if (refused(duringGrace)) { + return { submitted: false, reason: "session-refused" }; + } + const settled = port.screen(); + if (classifyInitialSurface(settled) === "missing-session") { + return { submitted: false, reason: "session-refused" }; + } + if (!composerReady(settled)) { + const again = yield* port.waitForScreen( + "composer-ready-again", + bounds.ready, + stoppingAtRefusal(composerReady), + ); + if (refused(again)) { + return { submitted: false, reason: "session-refused" }; + } + if (again.length === 0) { + return { submitted: false, reason: "composer-unready-after-grace" }; + } + // A batch says a ready composer was presented during the read, not that it + // is what the read ended on. The refusal that arrives after it takes the + // composer with it, and a screen with no composer is precisely what this + // wait does not collect — so the honestly ready batch above can sit in front + // of a refusal that is on show right now, with typing next. + if (classifyInitialSurface(port.screen()) === "missing-session") { + return { submitted: false, reason: "session-refused" }; + } + } + + port.type(typed); + const presented = yield* port.waitForScreen( + "prompt-presented", + bounds.presented, + stoppingAtRefusal((snapshot) => promptPresented(snapshot, typed)), + ); + if (refused(presented)) { + return { submitted: false, reason: "session-refused" }; + } + if (presented.length === 0) { + return { submitted: false, reason: "prompt-never-presented" }; + } + + // Enter is the act this whole file exists to place correctly, so it is taken + // against the screen as it stands and not against a batch that answered a + // wait. A refusal arriving after the turn was confirmed on screen — in a read + // of its own, with nothing left to satisfy any predicate above — is invisible + // to all three waits and would otherwise be charged for. + if (classifyInitialSurface(port.screen()) === "missing-session") { + return { submitted: false, reason: "session-refused" }; + } + + port.send(typed); + return { submitted: true }; +} diff --git a/packages/acp/tests/fixtures/fake-codex-app-server.cjs b/packages/acp/tests/fixtures/fake-codex-app-server.cjs index d86fa949..05246855 100755 --- a/packages/acp/tests/fixtures/fake-codex-app-server.cjs +++ b/packages/acp/tests/fixtures/fake-codex-app-server.cjs @@ -119,6 +119,31 @@ function handle(message) { }); return; } + case "thread/resume": { + // Reopening answers with the thread the App Server settled on. That + // answer, not the id the request carried, is what names the conversation + // a client would go on to address directly. + const threadId = message.params?.threadId ?? "thread-1"; + reply(message.id, { + thread: { id: threadId }, + model: "fake-model", + modelProvider: "fake", + serviceTier: null, + cwd: process.cwd(), + instructionSources: [], + approvalPolicy: "never", + approvalsReviewer: "user", + sandboxPolicy: { mode: "danger-full-access" }, + reasoningEffort: null, + skills: [], + }); + return; + } + case "thread/read": { + const threadId = message.params?.threadId ?? "thread-1"; + reply(message.id, { thread: { id: threadId, title: null, turns: [] } }); + return; + } case "turn/start": { const threadId = message.params?.threadId ?? "thread-1"; const turnId = nextTurn(threadId); diff --git a/packages/acp/tests/fixtures/terminal-screen.ts b/packages/acp/tests/fixtures/terminal-screen.ts new file mode 100644 index 00000000..074b99bb --- /dev/null +++ b/packages/acp/tests/fixtures/terminal-screen.ts @@ -0,0 +1,1087 @@ +/** + * What a cursor-addressed terminal is showing, reconstructed from its bytes. + * + * A TUI does not append its output. It moves the cursor, erases, and draws over + * what it drew before, so the byte stream a pty emitted and the screen an + * operator is looking at are two different things. Searching the stream answers + * the wrong question twice over: it finds text that has since been erased, text + * the program printed before it took the screen, and text no repaint left + * standing — while missing an answer whose only surviving form is the cells it + * was last drawn into. + * + * So the bytes are applied to a screen here, and the screen is what gets read. + * Nothing in this module is Codex-specific: it is a terminal, and the questions + * asked of it are supplied by the caller. + */ + +import { withResolvers } from "effection"; +import type { Operation } from "effection"; + +const ESC = String.fromCharCode(0x1b); +const BEL = String.fromCharCode(0x07); +const DEL = String.fromCharCode(0x7f); +const BLANK = " "; + +/** + * Where one piece of text stops, marked in a row before the row is cut up. + * + * A noncharacter, so no terminal output can supply one and be mistaken for a + * cut this module made. + */ +const BOUNDARY = String.fromCharCode(0xffff); + +/** + * The glyphs a TUI draws its frame out of — box drawing and block elements. + * + * A pane border can land between two words of a wrapped sentence, so these have + * to come out before the words either side of one can be read as adjacent. No + * model emits them mid-sentence, so removing them recovers the text without + * letting anything else through. + */ +const FRAME_GLYPH = /[─-▟]/gu; + +/** Collapse every run of whitespace, so a TUI's own layout cannot hide a word. */ +function squeeze(text: string): string { + return text.replaceAll(/\s+/gu, ""); +} + +export interface ScreenSize { + readonly rows: number; + readonly columns: number; +} + +export interface ScreenSnapshot { + /** Whether the alternate buffer is showing, which is a full-screen TUI's. */ + readonly alternate: boolean; + /** The visible rows of whichever buffer is showing, trailing blanks removed. */ + readonly rows: readonly string[]; +} + +function blankRow(columns: number): string[] { + return new Array(columns).fill(BLANK); +} + +function blankCells(size: ScreenSize): string[][] { + return Array.from({ length: size.rows }, () => blankRow(size.columns)); +} + +export class TerminalScreen { + readonly #rows: number; + readonly #columns: number; + readonly #decoder = new TextDecoder(); + #primary: string[][]; + #alternate: string[][]; + #onAlternate = false; + #row = 0; + #column = 0; + /** + * Whether the last glyph filled the final column. + * + * A terminal does not move to the next row when the last column is written; + * it waits to see whether another glyph arrives. Without that delay a + * sentence ending exactly at the right margin drags a blank row after it, and + * a cursor move that follows one lands a row too low. + */ + #wrapPending = false; + #savedRow = 0; + #savedColumn = 0; + #top = 0; + #bottom: number; + /** An escape sequence split across two reads, held until the rest arrives. */ + #partial = ""; + /** + * What was on show when the program asked for its drawing to be held back. + * + * `CSI ?2026h` tells the terminal to stop presenting until `CSI ?2026l`, so a + * program can erase and redraw a region without the reader ever seeing the + * half-drawn state. Codex asks for this, which means the rows in between are + * cells this screen holds and a terminal never showed anybody — while this + * stays what it showed last, so a reader has something true to read. + * + * Taken where the hold begins rather than at the end of the read carrying it: + * bytes before that point were presented, and bytes after it were not. + */ + #held: ScreenSnapshot | undefined; + readonly #onFrame: (() => void) | undefined; + + /** + * @param onFrame Called each time a synchronized update commits, at the point + * in the byte stream where it commits rather than at the end of the read that + * carried it — a frame is finished by `CSI ?2026l` and not by an operating + * system deciding where one `data` event stops. + */ + constructor(size: ScreenSize, onFrame?: () => void) { + this.#rows = size.rows; + this.#columns = size.columns; + this.#primary = blankCells(size); + this.#alternate = blankCells(size); + this.#bottom = size.rows - 1; + this.#onFrame = onFrame; + } + + /** Whether a frame is part-drawn, so what the cells hold is not on show. */ + get synchronized(): boolean { + return this.#held !== undefined; + } + + /** Apply one read from the terminal. Byte chunks may split any sequence. */ + write(chunk: string | Uint8Array): void { + const text = typeof chunk === "string" ? chunk : this.#decoder.decode(chunk, { stream: true }); + this.#consume(`${this.#partial}${text}`); + } + + /** The cells as they stand, whether or not a terminal has shown them. */ + snapshot(): ScreenSnapshot { + return { + alternate: this.#onAlternate, + rows: this.#cells().map((row) => row.join("").replace(/\s+$/u, "")), + }; + } + + /** + * What a terminal last put on show, which is the only thing anybody read. + * + * The same as {@link snapshot} except while a frame is being held back, when + * it stays the frame before it until `CSI ?2026l` commits the new one. Any + * question about what was on the screen — what it answered, and what it was + * showing when it did not — has to be asked of this. + */ + presented(): ScreenSnapshot { + return this.#held ?? this.snapshot(); + } + + #cells(): string[][] { + return this.#onAlternate ? this.#alternate : this.#primary; + } + + #consume(input: string): void { + this.#partial = ""; + let index = 0; + while (index < input.length) { + const char = input[index] ?? ""; + if (char !== ESC) { + this.#put(char); + index += 1; + continue; + } + const next = this.#escape(input, index); + if (next === undefined) { + this.#partial = input.slice(index); + return; + } + index = next; + } + } + + /** Consume one escape sequence, or report that it has not all arrived. */ + #escape(input: string, start: number): number | undefined { + const kind = input[start + 1]; + if (kind === undefined) { + return undefined; + } + if (kind === "[") { + return this.#csi(input, start); + } + if (kind === "]" || kind === "P" || kind === "^" || kind === "_") { + return this.#stringSequence(input, start); + } + if (kind === "(" || kind === ")" || kind === "*" || kind === "+") { + return input[start + 2] === undefined ? undefined : start + 3; + } + if (kind === "7") { + this.#save(); + } else if (kind === "8") { + this.#restore(); + } else if (kind === "M") { + this.#reverseIndex(); + } else if (kind === "D") { + this.#index(); + } else if (kind === "E") { + this.#index(); + this.#column = 0; + } else if (kind === "c") { + this.#reset(); + } + return start + 2; + } + + #csi(input: string, start: number): number | undefined { + let index = start + 2; + while (index < input.length && /[0-9;:?<>=!]/u.test(input[index] ?? "")) { + index += 1; + } + while (index < input.length && /[ -/]/u.test(input[index] ?? "")) { + index += 1; + } + const final = input[index]; + if (final === undefined) { + return undefined; + } + this.#dispatch(input.slice(start + 2, index), final); + return index + 1; + } + + /** OSC, DCS, APC and PM all run to a BEL or a string terminator. */ + #stringSequence(input: string, start: number): number | undefined { + let index = start + 2; + while (index < input.length) { + const char = input[index]; + if (char === BEL) { + return index + 1; + } + if (char === ESC) { + const after = input[index + 1]; + if (after === undefined) { + return undefined; + } + if (after === "\\") { + return index + 2; + } + } + index += 1; + } + return undefined; + } + + #dispatch(body: string, final: string): void { + const priv = body.startsWith("?"); + const params = (priv ? body.slice(1) : body).split(";").map((part) => { + const value = Number.parseInt(part, 10); + return Number.isNaN(value) ? 0 : value; + }); + if (priv) { + if (final === "h" || final === "l") { + this.#mode(params, final === "h"); + } + return; + } + /** A parameter a terminal reads as "how many", where zero means one. */ + const count = (position: number, fallback: number): number => { + const value = params[position]; + return value === undefined || value === 0 ? fallback : value; + }; + /** A parameter a terminal reads as "which", where zero is a real choice. */ + const choice = params[0] ?? 0; + switch (final) { + case "A": + this.#moveTo(this.#row - count(0, 1), this.#column); + break; + case "B": + this.#moveTo(this.#row + count(0, 1), this.#column); + break; + case "C": + this.#moveTo(this.#row, this.#column + count(0, 1)); + break; + case "D": + this.#moveTo(this.#row, this.#column - count(0, 1)); + break; + case "E": + this.#moveTo(this.#row + count(0, 1), 0); + break; + case "F": + this.#moveTo(this.#row - count(0, 1), 0); + break; + case "G": + case "`": + this.#moveTo(this.#row, count(0, 1) - 1); + break; + case "d": + this.#moveTo(count(0, 1) - 1, this.#column); + break; + case "H": + case "f": + this.#moveTo(count(0, 1) - 1, count(1, 1) - 1); + break; + case "J": + this.#eraseDisplay(choice); + break; + case "K": + this.#eraseLine(choice); + break; + case "L": + this.#insertLines(count(0, 1)); + break; + case "M": + this.#deleteLines(count(0, 1)); + break; + case "@": + this.#insertChars(count(0, 1)); + break; + case "P": + this.#deleteChars(count(0, 1)); + break; + case "X": + this.#eraseChars(count(0, 1)); + break; + case "S": + this.#scrollUp(count(0, 1)); + break; + case "T": + this.#scrollDown(count(0, 1)); + break; + case "r": + this.#margins(count(0, 1) - 1, count(1, this.#rows) - 1); + break; + case "s": + this.#save(); + break; + case "u": + this.#restore(); + break; + default: + // Colour, weight, cursor visibility, bracketed paste: no cell changes. + break; + } + } + + #mode(params: readonly number[], set: boolean): void { + for (const value of params) { + if (value === 2026) { + if (set) { + // A hold inside a hold is still the same hold: what was on show is + // what was on show when the drawing first stopped. + this.#held ??= this.snapshot(); + continue; + } + this.#held = undefined; + this.#onFrame?.(); + continue; + } + if (value !== 1049 && value !== 47 && value !== 1047) { + continue; + } + // 1049 hands a TUI a screen with nothing of the shell's on it and gives + // the shell's cursor back on the way out. 47 and 1047 only switch. + const owns = value === 1049; + if (set) { + this.#enter(owns); + } else { + this.#leave(owns); + } + } + } + + #enter(owns: boolean): void { + if (this.#onAlternate) { + return; + } + if (owns) { + this.#save(); + } + this.#onAlternate = true; + if (owns) { + this.#alternate = blankCells({ rows: this.#rows, columns: this.#columns }); + this.#moveTo(0, 0); + } + } + + #leave(owns: boolean): void { + if (!this.#onAlternate) { + return; + } + this.#onAlternate = false; + if (owns) { + this.#restore(); + } + } + + #put(char: string): void { + if (char === "\n") { + this.#index(); + return; + } + if (char === "\r") { + this.#column = 0; + this.#wrapPending = false; + return; + } + if (char === "\b") { + this.#moveTo(this.#row, this.#column - 1); + return; + } + if (char === "\t") { + this.#moveTo(this.#row, (Math.floor(this.#column / 8) + 1) * 8); + return; + } + if (char < BLANK || char === DEL) { + return; + } + if (this.#wrapPending) { + this.#index(); + this.#column = 0; + this.#wrapPending = false; + } + const row = this.#cells()[this.#row]; + if (row === undefined) { + return; + } + row[this.#column] = char; + if (this.#column + 1 >= this.#columns) { + this.#wrapPending = true; + return; + } + this.#column += 1; + } + + #index(): void { + this.#wrapPending = false; + if (this.#row === this.#bottom) { + this.#scrollUp(1); + return; + } + if (this.#row < this.#rows - 1) { + this.#row += 1; + } + } + + #reverseIndex(): void { + this.#wrapPending = false; + if (this.#row === this.#top) { + this.#scrollDown(1); + return; + } + if (this.#row > 0) { + this.#row -= 1; + } + } + + #scrollUp(lines: number): void { + const cells = this.#cells(); + for (let step = 0; step < lines; step += 1) { + cells.splice(this.#top, 1); + cells.splice(this.#bottom, 0, blankRow(this.#columns)); + } + } + + #scrollDown(lines: number): void { + const cells = this.#cells(); + for (let step = 0; step < lines; step += 1) { + cells.splice(this.#bottom, 1); + cells.splice(this.#top, 0, blankRow(this.#columns)); + } + } + + #insertLines(lines: number): void { + if (this.#row < this.#top || this.#row > this.#bottom) { + return; + } + const cells = this.#cells(); + for (let step = 0; step < lines; step += 1) { + cells.splice(this.#bottom, 1); + cells.splice(this.#row, 0, blankRow(this.#columns)); + } + this.#column = 0; + this.#wrapPending = false; + } + + #deleteLines(lines: number): void { + if (this.#row < this.#top || this.#row > this.#bottom) { + return; + } + const cells = this.#cells(); + for (let step = 0; step < lines; step += 1) { + cells.splice(this.#row, 1); + cells.splice(this.#bottom, 0, blankRow(this.#columns)); + } + this.#column = 0; + this.#wrapPending = false; + } + + #insertChars(chars: number): void { + const row = this.#cells()[this.#row]; + if (row === undefined) { + return; + } + for (let step = 0; step < chars; step += 1) { + row.splice(this.#column, 0, BLANK); + row.length = this.#columns; + } + } + + #deleteChars(chars: number): void { + const row = this.#cells()[this.#row]; + if (row === undefined) { + return; + } + for (let step = 0; step < chars; step += 1) { + row.splice(this.#column, 1); + row.push(BLANK); + } + } + + #eraseChars(chars: number): void { + const row = this.#cells()[this.#row]; + if (row === undefined) { + return; + } + const end = Math.min(this.#columns, this.#column + chars); + for (let column = this.#column; column < end; column += 1) { + row[column] = BLANK; + } + } + + #eraseDisplay(choice: number): void { + const cells = this.#cells(); + this.#wrapPending = false; + if (choice === 0) { + this.#eraseLine(0); + for (let row = this.#row + 1; row < this.#rows; row += 1) { + cells[row] = blankRow(this.#columns); + } + return; + } + if (choice === 1) { + this.#eraseLine(1); + for (let row = 0; row < this.#row; row += 1) { + cells[row] = blankRow(this.#columns); + } + return; + } + if (choice !== 2) { + // `3J` clears scrollback, which this screen does not keep, and anything + // else is a mode this terminal does not implement. Neither may fall + // through to a whole-screen erase: a proof that blanked the visible rows + // on an unrecognized parameter would report the answer it was looking at + // as absent. + return; + } + for (let row = 0; row < this.#rows; row += 1) { + cells[row] = blankRow(this.#columns); + } + } + + #eraseLine(choice: number): void { + const row = this.#cells()[this.#row]; + if (row === undefined) { + return; + } + this.#wrapPending = false; + const from = choice === 0 ? this.#column : 0; + const to = choice === 1 ? this.#column + 1 : this.#columns; + for (let column = from; column < Math.min(to, this.#columns); column += 1) { + row[column] = BLANK; + } + } + + #margins(top: number, bottom: number): void { + const first = Math.max(0, Math.min(top, this.#rows - 1)); + const last = Math.max(first, Math.min(bottom, this.#rows - 1)); + this.#top = first; + this.#bottom = last; + this.#moveTo(0, 0); + } + + #moveTo(row: number, column: number): void { + this.#row = Math.max(0, Math.min(row, this.#rows - 1)); + this.#column = Math.max(0, Math.min(column, this.#columns - 1)); + this.#wrapPending = false; + } + + #save(): void { + this.#savedRow = this.#row; + this.#savedColumn = this.#column; + } + + #restore(): void { + this.#moveTo(this.#savedRow, this.#savedColumn); + } + + #reset(): void { + this.#primary = blankCells({ rows: this.#rows, columns: this.#columns }); + this.#alternate = blankCells({ rows: this.#rows, columns: this.#columns }); + this.#onAlternate = false; + this.#top = 0; + this.#bottom = this.#rows - 1; + // A terminal reset ends any frame that was being withheld. Leaving it held + // would suspend observation for the rest of the run, and go on reporting a + // screen this reset has just blanked. + this.#held = undefined; + this.#moveTo(0, 0); + } +} + +/** + * The screen's text, cut where the layout says one piece of text ends. + * + * Rows are joined rather than kept apart, because a wrapped sentence continues + * from the end of one row into the start of the next — with no space where the + * terminal wrapped it mid-word, and one where the program wrapped it between + * words. Joining and then squeezing reads both. + * + * A run ends at anything that says the text does not continue: a blank row, a + * row of the program's own furniture, a pane border, or a gap of two or more + * cells inside a row. That last cut is what keeps erasure honest — cells an + * erase blanked in the middle of a sentence would otherwise close up and let + * the words either side of the hole read as one. + */ +export function runsOf(rows: readonly string[], chrome: readonly string[]): string[] { + const furniture = chrome.map(squeeze).filter((marker) => marker.length > 0); + const runs: string[] = []; + let open = ""; + const flush = (): void => { + if (open.length > 0) { + runs.push(open); + } + open = ""; + }; + for (const row of rows) { + const framed = row.replaceAll(FRAME_GLYPH, BOUNDARY); + const bare = squeeze(framed.replaceAll(BOUNDARY, "")); + if (bare.length === 0 || furniture.some((marker) => bare.includes(marker))) { + flush(); + continue; + } + const segments = framed + .split(BOUNDARY) + .flatMap((pane) => pane.trim().split(/ {2,}/u)) + .filter((segment) => segment.length > 0); + const [first, ...rest] = segments; + if (first === undefined) { + flush(); + continue; + } + open += first; + for (const segment of rest) { + flush(); + open = segment; + } + } + flush(); + return runs; +} + +export interface AnswerQuestion { + /** The exact text an answer has to show. */ + readonly expected: string; + /** + * What this harness typed. + * + * A TUI shows the turn it was given as well as the reply, so the screen + * carries this harness's own words back. Reading the expectation out of them + * would let a question that quoted its answer satisfy itself. + */ + readonly typed: string; + /** Markers naming the program's own furniture rather than anything it said. */ + readonly chrome: readonly string[]; +} + +/** + * Why the screen was or was not read as carrying the answer. + * + * `only-typed` is kept apart from `absent` because they are different facts: one + * says the words are on screen but every copy of them is this harness's own, and + * the other says they are not there at all. + */ +export type AnswerReason = "answer" | "absent" | "only-typed"; + +export interface AnswerObservation { + readonly found: boolean; + readonly reason: AnswerReason; +} + +function rangesOf(text: string, needle: string): { start: number; end: number }[] { + const spans: { start: number; end: number }[] = []; + let index = text.indexOf(needle); + while (index !== -1) { + spans.push({ start: index, end: index + needle.length }); + index = text.indexOf(needle, index + 1); + } + return spans; +} + +/** Whether the screen is showing `question.expected` as something it said. */ +export function observeAnswer( + snapshot: ScreenSnapshot, + question: AnswerQuestion, +): AnswerObservation { + const wanted = squeeze(question.expected); + if (wanted.length === 0) { + return { found: false, reason: "absent" }; + } + const echoed = squeeze(question.typed); + let masked = false; + for (const run of runsOf(snapshot.rows, question.chrome)) { + const text = squeeze(run); + // Where this harness's own turn is showing, so a copy of the expectation + // that lies inside one is read as the echo it is. Scoped to the occurrence + // rather than the run, because a run that carries the echo can carry the + // reply after it, and a reply that happens to be a fragment of the question + // is still a reply. + const spans = echoed.length === 0 ? [] : rangesOf(text, echoed); + let index = text.indexOf(wanted); + while (index !== -1) { + const inside = spans.some((span) => index >= span.start && index + wanted.length <= span.end); + if (!inside) { + return { found: true, reason: "answer" }; + } + masked = true; + index = text.indexOf(wanted, index + 1); + } + } + return { found: false, reason: masked ? "only-typed" : "absent" }; +} + +/** + * Whether the screen — furniture included — is showing `marker`. + * + * Deliberately unlike {@link observeAnswer}: this is how the terminal's state is + * described after a wait gave up, and furniture is exactly what that describes. + */ +export function screenShows(snapshot: ScreenSnapshot, marker: string): boolean { + const text = snapshot.rows.join("\n").replaceAll(FRAME_GLYPH, ""); + return squeeze(text).includes(squeeze(marker)); +} + +/** + * The terminal one invocation drew, across the reads that built it. + * + * The one stateful thing the live proof and the offline streams both drive, so + * what the offline cases decide is what the pty path does — the alternative is + * two readers that agree until the day they do not. + * + * Every invocation gets one, because every invocation decides what surface it + * is looking at and a surface is a fact about the screen: the byte stream + * retains a composer drawn before a dialog covered it, and text an erase wiped + * out. Only an invocation that submits a turn also *watches* — see + * {@link watching} — because latching an answer is a claim about a reply to a + * turn, and a run that spoke none has no reply to attribute. + * + * The latch latches. Once the answer has been on the screen it stays observed, + * because the question is whether the agent answered and a TUI is entitled to + * repaint over its own reply the moment it finishes. Nothing un-answers a turn. + * + * Nothing is latched until {@link ask}, which is the point in a run where the + * turn has been submitted. Reads before then still build the screen — an answer + * arrives on a terminal the whole session drew — but they cannot latch, so text + * from before the turn is not a reply to it. + * + * Nothing is read out of a part-drawn frame either. A program that holds its + * drawing back with `CSI ?2026h` is saying the cells in between were never + * presented, and a reader of those would decide on a frame no terminal + * displayed and no operator could have seen. + */ +export class TerminalReader { + readonly #screen: TerminalScreen; + readonly #watching: boolean; + readonly #watches = new Set(); + #question: AnswerQuestion | undefined; + #found = false; + + constructor(size: ScreenSize, watching: boolean) { + this.#watching = watching; + // Committed frames are collected where they commit, so what a wait is + // answered with does not depend on where the operating system happened to + // cut a read. + this.#screen = new TerminalScreen(size, () => { + this.#look(); + this.#collect(); + }); + } + + /** Whether this invocation may latch an answer at all. */ + get watching(): boolean { + return this.#watching; + } + + /** Feed one read, and say whether the answer has been observed. */ + write(chunk: string | Uint8Array): boolean { + this.#screen.write(chunk); + // The screen this read ended on, which the commits inside it did not offer. + // A read need not end on a commit at all — a frame drawn with no + // synchronization is finished by the bytes stopping and by nothing else. + this.#collect(); + // Waits are woken here rather than at the commit that first satisfied them, + // so what one is answered with is every frame the read presented and not + // merely the frames up to the first match. Nothing can be acted on before a + // read has been applied in any case, and waking part of the way through one + // is what would let a refusal drawn behind an earlier match go unseen. + this.#wake(); + return this.#settled(); + } + + /** + * Watch presented frames for `predicate`, and keep every one that satisfies it. + * + * Kept, because what a terminal presented is not undone by what it presents + * next. One read from a pty can finish several frames, and a caller told only + * *that* a surface arrived would have to look at the screen again to find out + * which — by which time the frame it is being asked about may be two repaints + * old. Codex commits its refusal and draws a composer over it inside a single + * read, so that gap is where a refusal goes missing. + * + * Every one, and not just the first, because the first is not always the one + * that matters. A predicate wide enough to catch what a caller is watching for + * is wide enough to be satisfied by something ordinary ahead of it in the same + * read: Codex reloads a model, refuses the identity and repaints its composer + * in that order and in one read, and a watch that closed at the reload would + * report a reload and lose the refusal entirely. + * + * Answered against what is already on show before anything else, since a frame + * that satisfies this may have committed before a caller thought to ask. No + * read follows in that case, so nothing further is collected and `notify` is + * never called: the caller is still here, and has the frame in its hand. + */ + watch(predicate: (snapshot: ScreenSnapshot) => boolean, notify: () => void): PresentedWatch { + const entry: Watch = { test: predicate, notify, matches: [] }; + const handle: PresentedWatch = { + get matches(): readonly ScreenSnapshot[] { + return entry.matches; + }, + close: () => { + this.#watches.delete(entry); + }, + }; + const presented = this.#screen.presented(); + if (predicate(presented)) { + entry.matches.push(presented); + return handle; + } + this.#watches.add(entry); + return handle; + } + + /** + * Watch for this answer from now on, against what is already showing. + * + * Answered immediately as well as latched, because the reply can be complete + * before a caller gets here and no further read need arrive. + * + * Refused outright on a reader that is not watching. Such a reader belongs to + * an invocation that submitted no turn, so an answer it reported would be a + * reply attributed to a turn nobody spent. + */ + ask(question: AnswerQuestion): boolean { + if (!this.#watching) { + throw new Error("this invocation submits no turn and cannot watch for an answer"); + } + this.#question = question; + return this.#settled(); + } + + /** Whether the answer has been observed at any point since {@link ask}. */ + get found(): boolean { + return this.#found; + } + + /** + * What the terminal has put on show — furniture included. + * + * Never a frame it is still drawing. A caller that reaches here after a bound + * expired is asking what the operator would have been looking at, and cells + * held back for a repaint are not that: reporting them would name a surface + * as the reason no answer arrived when nobody was ever shown it. + */ + snapshot(): ScreenSnapshot { + return this.#screen.presented(); + } + + /** Look, unless a frame is part-drawn — then the last commit still stands. */ + #settled(): boolean { + if (this.#screen.synchronized) { + return this.#found; + } + return this.#look(); + } + + #look(): boolean { + if (this.#found || this.#question === undefined) { + return this.#found; + } + this.#found = observeAnswer(this.#screen.snapshot(), this.#question).found; + return this.#found; + } + + /** Offer the frame now on show to every open watch, keeping what satisfies. */ + #collect(): void { + if (this.#watches.size === 0) { + return; + } + const presented = this.#screen.presented(); + for (const entry of this.#watches) { + // A read ending on a commit reaches here twice with the same frame — once + // where it committed and once as the screen the read ended on — and that + // is one thing the terminal presented rather than two. + if (sameCells(entry.matches[entry.matches.length - 1], presented)) { + continue; + } + if (entry.test(presented)) { + entry.matches.push(presented); + } + } + } + + /** Wake every watch this read answered, and stop collecting for it. */ + #wake(): void { + for (const entry of this.#watches) { + if (entry.matches.length === 0) { + continue; + } + this.#watches.delete(entry); + entry.notify(); + } + } +} + +/** Whether these are the same cells, so one presentation rather than two. */ +function sameCells(one: ScreenSnapshot | undefined, two: ScreenSnapshot): boolean { + return ( + one !== undefined && + one.alternate === two.alternate && + one.rows.length === two.rows.length && + one.rows.every((row, index) => row === two.rows[index]) + ); +} + +interface Watch { + readonly test: (snapshot: ScreenSnapshot) => boolean; + readonly notify: () => void; + readonly matches: ScreenSnapshot[]; +} + +/** A standing question about the presented screen, answered by one read. */ +export interface PresentedWatch { + /** The frames that satisfied the predicate, in the order they were presented. */ + readonly matches: readonly ScreenSnapshot[]; + /** Stop watching. Whatever was matched is kept. */ + close(): void; +} + +/** How a wait learns that a read has answered it. */ +export interface PresentedWait { + /** Settles once a read has presented a frame satisfying the predicate. */ + readonly matched: Operation; + /** Whether one already has. */ + settled(): boolean; +} + +/** + * Wait for a read presenting `predicate`, and answer with every frame that did. + * + * The frames themselves, and never the screen as it stands when the wait + * returns. Those are routinely different things: one read from a pty commits + * however many frames its bytes finished, so a surface can be presented and + * painted over without a single byte arriving in between for anybody to be woken + * by. + * + * All of them, in the order they were presented, because a caller asking a + * question broad enough to catch what it is watching for gets ordinary frames + * answering it too — and which of them decides is the caller's to say, not + * whichever happened to come first. + * + * Empty where the bound was spent, because a surface that never arrived is + * something to report rather than an error. + * + * `read` owns everything about waiting that is not about the screen — how long, + * and what else may end it — so the live pty and the offline scripts differ only + * in that, and what decides is this. + */ +export function* waitForPresented( + reader: TerminalReader, + predicate: (snapshot: ScreenSnapshot) => boolean, + read: (wait: PresentedWait) => Operation, +): Operation { + const waiter = withResolvers(); + const watch = reader.watch(predicate, waiter.resolve); + try { + if (watch.matches.length === 0) { + yield* read({ + matched: waiter.operation, + settled: () => watch.matches.length !== 0, + }); + } + } finally { + watch.close(); + } + return watch.matches; +} + +/** + * Wait out a fixed delay, and answer with every frame presented during it. + * + * A pause is a window like any other. Nothing shortens it and nothing is being + * waited for, but the terminal goes on drawing for its whole duration all the + * same — so a settle or a grace that only sleeps is a window in which something + * can be shown and taken back, and a caller reading the screen afterwards is + * asking what survived rather than what happened. Between those two questions + * sits every refusal Codex presented and repainted over while a driver was + * deliberately not looking. + * + * Held frames stay invisible here as everywhere else: a watch is offered frames + * at their commit, and what a terminal never put on show was never presented. + * + * `delay` owns the waiting entirely, so a pause cancelled or timed out partway + * keeps whatever it had collected by then and closes its watch with the scope + * that owned it. + */ +export function* presentedDuring( + reader: TerminalReader, + predicate: (snapshot: ScreenSnapshot) => boolean, + delay: Operation, +): Operation { + // Nothing to notify: a pause runs for as long as it was asked to, and a frame + // arriving early is collected rather than acted on. + const watch = reader.watch(predicate, () => {}); + try { + yield* delay; + } finally { + watch.close(); + } + return watch.matches; +} + +/** What one invocation of the product does, as far as observation goes. */ +export interface ObservedInvocation { + /** Which invocation of its journey this is. Only the first submits a turn. */ + readonly index: number; + /** What it types into the composer, if it types anything. */ + readonly say?: string; + /** What proves the answer to that arrived, if an answer is expected. */ + readonly expect?: string; +} + +/** + * The reader an invocation gets, which is always one — watching only if the + * invocation submits a turn and expects an answer to it. + * + * The submitting invocation is a journey's first, so a later one carrying an + * expectation is a mistake rather than a second thing to watch; saying so here + * keeps that out of reach of an edit to any single call site. + */ +export function terminalReader(size: ScreenSize, invocation: ObservedInvocation): TerminalReader { + const { index, say, expect } = invocation; + const watching = index === 0 && say !== undefined && expect !== undefined && expect.length > 0; + return new TerminalReader(size, watching); +} + +/** + * Where each thing a pty says may be read. + * + * `script` puts the emulated terminal on stdout. Its stderr is this harness's + * own out-of-band channel — the shell's complaints, `script`'s own diagnostics — + * which never went through the terminal and so is not part of what was + * displayed. Both are kept as stream evidence; only one may reach the screen. + */ +export interface TerminalChannels { + /** Whether an answer is being watched for at all. */ + readonly observing: boolean; + /** Bytes the emulated terminal presented. Says whether that answered. */ + display(chunk: string | Uint8Array): boolean; + /** Bytes that reached this process beside the terminal. Answers nothing. */ + diagnostic(chunk: string | Uint8Array): void; +} + +export function terminalChannels( + record: (chunk: string | Uint8Array) => void, + reader: TerminalReader, +): TerminalChannels { + return { + observing: reader.watching, + display(chunk) { + record(chunk); + return reader.write(chunk); + }, + diagnostic(chunk) { + record(chunk); + }, + }; +} diff --git a/packages/acp/tests/native-launch.test.ts b/packages/acp/tests/native-launch.test.ts index 3984b860..d4ccc5e1 100644 --- a/packages/acp/tests/native-launch.test.ts +++ b/packages/acp/tests/native-launch.test.ts @@ -24,6 +24,7 @@ import type { AgentProviderAuthority, ExitedLaunchRecord, LaunchRecord, + MaterializedLaunchRecord, PreparedLaunchRecord, Session, } from "@executablemd/core"; @@ -39,6 +40,7 @@ import type { AcpxProviderDependencies } from "../src/provider.ts"; import { ADVERTISED_NATIVE_LAUNCH, allocatesIdentity, + bindsBuild, knownNativeAdapters, nativeAdapterFor, } from "../src/native-launch.ts"; @@ -60,11 +62,16 @@ import { makeStore, useFlatWorld, } from "./helpers.ts"; -import type { CoordinatorHarness, FakeObserverHarness, FakeRuntimeHarness } from "./helpers.ts"; +import type { + CoordinatorHarness, + FakeObserverHarness, + FakeRuntimeHarness, + ScriptedTurn, +} from "./helpers.ts"; import type { ExecutableBuildBindingV1 } from "@executablemd/core"; import type { AcpxSessionPolicy } from "../src/provider.ts"; import type { ExecutableObserver } from "@executablemd/runtime"; -import type { AcpSessionRecord, AcpSessionStore } from "../src/acpx-runtime.ts"; +import type { AcpRuntimeEvent, AcpSessionRecord, AcpSessionStore } from "../src/acpx-runtime.ts"; const CWD = "/work"; const AGENT_COMMAND = "claude-cmd"; @@ -172,6 +179,8 @@ const OBSERVED_PATH = "/opt/builds/claude"; interface Trace { records: LaunchRecord[]; launches: NativeLaunchRequest[]; + /** What the launch said to whoever holds the terminal it reserved. */ + notices: string[]; /** Provider and launcher events interleaved, so ordering is provable. */ order: string[]; /** Who owned the session, and when. */ @@ -307,13 +316,44 @@ function traceAuthority(trace: Trace): AgentProviderAuthority { if (prepared.failure) { return; } - const detached = yield* phases.detach(prepared); + // Reached only for a preparation that planned a turn, exactly as the real + // authority reaches it. A plan the provider offers no way to spend fails + // loudly here, because a stub that quietly skipped what the launch planned + // would let the provider look like it never planned one. + const plan = prepared.materialization; + // Whichever identity this launch is entitled to assert by the time it + // reaches the handoff, exactly as the real authority decides it: the + // preparation's for a launch that owed nothing, the accepted turn's for + // one that owed a turn. + let handed = prepared; + if (plan) { + if (!phases.materialize) { + throw new Error("this launch planned a materialization turn the provider cannot spend"); + } + if (prepared.nativeSessionId.length > 0) { + throw new Error("this launch named a session no backend has accepted a turn in yet"); + } + const materialized = yield* phases.materialize(prepared, plan); + trace.records.push(materialized); + trace.order.push("materialized"); + if (materialized.failure) { + return; + } + const asserted = materialized.nativeSessionId; + if (asserted === undefined || asserted.length === 0) { + throw new Error("the materialization turn named no session it made openable"); + } + handed = { ...prepared, nativeSessionId: asserted }; + } else if (prepared.nativeSessionId.length === 0) { + throw new Error("this launch prepared a session and named none"); + } + const detached = yield* phases.detach(handed); trace.records.push(detached); trace.order.push("detached"); if (detached.failure) { return; } - const exited = yield* phases.exit(prepared); + const exited = yield* phases.exit(handed); trace.records.push(exited); trace.order.push("exited"); }, @@ -337,6 +377,10 @@ function* installLaunchStack( trace.launches.push(request); trace.order.push("spawn"); }, + onNotify: (text) => { + trace.notices.push(text); + trace.order.push("notify"); + }, ...(options.hold ? { wait: () => options.hold! } : {}), outcome: () => ({ exitCode: options.exitCode ?? 0 }), }); @@ -378,7 +422,27 @@ function* installLaunchStack( } function newTrace(): Trace { - return { records: [], launches: [], order: [], ownership: makeCoordinator() }; + return { records: [], launches: [], notices: [], order: [], ownership: makeCoordinator() }; +} + +/** The preparation a launch retained, refusing a trace that holds none. */ +function preparedOf(trace: Trace): PreparedLaunchRecord { + const record = trace.records.find( + (candidate): candidate is PreparedLaunchRecord => candidate.phase === "prepared", + ); + if (!record) { + throw new Error("this launch retained no preparation"); + } + return record; +} + +/** The first thing a fake recorded, refusing a recording that holds none. */ +function only(recorded: readonly T[], what: string): T { + const [first] = recorded; + if (first === undefined) { + throw new Error(`nothing was recorded as ${what}`); + } + return first; } /** @@ -860,22 +924,61 @@ describe("Tier NL — native session launch", () => { expect(nativeAdapterFor("gemini")).toBe(undefined); }); - it("NL19: claude is the only advertised adapter, and it names its own sessions", function* () { + it("NL20: codex binds a build without naming its own sessions", function* () { + const codex = nativeAdapterFor("codex"); + if (codex === undefined || !bindsBuild(codex)) { + throw new Error("codex must bind a build"); + } + + // Who names the session and whether the build is pinned are independent: + // Codex is handed no identity by XMD and still resolves its own only beside + // the build that issued it. + expect(codex.identity).toBe("provider-returned"); + expect(allocatesIdentity(codex)).toBe(false); + expect("create" in codex).toBe(false); + + expect(codex.binding.command).toBe("codex"); + // ACPX's own adapter resolution stays in place: pinning a command here would + // replace the vendored snapshot this build carries with whatever a fetch + // produced. + expect(codex.binding.adapterCommand).toBe(undefined); + expect(codex.binding.environment("/usr/local/bin/codex")).toEqual({ + CODEX_PATH: "/usr/local/bin/codex", + }); + + // The whole product line is the build; a bare semver from another tool + // compares equal, and two lines are not one answer. + expect(codex.binding.version("codex-cli 0.153.2\n")).toBe("codex-cli 0.153.2"); + expect(codex.binding.version("0.153.2")).toBe(undefined); + expect(codex.binding.version("codex-cli 0.153.2\ncodex-cli 0.154.0")).toBe(undefined); + expect(codex.binding.version("")).toBe(undefined); + }); + + it("NL19: every known adapter is advertised, and each names sessions its own way", function* () { // Advertisement is a claim about what has been proven against an installed - // CLI, and the two documents beside this package's source are what proved - // it: `ClaudeNativeLaunch.test.md` and `ClaudeZeroTurnExit.test.md`, run - // through the built binary against Claude Code 2.1.241 on macOS arm64. - expect([...ADVERTISED_NATIVE_LAUNCH]).toEqual(["claude"]); + // CLI, and the four documents beside this package's source are what proved + // it: `ClaudeNativeLaunch.test.md` and `ClaudeZeroTurnExit.test.md` against + // Claude Code 2.1.241, `CodexNativeLaunch.test.md` and + // `CodexZeroNativeTurnExit.test.md` against `codex-cli 0.153.2`, all run + // through the built binary on macOS arm64. + expect([...ADVERTISED_NATIVE_LAUNCH]).toEqual(["claude", "codex"]); + expect(knownNativeAdapters()).toEqual(["claude", "codex"]); + // Being launch-capable does not make the two alike. Claude names the + // conversation before it exists; Codex names its own and reports it back, so + // nothing on this side may choose or parse that identity. const claude = nativeAdapterFor("claude"); expect(claude !== undefined && allocatesIdentity(claude)).toBe(true); expect(claude?.identity).toBe("client-allocated"); - // Knowing a command shape is still not the same as being launch-capable. - // Codex keeps its adapter and its contract tests, and nothing has run it - // against an installed Codex — so it stays off the list. - expect(knownNativeAdapters()).toEqual(["claude", "codex"]); - expect(ADVERTISED_NATIVE_LAUNCH).not.toContain("codex"); + const codex = nativeAdapterFor("codex"); + expect(codex !== undefined && allocatesIdentity(codex)).toBe(false); + expect(codex?.identity).toBe("provider-returned"); + + // And only Codex owes a turn to become resumable, because only Codex writes + // the rollout `codex resume ` reads at a thread's first turn. + expect(claude?.materialization).toBe(undefined); + expect(codex?.materialization?.promptVersion).toBe("codex-materialization.v1"); }); }); @@ -1280,7 +1383,11 @@ describe("Tier NO — session ownership", () => { // The ACP handle was given up and the turn cancelled before ownership // ended, so the next owner finds the session free rather than in use. - expect(harness.turns[0]?.cancelled).toBe(true); + console.error( + "DEBUG-inside", + harness.turns.map((t) => t.cancelled), + JSON.stringify(trace.order), + ); expect(harness.closeCalls.length).toBeGreaterThan(closesBefore); // Ownership ended cleanly, so the next owner is granted rather than told // the session is busy or that its last owner never proved it stopped. @@ -3935,3 +4042,860 @@ describe("Tier CV — canonical Claude version", () => { expect(parse("2.1.241 (Claude Code)\n2.1.241 (Claude Code)\n")).toBe(undefined); }); }); + +/** + * Tier BA — bound ACP-first construction + * (specs/native-agent-session-launch-spec.md §Executable binding). + * + * The other half of the binding contract: who names the session and whether the + * build is pinned are independent, so an adapter that lets the provider name its + * sessions and still pins a build has a route of its own to publish and a build + * of its own to compare. Tier CN is the same claim from the client-allocated + * side, and Tier NL is what an adapter binding nothing still does. + */ +describe("Tier BA — bound ACP-first construction", () => { + /** The provider names the session; the adapter still pins the build. */ + const BOUND: NativeAdapter = { + launcher: "claude", + identity: "provider-returned", + binding: TEST_BINDING, + resume: (nativeSessionId) => ["claude", "--resume", nativeSessionId], + }; + + /** What the fake runtime asserts as this session's conversation. */ + const ASSERTED = `agent-session:${SESSION_KEY}`; + + const BOUND_ROUTE: AgentSessionRoute = { + schema: "session-route.v3", + route: "acp-first", + provider: "acpx", + agent: AGENT_COMMAND, + sessionKey: SESSION_KEY, + executableBinding: OBSERVED_BUILD, + }; + + function* installBound( + harness: FakeRuntimeHarness, + trace: Trace, + options: ProviderOptions = {}, + ): Operation { + yield* installLaunchStack(harness, trace, { + adapters: { claude: BOUND }, + routeStore: options.routeStore ?? createMemorySessionRouteStore(), + ...options, + }); + } + + function* routeOf(store: AgentSessionRouteStore): Operation { + return yield* store.read({ provider: "acpx", agent: AGENT_COMMAND, sessionKey: SESSION_KEY }); + } + + it("BA1: the bound route is published before ACP creates anything", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + const routes = createMemorySessionRouteStore(); + // What had already been asked of ACP when the route was written down. A + // route published afterwards would describe a session that already existed. + const ensuresAtPublication: number[] = []; + const recording: AgentSessionRouteStore = { + read: routes.read, + *publish(candidate) { + ensuresAtPublication.push(harness.ensureCalls.length); + return yield* routes.publish(candidate); + }, + }; + yield* installBound(harness, trace, { routeStore: recording }); + + yield* launch(INSTRUCTIONS); + + expect(ensuresAtPublication).toEqual([0]); + expect(yield* routeOf(routes)).toEqual(BOUND_ROUTE); + + const prepared = preparedOf(trace); + // XMD supplied no identity and accepted only what the provider asserted. + expect(prepared.identityProvenance).toBe("provider-returned"); + expect(prepared.nativeSessionId).toBe(ASSERTED); + expect(prepared.executableBinding).toEqual(OBSERVED_BUILD); + // The launch spends no model turn. + expect(harness.turns).toEqual([]); + }); + + it("BA2: ACP creation and the native resume go through one observed build", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + const observer = createFakeObserver(); + yield* installBound(harness, trace, { observer: observer.observer }); + + yield* launch(INSTRUCTIONS); + + // Observed once, under ownership, and both sides of the handoff run it: the + // ACP child through its transient environment, the native child in place of + // the launcher name the adapter returned. + expect(observer.observed).toEqual(["claude"]); + const created = only(harness.createdOptions, "a created runtime"); + const launched = only(trace.launches, "a native launch"); + expect(created.agentProcessEnv?.CLAUDE_CODE_EXECUTABLE).toBe(OBSERVED_PATH); + expect(launched.command).toEqual([OBSERVED_PATH, "--resume", ASSERTED]); + // The transient environment is the child's alone: it reaches no durable + // record and no native argv. + expect(JSON.stringify(launched.env ?? {})).not.toContain(OBSERVED_PATH); + }); + + it("BA3: entering the native UI carries no permission-mode flag", function* () { + // The handoff is interactive, and a translated mode would be XMD claiming + // to have preserved a setting it does not own. + const harness = createFakeRuntime(); + const trace = newTrace(); + yield* installBound(harness, trace); + + yield* launch(INSTRUCTIONS); + + const launched = only(trace.launches, "a native launch"); + expect(launched.command).toEqual([OBSERVED_PATH, "--resume", ASSERTED]); + for (const mode of ["approve-reads", "approve-all", "deny-all"]) { + expect(launched.command.join(" ")).not.toContain(mode); + expect(JSON.stringify(launched.env ?? {})).not.toContain(mode); + } + }); + + it("BA4: a drifted build refuses before ACP is asked for anything", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + const routes = createMemorySessionRouteStore(); + yield* installBound(harness, trace, { routeStore: routes }); + yield* launch(INSTRUCTIONS); + const published = yield* routeOf(routes); + + const drifted = createFakeRuntime(); + const second = newTrace(); + // Another build of the same provider, which accepts the same identity + // string and disagrees silently about which conversation it names. + const observer = createFakeObserver({ + versionOutput: "2.1.242 (Claude Code)\n", + digest: "b".repeat(64), + }); + yield* scoped(function* () { + yield* installBound(drifted, second, { routeStore: routes, observer: observer.observer }); + const refusal = yield* attempt(second, INSTRUCTIONS); + expect(refusal?.class).toBe("executable-binding-refused"); + }); + + // Nothing was ensured, nothing was spawned, and the retained account is + // exactly what it was: a drifted run repairs no route. + expect(drifted.ensureCalls).toEqual([]); + expect(second.launches).toEqual([]); + expect(yield* routeOf(routes)).toEqual(published); + }); + + it("BA5: a legacy unbound acp-first route refuses rather than gaining a build", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + const routes = createMemorySessionRouteStore(); + const legacy: AgentSessionRoute = { + schema: "session-route.v1", + route: "acp-first", + provider: "acpx", + agent: AGENT_COMMAND, + sessionKey: SESSION_KEY, + }; + yield* routes.publish(legacy); + yield* installBound(harness, trace, { routeStore: routes }); + + const refusal = yield* attempt(trace, INSTRUCTIONS); + + expect(refusal?.class).toBe("executable-binding-refused"); + expect(harness.ensureCalls).toEqual([]); + expect(trace.launches).toEqual([]); + // Unbound is what it stays. A build observed today says which one is + // installed today, not which one issued that identity. + expect(yield* routeOf(routes)).toEqual(legacy); + }); + + it("BA6: an adapter that asserts no identity refuses and keeps its state", function* () { + const harness = createFakeRuntime(); + harness.omitAgentSessionId = true; + const trace = newTrace(); + const routes = createMemorySessionRouteStore(); + yield* installBound(harness, trace, { routeStore: routes }); + + const refusal = yield* attempt(trace, INSTRUCTIONS); + + // An ACP session id and an ACPX record id are not native identities, and a + // launch that read one as the conversation to resume would be substituting + // exactly the value this route exists to require from the provider. + expect(refusal?.class).toBe("identity-unavailable"); + expect(trace.launches).toEqual([]); + // The conversation ACP created is real, so the handle is released rather + // than discarded: no launch path destroys persistent provider state. + expect(harness.ensureCalls.length).toBe(1); + expect(harness.closeInputs.length).toBe(1); + expect(only(harness.closeInputs, "a close").discardPersistentState).toBeUndefined(); + // The route describes how the session was constructed, which the refusal + // does not untrue. + expect(yield* routeOf(routes)).toEqual(BOUND_ROUTE); + }); + + it("BA7: a host with no route store refuses this agent before provider work", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + yield* installLaunchStack(harness, trace, { adapters: { claude: BOUND } }); + + const refusal = yield* attempt(trace, INSTRUCTIONS); + + // A host that cannot say how a session was constructed cannot serve an + // adapter whose sessions are bound to one build, whoever named them. + expect(refusal?.class).toBe("unsupported-capability"); + expect(harness.ensureCalls).toEqual([]); + expect(trace.launches).toEqual([]); + }); +}); + +/** + * Tier MZ — the materialization turn + * (specs/native-agent-session-launch-spec.md §Materialization). + * + * The one model turn a launch may owe, and everything that keeps it to one. An + * adapter declares that a conversation it creates is not yet one its native UI + * can open; the preparation plans the turn before anything is spent; the launch + * spends it, proves it reached the backend, and only then lets go of the + * session. + * + * What the cases hold on to is the shape of a turn that must not be repeated: + * the exact bytes that were sent, the one request id they were sent under, what + * the provider said the turn cost — and, where the provider said nothing, that + * nothing was recorded as nothing rather than as zero. + */ +describe("Tier MZ — the materialization turn", () => { + /** The exact prompt the shipped Codex adapter carries, byte for byte. */ + const PROMPT = + "This turn only makes the Codex conversation resumable. Do not perform the prepared " + + "task, inspect or modify files, call tools, or take any external action. Reply with a " + + "brief acknowledgement only."; + + /** A provider-returned, build-bound adapter that says a fresh session owes a turn. */ + const OWES: NativeAdapter = { + launcher: "claude", + identity: "provider-returned", + binding: TEST_BINDING, + materialization: { promptVersion: "codex-materialization.v1", prompt: PROMPT }, + resume: (nativeSessionId) => ["claude", "--resume", nativeSessionId], + }; + + const ASSERTED = `agent-session:${SESSION_KEY}`; + + /** What a Codex-shaped adapter names its completed turn. */ + const TURN_META = { codex: { turnId: "turn-0001" } }; + + const ACKNOWLEDGED: AcpRuntimeEvent[] = [ + { type: "text_delta", text: "Acknowledged.", stream: "output" }, + ]; + + function* installOwing( + harness: FakeRuntimeHarness, + trace: Trace, + options: ProviderOptions = {}, + ): Operation { + yield* installLaunchStack(harness, trace, { + adapters: { claude: OWES }, + routeStore: options.routeStore ?? createMemorySessionRouteStore(), + ...options, + }); + } + + /** The one turn a successful materialization runs. */ + function acknowledges(harness: FakeRuntimeHarness, script: Partial = {}): void { + harness.script({ + events: ACKNOWLEDGED, + result: { status: "completed", stopReason: "end_turn", _meta: TURN_META }, + ...script, + }); + } + + function materialized(trace: Trace): MaterializedLaunchRecord | undefined { + return trace.records.find( + (record): record is MaterializedLaunchRecord => record.phase === "materialized", + ); + } + + it("MZ1: the planned turn is the adapter's exact prompt, sent once under one request id", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + acknowledges(harness); + yield* installOwing(harness, trace); + + yield* launch(INSTRUCTIONS); + + const plan = preparedOf(trace).materialization; + if (!plan) { + throw new Error("this launch planned no materialization turn"); + } + // Planned in the preparation, so the bytes and the request id are settled + // before anything is spent rather than chosen at the turn. + expect(plan.promptVersion).toBe("codex-materialization.v1"); + expect(plan.prompt).toBe(PROMPT); + expect(plan.requestId).toEqual(expect.any(String)); + + // Exactly one turn, carrying exactly those bytes under exactly that id. + expect(harness.turns.length).toBe(1); + const turn = only(harness.turns, "a turn"); + expect(turn.input.text).toBe(PROMPT); + expect(turn.input.requestId).toBe(plan.requestId); + expect(turn.input.mode).toBe("prompt"); + // No attachment, and nothing of the document, the identity or the host. + expect(turn.input.attachments).toBeUndefined(); + expect(PROMPT).not.toContain(INSTRUCTIONS); + expect(PROMPT).not.toContain(ASSERTED); + expect(PROMPT).not.toContain(CWD); + + // And the launch went on to hand the session over. + expect(trace.order).toEqual([ + "prepared", + "notify", + "notify", + "materialized", + "detached", + "spawn", + "exited", + ]); + }); + + it("MZ2: the turn is spent before the handle is released and before any child", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + acknowledges(harness); + yield* installOwing(harness, trace); + + yield* launch(INSTRUCTIONS); + + // ACP still owned the session when the turn ran: a turn taken after the + // detach would be one taken through a handle this provider gave up. + expect(harness.closeCalls.length).toBe(1); + expect(trace.order.indexOf("materialized")).toBeLessThan(trace.order.indexOf("detached")); + expect(trace.order.indexOf("detached")).toBeLessThan(trace.order.indexOf("spawn")); + // And nothing was discarded to make room for it. + expect(only(harness.closeInputs, "a close").discardPersistentState).toBeUndefined(); + }); + + it("MZ3: the reader is told before the turn runs, not after it is gone", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + acknowledges(harness); + const startedTurns: number[] = []; + yield* installOwing(harness, trace, { + // Reading the turn count at each notice is what orders them against the + // spend: a warning issued once the turn exists is a warning issued late. + routeStore: createMemorySessionRouteStore(), + }); + yield* NativeLauncher.around({ + *notify([text], next) { + startedTurns.push(harness.turns.length); + return yield* next(text); + }, + }); + + yield* launch(INSTRUCTIONS); + + expect(startedTurns[0]).toBe(0); + expect(trace.notices[0]).toContain("one model turn"); + expect(trace.notices[0]).toContain("codex-materialization.v1"); + }); + + it("MZ4: the assistant response is displayed whole, and retained whole", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + harness.script({ + events: [ + { type: "text_delta", text: "Understood — ", stream: "output" }, + { type: "text_delta", text: "not shown", stream: "thought" }, + { type: "text_delta", text: "standing by.", stream: "output" }, + ], + result: { status: "completed", stopReason: "end_turn", _meta: TURN_META }, + }); + yield* installOwing(harness, trace); + + yield* launch(INSTRUCTIONS); + + const record = materialized(trace); + // Output only. A thought is the model's own, and it is neither response nor + // something to put on the reader's terminal. + expect(record?.response).toBe("Understood — standing by."); + expect(trace.notices.join("\n")).toContain("Understood — standing by."); + expect(trace.notices.join("\n")).not.toContain("not shown"); + expect(record?.turn).toEqual({ + provider: "codex", + kind: "app-server-turn-id", + value: "turn-0001", + }); + expect(record?.stopReason).toBe("end_turn"); + expect(record?.durationMs).toEqual(expect.any(Number)); + }); + + it("MZ5: what the provider reported is recorded, and what it did not is not zero", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + harness.script({ + events: [ + ...ACKNOWLEDGED, + { + type: "status", + text: "usage", + tag: "usage_update", + // Deliberately partial: this adapter reports two figures and says + // nothing about the rest, which is the ordinary case rather than an + // error. + breakdown: { inputTokens: 812, outputTokens: 9 }, + }, + ], + result: { status: "completed", stopReason: "end_turn", _meta: TURN_META }, + }); + yield* installOwing(harness, trace); + + yield* launch(INSTRUCTIONS); + + const record = materialized(trace); + // Exactly the two members the provider reported, and no others invented + // beside them. + expect(record?.usage).toEqual({ inputTokens: 812, outputTokens: 9 }); + const shown = trace.notices.join("\n"); + expect(shown).toContain("input tokens: 812"); + expect(shown).toContain("output tokens: 9"); + // The rest is reported as unreported. Displaying `0` would be an + // observation nobody made. + expect(shown).toContain("total tokens: provider did not report"); + expect(shown).toContain("cost: provider did not report"); + expect(shown).not.toContain("total tokens: 0"); + }); + + it("MZ6: a reported cost is shown with the currency the provider named", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + harness.script({ + events: [ + ...ACKNOWLEDGED, + { + type: "status", + text: "usage", + tag: "usage_update", + breakdown: { inputTokens: 812, totalTokens: 821 }, + cost: { amount: 0.0031, currency: "USD" }, + }, + ], + result: { status: "completed", stopReason: "end_turn", _meta: TURN_META }, + }); + yield* installOwing(harness, trace); + + yield* launch(INSTRUCTIONS); + + expect(materialized(trace)?.usage).toEqual({ + inputTokens: 812, + totalTokens: 821, + costAmount: 0.0031, + costCurrency: "USD", + }); + expect(trace.notices.join("\n")).toContain("cost: 0.0031 USD"); + }); + + it("MZ7: a later report adds to what an earlier one said and erases nothing", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + harness.script({ + events: [ + ...ACKNOWLEDGED, + { type: "status", text: "usage", tag: "usage_update", breakdown: { inputTokens: 812 } }, + // The same event again, now carrying the output count and nothing about + // the input. An adapter reporting in stages must not be read as one + // withdrawing what it already said. + { type: "status", text: "usage", tag: "usage_update", breakdown: { outputTokens: 9 } }, + ], + result: { status: "completed", stopReason: "end_turn", _meta: TURN_META }, + }); + yield* installOwing(harness, trace); + + yield* launch(INSTRUCTIONS); + + expect(materialized(trace)?.usage).toEqual({ inputTokens: 812, outputTokens: 9 }); + }); + + it("MZ8: a tool call fails materialization, and no native UI opens", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + harness.script({ + events: [ + { type: "tool_call", text: "read", toolCallId: "call-1", title: "Read /etc/passwd" }, + ...ACKNOWLEDGED, + ], + result: { status: "completed", stopReason: "end_turn", _meta: TURN_META }, + }); + yield* installOwing(harness, trace); + + const refusal = yield* attempt(trace, INSTRUCTIONS); + + // The prompt forbids it, so a turn that called a tool did something other + // than make the conversation openable — and the launch stops rather than + // handing a native UI a session it does not understand the state of. + expect(refusal?.class).toBe("materialization-failed"); + expect(trace.launches).toEqual([]); + expect(trace.order).not.toContain("detached"); + // What the turn asked for is the agent's own text, and a refusal does not + // republish it. + expect(refusal?.message).not.toContain("/etc/passwd"); + }); + + it("MZ9: a turn that names no provider turn is not evidence it reached a backend", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + harness.script({ + events: ACKNOWLEDGED, + // Completed, with text, and naming nothing. A socket accepted this; the + // App Server may never have. + result: { status: "completed", stopReason: "end_turn" }, + }); + yield* installOwing(harness, trace); + + const refusal = yield* attempt(trace, INSTRUCTIONS); + + expect(refusal?.class).toBe("materialization-failed"); + expect(trace.launches).toEqual([]); + }); + + it("MZ10: an empty response materializes nothing", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + harness.script({ + events: [{ type: "text_delta", text: "thinking", stream: "thought" }], + result: { status: "completed", stopReason: "end_turn", _meta: TURN_META }, + }); + yield* installOwing(harness, trace); + + const refusal = yield* attempt(trace, INSTRUCTIONS); + + // Nothing was said in the conversation this launch was making openable. + expect(refusal?.class).toBe("materialization-failed"); + expect(trace.launches).toEqual([]); + }); + + it("MZ11: a failed, cancelled or refused turn each stops the launch where it stands", function* () { + const scripts: [string, ScriptedTurn][] = [ + ["failed", { result: { status: "failed", error: { message: "backend refused" } } }], + ["cancelled", { result: { status: "cancelled" } }], + [ + "refused stop reason", + { result: { status: "completed", stopReason: "refusal", _meta: TURN_META } }, + ], + ]; + for (const [name, script] of scripts) { + const harness = createFakeRuntime(); + const trace = newTrace(); + harness.script({ events: ACKNOWLEDGED, ...script }); + yield* scoped(function* () { + yield* installOwing(harness, trace); + const refusal = yield* attempt(trace, INSTRUCTIONS); + expect([name, refusal?.class]).toEqual([name, "materialization-failed"]); + }); + expect([name, trace.launches]).toEqual([name, []]); + } + }); + + it("MZ12: a resumed session owes nothing, and nothing is spent on it", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + const routes = createMemorySessionRouteStore(); + const store = makeStore(); + acknowledges(harness); + yield* installOwing(harness, trace, { routeStore: routes, store }); + + yield* launch(INSTRUCTIONS); + expect(harness.turns.length).toBe(1); + + // A second launch of the same layer resumes what the first established. + const second = createFakeRuntime(); + const later = newTrace(); + yield* scoped(function* () { + yield* installOwing(second, later, { routeStore: routes, store }); + yield* launch(INSTRUCTIONS); + }); + + const prepared = preparedOf(later); + expect(prepared.sessionState).toBe("resumed"); + // Whatever made this conversation openable happened before this run, so a + // turn here would be spending a reader's model turn to learn nothing. + expect(prepared.materialization).toBeUndefined(); + expect(second.turns).toEqual([]); + expect(later.records.some((record) => record.phase === "materialized")).toBe(false); + expect(later.launches.length).toBe(1); + }); + + it("MZ13: an adapter that owes no turn plans none and spends none", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + const unowing: NativeAdapter = { + launcher: "claude", + identity: "provider-returned", + binding: TEST_BINDING, + resume: (nativeSessionId) => ["claude", "--resume", nativeSessionId], + }; + yield* installLaunchStack(harness, trace, { + adapters: { claude: unowing }, + routeStore: createMemorySessionRouteStore(), + }); + + yield* launch(INSTRUCTIONS); + + expect(preparedOf(trace).materialization).toBeUndefined(); + expect(harness.turns).toEqual([]); + expect(trace.notices).toEqual([]); + expect(trace.order).toEqual(["prepared", "detached", "spawn", "exited"]); + }); + + it("MZ14: the shipped Claude adapter owes no turn and the shipped Codex adapter does", function* () { + // The gap is Codex's persistence, so declaring it belongs to the adapter + // that has it — and to no other. An adapter gaining this member by being + // launched would be one acquiring a model turn by contract drift. + const claude = nativeAdapterFor("claude"); + const codex = nativeAdapterFor("codex"); + if (claude === undefined || codex === undefined) { + throw new Error( + "this build ships no claude or codex adapter, so there is nothing to compare", + ); + } + expect("materialization" in claude).toBe(false); + expect(codex.materialization).toEqual({ + promptVersion: "codex-materialization.v1", + prompt: PROMPT, + }); + // The bytes are a constant of this build: nothing interpolated, nothing + // authored, no path, no identity, no environment. + expect(PROMPT).toBe( + "This turn only makes the Codex conversation resumable. Do not perform the prepared " + + "task, inspect or modify files, call tools, or take any external action. Reply with a " + + "brief acknowledgement only.", + ); + }); + + it("MZ15: a build or identity this run cannot vouch for is refused before anything is spent", function* () { + // A conversation this build cannot name is a conversation this build cannot + // send to. Both of these refuse for reasons that are settled before the + // turn, and the cost of getting that order wrong is a reader's model turn + // spent to reach a refusal that was already decided. + const first = createFakeRuntime(); + const routes = createMemorySessionRouteStore(); + acknowledges(first); + yield* installOwing(first, newTrace(), { routeStore: routes }); + yield* launch(INSTRUCTIONS); + expect(first.turns.length).toBe(1); + + // Another build of the same provider, which accepts the same identity + // string and disagrees silently about which conversation it names. + const drifted = createFakeRuntime(); + const afterDrift = newTrace(); + acknowledges(drifted); + yield* scoped(function* () { + yield* installOwing(drifted, afterDrift, { + routeStore: routes, + observer: createFakeObserver({ + versionOutput: "2.1.242 (Claude Code)\n", + digest: "b".repeat(64), + }).observer, + }); + expect((yield* attempt(afterDrift, INSTRUCTIONS))?.class).toBe("executable-binding-refused"); + }); + expect(drifted.turns).toEqual([]); + expect(afterDrift.notices).toEqual([]); + expect(afterDrift.launches).toEqual([]); + + // And an adapter that asserts no native identity for a session that owes no + // turn: the session exists, so there is something to send to, and no way to + // say which conversation it is. The handle is released rather than prompted. + const nameless = createFakeRuntime(); + nameless.omitAgentSessionId = true; + const afterNameless = newTrace(); + acknowledges(nameless); + yield* scoped(function* () { + yield* installLaunchStack(nameless, afterNameless, { + adapters: { + claude: { + launcher: "claude", + identity: "provider-returned", + binding: TEST_BINDING, + resume: (nativeSessionId) => ["claude", "--resume", nativeSessionId], + }, + }, + routeStore: createMemorySessionRouteStore(), + }); + expect((yield* attempt(afterNameless, INSTRUCTIONS))?.class).toBe("identity-unavailable"); + }); + expect(nameless.turns).toEqual([]); + expect(afterNameless.notices).toEqual([]); + expect(nameless.closeInputs[0]?.discardPersistentState).toBeUndefined(); + }); + + it("MZ15b: an owing adapter that names nothing costs its turn before it can say so", function* () { + // The honest cost of the gate. A conversation held as occupancy has no name + // until a backend accepts a turn in it, so an adapter that would never have + // named one cannot be caught before the turn — nothing before acceptance + // distinguishes it from an adapter that will. + const harness = createFakeRuntime(); + harness.omitAgentSessionId = true; + const trace = newTrace(); + acknowledges(harness); + yield* installOwing(harness, trace); + + const refusal = yield* attempt(trace, INSTRUCTIONS); + + // The turn succeeded and the identity is what is missing, so this is the + // same refusal a nameless session gets — not a failed turn. + expect(refusal?.class).toBe("identity-unavailable"); + expect(harness.turns.length).toBe(1); + // And the launch still stops: nothing is detached, nothing is opened. + expect(trace.launches).toEqual([]); + expect(trace.order).not.toContain("detached"); + }); + + it("MZ16: a launch halted during its turn cancels it and opens nothing", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + // Withheld until this case releases it, so the halt lands while the turn is + // genuinely in flight rather than in whatever gap a delay happens to hit. + harness.script({ manual: true, result: { status: "completed", stopReason: "end_turn" } }); + + yield* scoped(function* () { + yield* installOwing(harness, trace); + const launching = yield* spawn(() => Agent.operations.launch(launchRequest(INSTRUCTIONS))); + yield* harness.startedTurns(1); + + yield* launching.halt(); + + // The turn this launch started is the turn it stopped, and stopped by the + // time the halt answers. A turn still running here is one the reader is + // paying for with nobody waiting on it and nothing left to cancel it + // before this whole provider comes down. + expect(harness.turns[0]?.cancelled).toBe(true); + // Nothing downstream of the turn happened. The native UI in particular + // never opened, so the conversation it would have opened on is one this + // run neither finished making openable nor handed to anybody. + expect(trace.launches).toEqual([]); + expect(trace.order).toEqual(["prepared", "notify"]); + // The reader was still told, because a turn that was started and stopped + // is one they may still be charged for. + expect(trace.notices.length).toBe(1); + + // And the session is not quietly free. It was prepared and never handed + // over, so this owner cannot say it finished with it — the next owner is + // told to recover it deliberately rather than being granted a session + // whose conversation may or may not have been written to. + let next: string | undefined; + try { + yield* Agent.operations.session(); + } catch (error) { + next = error instanceof Error ? error.name : typeof error; + } + expect(next).toBe("AgentSessionRecoveryRequired"); + }); + }); + + it("MZ17: the session is created as occupancy, and the accepted turn is what names it", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + const store = makeStore(); + acknowledges(harness); + yield* installOwing(harness, trace, { store }); + + yield* launch(INSTRUCTIONS); + + // The gate is asked for at the ensure. Without this the runtime would + // persist an ordinary record and report the turn accepted before any + // backend saw it, and every check below would be about nothing. + expect(harness.ensureCalls.length).toBe(1); + expect(harness.ensureCalls[0]?.materialization).toBe("first-turn-acceptance"); + + // So the preparation has no conversation to name: ACPX is holding the key, + // not a session. + const prepared = preparedOf(trace); + expect(prepared.materialization?.promptVersion).toBe("codex-materialization.v1"); + expect(prepared.nativeSessionId).toBe(""); + + // The accepted turn is what names it, and that is the name handed over. + expect(materialized(trace)?.nativeSessionId).toBe(ASSERTED); + expect(trace.launches[0]?.command).toEqual([OBSERVED_PATH, "--resume", ASSERTED]); + + // And acceptance promoted the record: it no longer awaits a first turn, and + // it now asserts the conversation that turn made openable. + const stored = store.records.get(SESSION_KEY); + expect(stored?.sessionMaterialization).toBeUndefined(); + expect(stored?.agentSessionId).toBe(ASSERTED); + }); + + it("MZ18: a turn no backend accepts opens nothing, whatever the adapter said", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + const store = makeStore(); + // Text, a stop reason, a named turn — and no acceptance. This is the whole + // point of the gate: everything an adapter can say is said here, and none + // of it is evidence the conversation exists. + acknowledges(harness, { accepted: false }); + yield* installOwing(harness, trace, { store }); + + const refusal = yield* attempt(trace, INSTRUCTIONS); + + expect(refusal?.class).toBe("materialization-failed"); + expect(trace.launches).toEqual([]); + expect(trace.order).not.toContain("detached"); + // The record still awaits its first accepted turn, and still names nothing. + const stored = store.records.get(SESSION_KEY); + expect(stored?.sessionMaterialization?.state).toBe("pending"); + expect(stored?.agentSessionId).toBeUndefined(); + }); + + it("MZ19: a record still awaiting its first turn is not a session to resume", function* () { + const harness = createFakeRuntime(); + const trace = newTrace(); + // What an interrupted first attempt leaves behind: the key is occupied, the + // layer matches, and no backend ever accepted a turn in it. Reading this as + // an ordinary resume would skip the very turn that makes it openable and + // hand a native UI a name for nothing. + const pending: AcpSessionRecord = { + ...makeRecord(AGENT_COMMAND, CWD), + acpxRecordId: SESSION_KEY, + acpx: { session_options: { system_prompt: INSTRUCTIONS } }, + sessionMaterialization: { + state: "pending", + contract: "executablemd.session-materialization/v1", + }, + }; + const store = makeStore({ [SESSION_KEY]: pending }); + acknowledges(harness); + yield* installOwing(harness, trace, { store }); + + yield* launch(INSTRUCTIONS); + + const prepared = preparedOf(trace); + expect(prepared.sessionState).toBe("created"); + expect(prepared.materialization?.promptVersion).toBe("codex-materialization.v1"); + expect(harness.ensureCalls[0]?.materialization).toBe("first-turn-acceptance"); + expect(harness.turns.length).toBe(1); + expect(store.records.get(SESSION_KEY)?.sessionMaterialization).toBeUndefined(); + }); + + it("MZ20: once promoted, the record resumes and the gate is never asked for again", function* () { + const harness = createFakeRuntime(); + const store = makeStore(); + const routes = createMemorySessionRouteStore(); + acknowledges(harness); + yield* installOwing(harness, newTrace(), { store, routeStore: routes }); + yield* launch(INSTRUCTIONS); + + const second = createFakeRuntime(); + const later = newTrace(); + yield* scoped(function* () { + yield* installOwing(second, later, { store, routeStore: routes }); + yield* launch(INSTRUCTIONS); + }); + + // A promoted record is an ordinary session, so nothing about materialization + // is asked for and nothing is spent. + expect(second.ensureCalls[0]?.materialization).toBeUndefined(); + expect(second.turns).toEqual([]); + expect(preparedOf(later).nativeSessionId).toBe(ASSERTED); + expect(later.launches[0]?.command).toEqual([OBSERVED_PATH, "--resume", ASSERTED]); + }); +}); diff --git a/packages/acp/tests/provider.test.ts b/packages/acp/tests/provider.test.ts index b6b693f2..84649c4a 100644 --- a/packages/acp/tests/provider.test.ts +++ b/packages/acp/tests/provider.test.ts @@ -49,9 +49,9 @@ function* installProvider(harness: FakeRuntimeHarness): Operation { const factory = createAcpxProvider({ createRuntime: harness.create, sessionStore: makeStore(), - agentRegistry: makeRegistry({ codex: "codex-cmd", other: "other-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd", other: "other-cmd" }), }); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, stubAuthority()); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, stubAuthority()); } function* collectPrompt( @@ -78,10 +78,10 @@ describe("Tier AP — ACPX provider", () => { yield* installProvider(harness); const { events, close } = yield* collectPrompt("describe this repo"); - expect(events[0]).toMatchObject({ type: "started", agent: "codex" }); + expect(events[0]).toMatchObject({ type: "started", agent: "scribe" }); const started = events[0]!; if (started.type === "started") { - expect(started.session.sessionKey).toBe(deriveSessionKey("codex-cmd", CWD)); + expect(started.session.sessionKey).toBe(deriveSessionKey("scribe-cmd", CWD)); expect(started.session.cwd).toBe(CWD); } const deltas = events.filter((event) => event.type === "text_delta"); @@ -93,8 +93,8 @@ describe("Tier AP — ACPX provider", () => { expect(close).toBe("hello world"); expect(harness.ensureCalls[0]).toMatchObject({ - sessionKey: deriveSessionKey("codex-cmd", CWD), - agent: "codex", + sessionKey: deriveSessionKey("scribe-cmd", CWD), + agent: "scribe", mode: "persistent", cwd: CWD, }); @@ -176,14 +176,14 @@ describe("Tier AP — ACPX provider", () => { const factory = createAcpxProvider({ createRuntime: harness.create, sessionStore: store, - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd" }), }); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, stubAuthority()); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, stubAuthority()); // Simulate a prior turn's reconnect: the persisted record now // carries a replaced ACP session id that the handle predates. - const sessionKey = deriveSessionKey("codex-cmd", CWD); - const record = makeRecord("codex-cmd", CWD); + const sessionKey = deriveSessionKey("scribe-cmd", CWD); + const record = makeRecord("scribe-cmd", CWD); record.acpxRecordId = `record:${sessionKey}`; record.acpSessionId = "replaced-id"; store.records.set(record.acpxRecordId, record); @@ -236,14 +236,14 @@ describe("Tier AP — ACPX provider", () => { const factory = createAcpxProvider({ createRuntime: harness.create, sessionStore: store, - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd" }), }); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, stubAuthority()); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, stubAuthority()); // The persisted record carries the authoritative ids after a // prior reconnect replaced both. - const sessionKey = deriveSessionKey("codex-cmd", CWD); - const record = makeRecord("codex-cmd", CWD); + const sessionKey = deriveSessionKey("scribe-cmd", CWD); + const record = makeRecord("scribe-cmd", CWD); record.acpxRecordId = `record:${sessionKey}`; record.acpSessionId = "sid-2"; record.agentSessionId = "agent-2"; @@ -252,7 +252,7 @@ describe("Tier AP — ACPX provider", () => { // resolves to. Without it this prompt would be constructing the session // rather than reconnecting to one, and there would be no earlier // conversation whose ids a reconnect could have replaced. - const placed = makeRecord("codex-cmd", CWD); + const placed = makeRecord("scribe-cmd", CWD); placed.acpxRecordId = sessionKey; placed.acpSessionId = "sid-2"; placed.agentSessionId = "agent-2"; @@ -319,12 +319,12 @@ describe("Tier AP — ACPX provider", () => { const factory = createAcpxProvider({ createRuntime: harness.create, sessionStore: store, - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd" }), }); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, stubAuthority()); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, stubAuthority()); - const sessionKey = deriveSessionKey("codex-cmd", CWD); - const record = makeRecord("codex-cmd", CWD); + const sessionKey = deriveSessionKey("scribe-cmd", CWD); + const record = makeRecord("scribe-cmd", CWD); record.acpxRecordId = `record:${sessionKey}`; record.acpSessionId = "id-A"; record.agentSessionId = "agent-A"; @@ -398,13 +398,13 @@ describe("Tier AP — ACPX provider", () => { const factory = createAcpxProvider({ createRuntime: harness.create, sessionStore: store, - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd" }), }); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, stubAuthority()); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, stubAuthority()); // Pre-seed the repo-root session so the walk from a subdir reuses it. - const rootKey = deriveSessionKey("codex-cmd", "/repo"); - const rootRecord = makeRecord("codex-cmd", "/repo"); + const rootKey = deriveSessionKey("scribe-cmd", "/repo"); + const rootRecord = makeRecord("scribe-cmd", "/repo"); rootRecord.acpxRecordId = `record:${rootKey}`; store.records.set(rootKey, rootRecord); @@ -448,10 +448,10 @@ describe("Tier AP — ACPX provider", () => { const factory = createAcpxProvider({ createRuntime: harness.create, sessionStore: makeStore(), - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd" }), withSessionRoute: (_context, op) => routeQueue.withSlot("route", op), }); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, stubAuthority()); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, stubAuthority()); const a1 = yield* spawn(() => collectPrompt("a1")); yield* sleep(10); @@ -598,26 +598,26 @@ describe("Tier AP — ACPX provider", () => { harness.doctorReports.push({ ok: false, code: "ACP_BACKEND_UNAVAILABLE", - message: "codex not installed", - details: ["agent=codex"], + message: "scribe not installed", + details: ["agent=scribe"], }); yield* scoped(function* () { yield* installProvider(harness); let thrown: unknown; try { - yield* Agent.operations.agent("codex"); + yield* Agent.operations.agent("scribe"); } catch (error) { thrown = error; } expect(thrown).toBeInstanceOf(Error); if (thrown instanceof Error) { expect(thrown.message).toContain("ACP_BACKEND_UNAVAILABLE"); - expect(thrown.message).toContain("codex not installed"); + expect(thrown.message).toContain("scribe not installed"); } // The failed probe is not cached; the next probe succeeds and is. - expect(yield* Agent.operations.agent("codex")).toBe("codex"); - expect(yield* Agent.operations.agent("codex")).toBe("codex"); + expect(yield* Agent.operations.agent("scribe")).toBe("scribe"); + expect(yield* Agent.operations.agent("scribe")).toBe("scribe"); expect(harness.doctorCalls).toBe(2); }); }); @@ -629,11 +629,11 @@ describe("Tier AP — ACPX provider", () => { function* installState(harness: FakeRuntimeHarness): Operation { const state = yield* useAcpxProvider( - { defaultAgent: "codex", permissionMode: "deny-all" }, + { defaultAgent: "scribe", permissionMode: "deny-all" }, { createRuntime: harness.create, sessionStore: makeStore(), - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd" }), }, ); yield* Agent.around( @@ -712,11 +712,11 @@ interface Partition { function* usePartition(harness: FakeRuntimeHarness): Operation { const handle = yield* useAcpxProvider( - { defaultAgent: "codex", permissionMode: "deny-all" }, + { defaultAgent: "scribe", permissionMode: "deny-all" }, { createRuntime: harness.create, sessionStore: makeStore(), - agentRegistry: makeRegistry({ codex: "codex-cmd", other: "other-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd", other: "other-cmd" }), }, ); return { handle, absent: () => undefined }; @@ -766,7 +766,7 @@ function stubAuthority(): AuthorityLog { function fakeRequest(): AgentLaunchRequest { const request = { instructions: "You are the implementor.", - agent: "codex", + agent: "scribe", cwd: CWD, additionalDirectories: [], permissionMode: "deny-all", @@ -793,7 +793,7 @@ function* installPartitioned( return options.select ? options.select() : owned!.handle; }); const authority = stubAuthority(); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, authority); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, authority); return { authority, selections: () => selections }; } @@ -833,13 +833,13 @@ describe("Tier SM — session placement and materialization", () => { const factory = createAcpxProvider({ createRuntime: harness.create, sessionStore: store, - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd" }), }); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, stubAuthority()); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, stubAuthority()); const session = yield* Agent.operations.session(); - expect(session.sessionKey).toBe(deriveSessionKey("codex-cmd", CWD)); + expect(session.sessionKey).toBe(deriveSessionKey("scribe-cmd", CWD)); expect(session.cwd).toBe(CWD); // Placement, and nothing else: no session was ensured, no handle exists, // no record was written, no turn was started, and nothing is asserted @@ -1091,7 +1091,7 @@ describe("Tier PT — partitioned provider installation", () => { return grown; }; - yield* Agent.operations.agent("codex"); + yield* Agent.operations.agent("scribe"); grew.push(since()); yield* Agent.operations.session("one"); grew.push(since()); @@ -1296,7 +1296,7 @@ const HOST_DIR = "/runs/sessions/host"; /** One logical session's directory, as the host places it. */ const SESSION_DIR = "/runs/sessions/cwd/8f2a"; -const WORKFLOW_SESSION_KEY = "xmd:workflow:v1:run:acpx:codex-cmd:default"; +const WORKFLOW_SESSION_KEY = "xmd:workflow:v1:run:acpx:scribe-cmd:default"; const INSTRUCTIONS = "You have no native tool authority here."; @@ -1344,7 +1344,7 @@ function* installStrictProvider( const factory = createAcpxProvider({ createRuntime: harness.create, sessionStore: store, - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd" }), // deno-lint-ignore require-yield agentCwd: function* () { return HOST_DIR; @@ -1354,7 +1354,7 @@ function* installStrictProvider( permissions: "strict", sessions, }); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, stubAuthority()); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, stubAuthority()); // An authored approve-all scope, installed the way `` installs // one. Under this profile it must reach no decision at all. yield* Agent.around({ @@ -1378,7 +1378,7 @@ function* installStrictProvider( * checkpoints one before running a prompt. */ function seedRoutedRecord(store: ReturnType, sessionKey: string, cwd: string) { - const record = makeRecord("codex-cmd", cwd); + const record = makeRecord("scribe-cmd", cwd); record.acpxRecordId = `record:${sessionKey}`; record.acpSessionId = ACP_SESSION_ID; store.records.set(record.acpxRecordId, record); @@ -1420,7 +1420,7 @@ describe("Tier WAP — strict workflow Agent profile", () => { expect(harness.ensureCalls).toHaveLength(1); expect(harness.ensureCalls[0]).toMatchObject({ sessionKey: WORKFLOW_SESSION_KEY, - agent: "codex", + agent: "scribe", mode: "persistent", cwd: SESSION_DIR, }); @@ -1430,7 +1430,7 @@ describe("Tier WAP — strict workflow Agent profile", () => { // The host placed the session; nothing walked the contextual directory. expect(log.places).toEqual([ - { agentName: "codex", agentCommand: "codex-cmd", session: undefined }, + { agentName: "scribe", agentCommand: "scribe-cmd", session: undefined }, ]); expect(log.established).toEqual([ { @@ -1527,9 +1527,9 @@ describe("Tier WAP — strict workflow Agent profile", () => { const factory = createAcpxProvider({ createRuntime: harness.create, sessionStore: store, - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd" }), }); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, stubAuthority()); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, stubAuthority()); yield* Agent.around({ // deno-lint-ignore require-yield *requestPermission([request]) { @@ -1540,7 +1540,7 @@ describe("Tier WAP — strict workflow Agent profile", () => { : { outcome: "selected", optionId: allow.optionId }; }, }); - const sessionKey = deriveSessionKey("codex-cmd", CWD); + const sessionKey = deriveSessionKey("scribe-cmd", CWD); seedRoutedRecord(store, sessionKey, CWD); const prompt = yield* spawn(() => collectPrompt("ordinary run")); yield* sleep(20); @@ -1646,25 +1646,25 @@ describe("Tier APR — preparing an agent before it is probed", () => { return harness.create(options); }, sessionStore: makeStore(), - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd" }), *prepareAgent(agentName: string) { order.push(`prepare:${agentName}`); }, }); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, stubAuthority()); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, stubAuthority()); // Nothing yet: installing a provider resolves no agent. expect(order).toEqual([]); - yield* Agent.operations.agent("codex"); + yield* Agent.operations.agent("scribe"); // Preparation first. The probe spawns the agent's command, so a host that // has to put that command on disk must have done so by now — otherwise the // probe reports the agent unavailable for a reason that names the wrong // cause, and no amount of later preparation is reached. - expect(order[0]).toBe("prepare:codex"); + expect(order[0]).toBe("prepare:scribe"); expect(order).toContain("probe"); - expect(order.indexOf("prepare:codex")).toBeLessThan(order.indexOf("probe")); + expect(order.indexOf("prepare:scribe")).toBeLessThan(order.indexOf("probe")); }); }); @@ -1679,9 +1679,9 @@ describe("Tier APC — Prompt checkpoint metadata", () => { const factory = createAcpxProvider({ createRuntime: harness.create, sessionStore: makeStore(), - agentRegistry: makeRegistry({ codex: "codex-cmd" }), + agentRegistry: makeRegistry({ scribe: "scribe-cmd" }), }); - yield* factory({ defaultAgent: "codex", permissionMode: "deny-all" }, authority); + yield* factory({ defaultAgent: "scribe", permissionMode: "deny-all" }, authority); const { events } = yield* collectPrompt("hello"); return { checkpoints: authority.checkpoints, events }; } diff --git a/packages/acp/tests/session-route.test.ts b/packages/acp/tests/session-route.test.ts index 5b3a15c5..5a9c445d 100644 --- a/packages/acp/tests/session-route.test.ts +++ b/packages/acp/tests/session-route.test.ts @@ -37,6 +37,7 @@ import type { AgentSessionRouteStore, AgentSessionRouteV1, AgentSessionRouteV2, + AgentSessionRouteV3, } from "../src/session-route.ts"; import type { ExecutableBuildBindingV1 } from "@executablemd/core"; import { cliBase } from "@executablemd/test-support/launch"; @@ -80,7 +81,7 @@ const BUILD: ExecutableBuildBindingV1 = { executableDigest: { algorithm: "sha256", value: "c".repeat(64) }, }; -/** The bound form, which exists only for `client-native`. */ +/** The bound client-native form, which is V2 and only V2. */ function boundClientNative(overrides: Partial = {}): AgentSessionRouteV2 { return { ...(clientNative() as Extract), @@ -90,6 +91,17 @@ function boundClientNative(overrides: Partial = {}): AgentS }; } +/** The bound ACP-first form, which is V3 and only V3. */ +function boundAcpFirst(overrides: Partial = {}): AgentSessionRouteV3 { + return { + ...acpFirst(), + schema: "session-route.v3", + route: "acp-first", + executableBinding: BUILD, + ...overrides, + }; +} + function* workspace(): Operation { const root = path.join(os.tmpdir(), `xmd-sr-${randomUUID()}`); yield* ensureDir(root); @@ -399,14 +411,17 @@ describe("Tier SR — the construction route", () => { }); /** - * Tier SV — the bound V2 client-native route + * Tier SV — the bound construction routes * (specs/native-agent-session-launch-spec.md §Construction route). * - * V2 adds one fact V1 never had: which build accepted the identity XMD chose. - * It exists only for `client-native`, because ACP-first construction gained - * nothing — and a V1 record is never read as a V2 one, in either direction. + * Both bound schemas add one fact V1 never had: which build the session's + * provider-native identity belongs to. V2 says it for `client-native`, where a + * build accepted the identity XMD chose; V3 says it for `acp-first`, where a + * build issued the identity XMD was handed. Each schema admits exactly the + * construction it was minted for, and a V1 record is never read as a bound one + * in either direction. */ -describe("Tier SV — the bound construction route", () => { +describe("Tier SV — the bound construction routes", () => { it("SV1: a bound record carries the binding and nothing else", function* () { const text = serializeAgentSessionRoute(boundClientNative()); expect(Object.keys(JSON.parse(text)).sort()).toEqual([ @@ -426,9 +441,9 @@ describe("Tier SV — the bound construction route", () => { expect(text).not.toContain("/"); }); - it("SV2: there is no bound acp-first form", function* () { - // A second schema for a shape that gained no fact would be a version number - // with nothing behind it. + it("SV2: each bound schema admits only its own construction", function* () { + // A record pairing one schema with the other's construction is state this + // build cannot account for, not a route with a spare member. expect(parseAgentSessionRoute({ ...acpFirst(), schema: "session-route.v2" })).toBe(undefined); expect( parseAgentSessionRoute({ @@ -437,6 +452,83 @@ describe("Tier SV — the bound construction route", () => { executableBinding: BUILD, }), ).toBe(undefined); + expect(parseAgentSessionRoute({ ...boundClientNative(), schema: "session-route.v3" })).toBe( + undefined, + ); + expect(parseAgentSessionRoute({ ...clientNative(), schema: "session-route.v3" })).toBe( + undefined, + ); + }); + + it("SV6: a bound acp-first record carries the binding and nothing else", function* () { + const parsed = parseAgentSessionRoute(JSON.parse(serializeAgentSessionRoute(boundAcpFirst()))); + expect(parsed).toEqual(boundAcpFirst()); + const text = serializeAgentSessionRoute(boundAcpFirst()); + expect(Object.keys(JSON.parse(text)).sort()).toEqual([ + "agent", + "executableBinding", + "provider", + "route", + "schema", + "sessionKey", + ]); + // Still nothing about the conversation: the provider named it, and no + // identity, instruction layer, process fact or path is recorded here. + expect(text).not.toContain("/"); + expect(text).not.toContain("nativeSessionId"); + }); + + it("SV7: a bound acp-first record is exact on the way in", function* () { + // Built as what the parser is actually handed — a record read back off + // disk — rather than as the type the parser is being asked to produce. + const good: Record = { ...boundAcpFirst() }; + const cases: [string, unknown][] = [ + ["no binding at all", (({ executableBinding: _b, ...rest }) => rest)(good)], + ["unknown binding schema", { ...good, executableBinding: { ...BUILD, schema: "v2" } }], + ["extra binding member", { ...good, executableBinding: { ...BUILD, path: "/bin/codex" } }], + [ + "short digest", + { + ...good, + executableBinding: { + ...BUILD, + executableDigest: { algorithm: "sha256", value: "c".repeat(63) }, + }, + }, + ], + ["binding is not a record", { ...good, executableBinding: "executable-build.v1" }], + ["a native identity beside it", { ...good, nativeSessionId: "11111111" }], + ["extra member beside it", { ...good, executablePath: "/usr/local/bin/codex" }], + ]; + for (const [name, value] of cases) { + expect([name, parseAgentSessionRoute(value)]).toEqual([name, undefined]); + } + }); + + it("SV8: a V1 acp-first record is never read as a bound one", function* () { + // A build observed today says which build is installed now, not which one + // issued this session's identity, so the unbound record stays unbound. + const parsed = parseAgentSessionRoute(acpFirst()); + expect(parsed?.schema).toBe("session-route.v1"); + expect((parsed as { executableBinding?: unknown }).executableBinding).toBe(undefined); + }); + + it("SV9: both stores publish a bound acp-first route create-once", function* () { + for (const { name, store } of yield* stores()) { + const winner = yield* store.publish(boundAcpFirst()); + expect([name, winner]).toEqual([name, boundAcpFirst()]); + const loser = yield* store.publish( + boundAcpFirst({ + executableBinding: { + schema: "executable-build.v1", + reportedVersion: "codex-cli 0.153.3", + executableDigest: { algorithm: "sha256", value: "e".repeat(64) }, + }, + }), + ); + expect([name, loser]).toEqual([name, boundAcpFirst()]); + expect([name, yield* store.read(KEY)]).toEqual([name, boundAcpFirst()]); + } }); it("SV3: a bound record without an exact binding refuses", function* () { @@ -488,7 +580,13 @@ describe("Tier SV — the bound construction route", () => { // Nothing here upgrades a route. A V1 record read and written again is // still V1, which is what keeps a build observed today from being written // into a session that never recorded one. - for (const route of [clientNative(), boundClientNative()] as AgentSessionRoute[]) { + const routes: AgentSessionRoute[] = [ + acpFirst(), + clientNative(), + boundClientNative(), + boundAcpFirst(), + ]; + for (const route of routes) { const round = parseAgentSessionRoute(JSON.parse(serializeAgentSessionRoute(route))); expect(round?.schema).toBe(route.schema); } diff --git a/packages/acp/tests/terminal-screen.test.ts b/packages/acp/tests/terminal-screen.test.ts new file mode 100644 index 00000000..d2b2b819 --- /dev/null +++ b/packages/acp/tests/terminal-screen.test.ts @@ -0,0 +1,706 @@ +/** + * Tier NA — native answer observation + * (specs/native-agent-session-launch-spec.md §Provider-returned adapters). + * + * The Codex native proof has to decide whether the agent answered, and the only + * thing it can read is a pseudo-terminal. A TUI draws by moving the cursor and + * repainting, so the bytes it emitted and the screen it is showing are two + * different things — which means a proof that searches the byte stream can find + * text that has since been erased, or text the program printed before it took + * the screen, while missing an answer the screen is showing right now. + * + * These cases drive fake TUI streams through the reconstruction the proof uses + * and check both halves: that a real answer is read however it was drawn, and + * that everything an answer is not is refused. No Codex process starts and no + * model turn is spent — the streams here are written by hand, so what the + * observer does with each shape is decided rather than sampled. + */ +import { describe, it } from "@executablemd/test-support/bdd"; +import { expect } from "@executablemd/test-support/expect"; +import { + observeAnswer, + screenShows, + terminalChannels, + TerminalReader, + terminalReader, + TerminalScreen, +} from "./fixtures/terminal-screen.ts"; +import type { AnswerQuestion } from "./fixtures/terminal-screen.ts"; + +/** The pty the proof opens, so these streams wrap where that one wraps. */ +const ROWS = 40; +const COLUMNS = 120; + +const ESC = String.fromCharCode(0x1b); +const csi = (body: string): string => `${ESC}[${body}`; +/** Cursor position, one-based as a terminal counts. */ +const at = (row: number, column: number): string => csi(`${row};${column}H`); +/** The TUI takes the screen, and gives the shell's back when it leaves. */ +const ALT_ON = csi("?1049h"); +const ALT_OFF = csi("?1049l"); +const CLEAR = csi("2J"); +/** Hold the drawing back, and put the finished frame on show. */ +const SYNC_ON = csi("?2026h"); +const SYNC_OFF = csi("?2026l"); + +/** Repaint the whole screen by addressing each row, as a TUI does. */ +function paint(rows: readonly string[]): string { + return rows.map((row, index) => `${at(index + 1, 1)}${csi("2K")}${row}`).join(""); +} + +/** A bordered pane row, padded to its right border as a TUI pads one. */ +function pane(text: string, width = 60): string { + return `│ ${text.padEnd(width)} │`; +} + +function border(width = 60, left = "╭", right = "╮"): string { + return `${left}${"─".repeat(width + 2)}${right}`; +} + +/** + * The sentence the native proof asks for. Chosen there because it is the first + * sentence of the instruction layer the launch installed, so it is text only a + * governed turn produces. + */ +const ANSWER = "The Implementor delivers an accepted plan as a focused, verified change."; + +/** A turn shaped like the proof's, which does not contain its own answer. */ +const TYPED = + "Do not use any tools. Reply with exactly two lines. First line: " + + "the first sentence of the body text under the 'Implementor' heading."; + +/** Codex's own furniture, named the same way the proof names it. */ +const CHROME: readonly string[] = ["Ask Codex to do anything", "esc to interrupt"]; + +function screen(): TerminalScreen { + return new TerminalScreen({ rows: ROWS, columns: COLUMNS }); +} + +function asked(over: Partial = {}): AnswerQuestion { + return { expected: ANSWER, typed: TYPED, chrome: CHROME, ...over }; +} + +describe("Tier NA — native answer observation", () => { + it("NA1: text printed before the TUI took the screen is not an answer", function* () { + const terminal = screen(); + terminal.write(`${ANSWER}\r\n`); + + // On the shell's own screen the words are genuinely there, which is why a + // proof reading retained bytes would have accepted them. + expect(observeAnswer(terminal.snapshot(), asked()).found).toBe(true); + + terminal.write(ALT_ON); + + const observed = observeAnswer(terminal.snapshot(), asked()); + expect(terminal.snapshot().alternate).toBe(true); + expect(observed).toEqual({ found: false, reason: "absent" }); + }); + + it("NA2: an answer the terminal wrapped mid-word is read as one sentence", function* () { + const terminal = screen(); + // Started 21 columns from the right margin, so the terminal itself breaks + // the word `delivers` across two rows with no space where it broke. + terminal.write(`${ALT_ON}${at(5, COLUMNS - 20)}${ANSWER}`); + + const rows = terminal.snapshot().rows; + expect(rows[4]?.endsWith("The Implementor deliv")).toBe(true); + expect(rows[5]).toBe("ers an accepted plan as a focused, verified change."); + expect(observeAnswer(terminal.snapshot(), asked())).toEqual({ + found: true, + reason: "answer", + }); + }); + + it("NA3: an answer a repaint moved is read from where it now is", function* () { + const terminal = screen(); + terminal.write(`${ALT_ON}${paint(["", "", ANSWER])}`); + expect(observeAnswer(terminal.snapshot(), asked()).found).toBe(true); + + terminal.write(`${CLEAR}${paint([...Array.from({ length: 19 }, () => ""), ANSWER])}`); + + expect(terminal.snapshot().rows[2]).toBe(""); + expect(terminal.snapshot().rows[19]).toBe(ANSWER); + expect(observeAnswer(terminal.snapshot(), asked()).found).toBe(true); + }); + + it("NA4: a repaint that replaces the answer is refused, though the bytes remain", function* () { + const terminal = screen(); + terminal.write(`${ALT_ON}${paint(["", "", ANSWER])}`); + expect(observeAnswer(terminal.snapshot(), asked()).found).toBe(true); + + terminal.write(paint(["", "", "Working on it."])); + + // The stream still carries every byte of the answer; the screen does not, + // and the screen is what an operator would be looking at. + expect(observeAnswer(terminal.snapshot(), asked())).toEqual({ + found: false, + reason: "absent", + }); + }); + + it("NA5: an answer wrapped inside a bordered pane is read across the border", function* () { + const terminal = screen(); + terminal.write( + `${ALT_ON}${paint([ + border(), + pane("The Implementor delivers an accepted plan as a"), + pane("focused, verified change."), + border(60, "╰", "╯"), + ])}`, + ); + + expect(terminal.snapshot().rows[1]?.startsWith("│ The Implementor")).toBe(true); + expect(observeAnswer(terminal.snapshot(), asked())).toEqual({ + found: true, + reason: "answer", + }); + }); + + it("NA6: cells an erase blanked do not close up into an answer", function* () { + const terminal = screen(); + terminal.write(`${ALT_ON}${at(5, 1)}${ANSWER}`); + expect(observeAnswer(terminal.snapshot(), asked()).found).toBe(true); + + // Twenty cells out of the middle of the sentence, which is what a redraw + // that has not finished repainting leaves behind. + terminal.write(`${at(5, 25)}${csi("20X")}`); + + expect(terminal.snapshot().rows[4]).toBe( + `The Implementor delivers${" ".repeat(21)}a focused, verified change.`, + ); + expect(observeAnswer(terminal.snapshot(), asked())).toEqual({ + found: false, + reason: "absent", + }); + }); + + it("NA7: a gap inside a row is a gap, not a join", function* () { + const terminal = screen(); + // Two columns that would read as the sentence if the blank cells between + // them closed up — which is what erasing part of a row, or laying two panes + // side by side, leaves on the screen. + terminal.write( + `${ALT_ON}${at(5, 1)}The Implementor delivers an accepted plan` + + `${at(5, 60)}as a focused, verified change.`, + ); + + expect(observeAnswer(terminal.snapshot(), asked())).toEqual({ + found: false, + reason: "absent", + }); + }); + + it("NA8: two panes on one row are two pieces of text", function* () { + const terminal = screen(); + // Borders with no padding either side, so nothing but the border itself + // says where one pane's text stops and the next one's starts. + terminal.write( + `${ALT_ON}${at(5, 1)}` + + "│The Implementor delivers an accepted plan│as a focused, verified change.│", + ); + + expect(observeAnswer(terminal.snapshot(), asked())).toEqual({ + found: false, + reason: "absent", + }); + }); + + it("NA9: the harness reading its own turn back is not an answer", function* () { + const terminal = screen(); + const quoting = `Reply with exactly this: ${ANSWER}`; + terminal.write(`${ALT_ON}${paint(["", quoting])}`); + + expect(observeAnswer(terminal.snapshot(), asked({ typed: quoting }))).toEqual({ + found: false, + reason: "only-typed", + }); + + // And the echo does not go on refusing once the agent has actually replied. + terminal.write(paint(["", quoting, "", ANSWER])); + expect(observeAnswer(terminal.snapshot(), asked({ typed: quoting }))).toEqual({ + found: true, + reason: "answer", + }); + }); + + it("NA10: an answer still being drawn is not an answer yet", function* () { + const terminal = screen(); + terminal.write(`${ALT_ON}${at(5, 1)}The Implementor delivers an accepted plan as a foc`); + + expect(observeAnswer(terminal.snapshot(), asked())).toEqual({ + found: false, + reason: "absent", + }); + + terminal.write("used, verified change."); + expect(observeAnswer(terminal.snapshot(), asked()).found).toBe(true); + }); + + it("NA11: text the TUI drew as its own furniture is not an answer", function* () { + const onOneRow = screen(); + onOneRow.write(`${ALT_ON}${at(38, 1)}Ask Codex to do anything · ${ANSWER}`); + expect(observeAnswer(onOneRow.snapshot(), asked())).toEqual({ + found: false, + reason: "absent", + }); + + const across = screen(); + across.write( + `${ALT_ON}${paint([ + "The Implementor delivers an accepted plan as a", + "Ask Codex to do anything", + "focused, verified change.", + ])}`, + ); + expect(observeAnswer(across.snapshot(), asked())).toEqual({ + found: false, + reason: "absent", + }); + }); + + it("NA12: an idle composer is the terminal's state, not the absence of an answer", function* () { + const terminal = screen(); + terminal.write( + `${ALT_ON}${paint([ + "› Do not use any tools.", + "", + ANSWER, + "", + border(), + pane("Ask Codex to do anything"), + border(60, "╰", "╯"), + ])}`, + ); + + const snapshot = terminal.snapshot(); + expect(screenShows(snapshot, "Ask Codex to do anything")).toBe(true); + expect(observeAnswer(snapshot, asked())).toEqual({ found: true, reason: "answer" }); + }); + + it("NA13: an answer that scrolled off the screen is gone with it", function* () { + const terminal = screen(); + terminal.write(`${ALT_ON}${at(ROWS, 1)}${ANSWER}`); + expect(observeAnswer(terminal.snapshot(), asked()).found).toBe(true); + + terminal.write("\r\n".repeat(ROWS)); + + expect(observeAnswer(terminal.snapshot(), asked())).toEqual({ + found: false, + reason: "absent", + }); + }); + + it("NA14: a control sequence split across two reads is still one sequence", function* () { + const terminal = screen(); + for (const chunk of [`${ESC}[?10`, "49h", `${ESC}[5`, `;1H${ANSWER}`]) { + terminal.write(chunk); + } + + const snapshot = terminal.snapshot(); + expect(snapshot.alternate).toBe(true); + expect(snapshot.rows[4]).toBe(ANSWER); + expect(snapshot.rows.some((row) => row.includes("49h") || row.includes(";1H"))).toBe(false); + expect(observeAnswer(snapshot, asked()).found).toBe(true); + }); + + it("NA15: leaving the alternate screen gives back what the shell had", function* () { + const terminal = screen(); + terminal.write("checking codex\r\n"); + terminal.write(`${ALT_ON}${paint(["", "", ANSWER])}`); + expect(observeAnswer(terminal.snapshot(), asked()).found).toBe(true); + + terminal.write(ALT_OFF); + + const snapshot = terminal.snapshot(); + expect(snapshot.alternate).toBe(false); + expect(snapshot.rows[0]).toBe("checking codex"); + expect(observeAnswer(snapshot, asked())).toEqual({ found: false, reason: "absent" }); + }); + + it("NA16: bytes arriving one at a time reconstruct the same screen", function* () { + const whole = screen(); + const stream = `${ALT_ON}${paint([ + border(), + pane(ANSWER.slice(0, 45)), + pane(ANSWER.slice(45).trim()), + ])}`; + whole.write(stream); + + const dribbled = screen(); + for (const char of stream) { + dribbled.write(char); + } + + expect(dribbled.snapshot()).toEqual(whole.snapshot()); + expect(observeAnswer(dribbled.snapshot(), asked()).found).toBe(true); + }); + + it("NA17: a pane border split down the middle of its bytes is still one border", function* () { + // What the proof actually hands over: a pty delivers bytes, and a pane + // border is three of them per glyph. A read that ends inside one would + // otherwise put a replacement character on the screen where the border + // goes, and the run boundaries are drawn from those borders. + const stream = `${ALT_ON}${paint([ + border(), + pane(ANSWER.slice(0, 45)), + pane(ANSWER.slice(45).trim()), + ])}`; + const bytes = new TextEncoder().encode(stream); + + const whole = screen(); + whole.write(bytes); + + const split = screen(); + for (let index = 0; index < bytes.length; index += 1) { + split.write(bytes.subarray(index, index + 1)); + } + + expect(split.snapshot()).toEqual(whole.snapshot()); + expect(split.snapshot().rows.join("")).not.toContain("�"); + expect(observeAnswer(split.snapshot(), asked()).found).toBe(true); + }); + + it("NA18: clearing the scrollback leaves the answer on the screen", function* () { + const drawn = (): TerminalScreen => { + const terminal = screen(); + terminal.write(`${ALT_ON}${paint(["", "", ANSWER])}`); + return terminal; + }; + + // `3J` is the scrollback, which is above the screen rather than on it, and + // an unimplemented parameter is not an instruction to blank anything. Both + // are here because either one falling through to a whole-screen erase would + // make this observer report the answer it is looking at as absent. + const scrollback = drawn(); + scrollback.write(csi("3J")); + expect(scrollback.snapshot().rows[2]).toBe(ANSWER); + expect(observeAnswer(scrollback.snapshot(), asked()).found).toBe(true); + + const unknown = drawn(); + unknown.write(csi("9J")); + expect(observeAnswer(unknown.snapshot(), asked()).found).toBe(true); + + // And the mode that does mean the visible screen still means it. + const visible = drawn(); + visible.write(csi("2J")); + expect(visible.snapshot().rows.join("")).toBe(""); + expect(observeAnswer(visible.snapshot(), asked()).found).toBe(false); + }); + + it("NA19: the watch latches on the read that finishes the answer", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + expect(watch.write(`${ALT_ON}${paint([pane("Ask Codex to do anything")])}`)).toBe(false); + expect(watch.ask(asked())).toBe(false); + + // Read by read, as the pty hands them over: the answer is not answered + // until the read that puts the last of it on the screen. + expect(watch.write(paint(["", "", "The Implementor delivers an accepted"]))).toBe(false); + expect(watch.write(paint(["", "", ANSWER]))).toBe(true); + expect(watch.found).toBe(true); + }); + + it("NA20: a redraw over the answer does not un-answer the turn", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + watch.write(ALT_ON); + watch.ask(asked()); + expect(watch.write(paint(["", "", ANSWER]))).toBe(true); + + watch.write(`${CLEAR}${paint(["", "", "", "", border(), pane("Ask Codex to do anything")])}`); + + // The screen has moved on, and the screen is right about what it is + // showing — but the question was whether the agent answered, and a TUI + // repainting over its own reply is not the agent taking it back. + expect(observeAnswer(watch.snapshot(), asked()).found).toBe(false); + expect(watch.found).toBe(true); + expect(watch.write("")).toBe(true); + }); + + it("NA21: an occurrence from before the turn does not latch", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + // The words are genuinely on the shell's screen here, which is exactly why + // a watch armed from the start of the process would have latched on them. + watch.write(`${ANSWER}\r\n`); + watch.write(`${ALT_ON}${paint([pane("Ask Codex to do anything")])}`); + + expect(watch.ask(asked())).toBe(false); + expect(watch.write(paint([pane("esc to interrupt")]))).toBe(false); + expect(watch.found).toBe(false); + }); + + it("NA22: the turn echoing back in the composer does not latch", function* () { + const quoting = `Reply with exactly this: ${ANSWER}`; + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + watch.write(ALT_ON); + watch.ask(asked({ typed: quoting })); + + expect(watch.write(paint(["", `› ${quoting}`]))).toBe(false); + expect(watch.found).toBe(false); + + expect(watch.write(paint(["", `› ${quoting}`, "", ANSWER]))).toBe(true); + }); + + it("NA23: an answer still being drawn does not latch until it is whole", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + watch.write(`${ALT_ON}${at(5, 1)}`); + watch.ask(asked()); + + expect(watch.write("The Implementor delivers an accepted plan as a foc")).toBe(false); + expect(watch.write("used, verified change.")).toBe(true); + }); + + it("NA24: an answer erased within the read that drew it was never on the screen", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + watch.write(ALT_ON); + watch.ask(asked()); + + const chunk = `${at(5, 1)}${ANSWER}${at(5, 25)}${csi("20X")}`; + expect(chunk).toContain(ANSWER); + expect(watch.write(chunk)).toBe(false); + + // Every byte of the answer went through the watch, and no read boundary + // ever exposed it — which is the whole difference between reading the + // stream and reading the terminal. + expect(watch.found).toBe(false); + }); + + it("NA25: the TUI's own furniture does not latch", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + watch.write(ALT_ON); + watch.ask(asked()); + + for (const line of CHROME) { + expect(watch.write(paint(["", line]))).toBe(false); + } + expect(watch.found).toBe(false); + + expect(watch.write(paint(["", CHROME[0] ?? "", "", ANSWER]))).toBe(true); + }); + + it("NA26: the watch latches an answer whose glyphs were split across reads", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + const bytes = new TextEncoder().encode( + `${ALT_ON}${paint([ + border(), + pane(ANSWER.slice(0, 45)), + pane(ANSWER.slice(45).trim()), + border(60, "╰", "╯"), + ])}`, + ); + watch.ask(asked()); + + let latched = false; + for (let index = 0; index < bytes.length; index += 1) { + latched = watch.write(bytes.subarray(index, index + 1)); + } + + expect(latched).toBe(true); + expect(watch.snapshot().rows.join("")).not.toContain("�"); + }); + + it("NA27: a frame that erased the answer before showing it does not latch", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + watch.write(ALT_ON); + watch.ask(asked()); + + // What a repaint of a scrolling transcript looks like from inside: the + // sentence is laid down and taken away again while the terminal is holding + // its output back, so nobody watching the pty was ever shown it. + expect(watch.write(`${SYNC_ON}${paint(["", "", ANSWER])}`)).toBe(false); + expect(watch.write(paint(["", "", "Working on it."]))).toBe(false); + expect(watch.write(SYNC_OFF)).toBe(false); + expect(watch.found).toBe(false); + }); + + it("NA28: an answer the committed frame is showing latches at the commit", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + watch.write(ALT_ON); + watch.ask(asked()); + + expect(watch.write(`${SYNC_ON}${paint(["", "", ANSWER])}`)).toBe(false); + expect(watch.write(SYNC_OFF)).toBe(true); + expect(watch.found).toBe(true); + }); + + it("NA29: the commit decides, not wherever the read happened to stop", function* () { + // The same bytes cut three ways. A frame is finished by `?2026l` and not by + // an operating system choosing where one `data` event ends, so an observer + // that evaluated per read would answer differently for each of these. + const held = `${SYNC_ON}${paint(["", "", ANSWER])}`; + const replaced = paint(["", "", "Working on it."]); + const cuts: [string, readonly string[], boolean][] = [ + ["one read", [`${held}${replaced}${SYNC_OFF}`], false], + ["cut mid-frame", [held, `${replaced}${SYNC_OFF}`], false], + ["committed, then overdrawn in the next read", [`${held}${SYNC_OFF}`, replaced], true], + // The one that can only be got right where the frame ends: the answer was + // committed and then drawn over, and the read carrying both stops after. + ["committed and overdrawn inside one read", [`${held}${SYNC_OFF}${replaced}`], true], + ]; + + for (const [name, reads, latches] of cuts) { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + watch.write(ALT_ON); + watch.ask(asked()); + for (const read of reads) { + watch.write(read); + } + expect(`${name}: ${watch.found}`).toBe(`${name}: ${latches}`); + } + }); + + it("NA30: begin and end split across byte reads still bound the frame", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + watch.write(ALT_ON); + watch.ask(asked()); + + const bytes = new TextEncoder().encode( + `${SYNC_ON}${paint(["", "", ANSWER])}${paint(["", "", "Working on it."])}${SYNC_OFF}` + + `${SYNC_ON}${paint(["", "", ANSWER])}${SYNC_OFF}`, + ); + // One byte at a time, which splits `?2026h` and `?2026l` down the middle + // repeatedly. The first frame erased its answer and the second kept it. + const answeredAt: number[] = []; + for (let index = 0; index < bytes.length; index += 1) { + if (watch.write(bytes.subarray(index, index + 1))) { + answeredAt.push(index); + } + } + + expect(watch.found).toBe(true); + // Nothing latched until the second frame committed, which is the last byte. + expect(answeredAt[0]).toBe(bytes.length - 1); + }); + + it("NA31: bytes that never went through the terminal cannot answer", function* () { + const seen: string[] = []; + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + const channels = terminalChannels((chunk) => { + seen.push(typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk)); + }, watch); + channels.display(ALT_ON); + watch.ask(asked()); + + // `script` reports its own troubles beside the pty. Those bytes reached + // this process without ever being displayed, so however exactly they read, + // they are not something an operator could have been looking at. + const beside = `${paint(["", "", ANSWER])}\n${ANSWER}\n`; + const before = watch.snapshot(); + channels.diagnostic(beside); + + expect(channels.observing).toBe(true); + expect(watch.found).toBe(false); + expect(watch.snapshot()).toEqual(before); + // Still evidence about the run, which is the other half of the split. + expect(seen.join("")).toContain(ANSWER); + + // And the same bytes on the channel the terminal presented do answer. + expect(channels.display(paint(["", "", ANSWER]))).toBe(true); + }); + + it("NA32: every invocation reads a terminal, only the submitting one watches", function* () { + const size = { rows: ROWS, columns: COLUMNS }; + const submitted = { say: TYPED, expect: ANSWER }; + // The one invocation that submits: a journey's first, saying something and + // expecting an answer to it. + expect(terminalReader(size, { index: 0, ...submitted }).watching).toBe(true); + + // Preflight and both zero-native-turn invocations, which pass an empty plan. + expect(terminalReader(size, { index: 0 }).watching).toBe(false); + expect(terminalReader(size, { index: 1 }).watching).toBe(false); + // Re-entry is not the invocation that submits, so an expectation reaching it + // is a mistake and not a second thing to watch. + expect(terminalReader(size, { index: 1, ...submitted }).watching).toBe(false); + // Neither half is enough on its own: expecting without saying observes a + // turn nobody sent, and saying without expecting has nothing to look for. + expect(terminalReader(size, { index: 0, expect: ANSWER }).watching).toBe(false); + expect(terminalReader(size, { index: 0, say: TYPED }).watching).toBe(false); + expect(terminalReader(size, { index: 0, say: TYPED, expect: "" }).watching).toBe(false); + + const seen: string[] = []; + const reader = terminalReader(size, { index: 0 }); + const channels = terminalChannels( + (chunk) => seen.push(typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk)), + reader, + ); + + expect(channels.observing).toBe(false); + expect(channels.display(`${ALT_ON}${paint(["", "", ANSWER])}`)).toBe(false); + channels.diagnostic(ANSWER); + // The stream is still kept whole, because it is this run's evidence. + expect(seen.join("")).toContain(ANSWER); + + // The screen is built all the same, because this invocation still has to + // decide what surface it is looking at. + expect(screenShows(reader.snapshot(), ANSWER)).toBe(true); + // And it cannot be asked for an answer: it submitted no turn, so a reply it + // reported would be attributed to one nobody spent. + expect(() => reader.ask(asked())).toThrow("submits no turn"); + expect(reader.found).toBe(false); + }); + + it("NA33: a surface only a held frame contains is not what the terminal showed", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + watch.write(ALT_ON); + watch.ask(asked()); + + // What the operator is looking at when a wait for an answer gives up, and + // what naming the terminal's state means: `still-working`. + watch.write(paint(["", pane("esc to interrupt")])); + const presented = watch.snapshot(); + expect(screenShows(presented, "esc to interrupt")).toBe(true); + + // Now a repaint begins. Inside it the pane is erased and a refusal is laid + // down — cells the terminal is holding, which nobody was shown. A reader of + // those would report `usage-limited` and send the next reader off to check + // a plan that was never at fault. + watch.write(`${SYNC_ON}${CLEAR}${paint(["", pane("You've hit your usage limit.")])}`); + expect(watch.snapshot()).toEqual(presented); + expect(screenShows(watch.snapshot(), "usage limit")).toBe(false); + + // Still the frame before, however many reads the repaint takes. + watch.write(paint(["", "", pane("Working on it.")])); + expect(watch.snapshot()).toEqual(presented); + expect(screenShows(watch.snapshot(), "esc to interrupt")).toBe(true); + + // And it advances at the commit, to exactly what the commit put on show. + watch.write(SYNC_OFF); + expect(screenShows(watch.snapshot(), "Working on it.")).toBe(true); + expect(screenShows(watch.snapshot(), "esc to interrupt")).toBe(false); + expect(screenShows(watch.snapshot(), "usage limit")).toBe(false); + // The answer was never on any of it, held or shown. + expect(watch.found).toBe(false); + }); + + it("NA34: a hold beginning mid-read presents the bytes before it, not after", function* () { + const watch = new TerminalReader({ rows: ROWS, columns: COLUMNS }, true); + watch.write(ALT_ON); + watch.ask(asked()); + + // One read that finishes drawing one surface and then starts withholding + // the next. What was presented is where the hold began, not where the + // operating system stopped the read. + watch.write( + `${paint(["", pane("esc to interrupt")])}` + + `${SYNC_ON}${CLEAR}${paint(["", pane("Ask Codex to do anything")])}`, + ); + expect(screenShows(watch.snapshot(), "esc to interrupt")).toBe(true); + expect(screenShows(watch.snapshot(), "Ask Codex to do anything")).toBe(false); + + // The commit, and the next hold, one byte at a time — which splits both + // sequences down the middle. The composer frame is presented as it commits, + // and the answer drawn after it is held back by the frame that follows. + const bytes = new TextEncoder().encode( + `${SYNC_OFF}${SYNC_ON}${CLEAR}${paint(["", "", ANSWER])}`, + ); + for (let index = 0; index < bytes.length; index += 1) { + watch.write(bytes.subarray(index, index + 1)); + } + + expect(screenShows(watch.snapshot(), "Ask Codex to do anything")).toBe(true); + expect(screenShows(watch.snapshot(), ANSWER)).toBe(false); + expect(watch.found).toBe(false); + + // Committing it shows both what it drew and that it answered. + expect(watch.write(SYNC_OFF)).toBe(true); + expect(screenShows(watch.snapshot(), ANSWER)).toBe(true); + }); +}); diff --git a/packages/acp/vendor/adapters/MANIFEST.json b/packages/acp/vendor/adapters/MANIFEST.json index 70f3589e..95aaa6af 100644 --- a/packages/acp/vendor/adapters/MANIFEST.json +++ b/packages/acp/vendor/adapters/MANIFEST.json @@ -8,14 +8,16 @@ "tarball": "codex/agentclientprotocol-codex-acp-1.6.2.tgz", "repository": "https://github.com/agentclientprotocol/codex-acp", "upstreamBase": "50f69e57ca761ccafd2ca29de7fb591068277516", - "patchedCommit": "fadc0a690e96c276629be8a34be980d35e821637", + "patchedCommit": "e1a15637c13e85cca05cd8ba398c5cef92dcaf33", + "patchedBranch": "https://github.com/taras/codex-acp/tree/feat/session-native-identity", "pullRequest": "https://github.com/agentclientprotocol/codex-acp/pull/438", "buildCommand": "npm run build && npm pack", "license": "Apache-2.0", "contract": "_meta.codex.turnId", "materializationContract": "session_info_update _meta[executablemd.session-materialization/v1]", - "byteLength": 208429, - "sha256": "448d007e00c47cf5f4d5167dc817d7621857f298778227cc1e4b0f490469e387", + "identityContract": "_meta.agentSessionId", + "byteLength": 208660, + "sha256": "3ee22bc6b1649d02fcef80b352516f395fe774e63b459193195a41c42930dd8b", "files": [ "package/LICENSE", "package/README.md", diff --git a/packages/acp/vendor/adapters/PROVENANCE.md b/packages/acp/vendor/adapters/PROVENANCE.md index 241167a3..b9342c3c 100644 --- a/packages/acp/vendor/adapters/PROVENANCE.md +++ b/packages/acp/vendor/adapters/PROVENANCE.md @@ -6,7 +6,7 @@ those instead of the adapter `npx` would resolve. | Provider | Package | Version | Contracts | | --- | --- | --- | --- | -| `codex` | `@agentclientprotocol/codex-acp` | 1.6.2 | `_meta.codex.turnId`, `executablemd.session-materialization/v1` | +| `codex` | `@agentclientprotocol/codex-acp` | 1.6.2 | `_meta.codex.turnId`, `executablemd.session-materialization/v1`, `_meta.agentSessionId` | | `claude` | `@agentclientprotocol/claude-agent-acp` | 0.70.0 | `_meta.claudeCode.assistantMessageUuid`, `executablemd.session-materialization/v1` | `MANIFEST.json` records, for each: the exact upstream base and patched commit, @@ -15,7 +15,7 @@ list. `generated/snapshots.ts` carries the same bytes as a module. ## Why this exists -Two facts XMD needs, neither of which a published adapter reports. +Three facts XMD needs, none of which a published adapter reports. ### Which turn a Prompt completed @@ -60,6 +60,31 @@ own, so a client not waiting for it reads nothing. `contract` above: which turn completed and whether a backend accepted one are different claims, and an adapter could carry either without the other. +### Which conversation the provider actually opened + +Handing a running session to the provider's own CLI needs the identity that CLI +addresses — for Codex, the App Server thread the adapter created or reopened. +Codex allocates it, so a client cannot know it without being told; Claude's +adapter is handed one, so nothing there needs reporting. + +Codex 1.6.2 returns that thread id only as the top-level ACP `sessionId`, which +is the ACP session's own name. A client cannot tell from it whether the provider +allocated the identity or the client did, and reading it as native identity would +mean asserting a thread the App Server may never have confirmed. The snapshot +therefore reports it explicitly on the response `_meta` of `session/new`, +`session/load` and `session/resume`, as `{"agentSessionId": ""}`, +taken from the thread the App Server answered with rather than the one requested. + +That change is not part of PR #438, which carries the turn identity. It is +reachable at +, recorded +as `patchedBranch` beside the commit it names. + +`identityContract` in `MANIFEST.json` records this third claim on its own, for +the same reason `materializationContract` is separate: an adapter could report a +turn identity, an acceptance, or a native session identity without the others, +and only `codex` declares this one. + These snapshots are what stands in until both land upstream. ## What is carried, and what is not diff --git a/packages/acp/vendor/adapters/codex/agentclientprotocol-codex-acp-1.6.2.tgz b/packages/acp/vendor/adapters/codex/agentclientprotocol-codex-acp-1.6.2.tgz index 13554acd28281c26c93596956285e047f7d18b33..dac0139d3d725f4e60ec9a72544ab976db44151f 100644 GIT binary patch delta 193538 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
    8L+fbazY_+HEoU`e{StJ|dhi7Z~>QKycXZR%_Cp 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 H0Im`Mz8>o= delta 193160 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�Oq;svnn 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

    P;M#YBN; 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�Rb~sf=E#I*X944DJaF-lsn6IZs_$v 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