@agent-compose/sdk 0.5.8 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent-context.d.ts +1 -1
- package/dist/agent/agent-loop.d.ts +14 -12
- package/dist/agent/pause-client.d.ts +50 -0
- package/dist/agent/pause-client.test.d.ts +1 -0
- package/dist/agent/steer-control.d.ts +22 -6
- package/dist/client.d.ts +12 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +2379 -1463
- package/dist/pause/checkpoint.d.ts +27 -10
- package/dist/pause/manager.d.ts +1 -0
- package/dist/pause/pause-core.d.ts +23 -0
- package/dist/pause/state-dir.d.ts +1 -1
- package/dist/processors/builtins.d.ts +20 -1
- package/dist/processors/gate-pause.d.ts +46 -0
- package/dist/processors/gate-pause.test.d.ts +1 -0
- package/dist/processors/index.d.ts +3 -1
- package/dist/processors/processor.d.ts +13 -0
- package/dist/runtimes/_acp-client.d.ts +140 -0
- package/dist/runtimes/_cli-agent.d.ts +155 -3
- package/dist/runtimes/amp.d.ts +2 -2
- package/dist/runtimes/cli-agent-acp-live.test.d.ts +30 -0
- package/dist/runtimes/cli-agent.test.d.ts +22 -6
- package/dist/runtimes/codex.d.ts +7 -2
- package/dist/runtimes/openai-desktop.js +2365 -1463
- package/dist/runtimes/vercel.js +389 -2
- package/dist/sandbox.d.ts +113 -19
- package/dist/step-invocation/__tests__/background-invoker.test.d.ts +1 -0
- package/dist/step-invocation/index.d.ts +2 -1
- package/dist/step-invocation/invoker.d.ts +36 -0
- package/dist/types/__tests__/environment-build-flag.test.d.ts +1 -0
- package/dist/types/__tests__/workflow-metadata-provider.test.d.ts +1 -0
- package/dist/types/execution-context.d.ts +0 -8
- package/dist/types/protocol.d.ts +32 -1
- package/dist/types/runtime.d.ts +14 -0
- package/dist/types/sandbox-environment.d.ts +6 -1
- package/dist/types/sandbox.d.ts +86 -6
- package/dist/types/workflow-metadata.d.ts +40 -10
- package/dist/types/workflow.d.ts +22 -6
- package/dist/utils/bundler.d.ts +5 -1
- package/dist/workflow-steps/observability.d.ts +28 -2
- package/dist/workflow-steps/types.d.ts +11 -7
- package/dist/workflow-steps/workflow.d.ts +3 -2
- package/package.json +3 -2
- package/src/agent/agent-context.ts +14 -6
- package/src/agent/agent-loop.ts +32 -10
- package/src/agent/pause-client.ts +108 -0
- package/src/agent/run-agent.ts +9 -4
- package/src/agent/steer-control.ts +21 -7
- package/src/client.ts +35 -1
- package/src/index.ts +20 -2
- package/src/pause/checkpoint.ts +33 -14
- package/src/pause/manager.ts +2 -2
- package/src/pause/pause-core.ts +35 -0
- package/src/pause/state-dir.ts +2 -2
- package/src/processors/builtins.ts +44 -1
- package/src/processors/gate-pause.ts +94 -0
- package/src/processors/index.ts +7 -0
- package/src/processors/processor.ts +13 -0
- package/src/runtimes/_acp-client.ts +516 -0
- package/src/runtimes/_cli-agent.ts +416 -3
- package/src/runtimes/claude.ts +31 -3
- package/src/runtimes/codex.ts +21 -1
- package/src/runtimes/vercel.ts +4 -1
- package/src/sandbox.ts +426 -56
- package/src/step-invocation/index.ts +2 -1
- package/src/step-invocation/invoker.ts +195 -84
- package/src/types/execution-context.ts +0 -8
- package/src/types/protocol.ts +27 -1
- package/src/types/runtime.ts +14 -0
- package/src/types/sandbox-environment.ts +12 -1
- package/src/types/sandbox.ts +84 -6
- package/src/types/workflow-metadata.ts +42 -10
- package/src/types/workflow.ts +22 -7
- package/src/utils/bundler.ts +6 -1
- package/src/workflow-steps/observability.ts +51 -5
- package/src/workflow-steps/runner.ts +9 -5
- package/src/workflow-steps/types.ts +11 -7
- package/src/workflow-steps/workflow.ts +3 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Disk-backed memoise across pause-resume — the engine behind durable
|
|
3
|
+
* `ctx.step(name, fn)` (ADR-0012).
|
|
3
4
|
*
|
|
4
5
|
* First call: `fn()` runs, the result is atomically written to
|
|
5
6
|
* `/tmp/wf/state/checkpoints/<name>.json`. On any subsequent invocation
|
|
@@ -16,13 +17,29 @@
|
|
|
16
17
|
* memoisation is in-sandbox only and would silently retry on a sandbox
|
|
17
18
|
* recreation that lost the checkpoint file.
|
|
18
19
|
*
|
|
19
|
-
*
|
|
20
|
+
* INTERNAL only — there is no public `ctx.checkpoint`. The durable
|
|
21
|
+
* `ctx.step` wires `scopedMemoize("step<idx>")` and reports a "restored"
|
|
22
|
+
* sub-step on a cache hit. See ADR-0012.
|
|
20
23
|
*/
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
/** Result of a memoise call — `restored` distinguishes a disk hit (fn was
|
|
25
|
+
* NOT run, value read from a prior subprocess) from a fresh run (fn ran,
|
|
26
|
+
* value just written). The durable `ctx.step` maps `restored` onto the
|
|
27
|
+
* duration-0 "restored" sub-step status. */
|
|
28
|
+
export interface MemoizeResult<T> {
|
|
29
|
+
value: T;
|
|
30
|
+
restored: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Read-or-run-and-write helper reporting whether the value was restored
|
|
33
|
+
* from disk. `name` is validated by the state-dir layer (rejects
|
|
34
|
+
* path-escape attempts); `fn` is awaited if it returns a Promise so sync
|
|
35
|
+
* and async callers compose identically.
|
|
36
|
+
*
|
|
37
|
+
* A pause inside `fn` (PauseSignal) propagates BEFORE any write — a
|
|
38
|
+
* partially-completed body is never memoised, so the resume re-runs it. */
|
|
39
|
+
export declare function memoize<T>(name: string, fn: () => Promise<T> | T): Promise<MemoizeResult<T>>;
|
|
40
|
+
/** A memoise function bound to a runner-owned scope. */
|
|
41
|
+
export type ScopedMemoize = <T>(name: string, fn: () => Promise<T> | T) => Promise<MemoizeResult<T>>;
|
|
42
|
+
/** Bind memoise names to a runner-owned namespace (e.g. `step<idx>`). The
|
|
43
|
+
* caller's `name` is still validated independently so a scope prefix
|
|
44
|
+
* cannot turn an empty or path-escaping name into a valid filename. */
|
|
45
|
+
export declare function scopedMemoize(scope: string): ScopedMemoize;
|
package/dist/pause/manager.d.ts
CHANGED
|
@@ -88,6 +88,29 @@ export declare class PauseSignal {
|
|
|
88
88
|
* `err instanceof PauseSignal` anywhere a signal may have been thrown by a
|
|
89
89
|
* different SDK instance (the runner catching a bundled workflow's signal). */
|
|
90
90
|
export declare function isPauseSignal(err: unknown): err is PauseSignal;
|
|
91
|
+
/**
|
|
92
|
+
* The run's pause boundary. The runner (`run-agent` `steerPause`) closes a
|
|
93
|
+
* `corePause` over the ambient `runId`/`stepIndex`; the agent loop / runtime
|
|
94
|
+
* supplies the per-iteration `agentScope` so the pauseId stays stable across
|
|
95
|
+
* the loop's iteration-skipping on resume. Absent outside a workflow step
|
|
96
|
+
* (local / non-sandbox callers) — see `boundProcessorPause`.
|
|
97
|
+
*/
|
|
98
|
+
export type BoundaryPauseFn = <T = unknown>(req: PauseRequest<T>, agentScope: {
|
|
99
|
+
agentId: string;
|
|
100
|
+
iteration: number;
|
|
101
|
+
}) => Promise<T>;
|
|
102
|
+
/**
|
|
103
|
+
* Build the `ProcessorContext.pause` callable for a given hook scope: binds the
|
|
104
|
+
* boundary to this hook's `{ agentId, iteration }` so a processor calling
|
|
105
|
+
* `ctx.pause(req)` gets a deterministic, resume-stable pauseId. When no boundary
|
|
106
|
+
* is wired (local tests, non-sandbox callers), the returned fn rejects loudly
|
|
107
|
+
* rather than silently no-op'ing — pause genuinely cannot work without the
|
|
108
|
+
* runner's snapshot/Temporal machinery.
|
|
109
|
+
*/
|
|
110
|
+
export declare function boundProcessorPause(boundary: BoundaryPauseFn | undefined, scope: {
|
|
111
|
+
agentId: string;
|
|
112
|
+
iteration: number;
|
|
113
|
+
}): <T = unknown>(req: PauseRequest<T>) => Promise<T>;
|
|
91
114
|
/** Wipe the ordinal counters. Called by the runner at first-boot alongside
|
|
92
115
|
* `resetStateDir()`; a fresh module load already starts empty, so this is
|
|
93
116
|
* belt-and-braces for any in-process reuse. */
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* agent-<agentInstanceId>.json ← agent loop state (iteration, messages, processor cursor)
|
|
8
8
|
* runtime-<agentInstanceId>.json ← runtime-private blob (opaque to the loop)
|
|
9
|
-
* checkpoints/<name>.json ←
|
|
9
|
+
* checkpoints/<name>.json ← durable `ctx.step(name, fn)` memoised values (keyed `step<idx>.<name>`)
|
|
10
10
|
* pauses/<pauseId>.json ← pause records (request + resume payload once available)
|
|
11
11
|
*
|
|
12
12
|
* Atomic writes (write-tmp → fsync → rename) so a mid-write `sandbox.snapshot()`
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* covers the most common gating needs. More can be authored by users; these
|
|
4
4
|
* exist as load-bearing examples and as defaults for common policies.
|
|
5
5
|
*/
|
|
6
|
-
import type { Processor } from "./processor.js";
|
|
6
|
+
import type { Processor, ToolCall } from "./processor.js";
|
|
7
7
|
/**
|
|
8
8
|
* Reject any tool call whose name appears in `names`. The reason is returned
|
|
9
9
|
* to the model as the tool result, so the model can react and try a
|
|
@@ -14,6 +14,25 @@ import type { Processor } from "./processor.js";
|
|
|
14
14
|
* (candidate #1).
|
|
15
15
|
*/
|
|
16
16
|
export declare function denyTools(names: readonly string[]): Processor;
|
|
17
|
+
/**
|
|
18
|
+
* Pause the run for HUMAN APPROVAL before a matching tool executes — the
|
|
19
|
+
* human-in-the-loop pre-tool gate (ADR-0006 `ctx.pause`). Over an ACP runtime
|
|
20
|
+
* this is exactly the `session/request_permission` path: the CLI asks to run a
|
|
21
|
+
* tool, the gate runs here, and `ctx.pause` snapshots the workflow and waits
|
|
22
|
+
* durably until a reviewer resolves the pause with `{ approved, reason? }`.
|
|
23
|
+
* Approve → the tool runs; deny → the reason is returned to the model as the
|
|
24
|
+
* tool result and the loop continues.
|
|
25
|
+
*
|
|
26
|
+
* tools? — only these tool names require approval (default: EVERY tool call).
|
|
27
|
+
* reason? — build the human-facing prompt from the call (default names the tool).
|
|
28
|
+
*
|
|
29
|
+
* Dormant on runtimes without pre-tool gating; active on those that wire
|
|
30
|
+
* `processToolCall` (the ACP CLI runtimes, the Claude pre-tool hook, Vercel).
|
|
31
|
+
*/
|
|
32
|
+
export declare function humanApproval(opts?: {
|
|
33
|
+
tools?: readonly string[];
|
|
34
|
+
reason?: (call: ToolCall) => string;
|
|
35
|
+
}): Processor;
|
|
17
36
|
/**
|
|
18
37
|
* Require the calling API key to carry every scope in `required`. Aborts the
|
|
19
38
|
* agent loop with a clear reason if any are missing — defence-in-depth on
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate-pause processor (ADR-0028).
|
|
3
|
+
*
|
|
4
|
+
* A pre-tool gate that, when its `policy` flags a tool call as needing human
|
|
5
|
+
* approval, pauses the run as a SERVER operation (`requestPauseAndAwait`) and
|
|
6
|
+
* blocks until a human answers — then allows or denies the tool.
|
|
7
|
+
*
|
|
8
|
+
* It pauses through the server pause API (`requestPauseAndAwait`), so the pause
|
|
9
|
+
* is a server operation no `try/catch` can swallow. It does NOT use today's
|
|
10
|
+
* `ctx.pause`, which still throws `PauseSignal` — exactly the swallowable path
|
|
11
|
+
* ADR-0028 supersedes for agents. (Routing `ctx.pause` itself through this same
|
|
12
|
+
* server pause is the natural unification — ADR-0028 open question — at which
|
|
13
|
+
* point "the pause API" and "ctx.pause" become one thing.) Because it lives in
|
|
14
|
+
* the shared `gateToolCall` chain, one implementation covers every runtime — the
|
|
15
|
+
* Claude Agent SDK `PreToolUse` hook and the ACP `session/request_permission`
|
|
16
|
+
* path both route through it.
|
|
17
|
+
*
|
|
18
|
+
* Opt-in: the workflow/agent supplies the `policy` (which may be a heuristic or
|
|
19
|
+
* an async classifier agent). The connection defaults to the run credential in
|
|
20
|
+
* the sandbox env; absent it (local / non-sandbox), the gate is a no-op and
|
|
21
|
+
* tool calls pass through untouched.
|
|
22
|
+
*/
|
|
23
|
+
import type { Processor, ProcessorContext, ToolCall } from "./processor.js";
|
|
24
|
+
export interface GatePauseApproval {
|
|
25
|
+
/** Human-readable question shown on the approval UI. */
|
|
26
|
+
reason: string;
|
|
27
|
+
/** Decision options the human picks from. Defaults to Approve / Deny. */
|
|
28
|
+
options?: Array<{
|
|
29
|
+
id: string;
|
|
30
|
+
label: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
/** Decide whether a tool call needs human approval. Return `false` to let it
|
|
34
|
+
* through untouched, or an approval request to pause the run until a human
|
|
35
|
+
* answers. May be async (e.g. a small classifier agent). */
|
|
36
|
+
export type GatePausePolicy = (call: ToolCall, ctx: ProcessorContext) => (false | GatePauseApproval) | Promise<false | GatePauseApproval>;
|
|
37
|
+
export interface GatePauseConnection {
|
|
38
|
+
baseUrl: string;
|
|
39
|
+
token: string;
|
|
40
|
+
runId: string;
|
|
41
|
+
}
|
|
42
|
+
export declare function createGatePauseProcessor(opts: {
|
|
43
|
+
policy: GatePausePolicy;
|
|
44
|
+
/** Override the server connection (defaults to the sandbox run-credential env). */
|
|
45
|
+
connection?: GatePauseConnection;
|
|
46
|
+
}): Processor;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type { Processor, ProcessorContext, ProcessorVerdict, ToolCall, } from "./processor.js";
|
|
2
2
|
export { Verdict } from "./processor.js";
|
|
3
3
|
export { runProcessorChain } from "./runner.js";
|
|
4
|
-
export { denyTools, requireScope, redactPattern, } from "./builtins.js";
|
|
4
|
+
export { denyTools, humanApproval, requireScope, redactPattern, } from "./builtins.js";
|
|
5
|
+
export { createGatePauseProcessor } from "./gate-pause.js";
|
|
6
|
+
export type { GatePausePolicy, GatePauseApproval, GatePauseConnection } from "./gate-pause.js";
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import type { AgentMessage } from "../types/protocol.js";
|
|
31
31
|
import type { RequestContext } from "../request-context/request-context.js";
|
|
32
|
+
import type { PauseRequest } from "../pause/pause-core.js";
|
|
32
33
|
/** Proposed tool call. Mirrors the relevant fields of AgentMessageToolUse but
|
|
33
34
|
* lives as its own type so runtime adapters (candidate #1) can populate it
|
|
34
35
|
* from their internal call shape without coupling to the agent message protocol. */
|
|
@@ -50,6 +51,18 @@ export interface ProcessorContext {
|
|
|
50
51
|
agentId: string;
|
|
51
52
|
/** Iteration of the agent loop (1-based). */
|
|
52
53
|
iteration: number;
|
|
54
|
+
/**
|
|
55
|
+
* Pause the run from inside a processor — the human-approval primitive for a
|
|
56
|
+
* pre-tool-use gate (ADR-0006 §"reachable from every user code path";
|
|
57
|
+
* ADR-0020 the ACP `session/request_permission` path). On the fresh pass it
|
|
58
|
+
* throws `PauseSignal` (internal control flow) so the workflow snapshots and
|
|
59
|
+
* waits durably; on step re-entry after resolution it returns the resume
|
|
60
|
+
* payload (validated against `req.schema` if given). The agent loop / runtime
|
|
61
|
+
* wires it to the run's pause boundary with a deterministic, resume-stable
|
|
62
|
+
* pauseId (keyed on this hook's agentId + iteration). Calling it outside a
|
|
63
|
+
* sandboxed workflow step throws — pause requires the runner's pause boundary.
|
|
64
|
+
*/
|
|
65
|
+
pause<T = unknown>(req: PauseRequest<T>): Promise<T>;
|
|
53
66
|
}
|
|
54
67
|
/**
|
|
55
68
|
* Verdict returned by every processor method.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP client peer — the JSON-RPC half of a CLI-agent runtime (WS-C / ADR-0020).
|
|
3
|
+
*
|
|
4
|
+
* `CliAgentRunner` spawns an agentic CLI in **ACP agent** mode and delegates the
|
|
5
|
+
* whole wire protocol to this class. We are the ACP *Client*; the CLI is the
|
|
6
|
+
* *Agent*. `AcpClientPeer` owns:
|
|
7
|
+
*
|
|
8
|
+
* - the `ClientSideConnection` (from `@agentclientprotocol/sdk`) over an
|
|
9
|
+
* `ndJsonStream`-framed duplex wired to the subprocess stdio;
|
|
10
|
+
* - the SINGLE `session/update` → `AgentMessage` normaliser (replacing every
|
|
11
|
+
* per-CLI `mapEvent`) — one place, defensive, never throws on an unknown
|
|
12
|
+
* `sessionUpdate` variant (returns `[]`);
|
|
13
|
+
* - the `session/request_permission` handler — builds a `ToolCall`, runs it
|
|
14
|
+
* through the runner's `gateToolCall` (the shared processor chain), and maps
|
|
15
|
+
* the verdict back to a `PermissionOption.optionId`. A `PauseSignal` thrown
|
|
16
|
+
* by a processor MUST propagate (recognised via `isPauseSignal`) — never
|
|
17
|
+
* swallowed by a JSON-RPC try/catch, or the `run_pauses` row is silently
|
|
18
|
+
* lost (ADR-0020 Q4 / ADR-0006);
|
|
19
|
+
* - `session/cancel` driven by the `sendMessage` `AbortSignal`.
|
|
20
|
+
*
|
|
21
|
+
* We advertise NO `fs` / `terminal` client capabilities: the CLI runs in-sandbox
|
|
22
|
+
* with direct access to the factory drive, so we never serve file or terminal
|
|
23
|
+
* ops. We register no handlers for them — the `Client` interface methods are
|
|
24
|
+
* simply absent, so a (conformant) agent won't request them.
|
|
25
|
+
*
|
|
26
|
+
* The wire `protocolVersion` is pinned at integer 1 at `initialize`; the
|
|
27
|
+
* package version is pinned separately. Version negotiation drives the
|
|
28
|
+
* capability fallback that lives in `CliAgentRunner` (a non-1 response → the
|
|
29
|
+
* runner closes this peer and falls back to the legacy JSONL path).
|
|
30
|
+
*/
|
|
31
|
+
import { RequestError, type Stream } from "@agentclientprotocol/sdk";
|
|
32
|
+
import type * as acp from "@agentclientprotocol/sdk";
|
|
33
|
+
import type { AgentMessage } from "../types/protocol.js";
|
|
34
|
+
import type { ProcessorContext, ToolCall } from "../processors/processor.js";
|
|
35
|
+
import type { ToolCallGateResult } from "../types/runtime.js";
|
|
36
|
+
/** The wire protocol version we pin at `initialize`. Bumped only on a breaking
|
|
37
|
+
* ACP wire change — never silently. The package version is pinned separately
|
|
38
|
+
* in package.json; this integer is the real compatibility gate (ADR-0020). */
|
|
39
|
+
export declare const ACP_PROTOCOL_VERSION = 1;
|
|
40
|
+
/** Collaborators `AcpClientPeer` needs from `CliAgentRunner`, kept as a small
|
|
41
|
+
* injected surface so the peer stays transport-agnostic and unit-testable. */
|
|
42
|
+
export interface AcpClientPeerDeps {
|
|
43
|
+
/** Duplex ACP stream (typically `ndJsonStream(stdinWritable, stdoutReadable)`
|
|
44
|
+
* wired to the subprocess). The peer does not own the subprocess — only the
|
|
45
|
+
* protocol over this stream. */
|
|
46
|
+
stream: Stream;
|
|
47
|
+
/** Run a proposed tool call through the runner's processor chain. Mirrors
|
|
48
|
+
* `ClaudeRunner.gateToolCall`. May throw `PauseSignal` (human-approval
|
|
49
|
+
* processor) — the peer re-raises it, never swallows it. */
|
|
50
|
+
gateToolCall(call: ToolCall, ctx: ProcessorContext): Promise<ToolCallGateResult>;
|
|
51
|
+
/** PROVIDER for the current turn's processor context (requestContext, abort
|
|
52
|
+
* signal, agentId, iteration). A provider — not a frozen value — because the
|
|
53
|
+
* peer is long-lived (persistent session) and reused across turns; it must
|
|
54
|
+
* read the LIVE turn's context so a permission->pause binds to the right
|
|
55
|
+
* iteration. The runner refreshes the backing value before each turn. */
|
|
56
|
+
procCtx: () => ProcessorContext;
|
|
57
|
+
/** Turn-scoped abort signal from `sendMessage`. On abort the peer sends
|
|
58
|
+
* `session/cancel` for the live session. */
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Map ONE ACP `session/update` to zero-or-more `AgentMessage`s. The single
|
|
63
|
+
* normaliser (ADR-0020) — defensive: any variant a given CLI never emits is
|
|
64
|
+
* simply absent → `[]`, and an unknown `sessionUpdate` discriminator returns
|
|
65
|
+
* `[]` rather than throwing (mirrors the legacy `mapEvent` `default: return []`).
|
|
66
|
+
*
|
|
67
|
+
* Lifecycle (`init` / `done`) and turn-level errors are synthesised by the
|
|
68
|
+
* runner / `prompt()` result, NOT here. Per-turn token usage rides the
|
|
69
|
+
* `session/prompt` result (`PromptResponse.usage`), not `usage_update` — the
|
|
70
|
+
* `usage_update` session update is a context-window/cost gauge with no per-token
|
|
71
|
+
* fields, so it maps to nothing (ADR-0020 Q1 verified against the live zod types).
|
|
72
|
+
*/
|
|
73
|
+
export declare function normalizeSessionUpdate(update: acp.SessionUpdate): AgentMessage[];
|
|
74
|
+
export declare class AcpClientPeer {
|
|
75
|
+
private readonly deps;
|
|
76
|
+
private readonly conn;
|
|
77
|
+
/** Messages mapped from inbound `session/update` notifications, drained by the
|
|
78
|
+
* active `prompt()` call. REPLACED with a fresh queue at the start of every
|
|
79
|
+
* turn: an `AsyncQueue` is single-use (once `close()`d it stays drained), so a
|
|
80
|
+
* long-lived peer reused across turns must hand each turn its own queue or the
|
|
81
|
+
* second turn's updates would be silently dropped. The inbound `sessionUpdate`
|
|
82
|
+
* handler always pushes onto whatever the current queue is. */
|
|
83
|
+
private updates;
|
|
84
|
+
/** Live session id (from `session/new` / `session/resume`), needed by
|
|
85
|
+
* `session/cancel`. */
|
|
86
|
+
private sessionId;
|
|
87
|
+
/** Captured `PauseSignal` thrown out of the permission handler. The JSON-RPC
|
|
88
|
+
* layer would otherwise convert the throw into an error response; we instead
|
|
89
|
+
* stash it and re-raise it from `prompt()` so it unwinds to `serveStep`. */
|
|
90
|
+
private pendingPause;
|
|
91
|
+
private cancelled;
|
|
92
|
+
/** Trips the in-flight `session/prompt` turn when `cancel()` / `close()` fires.
|
|
93
|
+
* The agent is supposed to answer `cancelled` after `session/cancel`, but a
|
|
94
|
+
* fully-silent / dead CLI may never resolve the prompt request — so the abort
|
|
95
|
+
* latch unblocks `prompt()` deterministically instead of hanging on the
|
|
96
|
+
* outstanding JSON-RPC request. `null` between turns. */
|
|
97
|
+
private abortPrompt;
|
|
98
|
+
constructor(deps: AcpClientPeerDeps);
|
|
99
|
+
/**
|
|
100
|
+
* Negotiate the wire protocol at `initialize`. Pins `protocolVersion` to
|
|
101
|
+
* integer 1 and advertises NO fs / terminal capabilities. Returns the
|
|
102
|
+
* version the agent negotiated — `CliAgentRunner` compares it to
|
|
103
|
+
* `ACP_PROTOCOL_VERSION` to decide ACP-path vs. legacy-JSONL fallback.
|
|
104
|
+
*/
|
|
105
|
+
initialize(): Promise<{
|
|
106
|
+
protocolVersion: number;
|
|
107
|
+
}>;
|
|
108
|
+
/**
|
|
109
|
+
* Start a session and return its id (threaded back onto the synthesised
|
|
110
|
+
* `init` / `done`). Resume is via the CLI's own persisted thread/rollout on
|
|
111
|
+
* the sandbox FS keyed by this id — not an ACP `session/resume` round-trip;
|
|
112
|
+
* the re-issued turn on resume continues that thread (ADR-0020 Q4, validated
|
|
113
|
+
* in the manual live step).
|
|
114
|
+
*/
|
|
115
|
+
startSession(cwd: string): Promise<string>;
|
|
116
|
+
/**
|
|
117
|
+
* Drive one `session/prompt` turn, yielding `AgentMessage`s as the agent
|
|
118
|
+
* streams `session/update` notifications. Resolves the generator when the
|
|
119
|
+
* prompt result lands (clean `stopReason`), or yields `{type:"error"}` on a
|
|
120
|
+
* `refusal` stop reason or a JSON-RPC error. A `cancelled` stop reason ends
|
|
121
|
+
* the turn with no error. Re-raises any `PauseSignal` captured by the
|
|
122
|
+
* permission handler so it unwinds past `sendMessage` to `serveStep`.
|
|
123
|
+
*/
|
|
124
|
+
prompt(text: string): AsyncGenerator<AgentMessage>;
|
|
125
|
+
/** The live session id, or undefined before `startSession`. Threaded back
|
|
126
|
+
* onto the synthesised `init` / `done` by the runner. */
|
|
127
|
+
get currentSessionId(): string | undefined;
|
|
128
|
+
/** Send `session/cancel` for the live session (notification, fire-and-forget).
|
|
129
|
+
* Idempotent. The pending `session/prompt` is expected to resolve
|
|
130
|
+
* `cancelled`; any outstanding `session/request_permission` resolves with
|
|
131
|
+
* `outcome:"cancelled"` via the handler's signal check. */
|
|
132
|
+
cancel(): void;
|
|
133
|
+
/** Tear down the connection. Trips the abort latch so any in-flight `prompt()`
|
|
134
|
+
* turn unblocks, and closes the updates queue. Used both on clean turn end and
|
|
135
|
+
* when the runner falls back to the legacy JSONL path after a version
|
|
136
|
+
* mismatch. */
|
|
137
|
+
close(): void;
|
|
138
|
+
private onRequestPermission;
|
|
139
|
+
}
|
|
140
|
+
export { RequestError };
|
|
@@ -23,9 +23,28 @@
|
|
|
23
23
|
* (see each spec's `authEnv`); `commands.run` inherits the sandbox env, so
|
|
24
24
|
* values set via `agentc secrets set` are visible to the CLI.
|
|
25
25
|
*/
|
|
26
|
-
import type { AgentMessage, ModelExecutionContract, RuntimeOptions, SandboxProvider } from "../index.js";
|
|
26
|
+
import type { AgentMessage, ModelExecutionContract, RuntimeOptions, SandboxProvider, ToolCallGateResult } from "../index.js";
|
|
27
|
+
import type { ProcessorContext, ToolCall } from "../processors/processor.js";
|
|
27
28
|
/** Single-quote a value for safe interpolation into a `sh -c` command line. */
|
|
28
29
|
export declare function shellQuote(value: string): string;
|
|
30
|
+
/** Hard deadline (ms) for the ACP `initialize` handshake AND the prompt turn.
|
|
31
|
+
* `peer.initialize()` resolves only when the agent answers; a real CLI that
|
|
32
|
+
* never speaks ACP (or blocks on stdin) would otherwise hang forever and the
|
|
33
|
+
* JSONL fallback would never be reached. On timeout we tear the peer down and
|
|
34
|
+
* route to JSONL exactly like a handshake error. Overridable via the env for
|
|
35
|
+
* ops tuning; defaults sane. */
|
|
36
|
+
export declare const ACP_HANDSHAKE_TIMEOUT_MS: number;
|
|
37
|
+
/** Readiness gate for the LIVE ACP attempt. The duplex-stdin transport in
|
|
38
|
+
* `spawnAcpProcess` is now real (`commands.spawnDuplex` on the local provider),
|
|
39
|
+
* so the agent's `initialize` request bytes are delivered and the handshake can
|
|
40
|
+
* actually negotiate. The transport is live; this is `true`.
|
|
41
|
+
*
|
|
42
|
+
* Readiness alone is not sufficient — the runner still requires the live
|
|
43
|
+
* provider to expose `commands.spawnDuplex`. A provider without it (the
|
|
44
|
+
* server→sandbox vercel/e2b view never spawns the in-VM CLI, so neither
|
|
45
|
+
* implements duplex) falls back to JSONL cleanly via the capability check in
|
|
46
|
+
* `sendMessage`. */
|
|
47
|
+
export declare const ACP_TRANSPORT_READY = true;
|
|
29
48
|
/** Per-CLI behaviour. The base owns the lifecycle (init/done/error) and the
|
|
30
49
|
* transport (spawn + JSONL parse); a spec owns the CLI-specific bits. */
|
|
31
50
|
export interface CliAgentSpec {
|
|
@@ -59,11 +78,27 @@ export interface CliAgentSpec {
|
|
|
59
78
|
}): string;
|
|
60
79
|
/** Map one parsed JSONL stdout event to `AgentMessage`s. The base emits
|
|
61
80
|
* `init`/`done`/`error` lifecycle itself, so a spec maps only content +
|
|
62
|
-
* usage (text / thinking / tool_use / tool_result / usage).
|
|
81
|
+
* usage (text / thinking / tool_use / tool_result / usage).
|
|
82
|
+
*
|
|
83
|
+
* LEGACY FALLBACK PATH. Retained for a pinned-old CLI that predates ACP
|
|
84
|
+
* support (the `initialize` handshake negotiates a non-1 version → the
|
|
85
|
+
* runner closes the ACP peer and drives this JSONL path instead). Removed
|
|
86
|
+
* only after the last subprocess CLI is on ACP (ADR-0020 increment 5). */
|
|
63
87
|
mapEvent(parsed: Record<string, unknown>): AgentMessage[];
|
|
64
88
|
/** Pull a session/thread id out of a parsed event so the next turn can
|
|
65
|
-
* resume it (codex `thread.started.thread_id`, amp `session_id`).
|
|
89
|
+
* resume it (codex `thread.started.thread_id`, amp `session_id`).
|
|
90
|
+
* LEGACY FALLBACK PATH (see `mapEvent`). */
|
|
66
91
|
extractSessionId(parsed: Record<string, unknown>): string | undefined;
|
|
92
|
+
/** ACP-mode invocation (the Zed `agent_servers` shape). When present, the
|
|
93
|
+
* runner spawns the CLI in ACP agent mode and delegates the whole wire
|
|
94
|
+
* protocol to `AcpClientPeer`; the legacy `promptPayload`/`buildCommand`/
|
|
95
|
+
* `extractSessionId`/`mapEvent` members are used only as the version-mismatch
|
|
96
|
+
* fallback. Absent → the spec is JSONL-only (legacy path always). */
|
|
97
|
+
acp?: {
|
|
98
|
+
command: string;
|
|
99
|
+
args: string[];
|
|
100
|
+
env?: Record<string, string>;
|
|
101
|
+
};
|
|
67
102
|
}
|
|
68
103
|
export declare class CliAgentRunner implements ModelExecutionContract {
|
|
69
104
|
private readonly sandbox;
|
|
@@ -71,8 +106,60 @@ export declare class CliAgentRunner implements ModelExecutionContract {
|
|
|
71
106
|
private readonly spec;
|
|
72
107
|
private readonly configModel?;
|
|
73
108
|
readonly kind: string;
|
|
109
|
+
/** Whether the live ACP path will actually run for this runner: the spec
|
|
110
|
+
* declares an `acp` invocation, the transport is ready, AND the live provider
|
|
111
|
+
* exposes the duplex-stdin primitive ACP needs. A provider without
|
|
112
|
+
* `commands.spawnDuplex` (the server→sandbox vercel/e2b view) can't carry the
|
|
113
|
+
* JSON-RPC duplex, so the runner falls back to JSONL — and in that state the
|
|
114
|
+
* CLI owns its own tool loop with no pre-tool seam. */
|
|
115
|
+
private get acpCapable();
|
|
116
|
+
/** ACP-capable specs gate tools through the shared processor chain (the CLI
|
|
117
|
+
* no longer owns its own unsupervised tool loop). True ONLY when the ACP path
|
|
118
|
+
* will actually run — a JSONL-only spec, an ACP spec while the transport is
|
|
119
|
+
* dormant (`acpTransportReady` false), OR a provider that can't spawn a duplex
|
|
120
|
+
* leaves the CLI in charge with NO pre-tool seam, so this must report false in
|
|
121
|
+
* those states. Otherwise the agent loop would suppress its "processToolCall
|
|
122
|
+
* gating is dormant" warning and a registered deny/gate processor would be
|
|
123
|
+
* silently un-enforced for the agent. */
|
|
124
|
+
get supportsToolCallProcessor(): boolean;
|
|
125
|
+
/** Once the handshake has decided ACP vs. legacy for this runner instance,
|
|
126
|
+
* cache it so every later turn skips re-probing. `undefined` = not yet
|
|
127
|
+
* decided. */
|
|
128
|
+
private acpDecision;
|
|
129
|
+
/** The persistent ACP session, established lazily on the first ACP turn and
|
|
130
|
+
* reused across every later turn (a real ACP agent is a LONG-LIVED JSON-RPC
|
|
131
|
+
* server — one process, one initialize, one session — driven turn-by-turn via
|
|
132
|
+
* `session/prompt`, NOT respawned per turn). `undefined` until the first
|
|
133
|
+
* successful handshake; never re-set after a fallback (we switch to JSONL
|
|
134
|
+
* for the whole runner instead). */
|
|
135
|
+
private acpSession;
|
|
136
|
+
/** The CURRENT turn's processor context. The long-lived ACP peer is built
|
|
137
|
+
* once but reused across turns, so it reads the gate context through a
|
|
138
|
+
* provider (`procCtx: () => this.currentAcpProcCtx`) rather than a frozen
|
|
139
|
+
* value — `sendMessageAcp` refreshes this before each prompt so a
|
|
140
|
+
* permission->pause binds to THIS turn's iteration (stable pauseId on
|
|
141
|
+
* resume), not the first turn's. */
|
|
142
|
+
private currentAcpProcCtx;
|
|
143
|
+
/** Per-instance copy of the live-ACP readiness gate (defaults to the module
|
|
144
|
+
* const `ACP_TRANSPORT_READY`). Tests that drive the ACP lifecycle/fallback/
|
|
145
|
+
* pause/parity paths flip it on via `enableAcpForTesting()` so they keep
|
|
146
|
+
* exercising the real `sendMessageAcp` even while the production default is
|
|
147
|
+
* dormant. Not a public API. */
|
|
148
|
+
private acpTransportReady;
|
|
74
149
|
constructor(sandbox: SandboxProvider, options: RuntimeOptions, spec: CliAgentSpec, configModel?: string | undefined);
|
|
150
|
+
/** TEST-ONLY: flip the live-ACP readiness gate on for this runner instance so
|
|
151
|
+
* the ACP lifecycle / fallback tests can exercise `sendMessageAcp` while the
|
|
152
|
+
* production default (`ACP_TRANSPORT_READY`) stays dormant. Not part of the
|
|
153
|
+
* public runtime surface. */
|
|
154
|
+
enableAcpForTesting(): void;
|
|
75
155
|
get model(): string | undefined;
|
|
156
|
+
/** Runtime-owned pre-tool gate. Runs the shared processor chain and maps its
|
|
157
|
+
* verdict to a `ToolCallGateResult` — identical to `ClaudeRunner.gateToolCall`.
|
|
158
|
+
* Invoked by `AcpClientPeer`'s `session/request_permission` handler. A
|
|
159
|
+
* processor that throws `PauseSignal` propagates through unchanged (the
|
|
160
|
+
* handler re-raises it; never swallowed). */
|
|
161
|
+
gateToolCall(call: ToolCall, ctx: ProcessorContext): Promise<ToolCallGateResult>;
|
|
162
|
+
private buildProcCtx;
|
|
76
163
|
private installed;
|
|
77
164
|
/** Provision the CLI on demand. A no-op once `bin` is on PATH — the steady
|
|
78
165
|
* state under `snapshots: { bootFrom: "reuse" }`, where the first run's
|
|
@@ -85,5 +172,70 @@ export declare class CliAgentRunner implements ModelExecutionContract {
|
|
|
85
172
|
iteration?: number;
|
|
86
173
|
signal?: AbortSignal;
|
|
87
174
|
}): AsyncGenerator<AgentMessage>;
|
|
175
|
+
/**
|
|
176
|
+
* Tear down the failed/probed ACP process on a fallback path: group-kill the
|
|
177
|
+
* subprocess (so the npx-spawned grandchild dies too, not just `sh`) and
|
|
178
|
+
* `.catch()` its `exited` promise so the abandoned process can't surface an
|
|
179
|
+
* unhandled rejection once we've stopped awaiting it. A best-effort stdin
|
|
180
|
+
* close is attempted first — a conformant CLI exits cleanly on EOF — but we
|
|
181
|
+
* rely on the group-kill for force-termination of one that won't.
|
|
182
|
+
*/
|
|
183
|
+
private teardownAcp;
|
|
184
|
+
/**
|
|
185
|
+
* Establish the persistent ACP session for this runner instance, ONCE,
|
|
186
|
+
* lazily on the first ACP turn: spawn the long-lived ACP agent process, run
|
|
187
|
+
* `initialize`, and open the single `session/new`. The result is cached on the
|
|
188
|
+
* instance and reused by every later turn — a real ACP agent
|
|
189
|
+
* (`npx @agentclientprotocol/codex-acp`) is a long-lived JSON-RPC server that
|
|
190
|
+
* does NOT exit after one turn, so respawning per turn would both hang on
|
|
191
|
+
* `proc.exited` and break multi-turn continuity (each turn would open a fresh
|
|
192
|
+
* session and lose the thread).
|
|
193
|
+
*
|
|
194
|
+
* Returns the cached session, or `null` to signal the version-mismatch /
|
|
195
|
+
* handshake-error / stall fallback — in which case the failed process has
|
|
196
|
+
* already been torn down (group-killed + exited-catch) by `teardownAcp`, and
|
|
197
|
+
* the caller yields `ACP_FALLBACK`. The persistent process is otherwise killed
|
|
198
|
+
* only on the abort signal (wired in `spawnAcpProcess`) and is reclaimed when
|
|
199
|
+
* the sandbox VM is torn down at run-end — we never block on its exit.
|
|
200
|
+
*/
|
|
201
|
+
private ensureAcpSession;
|
|
202
|
+
/**
|
|
203
|
+
* ACP transport, ONE turn. On the FIRST turn it lazily establishes the
|
|
204
|
+
* persistent session (`ensureAcpSession`); every later turn REUSES that same
|
|
205
|
+
* process + peer + session id — `session/prompt` against the live session, no
|
|
206
|
+
* respawn, no new session. On a handshake error / version mismatch / session
|
|
207
|
+
* open failure (first turn only) it yields `ACP_FALLBACK` so `sendMessage`
|
|
208
|
+
* switches to the JSONL path. Never yields a duplicate `init` (the caller
|
|
209
|
+
* already did). CRITICALLY: never `await proc.exited` — the ACP agent is a
|
|
210
|
+
* long-lived server that does not exit after a turn; blocking on its exit
|
|
211
|
+
* would hang the loop forever.
|
|
212
|
+
*/
|
|
213
|
+
private sendMessageAcp;
|
|
214
|
+
/**
|
|
215
|
+
* Spawn the CLI in ACP agent mode and wrap its stdio as an `ndJsonStream`
|
|
216
|
+
* duplex.
|
|
217
|
+
*
|
|
218
|
+
* The real stdin/stdout duplex comes from `commands.spawnDuplex` — the in-VM
|
|
219
|
+
* `child_process` pipe (`makeLocalSandboxProvider`). The runner runs INSIDE
|
|
220
|
+
* the sandbox VM and spawns the CLI via the local provider, so a duplex stdin
|
|
221
|
+
* is a normal pipe and works identically on Vercel/E2B/local (the
|
|
222
|
+
* @vercel/sandbox no-stdin limit is the server→sandbox boundary, not this
|
|
223
|
+
* one). `ndJsonStream(stdin, stdout)` then carries JSON-RPC both ways:
|
|
224
|
+
* outbound `initialize`/`session/*` request frames down stdin, inbound
|
|
225
|
+
* `session/update` notifications + results up stdout.
|
|
226
|
+
*
|
|
227
|
+
* Caller contract: only invoked when `commands.spawnDuplex` is present (the
|
|
228
|
+
* capability check in `sendMessage` gates the whole ACP attempt on it), so a
|
|
229
|
+
* provider without duplex never reaches here — it falls back to JSONL.
|
|
230
|
+
*/
|
|
231
|
+
private spawnAcpProcess;
|
|
232
|
+
/**
|
|
233
|
+
* Legacy JSONL transport (the version-mismatch fallback, ADR-0020 increment
|
|
234
|
+
* 5 retires it). Unchanged behaviour: write the prompt to a file, run the
|
|
235
|
+
* one-shot command, line-buffer stdout, `mapEvent` each parsed line, and cap
|
|
236
|
+
* with the runner-synthesised `done`. Does NOT re-yield `init` (the caller
|
|
237
|
+
* already did).
|
|
238
|
+
*/
|
|
239
|
+
private sendMessageJsonl;
|
|
88
240
|
}
|
|
89
241
|
export declare function createCliAgentRuntime(spec: CliAgentSpec, configModel?: string): import("../index.js").AgentRuntime<SandboxProvider>;
|
package/dist/runtimes/amp.d.ts
CHANGED
|
@@ -18,6 +18,6 @@ export interface AmpRuntimeConfig {
|
|
|
18
18
|
/** Amp uses its configured model; reserved for forward-compatibility. */
|
|
19
19
|
model?: string;
|
|
20
20
|
}
|
|
21
|
-
export declare function createAmpRuntime(config?: AmpRuntimeConfig): import("../index.js").AgentRuntime<import("../
|
|
22
|
-
declare const _default: import("../index.js").AgentRuntime<import("../
|
|
21
|
+
export declare function createAmpRuntime(config?: AmpRuntimeConfig): import("../index.js").AgentRuntime<import("../index.js").SandboxProvider>;
|
|
22
|
+
declare const _default: import("../index.js").AgentRuntime<import("../index.js").SandboxProvider>;
|
|
23
23
|
export default _default;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WS-C / ADR-0020 Increment 2 — END-TO-END transport tests for the live ACP
|
|
3
|
+
* path. Unlike `cli-agent.test.ts` (which drives `AcpClientPeer` over an
|
|
4
|
+
* in-memory cross-piped `TransformStream` against a fake `AgentSideConnection`
|
|
5
|
+
* in the SAME process), these spawn a REAL child process — the committed
|
|
6
|
+
* `__fixtures__/fake-acp-agent.mjs` — through
|
|
7
|
+
* `makeLocalSandboxProvider().commands.spawnDuplex` and drive a full
|
|
8
|
+
* `CliAgentRunner.sendMessage` turn against it.
|
|
9
|
+
*
|
|
10
|
+
* Why this matters: the in-memory fixtures never exercise stdin *delivery*. The
|
|
11
|
+
* historical no-duplex-stdin reality (the @vercel/sandbox server→sandbox
|
|
12
|
+
* boundary) meant a spawned "ACP" process never received the client's
|
|
13
|
+
* `initialize` frame, so the handshake hung and the runner fell back to JSONL.
|
|
14
|
+
* Increment 2's `commands.spawnDuplex` on the local provider is a genuine
|
|
15
|
+
* `child_process` pipe; these tests prove the JSON-RPC duplex round-trips over
|
|
16
|
+
* real OS pipes — `initialize` / `session/new` / `session/prompt` frames go
|
|
17
|
+
* DOWN the child's stdin and `session/update` notifications + results come back
|
|
18
|
+
* UP its stdout — end to end, with no in-memory shortcut.
|
|
19
|
+
*
|
|
20
|
+
* Three scenarios, one fake agent (mode selected via `FAKE_ACP_MODE`):
|
|
21
|
+
* 1. full turn — init → thinking/text/tool_use/tool_result/plan/usage → done
|
|
22
|
+
* 2. cancel mid-turn — AbortSignal fires; session/cancel is sent; turn unwinds
|
|
23
|
+
* 3. deny-gate — a denyTools-style processor rejects a tool over ACP
|
|
24
|
+
* WITHOUT ctx.pause (gateToolCall deny → reject option)
|
|
25
|
+
*
|
|
26
|
+
* The in-memory normaliser/lifecycle/fallback/pause suites in
|
|
27
|
+
* `cli-agent.test.ts` stay green — they validate the mapping; these validate
|
|
28
|
+
* the wire.
|
|
29
|
+
*/
|
|
30
|
+
export {};
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* WS-C / ADR-0020 — fixture-based parity + lifecycle tests for the ACP path.
|
|
3
|
+
*
|
|
4
|
+
* There is NO live Codex/Amp/Gemini CLI in the test sandbox, so parity is
|
|
5
|
+
* proven against fixtures rather than a running agent (design doc §Parity-test
|
|
6
|
+
* strategy). Two equivalent fixture sets live under `__fixtures__/`:
|
|
7
|
+
*
|
|
8
|
+
* - `acp/codex/<case>.ndjson` — ACP `session/update` notifications (one per
|
|
9
|
+
* line), plus, for the usage/error cases, a single `PromptResponse`.
|
|
10
|
+
* - `jsonl/codex/<case>.jsonl` — the EQUIVALENT legacy codex JSONL events the
|
|
11
|
+
* current `codexSpec.mapEvent` consumes.
|
|
12
|
+
*
|
|
13
|
+
* The parity gate feeds each ACP fixture through the single `normalizeSessionUpdate`
|
|
14
|
+
* normaliser and the JSONL fixture through `codexSpec.mapEvent`, then asserts the
|
|
15
|
+
* two `AgentMessage` arrays are EQUAL MODULO `timestamp` (timestamps are
|
|
16
|
+
* now()-derived; we strip them before comparing). New-kind cases (`plan`,
|
|
17
|
+
* structured `diffs`) have no legacy counterpart — they assert the normaliser
|
|
18
|
+
* output against a golden directly.
|
|
19
|
+
*
|
|
20
|
+
* The lifecycle / fallback / pause suites drive the REAL `AcpClientPeer` over an
|
|
21
|
+
* in-memory `ndJsonStream` duplex against a fake `AgentSideConnection`, so the
|
|
22
|
+
* synthesised init/done ordering, the version-1 handshake fallback, and the
|
|
23
|
+
* PauseSignal-propagation invariant are exercised end-to-end with no subprocess.
|
|
8
24
|
*/
|
|
9
25
|
export {};
|
package/dist/runtimes/codex.d.ts
CHANGED
|
@@ -12,10 +12,15 @@
|
|
|
12
12
|
* Verified against codex-cli 0.124.0: `codex exec --json` + resume-by-thread,
|
|
13
13
|
* with the command_execution / reasoning / agent_message item shapes below.
|
|
14
14
|
*/
|
|
15
|
+
import { type CliAgentSpec } from "./_cli-agent.js";
|
|
16
|
+
/** Exported for the fixture-based parity tests (ADR-0020): the legacy JSONL
|
|
17
|
+
* `mapEvent` is the golden the ACP normaliser is asserted equal to. Not part
|
|
18
|
+
* of the public runtime surface — `createCodexRuntime` stays the entry point. */
|
|
19
|
+
export declare const codexSpec: CliAgentSpec;
|
|
15
20
|
export interface CodexRuntimeConfig {
|
|
16
21
|
/** Codex model id (`-m`). Omit to use the codex CLI's configured default. */
|
|
17
22
|
model?: string;
|
|
18
23
|
}
|
|
19
|
-
export declare function createCodexRuntime(config?: CodexRuntimeConfig): import("../index.js").AgentRuntime<import("../
|
|
20
|
-
declare const _default: import("../index.js").AgentRuntime<import("../
|
|
24
|
+
export declare function createCodexRuntime(config?: CodexRuntimeConfig): import("../index.js").AgentRuntime<import("../index.js").SandboxProvider>;
|
|
25
|
+
declare const _default: import("../index.js").AgentRuntime<import("../index.js").SandboxProvider>;
|
|
21
26
|
export default _default;
|