@agent-compose/sdk 0.5.1 → 0.5.5
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/active-step.d.ts +60 -0
- package/dist/agent/agent-loop-steer.test.d.ts +1 -0
- package/dist/agent/agent-loop.d.ts +46 -0
- package/dist/agent/async-queue.d.ts +29 -0
- package/dist/agent/protocol.d.ts +9 -1
- package/dist/agent/resolve-agent-id.test.d.ts +1 -0
- package/dist/agent/run-agent.d.ts +16 -3
- package/dist/agent/steer-control.d.ts +57 -0
- package/dist/agent/steer-control.test.d.ts +1 -0
- package/dist/client.d.ts +161 -0
- package/dist/index.d.ts +16 -6
- package/dist/index.js +1586 -158
- package/dist/pause/__tests__/agent-loop-checkpoint.test.d.ts +1 -0
- package/dist/pause/__tests__/checkpoint.test.d.ts +1 -0
- package/dist/pause/__tests__/errors.test.d.ts +1 -0
- package/dist/pause/__tests__/manager.test.d.ts +1 -0
- package/dist/pause/__tests__/pause-core.test.d.ts +1 -0
- package/dist/pause/__tests__/state-dir.test.d.ts +1 -0
- package/dist/pause/__tests__/wrappers.test.d.ts +1 -0
- package/dist/pause/checkpoint.d.ts +28 -0
- package/dist/pause/errors.d.ts +52 -0
- package/dist/pause/manager.d.ts +63 -0
- package/dist/pause/pause-core.d.ts +101 -0
- package/dist/pause/state-dir.d.ts +80 -0
- package/dist/pause/wrappers.d.ts +41 -0
- package/dist/request-context/request-context.d.ts +12 -0
- package/dist/runtimes/_cli-agent.d.ts +72 -0
- package/dist/runtimes/amp.d.ts +22 -0
- package/dist/runtimes/claude.d.ts +6 -0
- package/dist/runtimes/codex.d.ts +20 -0
- package/dist/runtimes/openai-desktop.d.ts +2 -0
- package/dist/runtimes/openai-desktop.js +1578 -157
- package/dist/runtimes/vercel.d.ts +53 -1
- package/dist/runtimes/vercel.js +60 -8
- package/dist/runtimes/vercel.test.d.ts +1 -0
- package/dist/sse.d.ts +2 -3
- package/dist/step-invocation/index.d.ts +2 -2
- package/dist/step-invocation/invoker.d.ts +3 -0
- package/dist/step-invocation/protocol.d.ts +12 -0
- package/dist/step-invocation/server.d.ts +1 -0
- package/dist/step-invocation/types.d.ts +40 -5
- package/dist/types/events.d.ts +9 -0
- package/dist/types/execution-context.d.ts +25 -0
- package/dist/types/protocol.d.ts +8 -0
- package/dist/types/runtime.d.ts +55 -0
- package/dist/types/sandbox.d.ts +4 -4
- package/dist/utils/schemas.d.ts +2 -0
- package/dist/workflow-steps/__tests__/pause-wiring.test.d.ts +1 -0
- package/dist/workflow-steps/index.d.ts +2 -0
- package/dist/workflow-steps/observability.d.ts +43 -11
- package/dist/workflow-steps/run-callback.d.ts +39 -0
- package/dist/workflow-steps/runner.d.ts +8 -0
- package/package.json +1 -1
- package/src/active-step.ts +124 -0
- package/src/agent/agent-loop.ts +253 -19
- package/src/agent/async-queue.ts +61 -0
- package/src/agent/protocol.ts +12 -2
- package/src/agent/run-agent.ts +184 -8
- package/src/agent/steer-control.ts +125 -0
- package/src/client.ts +277 -0
- package/src/index.ts +38 -4
- package/src/pause/checkpoint.ts +44 -0
- package/src/pause/errors.ts +70 -0
- package/src/pause/manager.ts +177 -0
- package/src/pause/pause-core.ts +267 -0
- package/src/pause/state-dir.ts +262 -0
- package/src/pause/wrappers.ts +79 -0
- package/src/request-context/request-context.ts +17 -2
- package/src/runtimes/_cli-agent.ts +161 -0
- package/src/runtimes/amp.ts +94 -0
- package/src/runtimes/claude.ts +101 -6
- package/src/runtimes/codex.ts +109 -0
- package/src/runtimes/openai-desktop.ts +11 -0
- package/src/runtimes/vercel.ts +78 -2
- package/src/sandbox.ts +39 -20
- package/src/sse.ts +8 -6
- package/src/step-invocation/index.ts +2 -1
- package/src/step-invocation/invoker.ts +107 -29
- package/src/step-invocation/protocol.ts +16 -0
- package/src/step-invocation/server.ts +45 -12
- package/src/step-invocation/types.ts +43 -7
- package/src/tools/coding.ts +16 -5
- package/src/types/events.ts +9 -0
- package/src/types/execution-context.ts +25 -0
- package/src/types/protocol.ts +8 -0
- package/src/types/runtime.ts +52 -0
- package/src/types/sandbox.ts +8 -4
- package/src/types/workflow.ts +6 -1
- package/src/utils/bundler.ts +8 -3
- package/src/utils/schemas.ts +2 -0
- package/src/workflow-steps/index.ts +3 -0
- package/src/workflow-steps/observability.ts +84 -13
- package/src/workflow-steps/run-callback.ts +72 -0
- package/src/workflow-steps/runner.ts +70 -8
- package/dist/utils/discovery.d.ts +0 -2
- package/src/utils/discovery.ts +0 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ctx.checkpoint(name, fn)` — disk-backed memoise across pause-resume.
|
|
3
|
+
*
|
|
4
|
+
* First call: `fn()` runs, the result is atomically written to
|
|
5
|
+
* `/tmp/wf/state/checkpoints/<name>.json`. On any subsequent invocation
|
|
6
|
+
* of the same step body (e.g. after pause-resume re-enters the
|
|
7
|
+
* subprocess), the file exists in the restored sandbox; `fn` is NOT
|
|
8
|
+
* called, the recorded value is returned.
|
|
9
|
+
*
|
|
10
|
+
* Use for: large context loads, expensive deterministic transforms,
|
|
11
|
+
* idempotent-but-slow computations that you want to skip on resume.
|
|
12
|
+
*
|
|
13
|
+
* Do NOT use for: side effects with external systems (DB writes, HTTP
|
|
14
|
+
* POSTs, sending email). Those need `ctx.invokeChild(...)` — Temporal's
|
|
15
|
+
* exactly-once guarantee covers cross-process side effects; disk-backed
|
|
16
|
+
* memoisation is in-sandbox only and would silently retry on a sandbox
|
|
17
|
+
* recreation that lost the checkpoint file.
|
|
18
|
+
*
|
|
19
|
+
* See ADR-0006 §"`ctx.checkpoint(name, fn)` — disk-backed memoisation".
|
|
20
|
+
*/
|
|
21
|
+
/** Read-or-run-and-write helper. `name` is validated by the state-dir
|
|
22
|
+
* layer (rejects path-escape attempts); `fn` is awaited if it returns
|
|
23
|
+
* a Promise so sync and async callers compose identically. */
|
|
24
|
+
export declare function checkpoint<T>(name: string, fn: () => Promise<T> | T): Promise<T>;
|
|
25
|
+
/** Bind user checkpoint names to a runner-owned namespace. The user's
|
|
26
|
+
* `name` is still validated independently so a scope prefix cannot turn
|
|
27
|
+
* an empty or path-escaping name into a valid filename. */
|
|
28
|
+
export declare function scopedCheckpoint(scope: string): typeof checkpoint;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-facing pause errors (ADR-0006 / ADR-0011).
|
|
3
|
+
*
|
|
4
|
+
* These are thrown into USER step/agent code when a pause cannot return a
|
|
5
|
+
* value — the caller can catch them. They are distinct from `PauseSignal`
|
|
6
|
+
* (the internal control-flow signal in `pause-core.ts` that unwinds the
|
|
7
|
+
* stack to `serveStep`): `PauseSignal` never reaches user code, these do.
|
|
8
|
+
*
|
|
9
|
+
* Each carries a stable `code` so the activity boundary and the dashboard
|
|
10
|
+
* can classify the failure without string-matching the message — the same
|
|
11
|
+
* pattern as `StepExecutionError` (`step-invocation/types.ts`).
|
|
12
|
+
*
|
|
13
|
+
* `target: ESNext` (sdk/tsconfig.json) makes `extends Error` + `instanceof`
|
|
14
|
+
* work without a manual prototype fix; `this.name = new.target.name` gives
|
|
15
|
+
* each subclass its own class name on the instance.
|
|
16
|
+
*/
|
|
17
|
+
/** Stable, machine-readable discriminator for each pause error. */
|
|
18
|
+
export type PauseErrorCode = "pause_expired" | "pause_schema" | "pause_request";
|
|
19
|
+
/** Base for every user-facing pause error. Not thrown directly. */
|
|
20
|
+
export declare abstract class PauseError extends Error {
|
|
21
|
+
abstract readonly code: PauseErrorCode;
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The pause's TTL elapsed before it was resolved, and `onExpiry` was
|
|
26
|
+
* `{ mode: "throw" }` (the default). When `onExpiry` is
|
|
27
|
+
* `{ mode: "resolve", value }`, `ctx.pause` returns `value` instead of
|
|
28
|
+
* throwing this.
|
|
29
|
+
*/
|
|
30
|
+
export declare class PauseExpiredError extends PauseError {
|
|
31
|
+
readonly code = "pause_expired";
|
|
32
|
+
constructor(message?: string);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The resume payload failed the caller's `schema`. `issues` carries the
|
|
36
|
+
* validation detail for an in-sandbox `catch`; note only `message` survives
|
|
37
|
+
* the serveStep wire boundary, so do not rely on `issues` outside the
|
|
38
|
+
* throwing process.
|
|
39
|
+
*/
|
|
40
|
+
export declare class PauseSchemaError extends PauseError {
|
|
41
|
+
readonly code = "pause_schema";
|
|
42
|
+
readonly issues: unknown;
|
|
43
|
+
constructor(message: string, issues?: unknown);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The pause request itself is invalid before it can be emitted to the engine:
|
|
47
|
+
* empty reason, non-positive ttl, invalid kind, or a non-JSON payload.
|
|
48
|
+
*/
|
|
49
|
+
export declare class PauseRequestError extends PauseError {
|
|
50
|
+
readonly code = "pause_request";
|
|
51
|
+
constructor(message?: string);
|
|
52
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local pause/resume manager.
|
|
3
|
+
*
|
|
4
|
+
* This module owns the SDK-local state machine that makes a re-entered
|
|
5
|
+
* runner subprocess pick up agent work correctly. It deliberately does not
|
|
6
|
+
* know about Temporal, Postgres, HTTP resume routes, or dashboard feeds.
|
|
7
|
+
* Those layers own durable wait/audit state; this layer owns only the
|
|
8
|
+
* `/tmp/wf/state` files captured by sandbox snapshots.
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentStatus } from "../types/protocol.js";
|
|
11
|
+
import type { ModelExecutionContract } from "../types/runtime.js";
|
|
12
|
+
export interface AgentLoopProgressState {
|
|
13
|
+
iteration: number;
|
|
14
|
+
completedIterations: number;
|
|
15
|
+
lastSessionId: string;
|
|
16
|
+
lastStatus: AgentStatus | null;
|
|
17
|
+
lastResponseText: string;
|
|
18
|
+
lastResponseValidationError: string;
|
|
19
|
+
iterationsWithoutStatus: number;
|
|
20
|
+
blockerStreak: {
|
|
21
|
+
key: string;
|
|
22
|
+
count: number;
|
|
23
|
+
} | null;
|
|
24
|
+
/** PR 7 steer-pause intent — see RunningAgentLoopStateSchema. Optional so the
|
|
25
|
+
* loop can omit it until it wires steer (the schema defaults it to null). */
|
|
26
|
+
pendingSteerPause?: {
|
|
27
|
+
reason: string;
|
|
28
|
+
correlationKey: string | null;
|
|
29
|
+
at: number;
|
|
30
|
+
} | null;
|
|
31
|
+
}
|
|
32
|
+
export interface SettledAgentLoopResult<TResponse = unknown> {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
lastStatus: AgentStatus | null;
|
|
35
|
+
iterations: number;
|
|
36
|
+
response?: TResponse;
|
|
37
|
+
}
|
|
38
|
+
export type AgentLoopRestore<TResponse = unknown> = {
|
|
39
|
+
kind: "fresh";
|
|
40
|
+
} | {
|
|
41
|
+
kind: "invalid";
|
|
42
|
+
error: string;
|
|
43
|
+
} | {
|
|
44
|
+
kind: "running";
|
|
45
|
+
state: AgentLoopProgressState;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "settled";
|
|
48
|
+
result: SettledAgentLoopResult<TResponse>;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Coordinates the agent-loop files in `/tmp/wf/state`.
|
|
52
|
+
*
|
|
53
|
+
* Raw filesystem safety lives in `state-dir.ts`; runtime blob shape lives in
|
|
54
|
+
* each runtime adapter. `PauseManager` is the seam that binds those two to
|
|
55
|
+
* the agent-loop state machine.
|
|
56
|
+
*/
|
|
57
|
+
export declare class PauseManager {
|
|
58
|
+
private readonly agentId;
|
|
59
|
+
constructor(agentId: string);
|
|
60
|
+
restoreAgentLoop<TResponse = unknown>(runtime: ModelExecutionContract): Promise<AgentLoopRestore<TResponse>>;
|
|
61
|
+
saveRunningAgentLoop(state: AgentLoopProgressState, runtime: ModelExecutionContract): Promise<void>;
|
|
62
|
+
saveSettledAgentLoop<TResponse = unknown>(result: SettledAgentLoopResult<TResponse>): Promise<void>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pause-core` — the engine behind `ctx.pause` (ADR-0006 / ADR-0011 PR 6).
|
|
3
|
+
*
|
|
4
|
+
* How a pause works (and why this shape):
|
|
5
|
+
*
|
|
6
|
+
* First reach (fresh): read `pauses/<pauseId>.json` → absent/unresolved →
|
|
7
|
+
* write the placeholder record and `throw new PauseSignal(...)`. The
|
|
8
|
+
* exception unwinds the step body to `serveStep`, which emits the
|
|
9
|
+
* `__AC_STEP_PAUSE__` sentinel and exits 0. The engine snapshots, inserts
|
|
10
|
+
* a `run_pauses` row keyed by `pauseId`, and waits durably.
|
|
11
|
+
*
|
|
12
|
+
* Re-entry (resolved): the engine has written the resolution onto the same
|
|
13
|
+
* file. The runner re-runs the step body from the top in a fresh
|
|
14
|
+
* subprocess; the SAME `ctx.pause` call recomputes the SAME `pauseId`,
|
|
15
|
+
* reads the file, and returns the resume payload (or applies `onExpiry`).
|
|
16
|
+
*
|
|
17
|
+
* The whole thing hinges on `pauseId` being recomputed identically across the
|
|
18
|
+
* exit/re-enter boundary, with NO surviving in-process state. So `pauseId` is
|
|
19
|
+
* a **deterministic UUIDv5** of a stable coordinate — never random. UUIDv5
|
|
20
|
+
* (not a free-form string) because `run_pauses.id` is a `uuid` PRIMARY KEY.
|
|
21
|
+
*
|
|
22
|
+
* `PauseSignal` is internal control flow, NOT a user-facing error — `serveStep`
|
|
23
|
+
* and the agent loop must let it propagate (the user-facing `Pause*Error` classes in
|
|
24
|
+
* `errors.ts` are the ones user code catches). It deliberately does not extend
|
|
25
|
+
* `Error`, so a stray `catch (e) { if (e instanceof Error) … }` cannot quietly
|
|
26
|
+
* absorb it; callers add explicit `instanceof PauseSignal` re-throw guards.
|
|
27
|
+
*
|
|
28
|
+
* Runs in the runner subprocess (may use `node:crypto`), never in the Temporal
|
|
29
|
+
* workflow VM.
|
|
30
|
+
*/
|
|
31
|
+
import type { z } from "zod";
|
|
32
|
+
import { type StepPauseRequest } from "../step-invocation/types.js";
|
|
33
|
+
import type { StepObservability } from "../workflow-steps/observability.js";
|
|
34
|
+
/** Public `ctx.pause` argument shape (ADR-0006 §"SDK surface"). `schema` and
|
|
35
|
+
* `onExpiry` are SDK-local — consumed here, never sent on the wire. The wire
|
|
36
|
+
* `kind` is NOT a public field: a raw `ctx.pause` is always `kind: "custom"`;
|
|
37
|
+
* `decision`/`sleep`/`event` come only from the wrappers. */
|
|
38
|
+
export interface PauseRequest<T = unknown> {
|
|
39
|
+
reason: string;
|
|
40
|
+
payload?: Record<string, unknown>;
|
|
41
|
+
schema?: z.ZodType<T>;
|
|
42
|
+
ttlMs?: number;
|
|
43
|
+
onExpiry?: {
|
|
44
|
+
mode: "throw";
|
|
45
|
+
} | {
|
|
46
|
+
mode: "resolve";
|
|
47
|
+
value: T;
|
|
48
|
+
};
|
|
49
|
+
correlationKey?: string;
|
|
50
|
+
snapshot?: boolean;
|
|
51
|
+
}
|
|
52
|
+
/** The coordinate that makes `pauseId` deterministic + re-entry-stable. */
|
|
53
|
+
export interface PauseCoordinate {
|
|
54
|
+
runId: string;
|
|
55
|
+
stepIndex: number;
|
|
56
|
+
/** Present when the pause fires inside an agent loop (PR 7 steer-pause).
|
|
57
|
+
* Pins the id to the loop boundary (agentId + iteration) so it stays stable
|
|
58
|
+
* across the loop's iteration-skipping on resume — a bare per-step ordinal
|
|
59
|
+
* desyncs because completed iterations don't re-run, landing the ordinal at
|
|
60
|
+
* a different value. Step-body pauses leave it absent (the `:bare` form). */
|
|
61
|
+
agentScope?: {
|
|
62
|
+
agentId: string;
|
|
63
|
+
iteration: number;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Realm-global brand so `isPauseSignal` recognises a PauseSignal thrown by a
|
|
67
|
+
* DIFFERENT SDK instance. A bundled workflow inlines its own SDK copy (its
|
|
68
|
+
* `agent()` steer-pause throws the bundle's `PauseSignal`), but the runner
|
|
69
|
+
* catches with its own class — a plain `instanceof` is false across that
|
|
70
|
+
* boundary, so the pause was mis-classified as a step failure and no
|
|
71
|
+
* `run_pauses` row was written. `ctx.pause` avoided this only because its
|
|
72
|
+
* signal is runner-thrown. `Symbol.for` is shared across the realm, so the
|
|
73
|
+
* brand survives the instance split. */
|
|
74
|
+
declare const PAUSE_SIGNAL_BRAND: unique symbol;
|
|
75
|
+
/**
|
|
76
|
+
* Internal control-flow signal thrown by `corePause` on a fresh pause.
|
|
77
|
+
* Caught by `serveStep` (emits the sentinel + exits) and re-thrown past every
|
|
78
|
+
* intermediate catch. NOT an `Error` and NOT user-facing.
|
|
79
|
+
*/
|
|
80
|
+
export declare class PauseSignal {
|
|
81
|
+
readonly pauseId: string;
|
|
82
|
+
readonly pauseRequest: StepPauseRequest;
|
|
83
|
+
readonly observability?: StepObservability | undefined;
|
|
84
|
+
readonly [PAUSE_SIGNAL_BRAND]: true;
|
|
85
|
+
constructor(pauseId: string, pauseRequest: StepPauseRequest, observability?: StepObservability | undefined);
|
|
86
|
+
}
|
|
87
|
+
/** Cross-instance-safe `PauseSignal` check. Use this instead of
|
|
88
|
+
* `err instanceof PauseSignal` anywhere a signal may have been thrown by a
|
|
89
|
+
* different SDK instance (the runner catching a bundled workflow's signal). */
|
|
90
|
+
export declare function isPauseSignal(err: unknown): err is PauseSignal;
|
|
91
|
+
/** Wipe the ordinal counters. Called by the runner at first-boot alongside
|
|
92
|
+
* `resetStateDir()`; a fresh module load already starts empty, so this is
|
|
93
|
+
* belt-and-braces for any in-process reuse. */
|
|
94
|
+
export declare function resetPauseOrdinals(): void;
|
|
95
|
+
/**
|
|
96
|
+
* The body of `ctx.pause`. On a fresh pause it throws `PauseSignal`; on
|
|
97
|
+
* re-entry it returns the resume payload (or applies `onExpiry` / throws
|
|
98
|
+
* `PauseSchemaError`). See the module header.
|
|
99
|
+
*/
|
|
100
|
+
export declare function corePause<T = unknown>(req: PauseRequest<T>, coord: PauseCoordinate, kind?: StepPauseRequest["kind"]): Promise<T>;
|
|
101
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pause state directory — durable per-agent / per-checkpoint / per-pause
|
|
3
|
+
* blobs the sandbox snapshot carries across pause-resume.
|
|
4
|
+
*
|
|
5
|
+
* Layout under `/tmp/wf/state/`:
|
|
6
|
+
*
|
|
7
|
+
* agent-<agentInstanceId>.json ← agent loop state (iteration, messages, processor cursor)
|
|
8
|
+
* runtime-<agentInstanceId>.json ← runtime-private blob (opaque to the loop)
|
|
9
|
+
* checkpoints/<name>.json ← user `ctx.checkpoint(name, fn)` memoised values
|
|
10
|
+
* pauses/<pauseId>.json ← pause records (request + resume payload once available)
|
|
11
|
+
*
|
|
12
|
+
* Atomic writes (write-tmp → fsync → rename) so a mid-write `sandbox.snapshot()`
|
|
13
|
+
* cannot capture partial state. Most read helpers return `null` on missing-file
|
|
14
|
+
* rather than throwing — callers branch on presence to distinguish "first
|
|
15
|
+
* run" from "post-resume restore". User checkpoints use an explicit
|
|
16
|
+
* found/value result because `null` is a valid memoised value.
|
|
17
|
+
*
|
|
18
|
+
* The state-dir layout is treated as wire protocol between the SDK
|
|
19
|
+
* runner (writer) and the SDK on the next subprocess invocation (reader).
|
|
20
|
+
* Changing a filename or shape is a breaking change to pause resumption
|
|
21
|
+
* for any in-flight paused workflow. See ADR-0006.
|
|
22
|
+
*/
|
|
23
|
+
/** Root of the pause state-dir. Defaults to `/tmp/wf/state` — the
|
|
24
|
+
* canonical location inside the sandbox. Tests and non-`/tmp`-writable
|
|
25
|
+
* environments can override via `AGENT_COMPOSE_STATE_DIR`.
|
|
26
|
+
*
|
|
27
|
+
* Read at CALL TIME, not module load. An earlier draft cached it on
|
|
28
|
+
* first import, but that turned out to be test-ordering-fragile: once
|
|
29
|
+
* any test imported state-dir (directly or transitively via
|
|
30
|
+
* agent-loop) the path got locked to whatever env was set at that
|
|
31
|
+
* instant, and a later test setting `AGENT_COMPOSE_STATE_DIR` would
|
|
32
|
+
* silently write to the cached path instead. Reading the env each
|
|
33
|
+
* call costs nothing measurable next to the fsync. */
|
|
34
|
+
export declare function getStateDir(): string;
|
|
35
|
+
export declare function assertSafeCheckpointName(name: string): void;
|
|
36
|
+
export type CheckpointRead<T> = {
|
|
37
|
+
found: true;
|
|
38
|
+
value: T;
|
|
39
|
+
} | {
|
|
40
|
+
found: false;
|
|
41
|
+
};
|
|
42
|
+
export declare function readAgentState<T = unknown>(agentInstanceId: string): Promise<T | null>;
|
|
43
|
+
export declare function writeAgentState(agentInstanceId: string, state: unknown): Promise<void>;
|
|
44
|
+
export declare function readRuntimeCheckpoint<T = unknown>(agentInstanceId: string): Promise<T | null>;
|
|
45
|
+
export declare function writeRuntimeCheckpoint(agentInstanceId: string, blob: unknown): Promise<void>;
|
|
46
|
+
export declare function readCheckpoint<T = unknown>(name: string): Promise<CheckpointRead<T>>;
|
|
47
|
+
export declare function writeCheckpoint(name: string, value: unknown): Promise<void>;
|
|
48
|
+
/** The `pauses/<pauseId>.json` file — the only durable handoff between the
|
|
49
|
+
* paused subprocess and the resumed one.
|
|
50
|
+
*
|
|
51
|
+
* Written twice in two execution contexts:
|
|
52
|
+
* - At pause time the runner writes the placeholder `{ pauseId, request }`
|
|
53
|
+
* (no `resolved`). The snapshot captures it.
|
|
54
|
+
* - At resume time the engine activity writes the resolution — usually
|
|
55
|
+
* merged on top of the placeholder, but snapshotless pauses may only have
|
|
56
|
+
* `{ resolved: true, resumePayload, expired }` in a fresh sandbox. Those
|
|
57
|
+
* three resolution fields are always present together so the byte shape
|
|
58
|
+
* distinguishes a real resume (`expired:false`) from a TTL expiry
|
|
59
|
+
* (`expired:true`) from an unresolved placeholder (`resolved` absent).
|
|
60
|
+
*
|
|
61
|
+
* `corePause` reads it on re-entry: `resolved !== true` ⇒ still my own
|
|
62
|
+
* placeholder, pause again; `expired` ⇒ apply `onExpiry`; else return
|
|
63
|
+
* `resumePayload`. */
|
|
64
|
+
export interface PauseRecord {
|
|
65
|
+
pauseId: string;
|
|
66
|
+
request: unknown;
|
|
67
|
+
/** Set true once the engine has written the resolution (resume or expiry). */
|
|
68
|
+
resolved?: boolean;
|
|
69
|
+
/** Value to return from `ctx.pause` on a real resume; null for expiry. */
|
|
70
|
+
resumePayload?: unknown;
|
|
71
|
+
/** True when the resolution is a TTL expiry, not a caller-driven resume. */
|
|
72
|
+
expired?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export declare function readPauseRecord(pauseId: string): Promise<PauseRecord | null>;
|
|
75
|
+
export declare function writePauseRecord(record: PauseRecord): Promise<void>;
|
|
76
|
+
/** Wipe the entire state directory. Used by the runner at first-boot
|
|
77
|
+
* (no pauseId in env) so a freshly-dispatched run never inherits state
|
|
78
|
+
* from a prior dispatch that shared the sandbox snapshot template.
|
|
79
|
+
* No-op when the directory doesn't exist. */
|
|
80
|
+
export declare function resetStateDir(): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three opinionated pause wrappers (ADR-0006 §"SDK surface"), each a thin
|
|
3
|
+
* closure over `ctx.pause`:
|
|
4
|
+
*
|
|
5
|
+
* - `requestDecision` — pause for a typed human/agent decision (schema required).
|
|
6
|
+
* - `sleep` — a lightweight timed pause; resolves on its own TTL,
|
|
7
|
+
* skips the snapshot, returns void.
|
|
8
|
+
* - `waitForEvent` — pause until an event resumes by correlation key.
|
|
9
|
+
*
|
|
10
|
+
* Built from a `PauseFn` so the wrapper logic lives in one place and the step
|
|
11
|
+
* runner just spreads them onto the context next to `pause`.
|
|
12
|
+
*/
|
|
13
|
+
import type { z } from "zod";
|
|
14
|
+
import type { StepPauseRequest } from "../step-invocation/types.js";
|
|
15
|
+
import type { PauseRequest } from "./pause-core.js";
|
|
16
|
+
/** The public `ctx.pause` callable (always `kind: "custom"`). */
|
|
17
|
+
export type PauseFn = <T = unknown>(req: PauseRequest<T>) => Promise<T>;
|
|
18
|
+
/** Internal: pause with an explicit wire `kind`. The wrappers stamp
|
|
19
|
+
* `decision`/`sleep`/`event` through this; the public `ctx.pause` is always
|
|
20
|
+
* `custom` and never exposes it. */
|
|
21
|
+
export type KindedPauseFn = <T = unknown>(req: PauseRequest<T>, kind: StepPauseRequest["kind"]) => Promise<T>;
|
|
22
|
+
export interface RequestDecisionRequest<T> {
|
|
23
|
+
reason: string;
|
|
24
|
+
payload?: Record<string, unknown>;
|
|
25
|
+
/** Required — a decision is always validated against a shape. */
|
|
26
|
+
schema: z.ZodType<T>;
|
|
27
|
+
ttlMs?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface WaitForEventRequest<T> {
|
|
30
|
+
reason: string;
|
|
31
|
+
/** Required — the by-key resume route targets this. */
|
|
32
|
+
correlationKey: string;
|
|
33
|
+
schema?: z.ZodType<T>;
|
|
34
|
+
ttlMs?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface PauseWrappers {
|
|
37
|
+
requestDecision<T>(req: RequestDecisionRequest<T>): Promise<T>;
|
|
38
|
+
sleep(durationMs: number): Promise<void>;
|
|
39
|
+
waitForEvent<T = unknown>(req: WaitForEventRequest<T>): Promise<T>;
|
|
40
|
+
}
|
|
41
|
+
export declare function buildPauseWrappers(pause: KindedPauseFn): PauseWrappers;
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
* `invokeChild` propagation: only reserved keys flow to the child; freeform
|
|
39
39
|
* is per-run by design (cross-run state must be explicit via `input`).
|
|
40
40
|
*/
|
|
41
|
+
import { z } from "zod";
|
|
41
42
|
/** Reserved key namespace prefix — anything starting with this is platform-owned. */
|
|
42
43
|
export declare const AC_RESERVED_PREFIX = "ac__";
|
|
43
44
|
export declare const AC_TEAM_ID: "ac__teamId";
|
|
@@ -77,6 +78,17 @@ export interface RequestContextWire {
|
|
|
77
78
|
};
|
|
78
79
|
user: Record<string, unknown>;
|
|
79
80
|
}
|
|
81
|
+
export declare const RequestContextWireSchema: z.ZodObject<{
|
|
82
|
+
reserved: z.ZodObject<{
|
|
83
|
+
teamId: z.ZodString;
|
|
84
|
+
runId: z.ZodString;
|
|
85
|
+
workflowId: z.ZodString;
|
|
86
|
+
factoryId: z.ZodNullable<z.ZodString>;
|
|
87
|
+
apiKeyScopes: z.ZodReadonly<z.ZodArray<z.ZodString>>;
|
|
88
|
+
parentRunId: z.ZodNullable<z.ZodString>;
|
|
89
|
+
}, z.core.$strip>;
|
|
90
|
+
user: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
91
|
+
}, z.core.$strip>;
|
|
80
92
|
/** Thrown when a caller tries to `set` or `delete` a reserved key downstream. */
|
|
81
93
|
export declare class ReservedKeyError extends Error {
|
|
82
94
|
readonly key: string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-agent runtime base — drive an external agentic coding CLI inside the
|
|
3
|
+
* sandbox and map its JSON-Lines (JSONL) stream onto the `AgentMessage`
|
|
4
|
+
* contract.
|
|
5
|
+
*
|
|
6
|
+
* This is a different *mechanism* from the other runtimes: `claudeRuntime`
|
|
7
|
+
* drives the Anthropic Agent SDK and `vercelRuntime` drives the Vercel AI SDK,
|
|
8
|
+
* but a CLI-agent runtime spawns the provider's own CLI (`codex exec --json`,
|
|
9
|
+
* `amp -x --stream-json`) — the CLI brings its own agent loop + tools, and we
|
|
10
|
+
* only stream-parse the events it prints. Codex and Amp are the first two; this
|
|
11
|
+
* base is the shared machinery (spawn → line-buffer stdout → JSONL parse →
|
|
12
|
+
* AsyncQueue bridge → init/usage/done/error lifecycle), parameterised per-CLI
|
|
13
|
+
* by a `CliAgentSpec`.
|
|
14
|
+
*
|
|
15
|
+
* Requirements (per spec): the provider CLI is installed in the sandbox image,
|
|
16
|
+
* and the provider's API key is present in the sandbox environment (see each
|
|
17
|
+
* spec's `authEnv`). `commands.run` inherits the sandbox env, so secrets set via
|
|
18
|
+
* `agentc secrets set` are visible to the CLI.
|
|
19
|
+
*
|
|
20
|
+
* NOTE: the per-CLI command construction + resume flags + exact event shapes in
|
|
21
|
+
* the shipped specs are mapped from each tool's docs and have NOT been verified
|
|
22
|
+
* against a live CLI run — verify before relying on them in production.
|
|
23
|
+
*/
|
|
24
|
+
import type { AgentMessage, ModelExecutionContract, RuntimeOptions, SandboxProvider } from "../index.js";
|
|
25
|
+
/** Single-quote a value for safe interpolation into a `sh -c` command line. */
|
|
26
|
+
export declare function shellQuote(value: string): string;
|
|
27
|
+
/** Per-CLI behaviour. The base owns the lifecycle (init/done/error) and the
|
|
28
|
+
* transport (spawn + JSONL parse); a spec owns the CLI-specific bits. */
|
|
29
|
+
export interface CliAgentSpec {
|
|
30
|
+
/** Runtime self-id surfaced on `agent.spawned` (dashboard runtime icon). */
|
|
31
|
+
kind: string;
|
|
32
|
+
/** Env var the CLI reads for auth — documentation only (must be set in the
|
|
33
|
+
* sandbox env via a workflow secret). */
|
|
34
|
+
authEnv: string;
|
|
35
|
+
/** Default model id when none is configured; omit to let the CLI choose. */
|
|
36
|
+
defaultModel?: string;
|
|
37
|
+
/** Serialise the user prompt into the bytes written to the prompt file —
|
|
38
|
+
* plain text for a CLI that reads the prompt from stdin (codex `-`), or a
|
|
39
|
+
* JSONL user message for a `--stream-json-input` CLI (amp). */
|
|
40
|
+
promptPayload(prompt: string): string;
|
|
41
|
+
/** Build the one-shot shell command for a turn. `promptPath` is a file in the
|
|
42
|
+
* sandbox holding `promptPayload(prompt)`; `sessionId` continues a thread. */
|
|
43
|
+
buildCommand(args: {
|
|
44
|
+
promptPath: string;
|
|
45
|
+
sessionId?: string;
|
|
46
|
+
model?: string;
|
|
47
|
+
cwd?: string;
|
|
48
|
+
}): string;
|
|
49
|
+
/** Map one parsed JSONL stdout event to `AgentMessage`s. The base emits
|
|
50
|
+
* `init`/`done`/`error` lifecycle itself, so a spec maps only content +
|
|
51
|
+
* usage (text / thinking / tool_use / tool_result / usage). */
|
|
52
|
+
mapEvent(parsed: Record<string, unknown>): AgentMessage[];
|
|
53
|
+
/** Pull a session/thread id out of a parsed event so the next turn can
|
|
54
|
+
* resume it (codex `thread.started.thread_id`, amp `session_id`). */
|
|
55
|
+
extractSessionId(parsed: Record<string, unknown>): string | undefined;
|
|
56
|
+
}
|
|
57
|
+
export declare class CliAgentRunner implements ModelExecutionContract {
|
|
58
|
+
private readonly sandbox;
|
|
59
|
+
private readonly options;
|
|
60
|
+
private readonly spec;
|
|
61
|
+
private readonly configModel?;
|
|
62
|
+
readonly kind: string;
|
|
63
|
+
constructor(sandbox: SandboxProvider, options: RuntimeOptions, spec: CliAgentSpec, configModel?: string | undefined);
|
|
64
|
+
get model(): string | undefined;
|
|
65
|
+
sendMessage(opts: {
|
|
66
|
+
prompt: string;
|
|
67
|
+
sessionId?: string;
|
|
68
|
+
iteration?: number;
|
|
69
|
+
signal?: AbortSignal;
|
|
70
|
+
}): AsyncGenerator<AgentMessage>;
|
|
71
|
+
}
|
|
72
|
+
export declare function createCliAgentRuntime(spec: CliAgentSpec, configModel?: string): import("../index.js").AgentRuntime<SandboxProvider>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amp CLI runtime — drives Sourcegraph's `amp -x --stream-json` agentic CLI
|
|
3
|
+
* inside the sandbox and maps its (Claude-Code-compatible) JSONL stream onto
|
|
4
|
+
* the AgentMessage contract. Built on the shared CLI-agent base; Amp brings its
|
|
5
|
+
* own loop + tools, so we only stream-parse what it prints.
|
|
6
|
+
*
|
|
7
|
+
* Auth: set `AMP_API_KEY` (`sgamp_…`) in the sandbox env via a workflow secret.
|
|
8
|
+
* Requires the `amp` CLI (`@ampcode/cli`) installed in the sandbox image. The
|
|
9
|
+
* model is chosen by the AMP_API_KEY account (e.g. a GPT-only token runs GPT);
|
|
10
|
+
* the runtime doesn't pin a model.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ NOT verified against a live `amp` run — the thread-continue syntax and the
|
|
13
|
+
* exact assistant/result shapes are mapped from the docs (ampcode.com). Verify
|
|
14
|
+
* before production use.
|
|
15
|
+
*/
|
|
16
|
+
export interface AmpRuntimeConfig {
|
|
17
|
+
/** Amp uses its configured model; reserved for forward-compatibility. */
|
|
18
|
+
model?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function createAmpRuntime(config?: AmpRuntimeConfig): import("../index.js").AgentRuntime<import("../sandbox.js").SandboxProvider>;
|
|
21
|
+
declare const _default: import("../index.js").AgentRuntime<import("../sandbox.js").SandboxProvider>;
|
|
22
|
+
export default _default;
|
|
@@ -26,13 +26,19 @@ export declare class ClaudeRunner implements ModelExecutionContract {
|
|
|
26
26
|
private readonly options;
|
|
27
27
|
private readonly config;
|
|
28
28
|
supportsToolCallProcessor: boolean;
|
|
29
|
+
readonly kind = "claude";
|
|
29
30
|
constructor(_sandbox: SandboxProvider, options?: RuntimeOptions, config?: ClaudeRuntimeConfig);
|
|
31
|
+
get model(): string;
|
|
30
32
|
gateToolCall(call: ToolCall, ctx: ProcessorContext): Promise<ToolCallGateResult>;
|
|
31
33
|
sendMessage(opts: {
|
|
32
34
|
prompt: string;
|
|
33
35
|
sessionId?: string;
|
|
34
36
|
iteration?: number;
|
|
35
37
|
signal?: AbortSignal;
|
|
38
|
+
inboxStream?: AsyncIterable<{
|
|
39
|
+
text: string;
|
|
40
|
+
senderName?: string | null;
|
|
41
|
+
}>;
|
|
36
42
|
}): AsyncGenerator<AgentMessage>;
|
|
37
43
|
}
|
|
38
44
|
export declare function createClaudeRuntime(config?: ClaudeRuntimeConfig): import("../index.js").AgentRuntime<SandboxProvider>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI runtime — drives OpenAI's `codex exec --json` agentic CLI inside
|
|
3
|
+
* the sandbox and maps its JSONL event stream onto the AgentMessage contract.
|
|
4
|
+
* Built on the shared CLI-agent base; Codex brings its own loop + tools, so we
|
|
5
|
+
* only stream-parse what it prints.
|
|
6
|
+
*
|
|
7
|
+
* Auth: set `CODEX_API_KEY` (or `OPENAI_API_KEY`) in the sandbox env via a
|
|
8
|
+
* workflow secret. Requires the `codex` CLI installed in the sandbox image.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ NOT verified against a live `codex` run — the resume flag and the exact
|
|
11
|
+
* item shapes (command_execution / reasoning fields) are mapped from the docs
|
|
12
|
+
* (developers.openai.com/codex/noninteractive). Verify before production use.
|
|
13
|
+
*/
|
|
14
|
+
export interface CodexRuntimeConfig {
|
|
15
|
+
/** Codex model id (`-m`). Omit to use the codex CLI's configured default. */
|
|
16
|
+
model?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function createCodexRuntime(config?: CodexRuntimeConfig): import("../index.js").AgentRuntime<import("../sandbox.js").SandboxProvider>;
|
|
19
|
+
declare const _default: import("../index.js").AgentRuntime<import("../sandbox.js").SandboxProvider>;
|
|
20
|
+
export default _default;
|
|
@@ -10,6 +10,8 @@ export interface OpenAIDesktopRunnerOptions extends RuntimeOptions {
|
|
|
10
10
|
}
|
|
11
11
|
export declare class OpenAIDesktopRunner implements ModelExecutionContract {
|
|
12
12
|
private sandbox;
|
|
13
|
+
readonly kind = "openai-desktop";
|
|
14
|
+
readonly model = "computer-use-preview";
|
|
13
15
|
private openai;
|
|
14
16
|
private label;
|
|
15
17
|
constructor(sandbox: DesktopSandboxProvider, opts: OpenAIDesktopRunnerOptions);
|