@agent-compose/sdk 0.6.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 (126) 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 +34 -13
  10. package/dist/index.js +2984 -861
  11. package/dist/pause/wrappers.d.ts +31 -9
  12. package/dist/processors/ask-human.d.ts +30 -0
  13. package/dist/processors/ask-human.test.d.ts +1 -0
  14. package/dist/processors/index.d.ts +1 -0
  15. package/dist/runtimes/_acp-client.d.ts +46 -1
  16. package/dist/runtimes/_cli-agent.d.ts +58 -4
  17. package/dist/runtimes/_jsonl-guard.d.ts +103 -0
  18. package/dist/runtimes/amp.d.ts +2 -2
  19. package/dist/runtimes/claude-code.d.ts +59 -0
  20. package/dist/runtimes/claude-code.test.d.ts +14 -0
  21. package/dist/runtimes/claude.d.ts +16 -0
  22. package/dist/runtimes/claude.test.d.ts +8 -0
  23. package/dist/runtimes/codex.d.ts +9 -3
  24. package/dist/runtimes/cursor.d.ts +9 -0
  25. package/dist/runtimes/droid.d.ts +9 -0
  26. package/dist/runtimes/jsonl-guard.test.d.ts +19 -0
  27. package/dist/runtimes/openai-desktop.js +2922 -861
  28. package/dist/runtimes/opencode.d.ts +25 -0
  29. package/dist/runtimes/vercel.js +22 -1
  30. package/dist/sandbox/devbox.d.ts +42 -0
  31. package/dist/sandbox/exec-stream.d.ts +14 -0
  32. package/dist/sandbox/network-policy.d.ts +100 -0
  33. package/dist/sandbox/provider-def.d.ts +79 -0
  34. package/dist/sandbox/providers/desktop.d.ts +10 -0
  35. package/dist/sandbox/providers/e2b.d.ts +17 -0
  36. package/dist/sandbox/providers/local.d.ts +11 -0
  37. package/dist/sandbox/providers/vercel.d.ts +18 -0
  38. package/dist/sandbox/registry.d.ts +45 -0
  39. package/dist/sandbox/sizes.d.ts +68 -0
  40. package/dist/sandbox.d.ts +24 -299
  41. package/dist/step-invocation/__tests__/foreground-recovery.test.d.ts +1 -0
  42. package/dist/step-invocation/invoker.d.ts +24 -1
  43. package/dist/step-invocation/protocol.d.ts +13 -0
  44. package/dist/types/api-compliance.d.ts +71 -0
  45. package/dist/types/api-conversations.d.ts +492 -0
  46. package/dist/types/api-factory.d.ts +309 -0
  47. package/dist/types/api-projects.d.ts +131 -0
  48. package/dist/types/api-runs.d.ts +377 -0
  49. package/dist/types/api-scopes.d.ts +102 -0
  50. package/dist/types/conversation-stream.d.ts +191 -0
  51. package/dist/types/execution-context.d.ts +12 -2
  52. package/dist/types/protocol.d.ts +30 -1
  53. package/dist/types/sandbox-environment.d.ts +8 -5
  54. package/dist/types/sandbox.d.ts +79 -0
  55. package/dist/types/workflow-metadata.d.ts +33 -8
  56. package/dist/types/workflow-plan.d.ts +10 -0
  57. package/dist/types/workflow.d.ts +18 -193
  58. package/dist/utils/bundler.d.ts +12 -1
  59. package/dist/utils/errors.d.ts +9 -1
  60. package/dist/workflow-steps/index.d.ts +1 -1
  61. package/dist/workflow-steps/observability.d.ts +8 -1
  62. package/dist/workflow-steps/runner.d.ts +3 -3
  63. package/dist/workflow-steps/step.d.ts +15 -1
  64. package/dist/workflow-steps/types.d.ts +19 -5
  65. package/dist/workflow-steps/workflow.d.ts +22 -1
  66. package/dist/workflows/engine.d.ts +3 -2
  67. package/dist/workflows/invoke-child.d.ts +2 -2
  68. package/package.json +1 -1
  69. package/src/agent/agent-context.ts +206 -16
  70. package/src/agent/agent-loop.ts +40 -4
  71. package/src/agent/run-agent.ts +9 -1
  72. package/src/client.ts +909 -621
  73. package/src/directives.ts +184 -0
  74. package/src/display.ts +788 -0
  75. package/src/errors.ts +39 -0
  76. package/src/index.ts +117 -10
  77. package/src/pause/wrappers.ts +44 -9
  78. package/src/processors/ask-human.ts +136 -0
  79. package/src/processors/index.ts +5 -0
  80. package/src/runtimes/_acp-client.ts +72 -3
  81. package/src/runtimes/_cli-agent.ts +171 -38
  82. package/src/runtimes/_jsonl-guard.ts +219 -0
  83. package/src/runtimes/claude-code.ts +246 -0
  84. package/src/runtimes/claude.ts +32 -2
  85. package/src/runtimes/codex.ts +55 -3
  86. package/src/runtimes/cursor.ts +59 -0
  87. package/src/runtimes/droid.ts +63 -0
  88. package/src/runtimes/openai-desktop.ts +59 -14
  89. package/src/runtimes/opencode.ts +61 -0
  90. package/src/sandbox/devbox.ts +48 -0
  91. package/src/sandbox/exec-stream.ts +48 -0
  92. package/src/sandbox/network-policy.ts +181 -0
  93. package/src/sandbox/provider-def.ts +94 -0
  94. package/src/sandbox/providers/desktop.ts +57 -0
  95. package/src/sandbox/providers/e2b.ts +354 -0
  96. package/src/sandbox/providers/local.ts +106 -0
  97. package/src/sandbox/providers/vercel.ts +331 -0
  98. package/src/sandbox/registry.ts +198 -0
  99. package/src/sandbox/sizes.ts +95 -0
  100. package/src/sandbox.ts +59 -1263
  101. package/src/step-invocation/invoker.ts +319 -34
  102. package/src/step-invocation/protocol.ts +19 -0
  103. package/src/types/api-compliance.ts +79 -0
  104. package/src/types/api-conversations.ts +522 -0
  105. package/src/types/api-factory.ts +336 -0
  106. package/src/types/api-projects.ts +140 -0
  107. package/src/types/api-runs.ts +412 -0
  108. package/src/types/api-scopes.ts +102 -0
  109. package/src/types/conversation-stream.ts +231 -0
  110. package/src/types/execution-context.ts +10 -2
  111. package/src/types/protocol.ts +33 -0
  112. package/src/types/sandbox-environment.ts +28 -9
  113. package/src/types/sandbox.ts +78 -0
  114. package/src/types/workflow-metadata.ts +35 -8
  115. package/src/types/workflow-plan.ts +11 -0
  116. package/src/types/workflow.ts +25 -280
  117. package/src/utils/bundler.ts +32 -5
  118. package/src/utils/errors.ts +34 -2
  119. package/src/workflow-steps/index.ts +1 -0
  120. package/src/workflow-steps/observability.ts +19 -8
  121. package/src/workflow-steps/runner.ts +4 -4
  122. package/src/workflow-steps/step.ts +49 -1
  123. package/src/workflow-steps/types.ts +20 -5
  124. package/src/workflow-steps/workflow.ts +22 -1
  125. package/src/workflows/engine.ts +3 -2
  126. 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
@@ -114,6 +146,17 @@ export interface SandboxProvider {
114
146
  };
115
147
  files: {
116
148
  write(path: string, content: string): Promise<void>;
149
+ /** Read a file's text content over the provider's FILE transport. On E2B this
150
+ * is the envd HTTP API (`Sandbox.files.read`), a DIFFERENT transport from
151
+ * `commands` — so a large readback is immune to the connect-web gRPC
152
+ * message-compression that can abort `commands.run` output on a big frame
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). */
159
+ read?(path: string): Promise<string>;
117
160
  };
118
161
  kill(): Promise<void>;
119
162
  /** Capture the running sandbox's state as a reusable snapshot. Vercel and E2B
@@ -128,6 +171,15 @@ export interface SandboxProvider {
128
171
  snapshotId: string;
129
172
  sizeBytes?: number;
130
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;
131
183
  /** Suspend the live VM in place and return a handle to resume it (ADR-0027).
132
184
  * Present ONLY on process-resume-capable providers (E2B via `sandbox.pause()`,
133
185
  * returning the sandbox id; resume is `Sandbox.connect(handle)`, which
@@ -140,6 +192,33 @@ export interface SandboxProvider {
140
192
  pauseProcess?(): Promise<{
141
193
  resumeHandle: string;
142
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>;
143
222
  /** Replace the live sandbox's egress policy in place — so the server can
144
223
  * push a freshly resolved policy (with re-minted connector access tokens)
145
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,217 +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.
74
- *
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
- run: WorkflowFn<TOutput, TInput>;
94
- /**
95
- * All snapshot config — boot source plus capture mode.
96
- *
97
- * `snapshots.bootFrom`: the runner restores from this exact provider
98
- * snapshot id at run start. Omit to boot a fresh sandbox.
99
- *
100
- * `snapshots.saveLatest`: `true` captures one snapshot after each
101
- * successful step (latest-only — prior is freed).
102
- * `{ saveLatest: true, retainSteps: true }` keeps every step's
103
- * snapshot for fork / replay / time-travel.
104
- *
105
- * Snapshots are long-lived (never auto-expire). List + delete via
106
- * `agentc snapshot list/delete`. Per-invocation
107
- * `invoke({ snapshots })` overrides this default.
108
- */
109
- snapshots?: SnapshotConfig;
110
- /**
111
- * Sandbox resources — machine SKU (`size`, a `SandboxSize` vCPU string:
112
- * `2vcpu-4gb` | `4vcpu-8gb` | `8vcpu-16gb` | `32vcpu-64gb`) and `provider`
113
- * (`vercel` | `e2b`). `size` maps to provider machine specs at create
114
- * (Vercel: vCPUs, 2048 MB RAM per vCPU); E2B sizing is template-defined and
115
- * ignores it. Optional; omit → smallest SKU on the default provider.
116
- * Per-invocation `invoke({ size })` overrides the size.
117
- */
118
- resources?: SandboxResources;
119
- /**
120
- * Outbound network policy for the runner sandbox.
121
- * Use "*": [] to allow all traffic while still injecting headers for specific domains.
122
- * Secret values are referenced as $VARIABLE and resolved from GCP at dispatch time.
123
- * Vercel only — E2B ignores.
124
- *
125
- * @example
126
- * networkPolicy: {
127
- * allow: {
128
- * "*": [],
129
- * "api.github.com": [{ transform: [{ headers: { "Authorization": "basic:$GITHUB_TOKEN" } }] }],
130
- * }
131
- * }
132
- */
133
- networkPolicy?: SandboxNetworkPolicy;
134
- /**
135
- * Workflow-level processor chain — runs around every `agent(...)` loop the
136
- * workflow body launches, ahead of any agent-specific processors. Use for
137
- * org-wide gating (deny destructive tools, require scopes). Authors merge
138
- * with agent-specific lists via `[...ctx.processors, ...]`.
139
- */
140
- processors?: readonly Processor[];
141
- /**
142
- * Optional placeholder env var values for secrets referenced in the network policy.
143
- * By default, brokered secrets are removed from the runner env entirely — the real
144
- * values are only ever present inside the Vercel firewall config, never in the VM.
145
- * Only set this if a tool or SDK validates the env var format on startup before
146
- * making any requests (e.g. some CLIs check that ANTHROPIC_API_KEY looks like a
147
- * real key). The placeholder is a syntactically valid but non-functional stand-in.
148
- *
149
- * @example
150
- * placeholders: {
151
- * ANTHROPIC_API_KEY: `sk-ant-api03-${"a".repeat(95)}`,
152
- * }
153
- */
154
- placeholders?: Record<string, string>;
155
- /**
156
- * Connector requirements (ADR-0007). Declaring a provider makes the
157
- * server resolve an authorized OAuth grant for the calling principal at
158
- * dispatch time and inject a fresh access token at the network layer —
159
- * workflow code talks to the provider API with plain fetch/SDKs and
160
- * never holds the credential.
161
- *
162
- * @example
163
- * connectors: { github: { scopes: ["repo"] } }
164
- */
165
- connectors?: ConnectorRequirements;
166
- /**
167
- * Marks this workflow as a catalogue OPERATION of a connector — e.g.
168
- * the `create-issue` operation of the `github` connector. Pair with
169
- * `description` + `input`/`output` schemas so the operation is fully
170
- * self-describing (MCP-tool-like) to humans and agents browsing the
171
- * connector catalogue.
172
- *
173
- * @example
174
- * connectorOperation: { provider: "github", operation: "create-issue" }
175
- */
176
- connectorOperation?: ConnectorOperationTag;
177
- /**
178
- * Tier-1 invoke ACL (connector credential boundary). When this workflow
179
- * declares `connectors` (it brokers a credential) AND an `invokePolicy`,
180
- * the server evaluates the calling principal against the policy BEFORE
181
- * binding any grant — a caller matching no clause is refused with HTTP
182
- * 403. Has no effect on workflows that declare no connectors. Omit to
183
- * leave the workflow invokable by the whole team.
184
- *
185
- * @example
186
- * connectors: { github: { access: "read" } },
187
- * invokePolicy: { users: "owner", workflows: ["nightly-orchestrator"] }
188
- */
189
- invokePolicy?: InvokePolicy;
190
- /**
191
- * Internal — set by `defineSandboxEnvironment`, not by workflow authors.
192
- * Marks the workflow as an environment build (base-env / agent-env) so the
193
- * server skips mounting the shared factory drive for its runs (#13). See
194
- * `WorkflowMetadata.environmentBuild`.
195
- */
196
- environmentBuild?: boolean;
197
- }
198
- /**
199
- * Declare a workflow. Two forms; both return a `Workflow` whose
200
- * `metadata` field carries the server-readable declarations.
201
- *
202
- * Run form — wraps the legacy `(ctx, sandbox) => T` body as a single
203
- * step internally. Lifecycle granularity is workflow-level (one step).
29
+ * Declare a workflow — the typed step builder. Multiple steps with
30
+ * explicit input/output schemas, durability + replay at every step
31
+ * boundary:
204
32
  *
205
- * Step form — the typed builder. Multiple steps with explicit input/
206
- * output schemas, durability + replay at every step boundary.
33
+ * defineWorkflow({ id, input, output }).step(defineStep(...)).build()
207
34
  *
208
- * Both forms produce the same downstream shape: the bundler reads
209
- * `workflow.metadata.networkPolicy` / `placeholders` / etc.; the server
210
- * stores the step plan; runner subprocesses execute one step at a time
211
- * via the StepInvocation seam.
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.
212
38
  */
213
- export declare function defineWorkflow<TOutput = unknown, TInput extends Record<string, unknown> = Record<string, unknown>>(def: WorkflowDefinition<TOutput, TInput>): Workflow<TInput, TOutput>;
214
- 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>;
215
40
  /** Observability-only lifecycle hooks — passed to the workflow engine, not workflow authors. */
216
41
  export interface WorkflowHooks {
217
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;
@@ -1,2 +1,10 @@
1
- /** Convert any thrown value to a string message. */
1
+ /** Convert any thrown value to a string message, INCLUDING its `.cause` chain.
2
+ *
3
+ * Many wrapped errors carry the real reason on `.cause` and only a generic
4
+ * summary on `.message` — the Temporal SDK's "Failed to start Workflow" is the
5
+ * canonical example (its `.cause` is the actual gRPC rejection, e.g. "search
6
+ * attribute X is not defined"). Returning `.message` alone swallowed that, so
7
+ * failures surfaced as opaque one-liners. Walk the chain and join the messages
8
+ * so the root cause is always visible. Cycle-guarded against self-referential
9
+ * `cause` links. */
2
10
  export declare function formatError(err: unknown): string;
@@ -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
  }