@agent-compose/sdk 0.7.0 → 0.8.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.
Files changed (116) hide show
  1. package/README.md +66 -39
  2. package/dist/agent/__tests__/runtime-json-schema.test.d.ts +10 -0
  3. package/dist/agent/agent-context.d.ts +21 -1
  4. package/dist/agent/agent-loop.d.ts +24 -1
  5. package/dist/client.d.ts +338 -534
  6. package/dist/directives.d.ts +112 -0
  7. package/dist/display.d.ts +242 -0
  8. package/dist/errors.d.ts +24 -1
  9. package/dist/index.d.ts +24 -12
  10. package/dist/index.js +3545 -1667
  11. package/dist/pause/wrappers.d.ts +31 -9
  12. package/dist/runtimes/_acp-client.d.ts +46 -1
  13. package/dist/runtimes/_cli-agent.d.ts +49 -4
  14. package/dist/runtimes/_jsonl-guard.d.ts +103 -0
  15. package/dist/runtimes/amp.d.ts +2 -2
  16. package/dist/runtimes/claude-code.d.ts +59 -0
  17. package/dist/runtimes/claude-code.test.d.ts +14 -0
  18. package/dist/runtimes/claude.d.ts +16 -0
  19. package/dist/runtimes/claude.test.d.ts +8 -0
  20. package/dist/runtimes/codex.d.ts +9 -3
  21. package/dist/runtimes/cursor.d.ts +2 -2
  22. package/dist/runtimes/droid.d.ts +2 -2
  23. package/dist/runtimes/jsonl-guard.test.d.ts +19 -0
  24. package/dist/runtimes/openai-desktop.js +2691 -864
  25. package/dist/runtimes/opencode.d.ts +2 -2
  26. package/dist/runtimes/vercel.js +12 -1
  27. package/dist/sandbox/devbox.d.ts +42 -0
  28. package/dist/sandbox/exec-stream.d.ts +14 -0
  29. package/dist/sandbox/network-policy.d.ts +100 -0
  30. package/dist/sandbox/provider-def.d.ts +79 -0
  31. package/dist/sandbox/providers/desktop.d.ts +10 -0
  32. package/dist/sandbox/providers/e2b.d.ts +17 -0
  33. package/dist/sandbox/providers/local.d.ts +11 -0
  34. package/dist/sandbox/providers/vercel.d.ts +18 -0
  35. package/dist/sandbox/registry.d.ts +45 -0
  36. package/dist/sandbox/sizes.d.ts +68 -0
  37. package/dist/sandbox.d.ts +24 -299
  38. package/dist/step-invocation/__tests__/foreground-recovery.test.d.ts +1 -0
  39. package/dist/step-invocation/invoker.d.ts +10 -0
  40. package/dist/step-invocation/protocol.d.ts +5 -0
  41. package/dist/types/api-compliance.d.ts +71 -0
  42. package/dist/types/api-conversations.d.ts +492 -0
  43. package/dist/types/api-factory.d.ts +309 -0
  44. package/dist/types/api-projects.d.ts +131 -0
  45. package/dist/types/api-runs.d.ts +377 -0
  46. package/dist/types/api-scopes.d.ts +102 -0
  47. package/dist/types/conversation-stream.d.ts +191 -0
  48. package/dist/types/execution-context.d.ts +12 -2
  49. package/dist/types/protocol.d.ts +30 -1
  50. package/dist/types/sandbox-environment.d.ts +8 -5
  51. package/dist/types/sandbox.d.ts +74 -4
  52. package/dist/types/workflow-metadata.d.ts +33 -8
  53. package/dist/types/workflow-plan.d.ts +10 -0
  54. package/dist/types/workflow.d.ts +18 -205
  55. package/dist/utils/bundler.d.ts +12 -1
  56. package/dist/workflow-steps/index.d.ts +1 -1
  57. package/dist/workflow-steps/observability.d.ts +8 -1
  58. package/dist/workflow-steps/runner.d.ts +3 -3
  59. package/dist/workflow-steps/step.d.ts +15 -1
  60. package/dist/workflow-steps/types.d.ts +19 -5
  61. package/dist/workflow-steps/workflow.d.ts +22 -1
  62. package/dist/workflows/engine.d.ts +3 -2
  63. package/dist/workflows/invoke-child.d.ts +2 -2
  64. package/package.json +1 -1
  65. package/src/agent/agent-context.ts +186 -3
  66. package/src/agent/agent-loop.ts +31 -2
  67. package/src/client.ts +909 -621
  68. package/src/directives.ts +184 -0
  69. package/src/display.ts +788 -0
  70. package/src/errors.ts +39 -0
  71. package/src/index.ts +104 -10
  72. package/src/pause/wrappers.ts +44 -9
  73. package/src/runtimes/_acp-client.ts +72 -3
  74. package/src/runtimes/_cli-agent.ts +159 -36
  75. package/src/runtimes/_jsonl-guard.ts +219 -0
  76. package/src/runtimes/claude-code.ts +246 -0
  77. package/src/runtimes/claude.ts +32 -2
  78. package/src/runtimes/codex.ts +55 -3
  79. package/src/runtimes/openai-desktop.ts +59 -14
  80. package/src/sandbox/devbox.ts +48 -0
  81. package/src/sandbox/exec-stream.ts +48 -0
  82. package/src/sandbox/network-policy.ts +181 -0
  83. package/src/sandbox/provider-def.ts +94 -0
  84. package/src/sandbox/providers/desktop.ts +57 -0
  85. package/src/sandbox/providers/e2b.ts +354 -0
  86. package/src/sandbox/providers/local.ts +106 -0
  87. package/src/sandbox/providers/vercel.ts +331 -0
  88. package/src/sandbox/registry.ts +198 -0
  89. package/src/sandbox/sizes.ts +95 -0
  90. package/src/sandbox.ts +59 -1275
  91. package/src/step-invocation/invoker.ts +151 -28
  92. package/src/step-invocation/protocol.ts +8 -0
  93. package/src/types/api-compliance.ts +79 -0
  94. package/src/types/api-conversations.ts +522 -0
  95. package/src/types/api-factory.ts +336 -0
  96. package/src/types/api-projects.ts +140 -0
  97. package/src/types/api-runs.ts +412 -0
  98. package/src/types/api-scopes.ts +102 -0
  99. package/src/types/conversation-stream.ts +231 -0
  100. package/src/types/execution-context.ts +10 -2
  101. package/src/types/protocol.ts +33 -0
  102. package/src/types/sandbox-environment.ts +28 -9
  103. package/src/types/sandbox.ts +73 -4
  104. package/src/types/workflow-metadata.ts +35 -8
  105. package/src/types/workflow-plan.ts +11 -0
  106. package/src/types/workflow.ts +25 -292
  107. package/src/utils/bundler.ts +32 -5
  108. package/src/utils/errors.ts +16 -1
  109. package/src/workflow-steps/index.ts +1 -0
  110. package/src/workflow-steps/observability.ts +19 -8
  111. package/src/workflow-steps/runner.ts +4 -4
  112. package/src/workflow-steps/step.ts +49 -1
  113. package/src/workflow-steps/types.ts +20 -5
  114. package/src/workflow-steps/workflow.ts +22 -1
  115. package/src/workflows/engine.ts +3 -2
  116. package/src/workflows/invoke-child.ts +2 -2
@@ -1,8 +1,8 @@
1
1
  /** Shared execution context capabilities for workflow functions and steps. */
2
- import type { InvokeAndWaitOptions, RunStatus } from "../client.js";
2
+ import type { InvokeAndWaitOptions, RunStatus } from "./api-runs.js";
3
3
  import type { RequestContext } from "../request-context/request-context.js";
4
4
  import type { PauseRequest } from "../pause/pause-core.js";
5
- import type { WaitForEventRequest } from "../pause/wrappers.js";
5
+ import type { DecisionRequest, WaitForEventRequest } from "../pause/wrappers.js";
6
6
  import type { SandboxProvider } from "./sandbox.js";
7
7
  /** The identity of this workflow run. */
8
8
  export interface WorkflowRun {
@@ -34,4 +34,14 @@ export interface BaseExecutionContext {
34
34
  sleep(durationMs: number): Promise<void>;
35
35
  /** Pause until an event resumes by `correlationKey`. Wrapper over `pause`. */
36
36
  waitForEvent<T = unknown>(req: WaitForEventRequest<T>): Promise<T>;
37
+ /**
38
+ * Pause for a typed human/agent decision (ADR-0042). The prompt, options,
39
+ * draft, and approver refs surface in the dashboard's decision UI (run
40
+ * page AND the spawning conversation's run card); resolves with the frozen
41
+ * `{ decision: string }` payload. With `approvers` named, only those team
42
+ * members can resolve it — server-enforced. Wrapper over `pause`.
43
+ */
44
+ requestDecision(req: DecisionRequest): Promise<{
45
+ decision: string;
46
+ }>;
37
47
  }
@@ -12,6 +12,16 @@ export interface AgentMessageText extends AgentMessageBase {
12
12
  type: "text";
13
13
  text: string;
14
14
  }
15
+ /** LIVE-ONLY incremental chunk of the in-progress assistant text block
16
+ * (claude stream-json `content_block_delta`/`text_delta` under
17
+ * `--include-partial-messages`). Consumers that stream progressively
18
+ * accumulate deltas; everyone else ignores them — the terminating `text`
19
+ * message always carries the COMPLETE block and is the only durable form.
20
+ * Additive kind: existing producers never emit it. */
21
+ export interface AgentMessageTextDelta extends AgentMessageBase {
22
+ type: "text_delta";
23
+ text: string;
24
+ }
15
25
  export interface AgentMessageThinking extends AgentMessageBase {
16
26
  type: "thinking";
17
27
  text: string;
@@ -62,6 +72,25 @@ export interface AgentMessageUsage extends AgentMessageBase {
62
72
  durationMs: number;
63
73
  numTurns: number;
64
74
  }
75
+ /** LIVE-ONLY incremental usage off the harness's raw provider stream — the
76
+ * `text_delta` of token counts. claude-code's `--include-partial-messages`
77
+ * stream events carry per-model-call usage (`message_start` /
78
+ * `message_delta`) that the terminal `usage` report only totals at turn
79
+ * end; this forwards them so a streaming consumer (the cloud executor's
80
+ * live token counter) can tick in real time. Semantics per model call
81
+ * within the turn: `boundary: "call_start"` carries the call's input-side
82
+ * finals (input + cache tokens, known at call start); `"call_delta"`
83
+ * carries the call's CUMULATIVE output tokens so far. Never durable and
84
+ * never a substitute for `usage` — the agent loop drops it exactly like
85
+ * `text_delta`. Additive kind: existing producers never emit it. */
86
+ export interface AgentMessageUsageDelta extends AgentMessageBase {
87
+ type: "usage_delta";
88
+ boundary: "call_start" | "call_delta";
89
+ inputTokens: number;
90
+ outputTokens: number;
91
+ cacheReadTokens: number;
92
+ cacheCreationTokens: number;
93
+ }
65
94
  /** Structured execution plan emitted by an agent (ACP `plan` session update,
66
95
  * WS-C / ADR-0020 Q2). Each `plan` notification REPLACES the whole plan — the
67
96
  * normaliser emits one `AgentMessagePlan` per notification carrying the entire
@@ -76,7 +105,7 @@ export interface AgentMessagePlan extends AgentMessageBase {
76
105
  status: "pending" | "in_progress" | "completed";
77
106
  }[];
78
107
  }
79
- export type AgentMessage = AgentMessageInit | AgentMessageText | AgentMessageThinking | AgentMessageToolUse | AgentMessageToolResult | AgentMessageDone | AgentMessageError | AgentMessageUsage | AgentMessagePlan;
108
+ export type AgentMessage = AgentMessageInit | AgentMessageText | AgentMessageTextDelta | AgentMessageThinking | AgentMessageToolUse | AgentMessageToolResult | AgentMessageDone | AgentMessageError | AgentMessageUsage | AgentMessageUsageDelta | AgentMessagePlan;
80
109
  /** Status block the agent emits to signal iteration completion or blockers. */
81
110
  export interface AgentStatus {
82
111
  summary: string;
@@ -10,9 +10,10 @@
10
10
  *
11
11
  * snapshots: { bootFrom: { snapshotId: "snap_..." } }
12
12
  *
13
- * `defineSandboxEnvironment` is sugar over `defineWorkflow` — it makes the
14
- * setup recipe read like an imperative script by supplying the local
15
- * sandbox provider (the runner's own VM) to the callback.
13
+ * `defineSandboxEnvironment` is sugar over the step builder — it wraps the
14
+ * `setup` callback as a single-step workflow, supplying the local sandbox
15
+ * provider (the runner's own VM) so the recipe reads like an imperative
16
+ * script.
16
17
  *
17
18
  * Typical flow:
18
19
  * 1. Author a setup file with `defineSandboxEnvironment`.
@@ -51,7 +52,9 @@ export interface SandboxEnvironmentDefinition {
51
52
  * agent-env requires `resources: { provider: "e2b" }`. */
52
53
  resources?: SandboxResources;
53
54
  }
54
- /** Sugar over `defineWorkflow` for setup-only workflows that exist to
55
+ /** Sugar over the step builder for setup-only workflows that exist to
55
56
  * capture a snapshot. The workflow takes no meaningful input and returns
56
- * nothing — its value is the side effect on the sandbox VM. */
57
+ * nothing — its value is the side effect on the sandbox VM. Both schemas
58
+ * are deliberately `z.unknown()` so no input/output contract is captured
59
+ * into template metadata (there is nothing to render). */
57
60
  export declare function defineSandboxEnvironment(env: SandboxEnvironmentDefinition): Workflow<Record<string, unknown>, void>;
@@ -74,6 +74,38 @@ export interface SandboxSpawnDuplexOptions {
74
74
  cwd?: string;
75
75
  envs?: Record<string, string>;
76
76
  }
77
+ /** Options for opening a brokered interactive PTY in the sandbox
78
+ * (ADR-0055 — the session Terminal surface). */
79
+ export interface SandboxPtyOpts {
80
+ cols: number;
81
+ rows: number;
82
+ /** Working directory the shell opens in. Omitted → the guest user's home. */
83
+ cwd?: string;
84
+ /** Extra env for the shell. */
85
+ envs?: Record<string, string>;
86
+ /** Raw PTY output. */
87
+ onData: (data: Uint8Array) => void;
88
+ /** Fired once when the PTY process ends — shell exit, kill, or sandbox
89
+ * fault. The server's broker closes the WebSocket off it. */
90
+ onExit?: () => void;
91
+ }
92
+ /** A live PTY inside the guest, brokered over the server's attach WebSocket. */
93
+ export interface SandboxPtyHandle {
94
+ /** OS pid inside the VM. */
95
+ pid: number;
96
+ sendInput(data: Uint8Array): Promise<void>;
97
+ resize(size: {
98
+ cols: number;
99
+ rows: number;
100
+ }): Promise<void>;
101
+ kill(): Promise<void>;
102
+ /** Detach this handle's event stream WITHOUT killing the guest process —
103
+ * the PTY keeps running (and survives a VM pause) and is re-attachable
104
+ * later via `connectPty(pid)`. The detach half of the ADR-0055 §7
105
+ * persistent-terminal rework: the server's broker disconnects on socket
106
+ * close; killing is a separate, explicit route decision. */
107
+ disconnect(): Promise<void>;
108
+ }
77
109
  /**
78
110
  * A sandbox provider implements the RAW provider operations only. It does NOT
79
111
  * implement transient-failure retry/backoff: reconnecting and snapshotting both
@@ -118,10 +150,12 @@ export interface SandboxProvider {
118
150
  * is the envd HTTP API (`Sandbox.files.read`), a DIFFERENT transport from
119
151
  * `commands` — so a large readback is immune to the connect-web gRPC
120
152
  * message-compression that can abort `commands.run` output on a big frame
121
- * ("received unsupported compressed output"). This is what lets `launchStep`
122
- * recover the full logs + result after a live-stream fault. OPTIONAL —
123
- * implemented where durable file-readback is needed (E2B, local); providers
124
- * that never drive the recovery path (Vercel — foreground only) may omit it. */
153
+ * ("received unsupported compressed output"). On Vercel it is the HTTP file
154
+ * API (`readFileToBuffer`), independent of a faulted runCommand log stream.
155
+ * This is what lets the step recovery paths (`launchStep`'s wait,
156
+ * `invokeStep`'s foreground stream-fault recovery) backfill the full logs +
157
+ * result after a live-stream fault. OPTIONAL — implemented where durable
158
+ * file-readback is needed (E2B, Vercel, local). */
125
159
  read?(path: string): Promise<string>;
126
160
  };
127
161
  kill(): Promise<void>;
@@ -137,6 +171,15 @@ export interface SandboxProvider {
137
171
  snapshotId: string;
138
172
  sizeBytes?: number;
139
173
  }>;
174
+ /** Resolve a public forwarding host for a port bound inside the sandbox, or
175
+ * null when the provider cannot expose ports. E2B returns its native
176
+ * `*.e2b.app` host (`sb.getHost(port)`); Vercel/local leave it undefined.
177
+ * Pure string op — no retry wrapper (unlike reconnect/snapshot). The
178
+ * returned host is the RAW capability and MUST NOT reach a browser
179
+ * (ADR-0052 §2.1); the server's member-gated preview proxy is the only
180
+ * caller. The presence of this method IS the port-exposure capability
181
+ * flag; providers without it leave it undefined (the no-shim rule). */
182
+ getHost?(port: number): string | null;
140
183
  /** Suspend the live VM in place and return a handle to resume it (ADR-0027).
141
184
  * Present ONLY on process-resume-capable providers (E2B via `sandbox.pause()`,
142
185
  * returning the sandbox id; resume is `Sandbox.connect(handle)`, which
@@ -149,6 +192,33 @@ export interface SandboxProvider {
149
192
  pauseProcess?(): Promise<{
150
193
  resumeHandle: string;
151
194
  }>;
195
+ /** Reset the PROVIDER-side kill-clock: the sandbox lives at least `ms`
196
+ * more from now (shorter values SHORTEN the remaining lifetime — E2B's
197
+ * `setTimeout` replaces the deadline rather than extending it). The
198
+ * server's session lifecycle calls this on every attach-heartbeat tick
199
+ * and when it arms the deferred hot-window suspend, so its own suspend
200
+ * timer always beats the provider deadline — without it a long-attached
201
+ * terminal outlives the create-time timeout and E2B kills the VM before
202
+ * the pause can run (persistence silently lost; user-hit 2026-07-25).
203
+ * Presence-is-capability: only providers with a live timeout primitive
204
+ * (E2B `sandbox.setTimeout`) implement it; others leave it undefined and
205
+ * ride their create-time lifetime. */
206
+ extendLifetime?(ms: number): Promise<void>;
207
+ /** Open an interactive PTY in the guest (ADR-0055 — the brokered session
208
+ * terminal). Presence-is-capability, like `runBackground`: only E2B
209
+ * implements it (native `sb.pty`); Vercel/local leave it undefined and
210
+ * the server's terminal route answers 400 instead of shimming. */
211
+ createPty?(opts: SandboxPtyOpts): Promise<SandboxPtyHandle>;
212
+ /** Re-attach to a PTY that an earlier handle `disconnect()`ed from, by
213
+ * pid (ADR-0055 §7 rework — the silent-reattach half of `disconnect`).
214
+ * The process kept running (it survives socket close AND a VM
215
+ * pause/resume); connecting resumes its output stream on `opts.onData`.
216
+ * `opts.cols`/`rows`/`cwd`/`envs` describe the CALLER's viewport intent
217
+ * only — the guest process already exists, so providers apply what their
218
+ * transport accepts (E2B: onData only; the server jiggles a resize after
219
+ * connect to repaint). Presence-is-capability, E2B-only; throws when no
220
+ * PTY with that pid is running. */
221
+ connectPty?(pid: number, opts: SandboxPtyOpts): Promise<SandboxPtyHandle>;
152
222
  /** Replace the live sandbox's egress policy in place — so the server can
153
223
  * push a freshly resolved policy (with re-minted connector access tokens)
154
224
  * before each step instead of relying on the policy baked at create.
@@ -160,6 +160,23 @@ export interface SandboxResources {
160
160
  * at first dispatch and reused across pause/resume/replay. */
161
161
  provider?: WorkflowSandboxProvider;
162
162
  }
163
+ /**
164
+ * What happens to a drive branch at its producer's TERMINUS — the one
165
+ * vocabulary shared by both branch-producing surfaces (workflow runs and
166
+ * agent sessions).
167
+ *
168
+ * - `"auto"` — the branch folds into `main` at the terminus.
169
+ * - `"manual"` — at the SAME terminus a merge APPROVAL is proposed instead.
170
+ * The branch is durable, so `manual` never means "silently strand the
171
+ * work": it means one click instead of zero.
172
+ *
173
+ * **Default for a workflow is `"auto"`** — absent (`mergePolicy` unset) is
174
+ * exactly what every already-registered workflow does today, so no existing
175
+ * template needs re-registering. (Sessions default the other way, `manual`,
176
+ * for the same reason: it is what they do today. The default belongs to the
177
+ * SURFACE, not to this type.)
178
+ */
179
+ export type DriveMergePolicy = "auto" | "manual";
163
180
  export interface WorkflowMetadata {
164
181
  /** One-line, human-readable description of what the workflow does.
165
182
  * Surfaced on the dashboard template tile + run page header. Authors
@@ -169,20 +186,28 @@ export interface WorkflowMetadata {
169
186
  networkPolicy?: SandboxNetworkPolicy;
170
187
  placeholders?: Record<string, string>;
171
188
  /** Input schema captured at bundle time from the workflow's
172
- * declared `input` zod schema. Step-form workflows populate this
173
- * automatically; run-form workflows (no explicit input schema —
174
- * defaults to `z.unknown()`) leave it undefined. */
189
+ * declared `input` zod schema. Undefined when the schema is
190
+ * effectively `z.unknown()` — no contract to render. */
175
191
  inputSchema?: IOSchema;
176
192
  /** Output schema captured at bundle time from the workflow's
177
- * declared `output` zod schema. Step-form workflows populate this
178
- * automatically; run-form workflows whose `run()` returns
179
- * arbitrarily-typed values leave it undefined. */
193
+ * declared `output` zod schema. Undefined when the schema is
194
+ * effectively `z.unknown()`. */
180
195
  outputSchema?: IOSchema;
181
196
  /** All snapshot config — boot source + capture mode. */
182
197
  snapshots?: SnapshotConfig;
183
198
  /** Sandbox machine resources — size + provider. Optional; omit → smallest
184
199
  * SKU on the platform-default provider (`vercel`). */
185
200
  resources?: SandboxResources;
201
+ /** What happens to this workflow's run branch at the run's terminus:
202
+ * `"auto"` folds it into `main` (success AND failure — a failed run still
203
+ * produced real outputs); `"manual"` proposes a merge approval at the same
204
+ * terminus instead. A CANCELLED run does neither under either policy.
205
+ *
206
+ * Optional + additive: an ABSENT policy means `"auto"` — today's hard-coded
207
+ * behaviour — and contributes nothing to the canonical metadata hash
208
+ * (frozen-metadata rule), so existing workflows are not forced to
209
+ * re-register. */
210
+ mergePolicy?: DriveMergePolicy;
186
211
  processors?: readonly Processor[];
187
212
  /** Connector requirements — providers whose APIs this workflow calls.
188
213
  * Dispatch resolves an authorized grant per provider and injects a
@@ -208,8 +233,8 @@ export interface WorkflowMetadata {
208
233
  environmentBuild?: boolean;
209
234
  }
210
235
  /**
211
- * Pull the server-readable declarations off a source object (run-form
212
- * `WorkflowDefinition` or step-form `StepWorkflowDefinition`) into a
236
+ * Pull the server-readable declarations off a `StepWorkflowDefinition`
237
+ * (or any source overlapping `WorkflowMetadata` in field shape) into a
213
238
  * single `WorkflowMetadata` bag. Undefined fields are omitted so the
214
239
  * canonical metadata hash (server-side) is stable across re-registers
215
240
  * that left a field unspecified.
@@ -9,9 +9,19 @@
9
9
  * workflows are wrapped at the SDK boundary as a single-step compiled
10
10
  * workflow (step name = "run"); the bundler sees the same shape regardless.
11
11
  */
12
+ /** One artifact a step promises to produce — mirrors `StepDeliverable`,
13
+ * restated here so the plan stays a self-contained wire shape. */
14
+ export interface WorkflowStepDeliverable {
15
+ path: string;
16
+ description?: string;
17
+ }
12
18
  export interface WorkflowStepPlan {
13
19
  index: number;
14
20
  name: string;
21
+ /** Author-declared narrative (defineStep `summary`) — optional, additive. */
22
+ summary?: string;
23
+ /** Author-declared artifacts (defineStep `deliverables`) — optional, additive. */
24
+ deliverables?: WorkflowStepDeliverable[];
15
25
  }
16
26
  export interface WorkflowPlan {
17
27
  steps: WorkflowStepPlan[];
@@ -1,229 +1,42 @@
1
1
  /**
2
- * Workflow types — the context and function signature for authoring workflows.
2
+ * Workflow authoring types — `defineWorkflow` and the shared author-facing
3
+ * type surface.
3
4
  *
4
- * A workflow is `async (ctx, sandbox) => T`. Two positional args by design:
5
- *
6
- * - `ctx` carries facts + observability about THIS run (id, input,
7
- * setMetadata, step). Metadata bag.
8
- * - `sandbox` is a capability handed to you by the engine for doing work
9
- * (exec commands, write files). Pass it to `agent({ sandbox, ... })`
10
- * and to any helper that takes a SandboxProvider (git utilities, file
11
- * writers). Constructed once per run; reuse it.
5
+ * Workflows are STEP-FORM only: a builder of discrete, durable steps —
6
+ * `defineWorkflow({ id, input, output }).step(defineStep(...)).build()`.
7
+ * Every step is a replay checkpoint; pause/resume works at step
8
+ * granularity. The legacy run-form (`defineWorkflow({ run(ctx, sandbox)
9
+ * { … } })`) has been removed — passing a `run` key throws at
10
+ * definition time (i.e. at registration, loud and early).
12
11
  *
13
12
  * LLM agent loops live in `agent(opts)` (sdk/src/agent/run-agent.ts).
14
13
  * Invoking other workflows uses `AgentComposeClient.invoke[AndWait](...)`.
15
14
  */
16
- import { z } from "zod";
17
- import type { SandboxNetworkPolicy } from "../sandbox.js";
18
- import type { SandboxProvider } from "./sandbox.js";
19
15
  import type { AgentLifecycleEvent } from "../agent/agent-loop.js";
20
- import type { Processor } from "../processors/processor.js";
21
- import type { StepWorkflowDefinition } from "../workflow-steps/workflow.js";
22
- import type { Workflow } from "../workflow-steps/types.js";
23
- import type { BaseExecutionContext } from "./execution-context.js";
24
- import { type ConnectorRequirements, type ConnectorOperationTag, type InvokePolicy } from "./workflow-metadata.js";
16
+ import type { StepWorkflowDefinition, WorkflowBuilder } from "../workflow-steps/workflow.js";
25
17
  export type { WorkflowRun, InvokeChild, BaseExecutionContext } from "./execution-context.js";
26
- export type { WorkflowMetadata } from "./workflow-metadata.js";
18
+ export type { WorkflowMetadata, SnapshotConfig, BootSnapshot, ReuseSnapshot, IOSchema, OutputSchema, SandboxResources } from "./workflow-metadata.js";
27
19
  export interface AgentEventSink {
28
20
  emit(event: AgentLifecycleEvent): void | Promise<void>;
29
21
  }
30
- import type { SnapshotConfig, BootSnapshot, ReuseSnapshot, IOSchema, OutputSchema, SandboxResources } from "./workflow-metadata.js";
31
- export type { SnapshotConfig, BootSnapshot, ReuseSnapshot, IOSchema, OutputSchema, SandboxResources };
32
22
  /** Turn/iteration budget for `agent(opts)`. Re-exported here so authors
33
23
  * can type per-invoke budget overrides they pass as workflow input. */
34
24
  export interface AgentBudget {
35
25
  turnsPerIteration: number;
36
26
  maxIterations: number;
37
27
  }
38
- /** Context passed to a workflow function — facts + observability for this run. */
39
- export interface WorkflowCtx<TInput extends Record<string, unknown> = Record<string, unknown>> extends Omit<BaseExecutionContext, "sandbox"> {
40
- input?: TInput;
41
- /** Persist key-value metadata on the run record (e.g. prUrl, planUrl). */
42
- setMetadata: (data: Record<string, unknown>) => Promise<void>;
43
- /**
44
- * Durable named step (ADR-0012). Runs `fn` once and memoises its result to
45
- * the sandbox state-dir; on a pause-resume re-entry the recorded value is
46
- * returned and `fn` is NOT re-run (a duration-0 "restored" sub-step). Also
47
- * emits substep_started / substep_completed / substep_failed lifecycle
48
- * events with duration — use for long phases you want both durable and
49
- * visible on the run's timeline (setup, external API calls, submit).
50
- *
51
- * Names must be unique within a step body (they key the memoise file).
52
- * A body that pauses is never memoised — the resume re-runs it. For
53
- * cross-process side effects (DB writes, emails) use `invokeChild`.
54
- */
55
- step<T>(name: string, fn: () => Promise<T>): Promise<T>;
56
- /** Pass to `agent({ events: ctx.agentEvents })` to stream agent lifecycle events. */
57
- agentEvents: AgentEventSink;
58
- /**
59
- * Workflow-level processors registered via `defineWorkflow({ processors })`.
60
- * Read-only here. Authors merge with agent-specific lists when calling
61
- * `agent({ processors: [...ctx.processors, mySpecific] })`.
62
- *
63
- * Empty array when the workflow declared no processors.
64
- */
65
- processors: readonly Processor[];
66
- }
67
- /** A workflow is `async (ctx, sandbox) => TOutput`. */
68
- export type WorkflowFn<TOutput = unknown, TInput extends Record<string, unknown> = Record<string, unknown>> = (ctx: WorkflowCtx<TInput>, sandbox: SandboxProvider) => Promise<TOutput>;
69
28
  /**
70
- * Declare a workflow with server-side metadata.
71
- * Use `export default defineWorkflow({ run, networkPolicy, ... })` to attach
72
- * a runner network policy so the server brokers credentials for the workflow
73
- * sandbox.
29
+ * Declare a workflow — the typed step builder. Multiple steps with
30
+ * explicit input/output schemas, durability + replay at every step
31
+ * boundary:
74
32
  *
75
- * Without defineWorkflow, a plain `export default async (ctx) => {...}` still
76
- * works — the workflow just runs with secrets passed directly in env.
77
- */
78
- export interface WorkflowDefinition<TOutput = unknown, TInput extends Record<string, unknown> = Record<string, unknown>> {
79
- /** One-line, human-readable description of what this workflow does.
80
- * Surfaced on the dashboard template tile + run page header. */
81
- description?: string;
82
- /** Zod schema for the workflow's `input`. When declared, the SDK
83
- * bundler captures it as JSON-Schema-shaped `inputSchema` in
84
- * template metadata, the dashboard playground renders a typed
85
- * form, and the engine validates dispatched payloads against it
86
- * at the step boundary. Omit to leave inputs as `unknown` (the
87
- * legacy default — playground falls back to a freeform JSON
88
- * textarea). */
89
- input?: z.ZodType<TInput>;
90
- /** Same as `input`, for the workflow's return value. Captured into
91
- * `outputSchema` metadata and rendered in the IO panel. */
92
- output?: z.ZodType<TOutput>;
93
- /**
94
- * @deprecated Legacy run-form. Prefer step-form — the
95
- * `.step(defineStep(...))` builder — for per-step durability/replay and
96
- * working pause. A run-form body compiles to one opaque step
97
- * (`compileRunForm`), so any failure/resume re-runs the whole body.
98
- */
99
- run: WorkflowFn<TOutput, TInput>;
100
- /**
101
- * All snapshot config — boot source plus capture mode.
102
- *
103
- * `snapshots.bootFrom`: the runner restores from this exact provider
104
- * snapshot id at run start. Omit to boot a fresh sandbox.
105
- *
106
- * `snapshots.saveLatest`: `true` captures one snapshot after each
107
- * successful step (latest-only — prior is freed).
108
- * `{ saveLatest: true, retainSteps: true }` keeps every step's
109
- * snapshot for fork / replay / time-travel.
110
- *
111
- * Snapshots are long-lived (never auto-expire). List + delete via
112
- * `agentc snapshot list/delete`. Per-invocation
113
- * `invoke({ snapshots })` overrides this default.
114
- */
115
- snapshots?: SnapshotConfig;
116
- /**
117
- * Sandbox resources — machine SKU (`size`, a `SandboxSize` vCPU string:
118
- * `2vcpu-4gb` | `4vcpu-8gb` | `8vcpu-16gb` | `32vcpu-64gb`) and `provider`
119
- * (`vercel` | `e2b`). `size` maps to provider machine specs at create
120
- * (Vercel: vCPUs, 2048 MB RAM per vCPU); E2B sizing is template-defined and
121
- * ignores it. Optional; omit → smallest SKU on the default provider.
122
- * Per-invocation `invoke({ size })` overrides the size.
123
- */
124
- resources?: SandboxResources;
125
- /**
126
- * Outbound network policy for the runner sandbox.
127
- * Use "*": [] to allow all traffic while still injecting headers for specific domains.
128
- * Secret values are referenced as $VARIABLE and resolved from GCP at dispatch time.
129
- * Vercel only — E2B ignores.
130
- *
131
- * @example
132
- * networkPolicy: {
133
- * allow: {
134
- * "*": [],
135
- * "api.github.com": [{ transform: [{ headers: { "Authorization": "basic:$GITHUB_TOKEN" } }] }],
136
- * }
137
- * }
138
- */
139
- networkPolicy?: SandboxNetworkPolicy;
140
- /**
141
- * Workflow-level processor chain — runs around every `agent(...)` loop the
142
- * workflow body launches, ahead of any agent-specific processors. Use for
143
- * org-wide gating (deny destructive tools, require scopes). Authors merge
144
- * with agent-specific lists via `[...ctx.processors, ...]`.
145
- */
146
- processors?: readonly Processor[];
147
- /**
148
- * Optional placeholder env var values for secrets referenced in the network policy.
149
- * By default, brokered secrets are removed from the runner env entirely — the real
150
- * values are only ever present inside the Vercel firewall config, never in the VM.
151
- * Only set this if a tool or SDK validates the env var format on startup before
152
- * making any requests (e.g. some CLIs check that ANTHROPIC_API_KEY looks like a
153
- * real key). The placeholder is a syntactically valid but non-functional stand-in.
154
- *
155
- * @example
156
- * placeholders: {
157
- * ANTHROPIC_API_KEY: `sk-ant-api03-${"a".repeat(95)}`,
158
- * }
159
- */
160
- placeholders?: Record<string, string>;
161
- /**
162
- * Connector requirements (ADR-0007). Declaring a provider makes the
163
- * server resolve an authorized OAuth grant for the calling principal at
164
- * dispatch time and inject a fresh access token at the network layer —
165
- * workflow code talks to the provider API with plain fetch/SDKs and
166
- * never holds the credential.
167
- *
168
- * @example
169
- * connectors: { github: { scopes: ["repo"] } }
170
- */
171
- connectors?: ConnectorRequirements;
172
- /**
173
- * Marks this workflow as a catalogue OPERATION of a connector — e.g.
174
- * the `create-issue` operation of the `github` connector. Pair with
175
- * `description` + `input`/`output` schemas so the operation is fully
176
- * self-describing (MCP-tool-like) to humans and agents browsing the
177
- * connector catalogue.
178
- *
179
- * @example
180
- * connectorOperation: { provider: "github", operation: "create-issue" }
181
- */
182
- connectorOperation?: ConnectorOperationTag;
183
- /**
184
- * Tier-1 invoke ACL (connector credential boundary). When this workflow
185
- * declares `connectors` (it brokers a credential) AND an `invokePolicy`,
186
- * the server evaluates the calling principal against the policy BEFORE
187
- * binding any grant — a caller matching no clause is refused with HTTP
188
- * 403. Has no effect on workflows that declare no connectors. Omit to
189
- * leave the workflow invokable by the whole team.
190
- *
191
- * @example
192
- * connectors: { github: { access: "read" } },
193
- * invokePolicy: { users: "owner", workflows: ["nightly-orchestrator"] }
194
- */
195
- invokePolicy?: InvokePolicy;
196
- /**
197
- * Internal — set by `defineSandboxEnvironment`, not by workflow authors.
198
- * Marks the workflow as an environment build (base-env / agent-env) so the
199
- * server skips mounting the shared factory drive for its runs (#13). See
200
- * `WorkflowMetadata.environmentBuild`.
201
- */
202
- environmentBuild?: boolean;
203
- }
204
- /**
205
- * Declare a workflow. Two forms; both return a `Workflow` whose
206
- * `metadata` field carries the server-readable declarations.
33
+ * defineWorkflow({ id, input, output }).step(defineStep(...)).build()
207
34
  *
208
- * Run form — wraps the legacy `(ctx, sandbox) => T` body as a single
209
- * step internally. Lifecycle granularity is workflow-level (one step).
210
- *
211
- * Step form — the typed builder. Multiple steps with explicit input/
212
- * output schemas, durability + replay at every step boundary.
213
- *
214
- * Both forms produce the same downstream shape: the bundler reads
215
- * `workflow.metadata.networkPolicy` / `placeholders` / etc.; the server
216
- * stores the step plan; runner subprocesses execute one step at a time
217
- * via the StepInvocation seam.
218
- */
219
- /**
220
- * @deprecated Run-form is legacy. Use the step-form overload —
221
- * `defineWorkflow({ id, input, output }).step(defineStep(...)).build()` — for
222
- * durable, replayable steps and working pause. Run-form compiles to a single
223
- * opaque step (`compileRunForm`); there is no per-step replay.
35
+ * The bundler reads `workflow.metadata.networkPolicy` / `placeholders` /
36
+ * etc. off the built `Workflow`; the server stores the step plan; runner
37
+ * subprocesses execute one step at a time via the StepInvocation seam.
224
38
  */
225
- export declare function defineWorkflow<TOutput = unknown, TInput extends Record<string, unknown> = Record<string, unknown>>(def: WorkflowDefinition<TOutput, TInput>): Workflow<TInput, TOutput>;
226
- export declare function defineWorkflow<TInput, TOutput>(def: StepWorkflowDefinition<TInput, TOutput>): import("../workflow-steps/workflow.js").WorkflowBuilder<TInput, TInput>;
39
+ export declare function defineWorkflow<TInput, TOutput>(def: StepWorkflowDefinition<TInput, TOutput>): WorkflowBuilder<TInput, TInput>;
227
40
  /** Observability-only lifecycle hooks — passed to the workflow engine, not workflow authors. */
228
41
  export interface WorkflowHooks {
229
42
  onStepStart?: (step: string) => void;
@@ -20,8 +20,9 @@
20
20
  * manifest's `sourceHash` against the source it received.
21
21
  */
22
22
  import type { SnapshotConfig } from "../types/workflow.js";
23
- import type { IOSchema, ConnectorRequirements, ConnectorOperationTag, InvokePolicy, SandboxResources } from "../types/workflow-metadata.js";
23
+ import type { IOSchema, ConnectorRequirements, ConnectorOperationTag, InvokePolicy, SandboxResources, DriveMergePolicy } from "../types/workflow-metadata.js";
24
24
  import type { SandboxNetworkPolicy } from "../sandbox.js";
25
+ import type { Workflow } from "../workflow-steps/types.js";
25
26
  import { type WorkflowPlan } from "../types/workflow-plan.js";
26
27
  /** Bumped when the manifest contract changes in a way the server should
27
28
  * notice. The server pins its manifest schema to this exact value (no
@@ -87,6 +88,9 @@ export interface BundledWorkflow {
87
88
  /** Sandbox resources declared via `defineWorkflow({ resources })` — machine
88
89
  * SKU (`size`) and `provider` (`vercel` | `e2b`). */
89
90
  resources?: SandboxResources;
91
+ /** Run-branch merge policy declared via `defineWorkflow({ mergePolicy })`.
92
+ * Absent ⇒ `"auto"` (today's behaviour). */
93
+ mergePolicy?: DriveMergePolicy;
90
94
  workflowPlan: WorkflowPlan;
91
95
  /** Compact JSON-Schema-shaped description of the workflow's input
92
96
  * type. Extracted from the workflow's declared `input` zod schema
@@ -123,6 +127,13 @@ export interface BundledWorkflow {
123
127
  * from outside the SDK — `bundleWorkflow` is the supported entrypoint.
124
128
  */
125
129
  export declare function assertDefaultExportIsDefineWorkflow(source: string, label: string): void;
130
+ /**
131
+ * The registration-time step plan read off an (already evaluated) workflow
132
+ * object. Narrative fields (`summary`, `deliverables`) ride along additively;
133
+ * conditional spreads keep absent fields ABSENT — the plan travels as JSON,
134
+ * so no `undefined` keys. Exported for tests.
135
+ */
136
+ export declare function extractWorkflowPlan(workflow: Workflow<unknown, unknown>): WorkflowPlan;
126
137
  /** Bundle a workflow from source. */
127
138
  export declare function bundleWorkflow(workflowPath: string, overrides?: {
128
139
  networkPolicy?: SandboxNetworkPolicy;
@@ -4,7 +4,7 @@ export { createStepWorkflow, isWorkflow } from "./workflow.js";
4
4
  export type { StepWorkflowDefinition, WorkflowBuilder } from "./workflow.js";
5
5
  export { runWorkflowSteps, runWorkflowSingleStep, StepValidationError, WorkflowInputValidationError, WorkflowOutputValidationError, } from "./runner.js";
6
6
  export type { RunWorkflowStepsOpts, RunWorkflowStepsResult, RunWorkflowSingleStepOpts, RunWorkflowSingleStepResult, } from "./runner.js";
7
- export type { Step, StepContext, StepRunResult, Workflow, } from "./types.js";
7
+ export type { Step, StepContext, StepDeliverable, StepRunResult, Workflow, } from "./types.js";
8
8
  export { WORKFLOW_BRAND } from "./types.js";
9
9
  export { StepObservabilityCollector } from "./observability.js";
10
10
  export type { StepObservability, SubStepEvent } from "./observability.js";
@@ -52,7 +52,14 @@ export interface SubStepEvent {
52
52
  * empty snapshot (and the wire payload omits the field entirely). */
53
53
  export interface StepObservability {
54
54
  metadata?: Record<string, unknown>;
55
- events?: AgentLifecycleEvent[];
55
+ /** Residual events that failed live delivery, each carrying its ORIGINAL
56
+ * emit-time `seq`. The server keys its idempotency on that seq — the SAME
57
+ * key the live route used — so a live write whose ack was lost collapses
58
+ * on redelivery instead of duplicating. Array position is NOT the seq:
59
+ * acked events are stripped, so indices shift. */
60
+ events?: Array<AgentLifecycleEvent & {
61
+ seq: number;
62
+ }>;
56
63
  subSteps?: SubStepEvent[];
57
64
  }
58
65
  /**
@@ -25,7 +25,7 @@ import { z } from "zod";
25
25
  import type { Workflow, StepRunResult } from "./types.js";
26
26
  import type { RequestContext } from "../request-context/request-context.js";
27
27
  import type { SandboxProvider } from "../types/sandbox.js";
28
- import type { WorkflowRun, WorkflowCtx } from "../types/workflow.js";
28
+ import type { WorkflowRun, InvokeChild } from "../types/execution-context.js";
29
29
  import { type StepObservability } from "./observability.js";
30
30
  import type { LiveAgentEventEmitter } from "./run-callback.js";
31
31
  export declare class StepValidationError extends Error {
@@ -67,7 +67,7 @@ export interface RunWorkflowStepsOpts<TInput, TOutput> {
67
67
  /** Fire before a step runs (after cache check / before input validation). */
68
68
  onStepStarted?(stepIndex: number, stepName: string): void | Promise<void>;
69
69
  /** Child workflow invocation implementation. Defaults to a clear unsupported error. */
70
- invokeChild?: WorkflowCtx["invokeChild"];
70
+ invokeChild?: InvokeChild;
71
71
  /** Optional live-stream emitter for agent lifecycle events. The runner
72
72
  * passes a fetch-based emitter wired to the per-run callback token so
73
73
  * the dashboard sees events as the agent loop produces them; tests
@@ -89,7 +89,7 @@ export interface RunWorkflowSingleStepOpts {
89
89
  requestContext: RequestContext;
90
90
  sandbox?: SandboxProvider;
91
91
  abortSignal?: AbortSignal;
92
- invokeChild?: WorkflowCtx["invokeChild"];
92
+ invokeChild?: InvokeChild;
93
93
  /** Optional live-stream emitter — see `RunWorkflowStepsOpts.liveAgentEventEmitter`. */
94
94
  liveAgentEventEmitter?: LiveAgentEventEmitter;
95
95
  }