@agent-compose/sdk 0.5.1 → 0.5.2

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 (90) hide show
  1. package/dist/active-step.d.ts +60 -0
  2. package/dist/agent/agent-loop-steer.test.d.ts +1 -0
  3. package/dist/agent/agent-loop.d.ts +46 -0
  4. package/dist/agent/async-queue.d.ts +29 -0
  5. package/dist/agent/protocol.d.ts +9 -1
  6. package/dist/agent/resolve-agent-id.test.d.ts +1 -0
  7. package/dist/agent/run-agent.d.ts +16 -3
  8. package/dist/agent/steer-control.d.ts +57 -0
  9. package/dist/agent/steer-control.test.d.ts +1 -0
  10. package/dist/client.d.ts +161 -0
  11. package/dist/index.d.ts +7 -4
  12. package/dist/index.js +1339 -157
  13. package/dist/pause/__tests__/agent-loop-checkpoint.test.d.ts +1 -0
  14. package/dist/pause/__tests__/checkpoint.test.d.ts +1 -0
  15. package/dist/pause/__tests__/errors.test.d.ts +1 -0
  16. package/dist/pause/__tests__/manager.test.d.ts +1 -0
  17. package/dist/pause/__tests__/pause-core.test.d.ts +1 -0
  18. package/dist/pause/__tests__/state-dir.test.d.ts +1 -0
  19. package/dist/pause/__tests__/wrappers.test.d.ts +1 -0
  20. package/dist/pause/checkpoint.d.ts +28 -0
  21. package/dist/pause/errors.d.ts +52 -0
  22. package/dist/pause/manager.d.ts +63 -0
  23. package/dist/pause/pause-core.d.ts +101 -0
  24. package/dist/pause/state-dir.d.ts +80 -0
  25. package/dist/pause/wrappers.d.ts +41 -0
  26. package/dist/request-context/request-context.d.ts +12 -0
  27. package/dist/runtimes/claude.d.ts +6 -0
  28. package/dist/runtimes/openai-desktop.d.ts +2 -0
  29. package/dist/runtimes/openai-desktop.js +1336 -156
  30. package/dist/runtimes/vercel.d.ts +12 -0
  31. package/dist/runtimes/vercel.js +50 -7
  32. package/dist/runtimes/vercel.test.d.ts +1 -0
  33. package/dist/sse.d.ts +2 -3
  34. package/dist/step-invocation/index.d.ts +2 -2
  35. package/dist/step-invocation/invoker.d.ts +3 -0
  36. package/dist/step-invocation/protocol.d.ts +12 -0
  37. package/dist/step-invocation/server.d.ts +1 -0
  38. package/dist/step-invocation/types.d.ts +40 -5
  39. package/dist/types/events.d.ts +9 -0
  40. package/dist/types/execution-context.d.ts +25 -0
  41. package/dist/types/protocol.d.ts +8 -0
  42. package/dist/types/runtime.d.ts +55 -0
  43. package/dist/types/sandbox.d.ts +4 -4
  44. package/dist/utils/schemas.d.ts +2 -0
  45. package/dist/workflow-steps/__tests__/pause-wiring.test.d.ts +1 -0
  46. package/dist/workflow-steps/index.d.ts +2 -0
  47. package/dist/workflow-steps/observability.d.ts +43 -11
  48. package/dist/workflow-steps/run-callback.d.ts +39 -0
  49. package/dist/workflow-steps/runner.d.ts +8 -0
  50. package/package.json +1 -1
  51. package/src/active-step.ts +124 -0
  52. package/src/agent/agent-loop.ts +253 -19
  53. package/src/agent/async-queue.ts +61 -0
  54. package/src/agent/protocol.ts +12 -2
  55. package/src/agent/run-agent.ts +184 -8
  56. package/src/agent/steer-control.ts +125 -0
  57. package/src/client.ts +277 -0
  58. package/src/index.ts +18 -2
  59. package/src/pause/checkpoint.ts +44 -0
  60. package/src/pause/errors.ts +70 -0
  61. package/src/pause/manager.ts +177 -0
  62. package/src/pause/pause-core.ts +267 -0
  63. package/src/pause/state-dir.ts +262 -0
  64. package/src/pause/wrappers.ts +79 -0
  65. package/src/request-context/request-context.ts +17 -2
  66. package/src/runtimes/claude.ts +101 -6
  67. package/src/runtimes/openai-desktop.ts +11 -0
  68. package/src/runtimes/vercel.ts +26 -0
  69. package/src/sandbox.ts +39 -20
  70. package/src/sse.ts +8 -6
  71. package/src/step-invocation/index.ts +2 -1
  72. package/src/step-invocation/invoker.ts +107 -29
  73. package/src/step-invocation/protocol.ts +16 -0
  74. package/src/step-invocation/server.ts +45 -12
  75. package/src/step-invocation/types.ts +43 -7
  76. package/src/tools/coding.ts +16 -5
  77. package/src/types/events.ts +9 -0
  78. package/src/types/execution-context.ts +25 -0
  79. package/src/types/protocol.ts +8 -0
  80. package/src/types/runtime.ts +52 -0
  81. package/src/types/sandbox.ts +8 -4
  82. package/src/types/workflow.ts +6 -1
  83. package/src/utils/bundler.ts +8 -3
  84. package/src/utils/schemas.ts +2 -0
  85. package/src/workflow-steps/index.ts +3 -0
  86. package/src/workflow-steps/observability.ts +84 -13
  87. package/src/workflow-steps/run-callback.ts +72 -0
  88. package/src/workflow-steps/runner.ts +70 -8
  89. package/dist/utils/discovery.d.ts +0 -2
  90. package/src/utils/discovery.ts +0 -4
package/dist/index.js CHANGED
@@ -36,6 +36,17 @@ function q(value) {
36
36
  function resolvePath(path, cwd) {
37
37
  return cwd && !isAbsolute(path) ? join(cwd, path) : path;
38
38
  }
39
+ function formatCommandFailure(command, result) {
40
+ const parts = [`Command failed with exit code ${result.exitCode}: ${command}`];
41
+ if (result.stderr.trim())
42
+ parts.push(`stderr:
43
+ ${result.stderr}`);
44
+ if (result.stdout.trim())
45
+ parts.push(`stdout:
46
+ ${result.stdout}`);
47
+ return parts.join(`
48
+ `);
49
+ }
39
50
  async function readFile(sandbox, path, opts) {
40
51
  const script = `
41
52
  const fs = require("fs");
@@ -49,13 +60,19 @@ const end = Math.min(lines.length, start + Math.max(0, limit) - 1);
49
60
  for (let i = start; i <= end; i++) console.log(String(i) + ": " + lines[i - 1]);
50
61
  `;
51
62
  const args = [q(path), opts?.offset !== undefined ? String(opts.offset) : "", opts?.limit !== undefined ? String(opts.limit) : ""].filter(Boolean).map(q).join(" ");
52
- const { stdout } = await sandbox.commands.run(`node -e ${q(script)} ${args}`, { cwd: opts?.cwd, timeoutMs: 30000 });
53
- return stdout;
63
+ const command = `node -e ${q(script)} ${args}`;
64
+ const result = await sandbox.commands.run(command, { cwd: opts?.cwd, timeoutMs: 30000 });
65
+ if (result.exitCode !== 0)
66
+ throw new Error(formatCommandFailure(command, result));
67
+ return result.stdout;
54
68
  }
55
69
  async function readRawFile(sandbox, path, cwd) {
56
70
  const script = `const fs = require("fs"); process.stdout.write(fs.readFileSync(process.argv[1], "utf8"));`;
57
- const { stdout } = await sandbox.commands.run(`node -e ${q(script)} ${q(path)}`, { cwd, timeoutMs: 30000 });
58
- return stdout;
71
+ const command = `node -e ${q(script)} ${q(path)}`;
72
+ const result = await sandbox.commands.run(command, { cwd, timeoutMs: 30000 });
73
+ if (result.exitCode !== 0)
74
+ throw new Error(formatCommandFailure(command, result));
75
+ return result.stdout;
59
76
  }
60
77
  var readTool = {
61
78
  name: "Read",
@@ -116,8 +133,7 @@ var bashTool = {
116
133
  timeoutMs: timeoutMs ?? 120000
117
134
  });
118
135
  if (result.exitCode !== 0)
119
- throw new Error(`Command failed with exit code ${result.exitCode}${result.stdout ? `
120
- ${result.stdout}` : ""}`);
136
+ throw new Error(formatCommandFailure(command, result));
121
137
  return result.stdout;
122
138
  }
123
139
  };
@@ -138,6 +154,7 @@ async function runProcessorChain(processors, selectHook, initial, ctx) {
138
154
  }
139
155
 
140
156
  // src/request-context/request-context.ts
157
+ import { z as z2 } from "zod";
141
158
  var AC_RESERVED_PREFIX = "ac__";
142
159
  var AC_TEAM_ID = "ac__teamId";
143
160
  var AC_RUN_ID = "ac__runId";
@@ -163,6 +180,17 @@ var SERIALISABLE_RESERVED_KEYS = new Set([
163
180
  AC_API_KEY_SCOPES,
164
181
  AC_PARENT_RUN_ID
165
182
  ]);
183
+ var RequestContextWireSchema = z2.object({
184
+ reserved: z2.object({
185
+ teamId: z2.string(),
186
+ runId: z2.string(),
187
+ workflowId: z2.string(),
188
+ factoryId: z2.string().nullable(),
189
+ apiKeyScopes: z2.array(z2.string()).readonly(),
190
+ parentRunId: z2.string().nullable()
191
+ }),
192
+ user: z2.record(z2.string(), z2.unknown())
193
+ });
166
194
 
167
195
  class ReservedKeyError extends Error {
168
196
  key;
@@ -224,7 +252,8 @@ class RequestContext {
224
252
  return new RequestContext(reserved, new Map);
225
253
  }
226
254
  static deserialise(wire) {
227
- const ctx = new RequestContext({ ...wire.reserved, abortSignal: undefined }, new Map(Object.entries(wire.user)));
255
+ const parsed = RequestContextWireSchema.parse(wire);
256
+ const ctx = new RequestContext({ ...parsed.reserved, abortSignal: undefined }, new Map(Object.entries(parsed.user)));
228
257
  return ctx;
229
258
  }
230
259
  withAbortSignal(signal) {
@@ -372,6 +401,20 @@ class VercelRunner {
372
401
  return { kind: "allow", call: verdict.value };
373
402
  return verdict;
374
403
  }
404
+ captureCheckpoint() {
405
+ return { messages: [...this.messages] };
406
+ }
407
+ restoreCheckpoint(blob) {
408
+ if (!blob || typeof blob !== "object") {
409
+ throw new Error("Vercel runtime checkpoint is invalid: expected an object with messages[]");
410
+ }
411
+ const incoming = blob.messages;
412
+ if (!Array.isArray(incoming)) {
413
+ throw new Error("Vercel runtime checkpoint is invalid: expected messages[]");
414
+ }
415
+ this.messages.length = 0;
416
+ this.messages.push(...incoming);
417
+ }
375
418
  buildTools(iteration, signal) {
376
419
  const set = {};
377
420
  for (const t of this.tools) {
@@ -469,7 +512,7 @@ function createVercelRuntime(config) {
469
512
  });
470
513
  }
471
514
  // src/types/workflow.ts
472
- import { z as z2 } from "zod";
515
+ import { z as z3 } from "zod";
473
516
 
474
517
  // src/workflow-steps/types.ts
475
518
  var WORKFLOW_BRAND = Symbol.for("@agent-compose/sdk.Workflow");
@@ -546,7 +589,7 @@ function isWorkflow(value) {
546
589
 
547
590
  // src/types/workflow.ts
548
591
  function compileRunForm(def, metadata) {
549
- const unknownSchema = z2.unknown();
592
+ const unknownSchema = z3.unknown();
550
593
  const inputSchema = def.input ?? unknownSchema;
551
594
  const outputSchema = def.output ?? unknownSchema;
552
595
  const step = {
@@ -562,6 +605,11 @@ function compileRunForm(def, metadata) {
562
605
  setMetadata: stepCtx.setMetadata,
563
606
  step: stepCtx.step,
564
607
  agentEvents: stepCtx.agentEvents,
608
+ checkpoint: stepCtx.checkpoint,
609
+ pause: stepCtx.pause,
610
+ requestDecision: stepCtx.requestDecision,
611
+ sleep: stepCtx.sleep,
612
+ waitForEvent: stepCtx.waitForEvent,
565
613
  processors: metadata.processors ?? []
566
614
  };
567
615
  const sandbox = stepCtx.sandbox;
@@ -678,11 +726,14 @@ async function* parseSseStream(body) {
678
726
  continue;
679
727
  }
680
728
  if (line === "" && dataLines.length > 0) {
729
+ let data;
681
730
  try {
682
- const data = JSON.parse(dataLines.join(`
731
+ data = JSON.parse(dataLines.join(`
683
732
  `));
684
- yield { id: seq, event, data };
685
- } catch {}
733
+ } catch (err) {
734
+ throw new Error(`Malformed SSE JSON payload for event "${event || "message"}": ${err instanceof Error ? err.message : String(err)}`);
735
+ }
736
+ yield { id: seq, event, data };
686
737
  event = "";
687
738
  seq = 0;
688
739
  dataLines = [];
@@ -706,6 +757,12 @@ function templatePath(factorySlug, ...rest) {
706
757
  return `/api/v1/factories/${encodeURIComponent(factorySlug)}/templates${tail}`;
707
758
  }
708
759
  var PUBLIC_API_HOST = "https://api.agentcompose.ai";
760
+ function steerAnswerPayload(message, actor) {
761
+ if (typeof message !== "string" || message.trim().length === 0) {
762
+ throw new Error("answerSteer: `message` must be a non-empty string");
763
+ }
764
+ return { message, ...actor !== undefined ? { actor } : {} };
765
+ }
709
766
 
710
767
  class AgentComposeClient {
711
768
  fetch;
@@ -791,6 +848,56 @@ class AgentComposeClient {
791
848
  getStatus(runId) {
792
849
  return this.fetch(`/api/v1/workflows/${runId}/status`);
793
850
  }
851
+ async resumePause(runId, pauseId, payload, opts) {
852
+ const headers = {};
853
+ if (opts?.idempotencyKey)
854
+ headers["Idempotency-Key"] = opts.idempotencyKey;
855
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/${encodeURIComponent(pauseId)}/resume`, {
856
+ method: "POST",
857
+ body: { resumePayload: payload },
858
+ ...Object.keys(headers).length ? { headers } : {},
859
+ ...opts?.signal ? { signal: opts.signal } : {}
860
+ });
861
+ }
862
+ async resumePauseByKey(runId, correlationKey, payload, opts) {
863
+ const headers = {};
864
+ if (opts?.idempotencyKey)
865
+ headers["Idempotency-Key"] = opts.idempotencyKey;
866
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/by-key/${encodeURIComponent(correlationKey)}/resume`, {
867
+ method: "POST",
868
+ body: { resumePayload: payload },
869
+ ...Object.keys(headers).length ? { headers } : {},
870
+ ...opts?.signal ? { signal: opts.signal } : {}
871
+ });
872
+ }
873
+ async answerSteerByKey(runId, correlationKey, message, opts) {
874
+ return this.resumePauseByKey(runId, correlationKey, steerAnswerPayload(message, opts?.actor), opts);
875
+ }
876
+ async answerSteer(runId, pauseId, message, opts) {
877
+ return this.resumePause(runId, pauseId, steerAnswerPayload(message, opts?.actor), opts);
878
+ }
879
+ async requestAgentPause(runId, agentId, opts) {
880
+ const body = {};
881
+ if (opts?.reason !== undefined)
882
+ body.reason = opts.reason;
883
+ if (opts?.correlationKey !== undefined)
884
+ body.correlationKey = opts.correlationKey;
885
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/pause`, {
886
+ method: "POST",
887
+ body,
888
+ ...opts?.signal ? { signal: opts.signal } : {}
889
+ });
890
+ }
891
+ async sendAgentMessage(runId, agentId, message, opts) {
892
+ const body = { message };
893
+ if (opts?.senderName !== undefined)
894
+ body.senderName = opts.senderName;
895
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/messages`, {
896
+ method: "POST",
897
+ body,
898
+ ...opts?.signal ? { signal: opts.signal } : {}
899
+ });
900
+ }
794
901
  getRun(runId) {
795
902
  return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}`);
796
903
  }
@@ -930,10 +1037,6 @@ class AgentComposeClient {
930
1037
  }
931
1038
  }
932
1039
  }
933
- // src/utils/discovery.ts
934
- function discoverRuntimeName(source) {
935
- return source.match(/runtime:\s*["']([^"']+)["']/)?.[1] ?? null;
936
- }
937
1040
  // src/utils/bundler.ts
938
1041
  import { createHash } from "node:crypto";
939
1042
  import { parse as babelParse } from "@babel/parser";
@@ -986,8 +1089,8 @@ async function extractFromBundle(source, label, pick) {
986
1089
  } finally {
987
1090
  await loaded.cleanup();
988
1091
  }
989
- } catch {
990
- return;
1092
+ } catch (err) {
1093
+ throw new WorkflowSourceValidationError(`Bundled ${label} could not be evaluated: ${err instanceof Error ? err.message : String(err)}`);
991
1094
  }
992
1095
  }
993
1096
  var DEFINE_WORKFLOW_CALLEE = /^(?:defineWorkflow|defineSandboxEnvironment)\d*$/;
@@ -1080,10 +1183,10 @@ async function bundleWorkflow(workflowPath, overrides) {
1080
1183
  function extractIOSchema(zodSchema) {
1081
1184
  let json;
1082
1185
  try {
1083
- const z3 = __require("zod");
1084
- if (!z3.toJSONSchema)
1186
+ const z4 = __require("zod");
1187
+ if (!z4.toJSONSchema)
1085
1188
  return;
1086
- json = z3.toJSONSchema(zodSchema);
1189
+ json = z4.toJSONSchema(zodSchema);
1087
1190
  } catch {
1088
1191
  return;
1089
1192
  }
@@ -1116,24 +1219,26 @@ function extractIOSchema(zodSchema) {
1116
1219
  return out;
1117
1220
  }
1118
1221
  // src/utils/schemas.ts
1119
- import { z as z3 } from "zod";
1120
- var AgentStatusSchema = z3.object({
1121
- summary: z3.string(),
1122
- completed: z3.array(z3.string()),
1123
- blockers: z3.array(z3.string()),
1124
- exit_signal: z3.boolean()
1222
+ import { z as z4 } from "zod";
1223
+ var AgentStatusSchema = z4.object({
1224
+ summary: z4.string(),
1225
+ completed: z4.array(z4.string()),
1226
+ blockers: z4.array(z4.string()),
1227
+ exit_signal: z4.boolean(),
1228
+ needs_input: z4.boolean().optional(),
1229
+ question: z4.string().optional()
1125
1230
  });
1126
1231
  // src/runtimes/claude.ts
1127
1232
  import { query } from "@anthropic-ai/claude-agent-sdk";
1128
1233
 
1129
1234
  // src/agent/agent-loop.ts
1130
- import { z as z5 } from "zod";
1235
+ import { z as z9 } from "zod";
1131
1236
 
1132
1237
  // src/agent/protocol.ts
1133
- import { z as z4 } from "zod";
1134
- var AgentMessageSchema = z4.object({
1135
- type: z4.enum(["init", "text", "thinking", "tool_use", "tool_result", "done", "error", "usage"]),
1136
- timestamp: z4.string()
1238
+ import { z as z5 } from "zod";
1239
+ var AgentMessageSchema = z5.object({
1240
+ type: z5.enum(["init", "text", "thinking", "tool_use", "tool_result", "done", "error", "usage"]),
1241
+ timestamp: z5.string()
1137
1242
  }).passthrough();
1138
1243
  function parseAgentResponse(text) {
1139
1244
  const match = text.match(/<response>([\s\S]*?)<\/response>/);
@@ -1148,6 +1253,557 @@ function parseAgentResponse(text) {
1148
1253
 
1149
1254
  // src/agent/agent-loop.ts
1150
1255
  import { randomUUID } from "node:crypto";
1256
+
1257
+ // src/pause/manager.ts
1258
+ import { z as z6 } from "zod";
1259
+
1260
+ // src/pause/state-dir.ts
1261
+ import { promises as fs } from "node:fs";
1262
+ import { join as join2 } from "node:path";
1263
+ import { randomBytes } from "node:crypto";
1264
+ function getStateDir() {
1265
+ return process.env.AGENT_COMPOSE_STATE_DIR ?? "/tmp/wf/state";
1266
+ }
1267
+ var checkpointsDir = () => join2(getStateDir(), "checkpoints");
1268
+ var pausesDir = () => join2(getStateDir(), "pauses");
1269
+ var agentStatePath = (agentInstanceId) => join2(getStateDir(), `agent-${agentInstanceId}.json`);
1270
+ var runtimeStatePath = (agentInstanceId) => join2(getStateDir(), `runtime-${agentInstanceId}.json`);
1271
+ var checkpointPath = (name) => join2(checkpointsDir(), `${name}.json`);
1272
+ var pausePath = (pauseId) => join2(pausesDir(), `${pauseId}.json`);
1273
+ var NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/;
1274
+ function assertSafeName(label, name) {
1275
+ if (!NAME_RE.test(name)) {
1276
+ throw new Error(`${label} name must match ${NAME_RE} (got ${JSON.stringify(name)})`);
1277
+ }
1278
+ }
1279
+ function assertSafeCheckpointName(name) {
1280
+ assertSafeName("checkpoint", name);
1281
+ }
1282
+ async function ensureDirs() {
1283
+ await fs.mkdir(checkpointsDir(), { recursive: true });
1284
+ await fs.mkdir(pausesDir(), { recursive: true });
1285
+ }
1286
+ async function atomicWriteJSON(path, value) {
1287
+ await ensureDirs();
1288
+ const tmp = `${path}.tmp.${randomBytes(6).toString("hex")}`;
1289
+ const payload = JSON.stringify(value);
1290
+ if (payload === undefined) {
1291
+ throw new Error("pause state values must be JSON-serialisable");
1292
+ }
1293
+ const handle = await fs.open(tmp, "w");
1294
+ try {
1295
+ await handle.writeFile(payload, { encoding: "utf8" });
1296
+ await handle.sync();
1297
+ } finally {
1298
+ await handle.close();
1299
+ }
1300
+ await fs.rename(tmp, path);
1301
+ }
1302
+ async function readJSON(path) {
1303
+ try {
1304
+ const raw = await fs.readFile(path, "utf8");
1305
+ return JSON.parse(raw);
1306
+ } catch (err) {
1307
+ if (err.code === "ENOENT")
1308
+ return null;
1309
+ throw err;
1310
+ }
1311
+ }
1312
+ async function readJSONWithPresence(path) {
1313
+ try {
1314
+ const raw = await fs.readFile(path, "utf8");
1315
+ return { found: true, value: JSON.parse(raw) };
1316
+ } catch (err) {
1317
+ if (err.code === "ENOENT")
1318
+ return { found: false };
1319
+ throw err;
1320
+ }
1321
+ }
1322
+ async function readAgentState(agentInstanceId) {
1323
+ assertSafeName("agent", agentInstanceId);
1324
+ return readJSON(agentStatePath(agentInstanceId));
1325
+ }
1326
+ async function writeAgentState(agentInstanceId, state) {
1327
+ assertSafeName("agent", agentInstanceId);
1328
+ await atomicWriteJSON(agentStatePath(agentInstanceId), state);
1329
+ }
1330
+ async function readRuntimeCheckpoint(agentInstanceId) {
1331
+ assertSafeName("runtime", agentInstanceId);
1332
+ return readJSON(runtimeStatePath(agentInstanceId));
1333
+ }
1334
+ async function writeRuntimeCheckpoint(agentInstanceId, blob) {
1335
+ assertSafeName("runtime", agentInstanceId);
1336
+ await atomicWriteJSON(runtimeStatePath(agentInstanceId), blob);
1337
+ }
1338
+ async function readCheckpoint(name) {
1339
+ assertSafeCheckpointName(name);
1340
+ return readJSONWithPresence(checkpointPath(name));
1341
+ }
1342
+ async function writeCheckpoint(name, value) {
1343
+ assertSafeCheckpointName(name);
1344
+ await atomicWriteJSON(checkpointPath(name), value);
1345
+ }
1346
+ function hasOwn(obj, key) {
1347
+ return Object.prototype.hasOwnProperty.call(obj, key);
1348
+ }
1349
+ function validatePauseRecord(value, expectedPauseId) {
1350
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1351
+ throw new Error(`pause record ${expectedPauseId} is malformed: expected object`);
1352
+ }
1353
+ const record = value;
1354
+ if (record.pauseId !== undefined && record.pauseId !== expectedPauseId) {
1355
+ throw new Error(`pause record ${expectedPauseId} is malformed: pauseId mismatch`);
1356
+ }
1357
+ if (record.resolved === true) {
1358
+ if (typeof record.expired !== "boolean") {
1359
+ throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require boolean expired`);
1360
+ }
1361
+ if (!hasOwn(record, "resumePayload") || record.resumePayload === undefined) {
1362
+ throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require resumePayload`);
1363
+ }
1364
+ return {
1365
+ pauseId: expectedPauseId,
1366
+ request: hasOwn(record, "request") ? record.request : null,
1367
+ ...record
1368
+ };
1369
+ }
1370
+ if (record.pauseId !== expectedPauseId) {
1371
+ throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require matching pauseId`);
1372
+ }
1373
+ if (!hasOwn(record, "request") || record.request === undefined) {
1374
+ throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require request`);
1375
+ }
1376
+ if (record.resolved !== undefined && record.resolved !== false) {
1377
+ throw new Error(`pause record ${expectedPauseId} is malformed: resolved must be true, false, or absent`);
1378
+ }
1379
+ if (hasOwn(record, "expired") || hasOwn(record, "resumePayload")) {
1380
+ throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records must not include resolution fields`);
1381
+ }
1382
+ return record;
1383
+ }
1384
+ async function readPauseRecord(pauseId) {
1385
+ assertSafeName("pause", pauseId);
1386
+ const record = await readJSON(pausePath(pauseId));
1387
+ return record === null ? null : validatePauseRecord(record, pauseId);
1388
+ }
1389
+ async function writePauseRecord(record) {
1390
+ assertSafeName("pause", record.pauseId);
1391
+ validatePauseRecord(record, record.pauseId);
1392
+ await atomicWriteJSON(pausePath(record.pauseId), record);
1393
+ }
1394
+
1395
+ // src/pause/manager.ts
1396
+ var AGENT_LOOP_STATE_SCHEMA_VERSION = 2;
1397
+ var SUPPORTED_AGENT_LOOP_STATE_VERSIONS = z6.union([z6.literal(1), z6.literal(2)]);
1398
+ var RunningAgentLoopStateSchema = z6.object({
1399
+ schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1400
+ phase: z6.literal("running"),
1401
+ iteration: z6.number().int().nonnegative(),
1402
+ completedIterations: z6.number().int().nonnegative(),
1403
+ lastSessionId: z6.string(),
1404
+ lastStatus: AgentStatusSchema.nullable(),
1405
+ lastResponseText: z6.string(),
1406
+ lastResponseValidationError: z6.string(),
1407
+ iterationsWithoutStatus: z6.number().int().nonnegative(),
1408
+ blockerStreak: z6.object({ key: z6.string(), count: z6.number().int().nonnegative() }).nullable(),
1409
+ pendingSteerPause: z6.object({ reason: z6.string(), correlationKey: z6.string().nullable(), at: z6.number().int() }).nullable().default(null)
1410
+ });
1411
+ var SettledAgentLoopStateSchema = z6.object({
1412
+ schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1413
+ phase: z6.literal("settled"),
1414
+ result: z6.object({
1415
+ sessionId: z6.string(),
1416
+ lastStatus: AgentStatusSchema.nullable(),
1417
+ iterations: z6.number().int().nonnegative(),
1418
+ response: z6.unknown().optional()
1419
+ })
1420
+ });
1421
+ var AgentLoopStateSchema = z6.discriminatedUnion("phase", [
1422
+ RunningAgentLoopStateSchema,
1423
+ SettledAgentLoopStateSchema
1424
+ ]);
1425
+ function toProgressState(state) {
1426
+ return {
1427
+ iteration: state.iteration,
1428
+ completedIterations: state.completedIterations,
1429
+ lastSessionId: state.lastSessionId,
1430
+ lastStatus: state.lastStatus,
1431
+ lastResponseText: state.lastResponseText,
1432
+ lastResponseValidationError: state.lastResponseValidationError,
1433
+ iterationsWithoutStatus: state.iterationsWithoutStatus,
1434
+ blockerStreak: state.blockerStreak,
1435
+ pendingSteerPause: state.pendingSteerPause
1436
+ };
1437
+ }
1438
+
1439
+ class PauseManager {
1440
+ agentId;
1441
+ constructor(agentId) {
1442
+ this.agentId = agentId;
1443
+ }
1444
+ async restoreAgentLoop(runtime) {
1445
+ const priorState = await readAgentState(this.agentId);
1446
+ if (priorState === null)
1447
+ return { kind: "fresh" };
1448
+ const parsed = AgentLoopStateSchema.safeParse(priorState);
1449
+ if (!parsed.success)
1450
+ return { kind: "invalid", error: parsed.error.message };
1451
+ const state = parsed.data;
1452
+ if (state.phase === "settled") {
1453
+ return {
1454
+ kind: "settled",
1455
+ result: {
1456
+ sessionId: state.result.sessionId,
1457
+ lastStatus: state.result.lastStatus,
1458
+ iterations: state.result.iterations,
1459
+ ...Object.prototype.hasOwnProperty.call(state.result, "response") ? { response: state.result.response } : {}
1460
+ }
1461
+ };
1462
+ }
1463
+ const runtimeBlob = await readRuntimeCheckpoint(this.agentId);
1464
+ if (runtimeBlob !== null && !runtime.restoreCheckpoint) {
1465
+ throw new Error(`runtime checkpoint exists for resumed agentId=${this.agentId}, but runtime cannot restore it`);
1466
+ }
1467
+ if (runtime.restoreCheckpoint && runtimeBlob === null) {
1468
+ throw new Error(`missing runtime checkpoint for resumed agentId=${this.agentId}`);
1469
+ }
1470
+ if (runtimeBlob !== null)
1471
+ runtime.restoreCheckpoint?.(runtimeBlob);
1472
+ return { kind: "running", state: toProgressState(state) };
1473
+ }
1474
+ async saveRunningAgentLoop(state, runtime) {
1475
+ await writeAgentState(this.agentId, {
1476
+ schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1477
+ phase: "running",
1478
+ ...state
1479
+ });
1480
+ if (runtime.captureCheckpoint) {
1481
+ const blob = runtime.captureCheckpoint();
1482
+ if (blob !== undefined)
1483
+ await writeRuntimeCheckpoint(this.agentId, blob);
1484
+ }
1485
+ }
1486
+ async saveSettledAgentLoop(result) {
1487
+ await writeAgentState(this.agentId, {
1488
+ schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1489
+ phase: "settled",
1490
+ result: {
1491
+ sessionId: result.sessionId,
1492
+ lastStatus: result.lastStatus,
1493
+ iterations: result.iterations,
1494
+ ...result.response !== undefined ? { response: result.response } : {}
1495
+ }
1496
+ });
1497
+ }
1498
+ }
1499
+
1500
+ // src/pause/pause-core.ts
1501
+ import { createHash as createHash2 } from "node:crypto";
1502
+
1503
+ // src/step-invocation/types.ts
1504
+ import { z as z7 } from "zod";
1505
+ var StepPauseRequestSchema = z7.object({
1506
+ reason: z7.string().min(1),
1507
+ kind: z7.enum(["decision", "sleep", "event", "custom"]),
1508
+ payload: z7.record(z7.string(), z7.unknown()).optional(),
1509
+ ttlMs: z7.number().int().positive().optional(),
1510
+ correlationKey: z7.string().min(1).optional(),
1511
+ snapshot: z7.boolean().optional()
1512
+ });
1513
+
1514
+ class StepExecutionError extends Error {
1515
+ kind;
1516
+ exitCode;
1517
+ constructor(error, stepLabel) {
1518
+ super(`[${error.kind}] ${stepLabel}: ${error.message}`);
1519
+ this.name = "StepExecutionError";
1520
+ this.kind = error.kind;
1521
+ this.exitCode = error.kind === "runner-exit" ? error.exitCode : undefined;
1522
+ }
1523
+ }
1524
+
1525
+ // src/active-step.ts
1526
+ import { AsyncLocalStorage } from "node:async_hooks";
1527
+ var activeStepStorage = new AsyncLocalStorage;
1528
+ var fallbackActive = null;
1529
+ var BRIDGE_KEY = Symbol.for("agent-compose.activeStep.bridge");
1530
+ function bridgeSlot() {
1531
+ const g = globalThis;
1532
+ return g[BRIDGE_KEY] ??= { state: null };
1533
+ }
1534
+ function makeState(step) {
1535
+ return {
1536
+ stepIndex: step.stepIndex,
1537
+ nextAgentCallIndex: 0,
1538
+ pauseOrdinalByScope: new Map
1539
+ };
1540
+ }
1541
+ function currentState() {
1542
+ return activeStepStorage.getStore() ?? bridgeSlot().state ?? fallbackActive;
1543
+ }
1544
+ async function runWithActiveStep(step, fn) {
1545
+ return activeStepStorage.run(makeState(step), fn);
1546
+ }
1547
+ function setActiveStepBridge(step) {
1548
+ const slot = bridgeSlot();
1549
+ const prev = { state: slot.state };
1550
+ slot.state = step ? makeState(step) : null;
1551
+ return prev;
1552
+ }
1553
+ function restoreActiveStepBridge(prev) {
1554
+ bridgeSlot().state = prev.state;
1555
+ }
1556
+ function getActiveStep() {
1557
+ const state = currentState();
1558
+ return state ? { stepIndex: state.stepIndex } : null;
1559
+ }
1560
+ function nextAgentCallInActiveStep() {
1561
+ const state = currentState();
1562
+ if (!state)
1563
+ return null;
1564
+ const callIndex = state.nextAgentCallIndex;
1565
+ state.nextAgentCallIndex += 1;
1566
+ return { stepIndex: state.stepIndex, callIndex };
1567
+ }
1568
+ function nextPauseOrdinalInActiveStep(scope) {
1569
+ const state = currentState();
1570
+ if (!state)
1571
+ return null;
1572
+ const next = state.pauseOrdinalByScope.get(scope) ?? 0;
1573
+ state.pauseOrdinalByScope.set(scope, next + 1);
1574
+ return next;
1575
+ }
1576
+
1577
+ // src/pause/errors.ts
1578
+ class PauseError extends Error {
1579
+ constructor(message) {
1580
+ super(message);
1581
+ this.name = new.target.name;
1582
+ }
1583
+ }
1584
+
1585
+ class PauseExpiredError extends PauseError {
1586
+ code = "pause_expired";
1587
+ constructor(message = "pause expired before it was resolved") {
1588
+ super(message);
1589
+ }
1590
+ }
1591
+
1592
+ class PauseSchemaError extends PauseError {
1593
+ code = "pause_schema";
1594
+ issues;
1595
+ constructor(message, issues) {
1596
+ super(message);
1597
+ this.issues = issues;
1598
+ }
1599
+ }
1600
+
1601
+ class PauseRequestError extends PauseError {
1602
+ code = "pause_request";
1603
+ constructor(message = "pause request is invalid") {
1604
+ super(message);
1605
+ }
1606
+ }
1607
+
1608
+ // src/pause/pause-core.ts
1609
+ var PAUSE_SIGNAL_BRAND = Symbol.for("agent-compose.PauseSignal");
1610
+
1611
+ class PauseSignal {
1612
+ pauseId;
1613
+ pauseRequest;
1614
+ observability;
1615
+ [PAUSE_SIGNAL_BRAND] = true;
1616
+ constructor(pauseId, pauseRequest, observability) {
1617
+ this.pauseId = pauseId;
1618
+ this.pauseRequest = pauseRequest;
1619
+ this.observability = observability;
1620
+ }
1621
+ }
1622
+ function isPauseSignal(err) {
1623
+ return typeof err === "object" && err !== null && err[PAUSE_SIGNAL_BRAND] === true;
1624
+ }
1625
+ var PAUSE_NAMESPACE = "5f3e2b8a-0c41-4e9d-9a7b-6d2c1f8e3a40";
1626
+ function uuidToBytes(uuid) {
1627
+ return Buffer.from(uuid.replace(/-/g, ""), "hex");
1628
+ }
1629
+ function bytesToUuid(b) {
1630
+ const h = b.toString("hex");
1631
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
1632
+ }
1633
+ function uuidv5(name, namespace) {
1634
+ const hash = createHash2("sha1").update(uuidToBytes(namespace)).update(Buffer.from(name, "utf8")).digest();
1635
+ const bytes = Buffer.from(hash.subarray(0, 16));
1636
+ bytes[6] = bytes[6] & 15 | 80;
1637
+ bytes[8] = bytes[8] & 63 | 128;
1638
+ return bytesToUuid(bytes);
1639
+ }
1640
+ function scopeOf(coord) {
1641
+ return coord.agentScope ? `${coord.runId}:${coord.stepIndex}:${coord.agentScope.agentId}:${coord.agentScope.iteration}` : `${coord.runId}:${coord.stepIndex}:bare`;
1642
+ }
1643
+ var pauseOrdinalByScope = new Map;
1644
+ function nextPauseOrdinal(scope) {
1645
+ const activeOrdinal = nextPauseOrdinalInActiveStep(scope);
1646
+ if (activeOrdinal !== null)
1647
+ return activeOrdinal;
1648
+ const next = pauseOrdinalByScope.get(scope) ?? 0;
1649
+ pauseOrdinalByScope.set(scope, next + 1);
1650
+ return next;
1651
+ }
1652
+ function nextPauseId(coord) {
1653
+ const scope = scopeOf(coord);
1654
+ const ordinal = nextPauseOrdinal(scope);
1655
+ return uuidv5(`${scope}:${ordinal}`, PAUSE_NAMESPACE);
1656
+ }
1657
+ function nonJsonPath(value, path = "$", seen = new Set) {
1658
+ if (value === null)
1659
+ return null;
1660
+ const kind = typeof value;
1661
+ if (kind === "string" || kind === "boolean")
1662
+ return null;
1663
+ if (kind === "number")
1664
+ return Number.isFinite(value) ? null : path;
1665
+ if (kind === "undefined" || kind === "bigint" || kind === "function" || kind === "symbol")
1666
+ return path;
1667
+ if (kind !== "object")
1668
+ return path;
1669
+ const obj = value;
1670
+ if (seen.has(obj))
1671
+ return path;
1672
+ seen.add(obj);
1673
+ if (Array.isArray(value)) {
1674
+ for (let i = 0;i < value.length; i++) {
1675
+ const child = nonJsonPath(value[i], `${path}[${i}]`, seen);
1676
+ if (child)
1677
+ return child;
1678
+ }
1679
+ seen.delete(obj);
1680
+ return null;
1681
+ }
1682
+ const proto = Object.getPrototypeOf(obj);
1683
+ if (proto !== Object.prototype && proto !== null)
1684
+ return path;
1685
+ for (const key of Reflect.ownKeys(obj)) {
1686
+ if (typeof key !== "string")
1687
+ return `${path}.${String(key)}`;
1688
+ const desc = Object.getOwnPropertyDescriptor(obj, key);
1689
+ if (!desc?.enumerable)
1690
+ return `${path}.${key}`;
1691
+ const child = nonJsonPath(value[key], `${path}.${key}`, seen);
1692
+ if (child)
1693
+ return child;
1694
+ }
1695
+ seen.delete(obj);
1696
+ return null;
1697
+ }
1698
+ function toWireRequest(req, kind) {
1699
+ const wire = {
1700
+ reason: req.reason,
1701
+ kind,
1702
+ ...req.payload !== undefined ? { payload: req.payload } : {},
1703
+ ...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {},
1704
+ ...req.correlationKey !== undefined ? { correlationKey: req.correlationKey } : {},
1705
+ ...req.snapshot !== undefined ? { snapshot: req.snapshot } : {}
1706
+ };
1707
+ const parsed = StepPauseRequestSchema.safeParse(wire);
1708
+ if (!parsed.success) {
1709
+ throw new PauseRequestError(`pause request failed validation: ${parsed.error.message}`);
1710
+ }
1711
+ const badPath = nonJsonPath(parsed.data);
1712
+ if (badPath) {
1713
+ throw new PauseRequestError(`pause request must be JSON-serializable (invalid value at ${badPath})`);
1714
+ }
1715
+ return parsed.data;
1716
+ }
1717
+ async function corePause(req, coord, kind = "custom") {
1718
+ const wire = toWireRequest(req, kind);
1719
+ const pauseId = nextPauseId(coord);
1720
+ const record = await readPauseRecord(pauseId);
1721
+ if (record === null || record.resolved !== true) {
1722
+ await writePauseRecord({ pauseId, request: wire });
1723
+ throw new PauseSignal(pauseId, wire);
1724
+ }
1725
+ if (record.expired === true) {
1726
+ const onExpiry = req.onExpiry ?? { mode: "throw" };
1727
+ if (onExpiry.mode === "resolve")
1728
+ return onExpiry.value;
1729
+ throw new PauseExpiredError(`pause "${req.reason}" expired before it was resolved`);
1730
+ }
1731
+ const payload = record.resumePayload ?? null;
1732
+ if (req.schema) {
1733
+ const parsed = req.schema.safeParse(payload);
1734
+ if (!parsed.success) {
1735
+ throw new PauseSchemaError(`resume payload for pause "${req.reason}" failed schema validation: ${parsed.error.message}`, parsed.error.issues);
1736
+ }
1737
+ return parsed.data;
1738
+ }
1739
+ return payload;
1740
+ }
1741
+
1742
+ // src/agent/steer-control.ts
1743
+ import { z as z8 } from "zod";
1744
+ var SteerDecisionSchema = z8.object({
1745
+ message: z8.string().min(1),
1746
+ actor: z8.string().nullish()
1747
+ });
1748
+ var steerPending = new Map;
1749
+ function signalSteerPending(agentId, payload) {
1750
+ const queue = steerPending.get(agentId) ?? [];
1751
+ const existing = queue.findIndex((p) => p.correlationKey === payload.correlationKey);
1752
+ if (existing >= 0)
1753
+ queue[existing] = payload;
1754
+ else
1755
+ queue.push(payload);
1756
+ steerPending.set(agentId, queue);
1757
+ }
1758
+ function consumeSteerPending(agentId) {
1759
+ const queue = steerPending.get(agentId);
1760
+ if (queue === undefined || queue.length === 0)
1761
+ return null;
1762
+ const next = queue.shift();
1763
+ if (queue.length === 0)
1764
+ steerPending.delete(agentId);
1765
+ return next;
1766
+ }
1767
+ async function runControlPoller(opts) {
1768
+ const headers = { authorization: `Bearer ${opts.token}`, accept: "application/json" };
1769
+ const url = `${opts.baseUrl.replace(/\/+$/, "")}/api/v1/internal/runs/${opts.runId}/control`;
1770
+ let backoffMs = 0;
1771
+ while (!opts.signal.aborted) {
1772
+ if (backoffMs > 0) {
1773
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
1774
+ if (opts.signal.aborted)
1775
+ return;
1776
+ }
1777
+ try {
1778
+ const res = await fetch(url, { method: "GET", headers, signal: opts.signal });
1779
+ if (!res.ok) {
1780
+ if (res.status === 401 || res.status === 403)
1781
+ return;
1782
+ backoffMs = Math.min(8000, (backoffMs || 500) * 2);
1783
+ continue;
1784
+ }
1785
+ backoffMs = 0;
1786
+ const body = await res.json();
1787
+ const ctrl = body.control;
1788
+ if (ctrl && ctrl.kind === "steer_pause" && (ctrl.agentId == null || ctrl.agentId === opts.agentId)) {
1789
+ signalSteerPending(opts.agentId, {
1790
+ reason: ctrl.reason ?? null,
1791
+ correlationKey: ctrl.correlationKey ?? null
1792
+ });
1793
+ }
1794
+ } catch (err) {
1795
+ if (opts.signal.aborted)
1796
+ return;
1797
+ backoffMs = Math.min(8000, (backoffMs || 500) * 2);
1798
+ if (backoffMs >= 4000) {
1799
+ process.stderr.write(`[control-poller] poll failed, backing off ${backoffMs}ms: ${err instanceof Error ? err.message : String(err)}
1800
+ `);
1801
+ }
1802
+ }
1803
+ }
1804
+ }
1805
+
1806
+ // src/agent/agent-loop.ts
1151
1807
  var DEFAULT_CLAUDE_MODEL = "claude-opus-4-7";
1152
1808
  var SAME_BLOCKER_ITERATIONS = 3;
1153
1809
  var STALL_ITERATIONS = 3;
@@ -1183,7 +1839,7 @@ function summarizeAgentMessage(msg) {
1183
1839
  case "thinking":
1184
1840
  return { type: "thinking", text: msg.text };
1185
1841
  case "tool_use":
1186
- return { type: "tool_use", toolName: msg.toolName, toolUseId: msg.toolUseId, toolInputPreview: preview(msg.toolInput) };
1842
+ return { type: "tool_use", toolName: msg.toolName, toolUseId: msg.toolUseId, toolInput: msg.toolInput, toolInputPreview: preview(msg.toolInput) };
1187
1843
  case "tool_result":
1188
1844
  return { type: "tool_result", toolUseId: msg.toolUseId, output: truncate(msg.output), isError: msg.isError };
1189
1845
  case "usage":
@@ -1236,7 +1892,7 @@ async function agentLoop(opts) {
1236
1892
  processors,
1237
1893
  requestContext,
1238
1894
  agentId,
1239
- ...opts.responseSchema ? { outputFormat: { type: "json_schema", schema: z5.toJSONSchema(opts.responseSchema) } } : {}
1895
+ ...opts.responseSchema ? { outputFormat: { type: "json_schema", schema: z9.toJSONSchema(opts.responseSchema) } } : {}
1240
1896
  });
1241
1897
  if (processors.some((p) => p.processToolCall) && !client.supportsToolCallProcessor) {
1242
1898
  process.stderr.write(`${logLabel} processToolCall registered but runtime does not support pre-tool-use gating — hook is dormant
@@ -1249,11 +1905,101 @@ async function agentLoop(opts) {
1249
1905
  let completedIterations = 0;
1250
1906
  let lastResponseText = "";
1251
1907
  let lastResponseValidationError = "";
1252
- opts.onAgentLifecycleEvent?.({ event: "agent.spawned", at: startedAt, agentId, label, allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS });
1908
+ let startIteration = 0;
1909
+ let resumed = false;
1910
+ let pendingSteerPause = null;
1911
+ const pauseManager = new PauseManager(agentId);
1912
+ const restore = await pauseManager.restoreAgentLoop(client);
1913
+ if (restore.kind === "settled") {
1914
+ process.stdout.write(`${logLabel} restored settled agent result from checkpoint after ${restore.result.iterations} iterations
1915
+ `);
1916
+ return { agentId, label, ...restore.result };
1917
+ }
1918
+ if (restore.kind === "running") {
1919
+ const s = restore.state;
1920
+ startIteration = s.iteration;
1921
+ completedIterations = s.completedIterations;
1922
+ lastSessionId = s.lastSessionId;
1923
+ lastStatus = s.lastStatus;
1924
+ lastResponseText = s.lastResponseText;
1925
+ lastResponseValidationError = s.lastResponseValidationError;
1926
+ iterationsWithoutStatus = s.iterationsWithoutStatus;
1927
+ blockerStreak = s.blockerStreak;
1928
+ pendingSteerPause = s.pendingSteerPause ?? null;
1929
+ resumed = true;
1930
+ process.stdout.write(`${logLabel} resumed from agent-state checkpoint at iteration ${startIteration}/${maxIterations}
1931
+ `);
1932
+ }
1933
+ if (restore.kind === "invalid") {
1934
+ process.stderr.write(`${logLabel} agent-state checkpoint at agentId=${agentId} is unrecognised — starting fresh. ` + `Schema version mismatch or corrupt file: ${restore.error}
1935
+ `);
1936
+ }
1937
+ if (!resumed) {
1938
+ opts.onAgentLifecycleEvent?.({
1939
+ event: "agent.spawned",
1940
+ at: startedAt,
1941
+ agentId,
1942
+ label,
1943
+ allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS,
1944
+ ...client.model != null ? { model: client.model } : {},
1945
+ ...client.kind != null ? { runtimeKind: client.kind } : {}
1946
+ });
1947
+ }
1948
+ const flushState = async (currentIteration) => {
1949
+ await pauseManager.saveRunningAgentLoop({
1950
+ iteration: currentIteration,
1951
+ completedIterations,
1952
+ lastSessionId,
1953
+ lastStatus,
1954
+ lastResponseText,
1955
+ lastResponseValidationError,
1956
+ iterationsWithoutStatus,
1957
+ blockerStreak,
1958
+ pendingSteerPause
1959
+ }, client);
1960
+ };
1961
+ const writeSettledState = async (result) => {
1962
+ await pauseManager.saveSettledAgentLoop({
1963
+ sessionId: result.sessionId,
1964
+ lastStatus: result.lastStatus,
1965
+ iterations: result.iterations,
1966
+ ...result.response !== undefined ? { response: result.response } : {}
1967
+ });
1968
+ };
1253
1969
  try {
1254
- for (let iteration = 0;iteration < maxIterations; iteration++) {
1970
+ for (let iteration = startIteration;iteration < maxIterations || !!opts.pause && pendingSteerPause !== null; iteration++) {
1971
+ await flushState(iteration);
1972
+ let steerDecision = null;
1973
+ if (opts.pause) {
1974
+ if (pendingSteerPause === null) {
1975
+ const steer = opts.consumeSteerPending?.() ?? null;
1976
+ if (steer) {
1977
+ pendingSteerPause = {
1978
+ reason: steer.reason ?? "human steer",
1979
+ correlationKey: steer.correlationKey,
1980
+ at: Date.now()
1981
+ };
1982
+ }
1983
+ }
1984
+ if (pendingSteerPause !== null) {
1985
+ await flushState(iteration);
1986
+ steerDecision = await opts.pause({
1987
+ reason: pendingSteerPause.reason,
1988
+ ...pendingSteerPause.correlationKey !== null ? { correlationKey: pendingSteerPause.correlationKey } : {},
1989
+ schema: SteerDecisionSchema
1990
+ }, { agentId, iteration });
1991
+ pendingSteerPause = null;
1992
+ await flushState(iteration);
1993
+ }
1994
+ }
1255
1995
  const procCtx = buildProcCtx(iteration + 1);
1256
- const initialPrompt = opts.buildPrompt(lastStatus, iteration);
1996
+ let initialPrompt = opts.buildPrompt(lastStatus, iteration);
1997
+ if (steerDecision) {
1998
+ const who = steerDecision.actor ? ` from ${steerDecision.actor}` : "";
1999
+ initialPrompt = `${initialPrompt}
2000
+
2001
+ [human steer${who}]: ${steerDecision.message}`;
2002
+ }
1257
2003
  const inputVerdict = await runProcessorChain(processors, (p) => p.processInput, initialPrompt, procCtx);
1258
2004
  if (inputVerdict.kind !== "continue") {
1259
2005
  loopAbort.abort();
@@ -1263,7 +2009,12 @@ async function agentLoop(opts) {
1263
2009
  process.stdout.write(`${logLabel} iteration ${iteration + 1}/${maxIterations} · ${turnsPerIteration} turns
1264
2010
  `);
1265
2011
  let responseText = "";
1266
- for await (const rawMsg of client.sendMessage({ prompt, sessionId: iteration > 0 ? lastSessionId : undefined, iteration: iteration + 1 })) {
2012
+ for await (const rawMsg of client.sendMessage({
2013
+ prompt,
2014
+ sessionId: iteration > 0 ? lastSessionId : undefined,
2015
+ iteration: iteration + 1,
2016
+ ...opts.inbox ? { inboxStream: opts.inbox } : {}
2017
+ })) {
1267
2018
  const outputVerdict = await runProcessorChain(processors, (p) => p.processOutput, rawMsg, procCtx);
1268
2019
  if (outputVerdict.kind === "abort") {
1269
2020
  loopAbort.abort();
@@ -1287,16 +2038,23 @@ async function agentLoop(opts) {
1287
2038
  lastResponseText = responseText;
1288
2039
  let status = parseAgentStatus(responseText);
1289
2040
  const rawResponse = opts.responseSchema ? parseAgentResponse(responseText) ?? parseRawJsonResponse(responseText) : null;
2041
+ const validateResponse = (target) => {
2042
+ const parsed = opts.responseSchema.safeParse(target);
2043
+ if (!parsed.success)
2044
+ lastResponseValidationError = parsed.error.message;
2045
+ return parsed;
2046
+ };
1290
2047
  if (opts.responseSchema && rawResponse !== null) {
1291
- const parsed = opts.responseSchema.safeParse(rawResponse);
2048
+ const parsed = validateResponse(rawResponse);
1292
2049
  if (parsed.success) {
1293
2050
  const successStatus = status ?? { summary: "structured response completed", completed: [], blockers: [], exit_signal: true };
1294
2051
  process.stdout.write(`${logLabel} structured response validated after ${iteration + 1}/${maxIterations} iterations
1295
2052
  `);
2053
+ const result2 = { agentId, label, sessionId: lastSessionId, lastStatus: successStatus, iterations: iteration + 1, response: parsed.data };
2054
+ await writeSettledState(result2);
1296
2055
  opts.onAgentLifecycleEvent?.({ event: "agent.settled", at: Date.now(), agentId, label, outcome: "success", iterations: iteration + 1, durationMs: Date.now() - startedAt });
1297
- return { agentId, label, sessionId: lastSessionId, lastStatus: successStatus, iterations: iteration + 1, response: parsed.data };
2056
+ return result2;
1298
2057
  }
1299
- lastResponseValidationError = parsed.error.message;
1300
2058
  }
1301
2059
  completedIterations = iteration + 1;
1302
2060
  process.stdout.write(`${logLabel} iteration ${iteration + 1} status: exit_signal=${status?.exit_signal ?? "(no status)"} blockers=${JSON.stringify(status?.blockers ?? [])}
@@ -1314,9 +2072,8 @@ async function agentLoop(opts) {
1314
2072
  opts.onIteration?.(iteration + 1, status);
1315
2073
  continue;
1316
2074
  }
1317
- const parsed = opts.responseSchema.safeParse({ ...status, ...rawResponse });
2075
+ const parsed = validateResponse({ ...status, ...rawResponse });
1318
2076
  if (!parsed.success) {
1319
- lastResponseValidationError = parsed.error.message;
1320
2077
  process.stderr.write(`${logLabel} <response> SCHEMA FAILED: ${parsed.error.message}
1321
2078
  raw: ${JSON.stringify(rawResponse).slice(0, 400)}
1322
2079
  `);
@@ -1328,8 +2085,19 @@ raw: ${JSON.stringify(rawResponse).slice(0, 400)}
1328
2085
  }
1329
2086
  process.stdout.write(`${logLabel} done after ${iteration + 1}/${maxIterations} iterations
1330
2087
  `);
2088
+ const result2 = { agentId, label, sessionId: lastSessionId, lastStatus: status, iterations: iteration + 1, response };
2089
+ await writeSettledState(result2);
1331
2090
  opts.onAgentLifecycleEvent?.({ event: "agent.settled", at: Date.now(), agentId, label, outcome: "success", iterations: iteration + 1, durationMs: Date.now() - startedAt });
1332
- return { agentId, label, sessionId: lastSessionId, lastStatus: status, iterations: iteration + 1, response };
2091
+ return result2;
2092
+ }
2093
+ if (opts.pause && opts.mode === "hitl" && status?.needs_input && pendingSteerPause === null) {
2094
+ pendingSteerPause = {
2095
+ reason: status.question?.trim() || "the agent requested human input",
2096
+ correlationKey: null,
2097
+ at: Date.now()
2098
+ };
2099
+ blockerStreak = null;
2100
+ continue;
1333
2101
  }
1334
2102
  if (!status) {
1335
2103
  if (++iterationsWithoutStatus >= STALL_ITERATIONS)
@@ -1356,9 +2124,13 @@ raw: ${JSON.stringify(rawResponse).slice(0, 400)}
1356
2124
  throw new Error(`${logLabel} did not produce a valid <response> after ${maxIterations} iterations${lastResponseValidationError ? `: ${lastResponseValidationError}` : ""}. Response tail: ${lastResponseText.slice(-600)}`);
1357
2125
  process.stderr.write(`${logLabel} exhausted ${maxIterations} iterations, proceeding with available work
1358
2126
  `);
2127
+ const result = { agentId, label, sessionId: lastSessionId, lastStatus, iterations: maxIterations };
2128
+ await writeSettledState(result);
1359
2129
  opts.onAgentLifecycleEvent?.({ event: "agent.settled", at: Date.now(), agentId, label, outcome: "success", iterations: maxIterations, durationMs: Date.now() - startedAt });
1360
- return { agentId, label, sessionId: lastSessionId, lastStatus, iterations: maxIterations };
2130
+ return result;
1361
2131
  } catch (err) {
2132
+ if (isPauseSignal(err))
2133
+ throw err;
1362
2134
  opts.onAgentLifecycleEvent?.({ event: "agent.settled", at: Date.now(), agentId, label, outcome: "failed", iterations: completedIterations, durationMs: Date.now() - startedAt, failureReason: err instanceof Error ? err.message : String(err) });
1363
2135
  throw err;
1364
2136
  }
@@ -1374,6 +2146,50 @@ function parseRawJsonResponse(text) {
1374
2146
  }
1375
2147
  }
1376
2148
 
2149
+ // src/agent/async-queue.ts
2150
+ class AsyncQueue {
2151
+ buffer = [];
2152
+ resolvers = [];
2153
+ done = false;
2154
+ push(value) {
2155
+ if (this.done)
2156
+ return;
2157
+ const resolve = this.resolvers.shift();
2158
+ if (resolve) {
2159
+ resolve({ value, done: false });
2160
+ } else {
2161
+ this.buffer.push(value);
2162
+ }
2163
+ }
2164
+ close() {
2165
+ if (this.done)
2166
+ return;
2167
+ this.done = true;
2168
+ while (this.resolvers.length > 0) {
2169
+ const resolve = this.resolvers.shift();
2170
+ resolve({ value: undefined, done: true });
2171
+ }
2172
+ }
2173
+ [Symbol.asyncIterator]() {
2174
+ return {
2175
+ next: () => {
2176
+ const buffered = this.buffer.shift();
2177
+ if (buffered !== undefined)
2178
+ return Promise.resolve({ value: buffered, done: false });
2179
+ if (this.done)
2180
+ return Promise.resolve({ value: undefined, done: true });
2181
+ return new Promise((resolve) => {
2182
+ this.resolvers.push(resolve);
2183
+ });
2184
+ },
2185
+ return: () => {
2186
+ this.close();
2187
+ return Promise.resolve({ value: undefined, done: true });
2188
+ }
2189
+ };
2190
+ }
2191
+ }
2192
+
1377
2193
  // src/runtimes/claude.ts
1378
2194
  function now2() {
1379
2195
  return new Date().toISOString();
@@ -1393,6 +2209,8 @@ function translateMessage(message) {
1393
2209
  msgs.push({ type: "text", text: String(b.text ?? ""), timestamp: ts });
1394
2210
  if (b.type === "thinking")
1395
2211
  msgs.push({ type: "thinking", text: String(b.thinking ?? ""), timestamp: ts });
2212
+ if (b.type === "redacted_thinking")
2213
+ msgs.push({ type: "thinking", text: "(redacted)", timestamp: ts });
1396
2214
  if (b.type === "tool_use")
1397
2215
  msgs.push({ type: "tool_use", toolName: String(b.name ?? ""), toolInput: b.input ?? {}, toolUseId: String(b.id ?? ""), timestamp: ts });
1398
2216
  }
@@ -1431,15 +2249,20 @@ function translateMessage(message) {
1431
2249
  }
1432
2250
  return msgs;
1433
2251
  }
2252
+ var DEFAULT_CLAUDE_PATH = "/usr/local/bin/claude";
1434
2253
 
1435
2254
  class ClaudeRunner {
1436
2255
  options;
1437
2256
  config;
1438
2257
  supportsToolCallProcessor = true;
2258
+ kind = "claude";
1439
2259
  constructor(_sandbox, options = {}, config = {}) {
1440
2260
  this.options = options;
1441
2261
  this.config = config;
1442
2262
  }
2263
+ get model() {
2264
+ return this.config.model ?? this.options.model ?? DEFAULT_CLAUDE_MODEL;
2265
+ }
1443
2266
  async gateToolCall(call, ctx) {
1444
2267
  const verdict = await runProcessorChain(this.options.processors ?? [], (p) => p.processToolCall, call, ctx);
1445
2268
  if (verdict.kind === "continue")
@@ -1475,10 +2298,33 @@ class ClaudeRunner {
1475
2298
  return { hookSpecificOutput: { hookEventName: pre.hook_event_name, permissionDecision: "deny", permissionDecisionReason: gate.reason } };
1476
2299
  return { continue: false, systemMessage: gate.reason };
1477
2300
  };
2301
+ const inboxQueue = opts.inboxStream ? new AsyncQueue : null;
2302
+ if (inboxQueue) {
2303
+ inboxQueue.push({
2304
+ type: "user",
2305
+ message: { role: "user", content: opts.prompt },
2306
+ parent_tool_use_id: null
2307
+ });
2308
+ (async () => {
2309
+ try {
2310
+ for await (const ev of opts.inboxStream) {
2311
+ const tag = ev.senderName ? `[message from ${ev.senderName}]
2312
+ ` : `[user message]
2313
+ `;
2314
+ inboxQueue.push({
2315
+ type: "user",
2316
+ message: { role: "user", content: tag + ev.text },
2317
+ parent_tool_use_id: null
2318
+ });
2319
+ }
2320
+ } catch {}
2321
+ })();
2322
+ }
2323
+ const promptForQuery = inboxQueue ?? opts.prompt;
1478
2324
  try {
1479
2325
  let emittedAssistantText = false;
1480
2326
  for await (const message of query({
1481
- prompt: opts.prompt,
2327
+ prompt: promptForQuery,
1482
2328
  options: {
1483
2329
  tools: this.options.allowedTools,
1484
2330
  allowedTools: this.options.allowedTools,
@@ -1489,7 +2335,7 @@ class ClaudeRunner {
1489
2335
  effort: this.config.effort,
1490
2336
  cwd: this.options.cwd,
1491
2337
  env: { ...process.env, ...this.config.env ?? {} },
1492
- pathToClaudeCodeExecutable: this.config.pathToClaudeCodeExecutable ?? process.env.CLAUDE_CODE_EXECUTABLE ?? "claude",
2338
+ pathToClaudeCodeExecutable: this.config.pathToClaudeCodeExecutable ?? process.env.CLAUDE_CODE_EXECUTABLE ?? DEFAULT_CLAUDE_PATH,
1493
2339
  ...this.config.claudeMdContent ? { systemPrompt: { type: "preset", preset: "claude_code", append: this.config.claudeMdContent } } : {},
1494
2340
  resume: opts.sessionId,
1495
2341
  mcpServers: this.config.mcpServers,
@@ -1506,9 +2352,13 @@ class ClaudeRunner {
1506
2352
  emittedAssistantText = true;
1507
2353
  yield msg;
1508
2354
  }
2355
+ if (raw.type === "result" && inboxQueue)
2356
+ inboxQueue.close();
1509
2357
  }
1510
2358
  } catch (err) {
1511
2359
  yield { type: "error", text: formatError(err), timestamp: now2() };
2360
+ } finally {
2361
+ inboxQueue?.close();
1512
2362
  }
1513
2363
  }
1514
2364
  }
@@ -1519,7 +2369,7 @@ function createClaudeRuntime(config = {}) {
1519
2369
  }
1520
2370
  var claude_default = createClaudeRuntime();
1521
2371
  // src/sandbox.ts
1522
- import { promises as fs } from "node:fs";
2372
+ import { promises as fs2 } from "node:fs";
1523
2373
  import { dirname as dirname2 } from "node:path";
1524
2374
  import { spawn } from "node:child_process";
1525
2375
  import { Sandbox } from "e2b";
@@ -1528,9 +2378,26 @@ import pRetry from "p-retry";
1528
2378
  var AGENT_COMPOSE_TAG = process.env.AGENT_COMPOSE_TAG ?? `agent-compose-${process.env.AGENT_COMPOSE_ENV ?? "dev"}`;
1529
2379
  var VERCEL_VM_LIFETIME_WINDOW_MS = 6 * 60 * 60 * 1000;
1530
2380
  function makeSandboxProvider(sb) {
2381
+ const commands = sb.commands;
1531
2382
  return {
1532
2383
  sandboxId: sb.sandboxId,
1533
- commands: sb.commands,
2384
+ commands: {
2385
+ async run(cmd, opts) {
2386
+ let stderr = "";
2387
+ const result = await commands.run(cmd, {
2388
+ ...opts,
2389
+ onStderr: (chunk) => {
2390
+ stderr += chunk;
2391
+ opts?.onStderr?.(chunk);
2392
+ }
2393
+ });
2394
+ return {
2395
+ exitCode: result.exitCode,
2396
+ stdout: result.stdout,
2397
+ stderr: typeof result.stderr === "string" ? result.stderr : stderr
2398
+ };
2399
+ }
2400
+ },
1534
2401
  files: {
1535
2402
  async write(path, content) {
1536
2403
  await sb.files.write(path, content);
@@ -1555,7 +2422,7 @@ function makeDesktopSandboxProvider(sb) {
1555
2422
  };
1556
2423
  }
1557
2424
  async function parseSseExecStream(body, opts) {
1558
- let stdout = "", exitCode = 0, exited = false;
2425
+ let stdout = "", stderr = "", exitCode = 0, exited = false;
1559
2426
  const reader = body.getReader(), decoder = new TextDecoder;
1560
2427
  let buf = "";
1561
2428
  while (true) {
@@ -1573,13 +2440,14 @@ async function parseSseExecStream(body, opts) {
1573
2440
  let event;
1574
2441
  try {
1575
2442
  event = JSON.parse(line.slice(6));
1576
- } catch {
1577
- continue;
2443
+ } catch (err) {
2444
+ throw new Error(`Malformed sandbox exec event: ${err instanceof Error ? err.message : String(err)}`);
1578
2445
  }
1579
2446
  if (event.type === "stdout") {
1580
2447
  stdout += event.data ?? "";
1581
2448
  opts?.onStdout?.(event.data ?? "");
1582
2449
  } else if (event.type === "stderr") {
2450
+ stderr += event.data ?? "";
1583
2451
  opts?.onStderr?.(event.data ?? "");
1584
2452
  } else if (event.type === "exit") {
1585
2453
  exitCode = event.exitCode ?? 0;
@@ -1591,7 +2459,9 @@ async function parseSseExecStream(body, opts) {
1591
2459
  if (exited)
1592
2460
  break;
1593
2461
  }
1594
- return { exitCode, stdout };
2462
+ if (!exited)
2463
+ throw new Error("Sandbox exec stream ended without an exit event");
2464
+ return { exitCode, stdout, stderr };
1595
2465
  }
1596
2466
  function makeVercelSandboxProvider(sb, globalEnvs) {
1597
2467
  const mergeEnvs = (cmdEnvs) => globalEnvs ? { ...globalEnvs, ...cmdEnvs } : cmdEnvs;
@@ -1599,27 +2469,28 @@ function makeVercelSandboxProvider(sb, globalEnvs) {
1599
2469
  sandboxId: sb.sandboxId,
1600
2470
  commands: {
1601
2471
  async run(cmd, opts) {
1602
- const sudo = opts?.sudo === true;
1603
- if (opts?.background) {
1604
- sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts.cwd, env: mergeEnvs(opts.envs), detached: true, ...sudo ? { sudo: true } : {} }).catch((err) => console.error(`[sandbox] background command failed: ${err instanceof Error ? err.message : String(err)}`));
1605
- return { exitCode: 0, stdout: "" };
1606
- }
1607
2472
  const signal = opts?.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined;
1608
2473
  let stdout = "";
1609
- const handle = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal, ...sudo ? { sudo: true } : {} });
2474
+ let stderr = "";
2475
+ const handle = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal, ...opts?.sudo ? { sudo: true } : {} });
1610
2476
  await pRetry(async (attempt) => {
1611
2477
  const h = attempt === 1 ? handle : await sb.getCommand(handle.cmdId);
2478
+ if (attempt > 1) {
2479
+ stdout = "";
2480
+ stderr = "";
2481
+ }
1612
2482
  for await (const log of h.logs()) {
1613
2483
  if (log.stream === "stdout") {
1614
2484
  stdout += log.data;
1615
2485
  opts?.onStdout?.(log.data);
1616
2486
  } else {
2487
+ stderr += log.data;
1617
2488
  opts?.onStderr?.(log.data);
1618
2489
  }
1619
2490
  }
1620
2491
  }, { retries: 3, minTimeout: 1000, factor: 2 });
1621
2492
  const finished = await handle.wait();
1622
- return { exitCode: finished.exitCode, stdout };
2493
+ return { exitCode: finished.exitCode, stdout, stderr };
1623
2494
  }
1624
2495
  },
1625
2496
  files: {
@@ -1653,6 +2524,7 @@ function makeLocalSandboxProvider() {
1653
2524
  stdio: ["ignore", "pipe", "pipe"]
1654
2525
  });
1655
2526
  let stdout = "";
2527
+ let stderr = "";
1656
2528
  proc.stdout?.setEncoding("utf8");
1657
2529
  proc.stderr?.setEncoding("utf8");
1658
2530
  proc.stdout?.on("data", (chunk) => {
@@ -1660,17 +2532,18 @@ function makeLocalSandboxProvider() {
1660
2532
  opts?.onStdout?.(chunk);
1661
2533
  });
1662
2534
  proc.stderr?.on("data", (chunk) => {
2535
+ stderr += chunk;
1663
2536
  opts?.onStderr?.(chunk);
1664
2537
  });
1665
2538
  proc.on("error", reject);
1666
- proc.on("close", (code) => resolve({ exitCode: code ?? 0, stdout }));
2539
+ proc.on("close", (code) => resolve({ exitCode: code ?? 0, stdout, stderr }));
1667
2540
  });
1668
2541
  }
1669
2542
  },
1670
2543
  files: {
1671
2544
  async write(path, content) {
1672
- await fs.mkdir(dirname2(path), { recursive: true });
1673
- await fs.writeFile(path, content);
2545
+ await fs2.mkdir(dirname2(path), { recursive: true });
2546
+ await fs2.writeFile(path, content);
1674
2547
  }
1675
2548
  },
1676
2549
  async kill() {}
@@ -1845,10 +2718,18 @@ async function killAllSandboxes(onError) {
1845
2718
  }));
1846
2719
  }
1847
2720
  // src/workflow-steps/observability.ts
2721
+ var LIVE_EMIT_DRAIN_TIMEOUT_MS = 1500;
2722
+
1848
2723
  class StepObservabilityCollector {
1849
2724
  metadata = {};
1850
2725
  events = [];
1851
2726
  subSteps = [];
2727
+ liveEmitter;
2728
+ ackedSeqs = new Set;
2729
+ inFlight = new Set;
2730
+ constructor(opts = {}) {
2731
+ this.liveEmitter = opts.liveEmitter;
2732
+ }
1852
2733
  setMetadata = async (data) => {
1853
2734
  Object.assign(this.metadata, data);
1854
2735
  };
@@ -1864,6 +2745,8 @@ class StepObservabilityCollector {
1864
2745
  });
1865
2746
  return result;
1866
2747
  } catch (err) {
2748
+ if (isPauseSignal(err))
2749
+ throw err;
1867
2750
  this.subSteps.push({
1868
2751
  name,
1869
2752
  startedAt,
@@ -1876,26 +2759,92 @@ class StepObservabilityCollector {
1876
2759
  };
1877
2760
  agentEvents = {
1878
2761
  emit: (event) => {
2762
+ const seq = this.events.length;
1879
2763
  this.events.push(event);
2764
+ if (this.liveEmitter) {
2765
+ const tracked = this.liveEmitter(event, seq).then((ok) => {
2766
+ if (ok)
2767
+ this.ackedSeqs.add(seq);
2768
+ }).catch(() => {});
2769
+ this.inFlight.add(tracked);
2770
+ tracked.finally(() => this.inFlight.delete(tracked));
2771
+ }
1880
2772
  }
1881
2773
  };
1882
- snapshot() {
2774
+ async drainInFlight(timeoutMs = LIVE_EMIT_DRAIN_TIMEOUT_MS) {
2775
+ if (this.inFlight.size === 0)
2776
+ return;
2777
+ await Promise.race([
2778
+ Promise.allSettled([...this.inFlight]),
2779
+ new Promise((resolve) => setTimeout(resolve, timeoutMs))
2780
+ ]);
2781
+ }
2782
+ async snapshot() {
2783
+ await this.drainInFlight();
1883
2784
  const hasMetadata = Object.keys(this.metadata).length > 0;
1884
- const hasEvents = this.events.length > 0;
1885
2785
  const hasSubSteps = this.subSteps.length > 0;
2786
+ const remainingEvents = this.events.filter((_, idx) => !this.ackedSeqs.has(idx));
2787
+ const hasEvents = remainingEvents.length > 0;
1886
2788
  if (!hasMetadata && !hasEvents && !hasSubSteps)
1887
2789
  return;
1888
2790
  const result = {};
1889
2791
  if (hasMetadata)
1890
2792
  result.metadata = { ...this.metadata };
1891
2793
  if (hasEvents)
1892
- result.events = [...this.events];
2794
+ result.events = remainingEvents;
1893
2795
  if (hasSubSteps)
1894
2796
  result.subSteps = [...this.subSteps];
1895
2797
  return result;
1896
2798
  }
1897
2799
  }
1898
2800
 
2801
+ // src/pause/checkpoint.ts
2802
+ async function checkpoint(name, fn) {
2803
+ const existing = await readCheckpoint(name);
2804
+ if (existing.found)
2805
+ return existing.value;
2806
+ const value = await fn();
2807
+ await writeCheckpoint(name, value);
2808
+ return value;
2809
+ }
2810
+ function scopedCheckpoint(scope) {
2811
+ assertSafeCheckpointName(scope);
2812
+ return async (name, fn) => {
2813
+ assertSafeCheckpointName(name);
2814
+ return checkpoint(`${scope}.${name}`, fn);
2815
+ };
2816
+ }
2817
+
2818
+ // src/pause/wrappers.ts
2819
+ function buildPauseWrappers(pause) {
2820
+ return {
2821
+ requestDecision(req) {
2822
+ return pause({
2823
+ reason: req.reason,
2824
+ schema: req.schema,
2825
+ ...req.payload !== undefined ? { payload: req.payload } : {},
2826
+ ...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {}
2827
+ }, "decision");
2828
+ },
2829
+ sleep(durationMs) {
2830
+ return pause({
2831
+ reason: `sleep ${durationMs}ms`,
2832
+ ttlMs: durationMs,
2833
+ snapshot: false,
2834
+ onExpiry: { mode: "resolve", value: undefined }
2835
+ }, "sleep");
2836
+ },
2837
+ waitForEvent(req) {
2838
+ return pause({
2839
+ reason: req.reason,
2840
+ correlationKey: req.correlationKey,
2841
+ ...req.schema !== undefined ? { schema: req.schema } : {},
2842
+ ...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {}
2843
+ }, "event");
2844
+ }
2845
+ };
2846
+ }
2847
+
1899
2848
  // src/workflow-steps/runner.ts
1900
2849
  class StepValidationError extends Error {
1901
2850
  stepName;
@@ -1938,7 +2887,14 @@ async function runWorkflowSingleStep(opts) {
1938
2887
  const parsedInput = step.input.safeParse(opts.input);
1939
2888
  if (!parsedInput.success)
1940
2889
  throw new StepValidationError(step.name, "input", parsedInput.error);
1941
- const collector = new StepObservabilityCollector;
2890
+ const collector = new StepObservabilityCollector({ liveEmitter: opts.liveAgentEventEmitter });
2891
+ const coord = { runId: opts.run.id, stepIndex: opts.stepIndex };
2892
+ const pauseWithKind = function(req, kind) {
2893
+ return corePause(req, coord, kind);
2894
+ };
2895
+ const pause = function(req) {
2896
+ return corePause(req, coord);
2897
+ };
1942
2898
  const stepCtx = {
1943
2899
  input: parsedInput.data,
1944
2900
  requestContext: opts.requestContext,
@@ -1951,13 +2907,30 @@ async function runWorkflowSingleStep(opts) {
1951
2907
  }),
1952
2908
  setMetadata: collector.setMetadata,
1953
2909
  step: collector.step,
1954
- agentEvents: collector.agentEvents
2910
+ agentEvents: collector.agentEvents,
2911
+ checkpoint: scopedCheckpoint(`step${opts.stepIndex}`),
2912
+ pause,
2913
+ ...buildPauseWrappers(pauseWithKind)
1955
2914
  };
1956
- const output = await step.run(stepCtx);
2915
+ let output;
2916
+ try {
2917
+ const bridged = setActiveStepBridge({ stepIndex: opts.stepIndex });
2918
+ try {
2919
+ output = await runWithActiveStep({ stepIndex: opts.stepIndex }, () => Promise.resolve(step.run(stepCtx)));
2920
+ } finally {
2921
+ restoreActiveStepBridge(bridged);
2922
+ }
2923
+ } catch (err) {
2924
+ if (isPauseSignal(err)) {
2925
+ const observability2 = await collector.snapshot();
2926
+ throw new PauseSignal(err.pauseId, err.pauseRequest, observability2);
2927
+ }
2928
+ throw err;
2929
+ }
1957
2930
  const parsedOutput = step.output.safeParse(output);
1958
2931
  if (!parsedOutput.success)
1959
2932
  throw new StepValidationError(step.name, "output", parsedOutput.error);
1960
- const observability = collector.snapshot();
2933
+ const observability = await collector.snapshot();
1961
2934
  return observability === undefined ? { output: parsedOutput.data } : { output: parsedOutput.data, observability };
1962
2935
  }
1963
2936
  async function runWorkflowSteps(opts) {
@@ -1991,7 +2964,14 @@ async function runWorkflowSteps(opts) {
1991
2964
  await opts.onStepFailed?.(i, step.name, err, 0);
1992
2965
  throw err;
1993
2966
  }
1994
- const collector = new StepObservabilityCollector;
2967
+ const collector = new StepObservabilityCollector({ liveEmitter: opts.liveAgentEventEmitter });
2968
+ const coord = { runId: run.id, stepIndex: i };
2969
+ const pauseWithKind = function(req, kind) {
2970
+ return corePause(req, coord, kind);
2971
+ };
2972
+ const pause = function(req) {
2973
+ return corePause(req, coord);
2974
+ };
1995
2975
  const stepCtx = {
1996
2976
  input: parsedStepInput.data,
1997
2977
  requestContext,
@@ -2002,16 +2982,23 @@ async function runWorkflowSteps(opts) {
2002
2982
  invokeChild,
2003
2983
  setMetadata: collector.setMetadata,
2004
2984
  step: collector.step,
2005
- agentEvents: collector.agentEvents
2985
+ agentEvents: collector.agentEvents,
2986
+ checkpoint: scopedCheckpoint(`step${i}`),
2987
+ pause,
2988
+ ...buildPauseWrappers(pauseWithKind)
2006
2989
  };
2007
2990
  const startedAt = Date.now();
2008
2991
  let output;
2009
2992
  try {
2010
- output = await step.run(stepCtx);
2993
+ output = await runWithActiveStep({ stepIndex: i }, () => Promise.resolve(step.run(stepCtx)));
2011
2994
  } catch (err) {
2995
+ if (isPauseSignal(err)) {
2996
+ const observability3 = await collector.snapshot();
2997
+ throw new PauseSignal(err.pauseId, err.pauseRequest, observability3);
2998
+ }
2012
2999
  const wrapped = err instanceof Error ? err : new Error(String(err));
2013
3000
  const durationMs2 = Date.now() - startedAt;
2014
- const observability2 = collector.snapshot();
3001
+ const observability2 = await collector.snapshot();
2015
3002
  stepResults.push({
2016
3003
  name: step.name,
2017
3004
  status: "failed",
@@ -2026,7 +3013,7 @@ async function runWorkflowSteps(opts) {
2026
3013
  if (!parsedOutput.success) {
2027
3014
  const err = new StepValidationError(step.name, "output", parsedOutput.error);
2028
3015
  const durationMs2 = Date.now() - startedAt;
2029
- const observability2 = collector.snapshot();
3016
+ const observability2 = await collector.snapshot();
2030
3017
  stepResults.push({
2031
3018
  name: step.name,
2032
3019
  status: "failed",
@@ -2039,7 +3026,7 @@ async function runWorkflowSteps(opts) {
2039
3026
  }
2040
3027
  const durationMs = Date.now() - startedAt;
2041
3028
  current = parsedOutput.data;
2042
- const observability = collector.snapshot();
3029
+ const observability = await collector.snapshot();
2043
3030
  stepResults.push({
2044
3031
  name: step.name,
2045
3032
  status: "completed",
@@ -2164,9 +3151,13 @@ function defineStep(opts) {
2164
3151
  }
2165
3152
  // src/step-invocation/protocol.ts
2166
3153
  var STEP_RESULT_PREFIX = "__AC_STEP_RESULT__";
3154
+ var STEP_PAUSE_PREFIX = "__AC_STEP_PAUSE__";
2167
3155
  function stepResultLinePrefix(token) {
2168
3156
  return `${STEP_RESULT_PREFIX}${token}:`;
2169
3157
  }
3158
+ function stepPauseLinePrefix(token) {
3159
+ return `${STEP_PAUSE_PREFIX}${token}:`;
3160
+ }
2170
3161
  var RUNNER_BUNDLE_PATH = "/tmp/runner.bundle.js";
2171
3162
  var RUNNER_COMMAND = `node ${RUNNER_BUNDLE_PATH}`;
2172
3163
  var STEP_ENV = {
@@ -2175,7 +3166,8 @@ var STEP_ENV = {
2175
3166
  STEP_INDEX: "AC_STEP_INDEX",
2176
3167
  STEP_INPUT_PATH: "AC_STEP_INPUT_PATH",
2177
3168
  REQUEST_CONTEXT_PATH: "AC_REQUEST_CONTEXT_PATH",
2178
- STEP_RESULT_TOKEN: "AC_STEP_RESULT_TOKEN"
3169
+ STEP_RESULT_TOKEN: "AC_STEP_RESULT_TOKEN",
3170
+ STEP_RESUME: "AC_STEP_RESUME"
2179
3171
  };
2180
3172
  function stepInputPath(stepIndex) {
2181
3173
  return `/tmp/wf/step-input-${stepIndex}.json`;
@@ -2184,7 +3176,36 @@ function requestContextPath(stepIndex) {
2184
3176
  return `/tmp/wf/request-context-${stepIndex}.json`;
2185
3177
  }
2186
3178
  // src/step-invocation/invoker.ts
2187
- import { randomBytes } from "node:crypto";
3179
+ import { randomBytes as randomBytes2 } from "node:crypto";
3180
+ import { z as z10 } from "zod";
3181
+ var StepObservabilitySchema = z10.object({
3182
+ metadata: z10.record(z10.string(), z10.unknown()).optional(),
3183
+ events: z10.array(z10.unknown()).optional(),
3184
+ subSteps: z10.array(z10.object({
3185
+ name: z10.string(),
3186
+ startedAt: z10.number(),
3187
+ durationMs: z10.number(),
3188
+ status: z10.enum(["completed", "failed"]),
3189
+ error: z10.string().optional()
3190
+ })).optional()
3191
+ }).passthrough();
3192
+ var StepResultEnvelopeSchema = z10.discriminatedUnion("ok", [
3193
+ z10.object({
3194
+ ok: z10.literal(true),
3195
+ output: z10.unknown().optional(),
3196
+ observability: StepObservabilitySchema.optional()
3197
+ }),
3198
+ z10.object({
3199
+ ok: z10.literal(false),
3200
+ kind: z10.enum(["protocol", "user-step"]),
3201
+ error: z10.string().optional()
3202
+ })
3203
+ ]);
3204
+ var StepPauseEnvelopeSchema = z10.object({
3205
+ pauseId: z10.string().min(1),
3206
+ pauseRequest: StepPauseRequestSchema,
3207
+ observability: StepObservabilitySchema.optional()
3208
+ });
2188
3209
  function buildStepEnvs(args) {
2189
3210
  return {
2190
3211
  [STEP_ENV.RUN_ID]: args.runId,
@@ -2192,30 +3213,64 @@ function buildStepEnvs(args) {
2192
3213
  [STEP_ENV.STEP_INDEX]: String(args.stepIndex),
2193
3214
  [STEP_ENV.STEP_INPUT_PATH]: stepInputPath(args.stepIndex),
2194
3215
  [STEP_ENV.REQUEST_CONTEXT_PATH]: requestContextPath(args.stepIndex),
2195
- [STEP_ENV.STEP_RESULT_TOKEN]: args.resultToken
3216
+ [STEP_ENV.STEP_RESULT_TOKEN]: args.resultToken,
3217
+ [STEP_ENV.STEP_RESUME]: args.isResume ? "1" : "0"
2196
3218
  };
2197
3219
  }
2198
3220
  function parseStepResult(stdout, resultToken) {
2199
- const prefix = stepResultLinePrefix(resultToken);
2200
- const line = stdout.split(/\r?\n/).find((l) => l.startsWith(prefix));
2201
- if (!line)
3221
+ const resultPrefix = stepResultLinePrefix(resultToken);
3222
+ const pausePrefix = stepPauseLinePrefix(resultToken);
3223
+ let resultLine;
3224
+ let pauseLine;
3225
+ for (const line of stdout.split(/\r?\n/)) {
3226
+ if (!pauseLine && line.startsWith(pausePrefix))
3227
+ pauseLine = line;
3228
+ if (!resultLine && line.startsWith(resultPrefix))
3229
+ resultLine = line;
3230
+ if (pauseLine && resultLine)
3231
+ break;
3232
+ }
3233
+ if (pauseLine) {
3234
+ try {
3235
+ const raw = JSON.parse(pauseLine.slice(pausePrefix.length));
3236
+ const parsed2 = StepPauseEnvelopeSchema.safeParse(raw);
3237
+ if (!parsed2.success) {
3238
+ return { ok: false, error: { kind: "protocol", message: `step pause schema validation failed: ${parsed2.error.message}` } };
3239
+ }
3240
+ return {
3241
+ ok: "paused",
3242
+ pauseId: parsed2.data.pauseId,
3243
+ pauseRequest: parsed2.data.pauseRequest,
3244
+ ...parsed2.data.observability ? { observability: parsed2.data.observability } : {}
3245
+ };
3246
+ } catch {
3247
+ return { ok: false, error: { kind: "protocol", message: "step pause JSON parse failed" } };
3248
+ }
3249
+ }
3250
+ if (!resultLine)
2202
3251
  return null;
2203
3252
  let parsed;
2204
3253
  try {
2205
- parsed = JSON.parse(line.slice(prefix.length));
3254
+ const raw = JSON.parse(resultLine.slice(resultPrefix.length));
3255
+ const result = StepResultEnvelopeSchema.safeParse(raw);
3256
+ if (!result.success) {
3257
+ const kind2 = raw && typeof raw === "object" && typeof raw.kind === "string" ? ` (kind=${raw.kind})` : "";
3258
+ return { ok: false, error: { kind: "protocol", message: `step result schema validation failed${kind2}: ${result.error.message}` } };
3259
+ }
3260
+ parsed = result.data;
2206
3261
  } catch {
2207
3262
  return { ok: false, error: { kind: "protocol", message: "step result JSON parse failed" } };
2208
3263
  }
2209
3264
  if (parsed.ok) {
2210
3265
  return parsed.observability ? { ok: true, output: parsed.output, observability: parsed.observability } : { ok: true, output: parsed.output };
2211
3266
  }
2212
- const kind = parsed.kind === "user-step" ? "user-step" : "protocol";
3267
+ const kind = parsed.kind;
2213
3268
  const baseMessage = parsed.error ?? "step body failed without a message";
2214
- const message = parsed.kind && parsed.kind !== "user-step" && parsed.kind !== "protocol" ? `(unknown kind "${parsed.kind}") ${baseMessage}` : baseMessage;
3269
+ const message = baseMessage;
2215
3270
  return { ok: false, error: { kind, message } };
2216
3271
  }
2217
3272
  async function invokeStep(sandbox, request, opts) {
2218
- const resultToken = randomBytes(16).toString("hex");
3273
+ const resultToken = randomBytes2(16).toString("hex");
2219
3274
  await Promise.all([
2220
3275
  sandbox.files.write(stepInputPath(request.stepIndex), JSON.stringify(request.input)),
2221
3276
  sandbox.files.write(requestContextPath(request.stepIndex), JSON.stringify(request.requestContext))
@@ -2225,10 +3280,13 @@ async function invokeStep(sandbox, request, opts) {
2225
3280
  ...buildStepEnvs({
2226
3281
  runId: request.runId,
2227
3282
  stepIndex: request.stepIndex,
2228
- resultToken
3283
+ resultToken,
3284
+ isResume: opts?.isResume
2229
3285
  })
2230
3286
  };
2231
- const sentinelPrefix = stepResultLinePrefix(resultToken);
3287
+ const resultSentinel = stepResultLinePrefix(resultToken);
3288
+ const pauseSentinel = stepPauseLinePrefix(resultToken);
3289
+ const isSentinel = (line) => line.startsWith(resultSentinel) || line.startsWith(pauseSentinel);
2232
3290
  const makeLineSplitter = (sink, filterSentinel) => {
2233
3291
  if (!sink)
2234
3292
  return { onChunk: undefined, flush: () => {} };
@@ -2241,7 +3299,7 @@ async function invokeStep(sandbox, request, opts) {
2241
3299
  `)) !== -1) {
2242
3300
  const line = buf.slice(0, nl);
2243
3301
  buf = buf.slice(nl + 1);
2244
- if (filterSentinel && line.startsWith(sentinelPrefix))
3302
+ if (filterSentinel && isSentinel(line))
2245
3303
  continue;
2246
3304
  sink(line);
2247
3305
  }
@@ -2251,7 +3309,7 @@ async function invokeStep(sandbox, request, opts) {
2251
3309
  return;
2252
3310
  const line = buf;
2253
3311
  buf = "";
2254
- if (filterSentinel && line.startsWith(sentinelPrefix))
3312
+ if (filterSentinel && isSentinel(line))
2255
3313
  return;
2256
3314
  sink(line);
2257
3315
  }
@@ -2271,11 +3329,21 @@ async function invokeStep(sandbox, request, opts) {
2271
3329
  if (parsed)
2272
3330
  return parsed;
2273
3331
  if (result.exitCode !== 0) {
3332
+ const stderrTail = result.stderr.trim().slice(-2000);
3333
+ const stdoutTail = result.stdout.trim().slice(-2000);
3334
+ const details = [
3335
+ stderrTail ? `stderr:
3336
+ ${stderrTail}` : "",
3337
+ stdoutTail ? `stdout:
3338
+ ${stdoutTail}` : ""
3339
+ ].filter(Boolean).join(`
3340
+ `);
2274
3341
  return {
2275
3342
  ok: false,
2276
3343
  error: {
2277
3344
  kind: "runner-exit",
2278
- message: `runner subprocess exited ${result.exitCode} before emitting a step result`,
3345
+ message: `runner subprocess exited ${result.exitCode} before emitting a step result${details ? `
3346
+ ${details}` : ""}`,
2279
3347
  exitCode: result.exitCode
2280
3348
  }
2281
3349
  };
@@ -2287,10 +3355,8 @@ async function invokeStep(sandbox, request, opts) {
2287
3355
  }
2288
3356
  // src/step-invocation/server.ts
2289
3357
  import { readFileSync } from "node:fs";
2290
- function emitResult(token, payload) {
3358
+ function writeSentinelLine(line) {
2291
3359
  return new Promise((resolve, reject) => {
2292
- const line = `${STEP_RESULT_PREFIX}${token}:${JSON.stringify(payload)}
2293
- `;
2294
3360
  process.stdout.write(line, (err) => {
2295
3361
  if (err)
2296
3362
  reject(err);
@@ -2299,13 +3365,27 @@ function emitResult(token, payload) {
2299
3365
  });
2300
3366
  });
2301
3367
  }
3368
+ function emitResult(token, payload) {
3369
+ return writeSentinelLine(`${STEP_RESULT_PREFIX}${token}:${JSON.stringify(payload)}
3370
+ `);
3371
+ }
3372
+ function emitPause(token, signal) {
3373
+ const body = JSON.stringify({
3374
+ pauseId: signal.pauseId,
3375
+ pauseRequest: signal.pauseRequest,
3376
+ ...signal.observability ? { observability: signal.observability } : {}
3377
+ });
3378
+ return writeSentinelLine(`${STEP_PAUSE_PREFIX}${token}:${body}
3379
+ `);
3380
+ }
2302
3381
  function scrubProtocolEnvs() {
2303
3382
  for (const key of [
2304
3383
  STEP_ENV.STEP_MODE,
2305
3384
  STEP_ENV.STEP_INDEX,
2306
3385
  STEP_ENV.STEP_INPUT_PATH,
2307
3386
  STEP_ENV.REQUEST_CONTEXT_PATH,
2308
- STEP_ENV.STEP_RESULT_TOKEN
3387
+ STEP_ENV.STEP_RESULT_TOKEN,
3388
+ STEP_ENV.STEP_RESUME
2309
3389
  ]) {
2310
3390
  delete process.env[key];
2311
3391
  }
@@ -2319,12 +3399,14 @@ async function serveStep(handler) {
2319
3399
  }
2320
3400
  let exitCode = 0;
2321
3401
  let payload;
3402
+ let pauseSignal;
2322
3403
  let setupResult;
2323
3404
  try {
2324
3405
  const runId = process.env[STEP_ENV.RUN_ID];
2325
3406
  const stepIndexEnv = process.env[STEP_ENV.STEP_INDEX];
2326
3407
  const inputPath = process.env[STEP_ENV.STEP_INPUT_PATH];
2327
3408
  const ctxPath = process.env[STEP_ENV.REQUEST_CONTEXT_PATH];
3409
+ const isResume = process.env[STEP_ENV.STEP_RESUME] === "1";
2328
3410
  const stepIndex = stepIndexEnv === undefined ? Number.NaN : Number(stepIndexEnv);
2329
3411
  if (!runId || !inputPath || !ctxPath || !Number.isFinite(stepIndex)) {
2330
3412
  throw new Error(`step-server missing required env: ${[
@@ -2335,8 +3417,8 @@ async function serveStep(handler) {
2335
3417
  ].filter(Boolean).join(", ")}`);
2336
3418
  }
2337
3419
  const input = JSON.parse(readFileSync(inputPath, "utf8"));
2338
- const requestContext = JSON.parse(readFileSync(ctxPath, "utf8"));
2339
- setupResult = { runId, stepIndex, input, requestContext };
3420
+ const requestContext = RequestContextWireSchema.parse(JSON.parse(readFileSync(ctxPath, "utf8")));
3421
+ setupResult = { runId, stepIndex, isResume, input, requestContext };
2340
3422
  } catch (err) {
2341
3423
  exitCode = 1;
2342
3424
  payload = { ok: false, kind: "protocol", error: err instanceof Error ? err.message : String(err) };
@@ -2347,32 +3429,76 @@ async function serveStep(handler) {
2347
3429
  const { output, observability } = await handler(setupResult);
2348
3430
  payload = observability ? { ok: true, output, observability } : { ok: true, output };
2349
3431
  } catch (err) {
2350
- exitCode = 1;
2351
- payload = { ok: false, kind: "user-step", error: err instanceof Error ? err.message : String(err) };
3432
+ if (isPauseSignal(err)) {
3433
+ pauseSignal = err;
3434
+ } else {
3435
+ exitCode = 1;
3436
+ payload = { ok: false, kind: "user-step", error: err instanceof Error ? err.message : String(err) };
3437
+ }
2352
3438
  }
2353
3439
  }
3440
+ if (pauseSignal) {
3441
+ await emitPause(token, pauseSignal);
3442
+ process.exit(0);
3443
+ }
2354
3444
  await emitResult(token, payload);
2355
3445
  process.exit(exitCode);
2356
3446
  }
2357
- // src/step-invocation/types.ts
2358
- class StepExecutionError extends Error {
2359
- kind;
2360
- exitCode;
2361
- constructor(error, stepLabel) {
2362
- super(`[${error.kind}] ${stepLabel}: ${error.message}`);
2363
- this.name = "StepExecutionError";
2364
- this.kind = error.kind;
2365
- this.exitCode = error.kind === "runner-exit" ? error.exitCode : undefined;
2366
- }
2367
- }
2368
3447
  // src/agent/run-agent.ts
2369
- import { z as z6 } from "zod";
3448
+ import { z as z11 } from "zod";
3449
+ import { randomUUID as randomUUID2 } from "node:crypto";
2370
3450
 
2371
3451
  // src/agent/protocol-suffix.md
2372
3452
  var protocol_suffix_default = '## Status Signal\n\nWhen you have finished your work or are blocked, emit a `<status>` block at the end of your response:\n\n```json\n<status>\n{\n "summary": "one sentence describing what was done or what is blocking",\n "completed": ["each acceptance criterion that is now fully met"],\n "blockers": [],\n "changed_files": ["relative/path/to/file"],\n "tests_run": true,\n "exit_signal": true\n}\n</status>\n```\n\n**Field semantics:**\n- `summary`: one sentence — what was accomplished or what is blocking\n- `completed`: acceptance criteria items that are fully done — be specific\n- `blockers`: non-empty when `exit_signal: false` — describe the exact obstacle\n- `changed_files`: relative paths of files you created or modified\n- `tests_run`: `true` if you ran any test suite (pass or fail); `false` if no tests exist or you skipped them\n- `exit_signal: true` — set when ALL acceptance criteria are met and no blockers remain\n- `exit_signal: false` — set when blocked or unfinished; `blockers` must be non-empty\n\n**If you are still actively working** and have not reached a natural stopping point, do NOT emit a `<status>` block — just keep working.\n\n**Example (done):**\n\n```json\n<status>\n{\n "summary": "Added input validation middleware to /api/tasks with tests",\n "completed": ["POST /api/tasks validates required fields", "Returns 400 with details on invalid input", "Unit tests passing"],\n "blockers": [],\n "changed_files": ["src/middleware/validate.ts", "src/routes/tasks.ts", "tests/validate.test.ts"],\n "tests_run": true,\n "exit_signal": true\n}\n</status>\n```\n\n**Example (blocked):**\n\n```json\n<status>\n{\n "summary": "Implemented middleware but tests are failing due to module resolution",\n "completed": ["Middleware created and wired into route"],\n "blockers": ["Tests fail: cannot resolve import \'./validate\' — module resolution config unclear"],\n "changed_files": ["src/middleware/validate.ts"],\n "tests_run": true,\n "exit_signal": false\n}\n</status>\n```\n';
2373
3453
 
2374
3454
  // src/agent/run-agent.ts
3455
+ async function runInboxPoller(opts) {
3456
+ let afterSeq = 0;
3457
+ const headers = {
3458
+ authorization: `Bearer ${opts.token}`,
3459
+ accept: "application/json"
3460
+ };
3461
+ let backoffMs = 0;
3462
+ while (!opts.signal.aborted) {
3463
+ if (backoffMs > 0) {
3464
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
3465
+ if (opts.signal.aborted)
3466
+ return;
3467
+ }
3468
+ const url = `${opts.baseUrl.replace(/\/+$/, "")}` + `/api/v1/internal/runs/${opts.runId}/agents/${encodeURIComponent(opts.agentId)}/inbox?afterSeq=${afterSeq}`;
3469
+ try {
3470
+ const res = await fetch(url, { method: "GET", headers, signal: opts.signal });
3471
+ if (!res.ok) {
3472
+ if (res.status === 401 || res.status === 403)
3473
+ return;
3474
+ backoffMs = Math.min(8000, (backoffMs || 500) * 2);
3475
+ continue;
3476
+ }
3477
+ backoffMs = 0;
3478
+ const body = await res.json();
3479
+ for (const m of body.messages ?? []) {
3480
+ opts.inbox.push({ text: m.body, senderName: m.senderName });
3481
+ if (m.seq > afterSeq)
3482
+ afterSeq = m.seq;
3483
+ }
3484
+ } catch (err) {
3485
+ if (opts.signal.aborted)
3486
+ return;
3487
+ backoffMs = Math.min(8000, (backoffMs || 500) * 2);
3488
+ if (backoffMs >= 4000) {
3489
+ process.stderr.write(`[inbox-poller] poll failed, backing off ${backoffMs}ms: ${err instanceof Error ? err.message : String(err)}
3490
+ `);
3491
+ }
3492
+ }
3493
+ }
3494
+ }
2375
3495
  var PROTOCOL_SUFFIX = stripFrontmatter(protocol_suffix_default);
3496
+ var HITL_ASK_INSTRUCTION = [
3497
+ "## Asking a human",
3498
+ "",
3499
+ "If you hit a decision or need information only a human can provide, set " + '`"needs_input": true` in your `<status>` block, put your question in a ' + '`"question"` field, set `"exit_signal": false`, and stop. The workflow ' + "pauses and a human answers; their reply arrives as your next message. Use " + "this only when you genuinely cannot proceed on your own — prefer making a " + "reasonable decision and continuing."
3500
+ ].join(`
3501
+ `);
2376
3502
  function stripFrontmatter(content) {
2377
3503
  if (!content.startsWith("---"))
2378
3504
  return content;
@@ -2381,29 +3507,29 @@ function stripFrontmatter(content) {
2381
3507
  return end === -1 ? content : content.slice(end + 4).trimStart();
2382
3508
  }
2383
3509
  function zodTypeName(schema) {
2384
- if (schema instanceof z6.ZodString)
3510
+ if (schema instanceof z11.ZodString)
2385
3511
  return "string";
2386
- if (schema instanceof z6.ZodNumber)
3512
+ if (schema instanceof z11.ZodNumber)
2387
3513
  return "number";
2388
- if (schema instanceof z6.ZodBoolean)
3514
+ if (schema instanceof z11.ZodBoolean)
2389
3515
  return "boolean";
2390
- if (schema instanceof z6.ZodArray)
3516
+ if (schema instanceof z11.ZodArray)
2391
3517
  return `${zodTypeName(schema.element)}[]`;
2392
- if (schema instanceof z6.ZodEnum)
3518
+ if (schema instanceof z11.ZodEnum)
2393
3519
  return schema.options.map((o) => `"${o}"`).join(" | ");
2394
- if (schema instanceof z6.ZodOptional)
3520
+ if (schema instanceof z11.ZodOptional)
2395
3521
  return `${zodTypeName(schema.unwrap())} (optional)`;
2396
- if (schema instanceof z6.ZodNullable)
3522
+ if (schema instanceof z11.ZodNullable)
2397
3523
  return `${zodTypeName(schema.unwrap())} | null`;
2398
- if (schema instanceof z6.ZodLiteral)
3524
+ if (schema instanceof z11.ZodLiteral)
2399
3525
  return `"${String(schema.value)}"`;
2400
- if (schema instanceof z6.ZodObject)
3526
+ if (schema instanceof z11.ZodObject)
2401
3527
  return "object";
2402
3528
  return "any";
2403
3529
  }
2404
3530
  function buildResponseFormatAppendix(schema) {
2405
3531
  const header = ["## Response Format", "", "When you emit `exit_signal: true` in your `<status>` block, also include a `<response>` block containing JSON immediately after `<status>`:", ""];
2406
- const shape = schema instanceof z6.ZodObject ? schema.shape : null;
3532
+ const shape = schema instanceof z11.ZodObject ? schema.shape : null;
2407
3533
  const body = shape ? (() => {
2408
3534
  const entries = Object.entries(shape);
2409
3535
  const example = Object.fromEntries(entries.map(([k, v]) => [k, `<${zodTypeName(v)}>`]));
@@ -2428,44 +3554,96 @@ function buildResponseFormatAppendix(schema) {
2428
3554
  return [...header, ...body].join(`
2429
3555
  `);
2430
3556
  }
3557
+ function resolveAgentId(explicitId) {
3558
+ if (explicitId)
3559
+ return explicitId;
3560
+ const activeCall = nextAgentCallInActiveStep();
3561
+ if (activeCall === null)
3562
+ return randomUUID2();
3563
+ return `step${activeCall.stepIndex}-agent-${activeCall.callIndex}`;
3564
+ }
2431
3565
  async function agent(opts) {
2432
3566
  const workingDir = opts.workingDir ?? "";
2433
- return agentLoop({
2434
- runtime: (runtimeOpts) => opts.runtime.create(opts.sandbox, runtimeOpts),
2435
- ...opts.agentId !== undefined ? { agentId: opts.agentId } : {},
2436
- ...opts.label !== undefined ? { label: opts.label } : {},
2437
- ...opts.budget?.turnsPerIteration !== undefined ? { turnsPerIteration: opts.budget.turnsPerIteration } : {},
2438
- ...opts.budget?.maxIterations !== undefined ? { maxIterations: opts.budget.maxIterations } : {},
2439
- ...opts.tools !== undefined ? { allowedTools: opts.tools } : {},
2440
- ...opts.responseSchema !== undefined ? { responseSchema: opts.responseSchema } : {},
2441
- ...workingDir ? { cwd: workingDir } : {},
2442
- buildPrompt: (_lastStatus, iteration) => {
2443
- if (iteration > 0)
2444
- return PROTOCOL_SUFFIX;
2445
- const base = stripFrontmatter(opts.prompt);
2446
- return opts.responseSchema ? `${base}
2447
-
2448
- ${PROTOCOL_SUFFIX}
2449
-
2450
- ${buildResponseFormatAppendix(opts.responseSchema)}` : `${base}
3567
+ const agentId = resolveAgentId(opts.agentId);
3568
+ const callbackUrl = process.env.AGENT_COMPOSE_URL;
3569
+ const callbackToken = process.env.AGENT_COMPOSE_RUN_TOKEN;
3570
+ const runId = process.env.RUN_ID;
3571
+ const inbox = callbackUrl && callbackToken && runId ? new AsyncQueue : undefined;
3572
+ const inboxAbort = new AbortController;
3573
+ if (inbox && callbackUrl && callbackToken && runId) {
3574
+ runInboxPoller({
3575
+ baseUrl: callbackUrl,
3576
+ token: callbackToken,
3577
+ runId,
3578
+ agentId,
3579
+ inbox,
3580
+ signal: inboxAbort.signal
3581
+ });
3582
+ }
3583
+ if (callbackUrl && callbackToken && runId) {
3584
+ runControlPoller({
3585
+ baseUrl: callbackUrl,
3586
+ token: callbackToken,
3587
+ runId,
3588
+ agentId,
3589
+ signal: inboxAbort.signal
3590
+ });
3591
+ }
3592
+ const activeStepForPause = getActiveStep();
3593
+ const steerPause = activeStepForPause && runId ? function steerPauseFn(req, agentScope) {
3594
+ return corePause(req, {
3595
+ runId,
3596
+ stepIndex: activeStepForPause.stepIndex,
3597
+ agentScope
3598
+ });
3599
+ } : undefined;
3600
+ const mode = opts.mode ?? "auto";
3601
+ try {
3602
+ return await agentLoop({
3603
+ runtime: (runtimeOpts) => opts.runtime.create(opts.sandbox, runtimeOpts),
3604
+ agentId,
3605
+ ...opts.label !== undefined ? { label: opts.label } : {},
3606
+ ...opts.budget?.turnsPerIteration !== undefined ? { turnsPerIteration: opts.budget.turnsPerIteration } : {},
3607
+ ...opts.budget?.maxIterations !== undefined ? { maxIterations: opts.budget.maxIterations } : {},
3608
+ ...opts.tools !== undefined ? { allowedTools: opts.tools } : {},
3609
+ ...opts.responseSchema !== undefined ? { responseSchema: opts.responseSchema } : {},
3610
+ ...workingDir ? { cwd: workingDir } : {},
3611
+ buildPrompt: (_lastStatus, iteration) => {
3612
+ if (iteration > 0)
3613
+ return PROTOCOL_SUFFIX;
3614
+ const base = stripFrontmatter(opts.prompt);
3615
+ const parts = [base, PROTOCOL_SUFFIX];
3616
+ if (mode === "hitl")
3617
+ parts.push(HITL_ASK_INSTRUCTION);
3618
+ if (opts.responseSchema)
3619
+ parts.push(buildResponseFormatAppendix(opts.responseSchema));
3620
+ return parts.join(`
2451
3621
 
2452
- ${PROTOCOL_SUFFIX}`;
2453
- },
2454
- ...opts.events ? { onAgentLifecycleEvent: (event) => {
2455
- opts.events?.emit(event);
2456
- } } : {},
2457
- ...opts.onAgentEvent ? { onAgentEvent: opts.onAgentEvent } : {},
2458
- ...opts.onIteration ? { onIteration: opts.onIteration } : {},
2459
- ...opts.processors?.length ? { processors: opts.processors } : {},
2460
- requestContext: opts.requestContext ?? RequestContext.fromReserved({
2461
- teamId: "",
2462
- runId: "",
2463
- workflowId: "",
2464
- factoryId: null,
2465
- apiKeyScopes: [],
2466
- parentRunId: null
2467
- })
2468
- });
3622
+ `);
3623
+ },
3624
+ ...opts.events ? { onAgentLifecycleEvent: (event) => {
3625
+ opts.events?.emit(event);
3626
+ } } : {},
3627
+ ...opts.onAgentEvent ? { onAgentEvent: opts.onAgentEvent } : {},
3628
+ ...opts.onIteration ? { onIteration: opts.onIteration } : {},
3629
+ ...opts.processors?.length ? { processors: opts.processors } : {},
3630
+ requestContext: opts.requestContext ?? RequestContext.fromReserved({
3631
+ teamId: "",
3632
+ runId: "",
3633
+ workflowId: "",
3634
+ factoryId: null,
3635
+ apiKeyScopes: [],
3636
+ parentRunId: null
3637
+ }),
3638
+ ...inbox ? { inbox } : {},
3639
+ mode,
3640
+ consumeSteerPending: () => consumeSteerPending(agentId),
3641
+ ...steerPause ? { pause: steerPause } : {}
3642
+ });
3643
+ } finally {
3644
+ inbox?.close();
3645
+ inboxAbort.abort();
3646
+ }
2469
3647
  }
2470
3648
  export {
2471
3649
  writeTool,
@@ -2497,7 +3675,6 @@ export {
2497
3675
  getSandboxQuotas,
2498
3676
  formatError,
2499
3677
  editTool,
2500
- discoverRuntimeName,
2501
3678
  denyTools,
2502
3679
  deleteSandboxSnapshot,
2503
3680
  defineWorkflow,
@@ -2527,11 +3704,16 @@ export {
2527
3704
  StepValidationError,
2528
3705
  StepExecutionError,
2529
3706
  STEP_RESULT_PREFIX,
3707
+ STEP_PAUSE_PREFIX,
2530
3708
  STEP_ENV,
2531
3709
  ReservedKeyError,
2532
3710
  RequestContext,
2533
3711
  RUNNER_COMMAND,
2534
3712
  RUNNER_BUNDLE_PATH,
3713
+ PauseSchemaError,
3714
+ PauseRequestError,
3715
+ PauseExpiredError,
3716
+ PauseError,
2535
3717
  NonSerialisableValueError,
2536
3718
  LATEST_VERSION,
2537
3719
  EngineError,