@agent-compose/sdk 0.5.1 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) 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 +16 -6
  12. package/dist/index.js +1586 -158
  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/_cli-agent.d.ts +72 -0
  28. package/dist/runtimes/amp.d.ts +22 -0
  29. package/dist/runtimes/claude.d.ts +6 -0
  30. package/dist/runtimes/codex.d.ts +20 -0
  31. package/dist/runtimes/openai-desktop.d.ts +2 -0
  32. package/dist/runtimes/openai-desktop.js +1578 -157
  33. package/dist/runtimes/vercel.d.ts +53 -1
  34. package/dist/runtimes/vercel.js +60 -8
  35. package/dist/runtimes/vercel.test.d.ts +1 -0
  36. package/dist/sse.d.ts +2 -3
  37. package/dist/step-invocation/index.d.ts +2 -2
  38. package/dist/step-invocation/invoker.d.ts +3 -0
  39. package/dist/step-invocation/protocol.d.ts +12 -0
  40. package/dist/step-invocation/server.d.ts +1 -0
  41. package/dist/step-invocation/types.d.ts +40 -5
  42. package/dist/types/events.d.ts +9 -0
  43. package/dist/types/execution-context.d.ts +25 -0
  44. package/dist/types/protocol.d.ts +8 -0
  45. package/dist/types/runtime.d.ts +55 -0
  46. package/dist/types/sandbox.d.ts +4 -4
  47. package/dist/utils/schemas.d.ts +2 -0
  48. package/dist/workflow-steps/__tests__/pause-wiring.test.d.ts +1 -0
  49. package/dist/workflow-steps/index.d.ts +2 -0
  50. package/dist/workflow-steps/observability.d.ts +43 -11
  51. package/dist/workflow-steps/run-callback.d.ts +39 -0
  52. package/dist/workflow-steps/runner.d.ts +8 -0
  53. package/package.json +1 -1
  54. package/src/active-step.ts +124 -0
  55. package/src/agent/agent-loop.ts +253 -19
  56. package/src/agent/async-queue.ts +61 -0
  57. package/src/agent/protocol.ts +12 -2
  58. package/src/agent/run-agent.ts +184 -8
  59. package/src/agent/steer-control.ts +125 -0
  60. package/src/client.ts +277 -0
  61. package/src/index.ts +38 -4
  62. package/src/pause/checkpoint.ts +44 -0
  63. package/src/pause/errors.ts +70 -0
  64. package/src/pause/manager.ts +177 -0
  65. package/src/pause/pause-core.ts +267 -0
  66. package/src/pause/state-dir.ts +262 -0
  67. package/src/pause/wrappers.ts +79 -0
  68. package/src/request-context/request-context.ts +17 -2
  69. package/src/runtimes/_cli-agent.ts +161 -0
  70. package/src/runtimes/amp.ts +94 -0
  71. package/src/runtimes/claude.ts +101 -6
  72. package/src/runtimes/codex.ts +109 -0
  73. package/src/runtimes/openai-desktop.ts +11 -0
  74. package/src/runtimes/vercel.ts +78 -2
  75. package/src/sandbox.ts +39 -20
  76. package/src/sse.ts +8 -6
  77. package/src/step-invocation/index.ts +2 -1
  78. package/src/step-invocation/invoker.ts +107 -29
  79. package/src/step-invocation/protocol.ts +16 -0
  80. package/src/step-invocation/server.ts +45 -12
  81. package/src/step-invocation/types.ts +43 -7
  82. package/src/tools/coding.ts +16 -5
  83. package/src/types/events.ts +9 -0
  84. package/src/types/execution-context.ts +25 -0
  85. package/src/types/protocol.ts +8 -0
  86. package/src/types/runtime.ts +52 -0
  87. package/src/types/sandbox.ts +8 -4
  88. package/src/types/workflow.ts +6 -1
  89. package/src/utils/bundler.ts +8 -3
  90. package/src/utils/schemas.ts +2 -0
  91. package/src/workflow-steps/index.ts +3 -0
  92. package/src/workflow-steps/observability.ts +84 -13
  93. package/src/workflow-steps/run-callback.ts +72 -0
  94. package/src/workflow-steps/runner.ts +70 -8
  95. package/dist/utils/discovery.d.ts +0 -2
  96. package/src/utils/discovery.ts +0 -4
@@ -18,7 +18,7 @@ var __toESM = (mod, isNodeMode, target) => {
18
18
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
19
 
20
20
  // src/runtimes/vercel.ts
21
- import { streamText, stepCountIs, tool } from "ai";
21
+ import { streamText, stepCountIs, tool, gateway } from "ai";
22
22
 
23
23
  // src/types/runtime.ts
24
24
  function defineRuntime(pkg) {
@@ -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) {
@@ -358,6 +387,8 @@ class VercelRunner {
358
387
  options;
359
388
  config;
360
389
  supportsToolCallProcessor = true;
390
+ kind;
391
+ model;
361
392
  tools;
362
393
  messages = [];
363
394
  constructor(sandbox, options, config) {
@@ -365,6 +396,8 @@ class VercelRunner {
365
396
  this.options = options;
366
397
  this.config = config;
367
398
  this.tools = config.tools ?? codingTools;
399
+ this.kind = config.kind;
400
+ this.model = config.modelId;
368
401
  }
369
402
  async gateToolCall(call, ctx) {
370
403
  const verdict = await runProcessorChain(this.options.processors ?? [], (p) => p.processToolCall, call, ctx);
@@ -372,6 +405,20 @@ class VercelRunner {
372
405
  return { kind: "allow", call: verdict.value };
373
406
  return verdict;
374
407
  }
408
+ captureCheckpoint() {
409
+ return { messages: [...this.messages] };
410
+ }
411
+ restoreCheckpoint(blob) {
412
+ if (!blob || typeof blob !== "object") {
413
+ throw new Error("Vercel runtime checkpoint is invalid: expected an object with messages[]");
414
+ }
415
+ const incoming = blob.messages;
416
+ if (!Array.isArray(incoming)) {
417
+ throw new Error("Vercel runtime checkpoint is invalid: expected messages[]");
418
+ }
419
+ this.messages.length = 0;
420
+ this.messages.push(...incoming);
421
+ }
375
422
  buildTools(iteration, signal) {
376
423
  const set = {};
377
424
  for (const t of this.tools) {
@@ -468,8 +515,12 @@ function createVercelRuntime(config) {
468
515
  create: (sandbox, opts) => new VercelRunner(sandbox, opts, config)
469
516
  });
470
517
  }
518
+ async function listVercelRuntimeModels() {
519
+ const { models } = await gateway.getAvailableModels();
520
+ return models.filter((m) => m.modelType == null || m.modelType === "language").map((m) => ({ id: m.id, name: m.name })).sort((a, b) => a.id.localeCompare(b.id));
521
+ }
471
522
  // src/types/workflow.ts
472
- import { z as z2 } from "zod";
523
+ import { z as z3 } from "zod";
473
524
 
474
525
  // src/workflow-steps/types.ts
475
526
  var WORKFLOW_BRAND = Symbol.for("@agent-compose/sdk.Workflow");
@@ -546,7 +597,7 @@ function isWorkflow(value) {
546
597
 
547
598
  // src/types/workflow.ts
548
599
  function compileRunForm(def, metadata) {
549
- const unknownSchema = z2.unknown();
600
+ const unknownSchema = z3.unknown();
550
601
  const inputSchema = def.input ?? unknownSchema;
551
602
  const outputSchema = def.output ?? unknownSchema;
552
603
  const step = {
@@ -562,6 +613,11 @@ function compileRunForm(def, metadata) {
562
613
  setMetadata: stepCtx.setMetadata,
563
614
  step: stepCtx.step,
564
615
  agentEvents: stepCtx.agentEvents,
616
+ checkpoint: stepCtx.checkpoint,
617
+ pause: stepCtx.pause,
618
+ requestDecision: stepCtx.requestDecision,
619
+ sleep: stepCtx.sleep,
620
+ waitForEvent: stepCtx.waitForEvent,
565
621
  processors: metadata.processors ?? []
566
622
  };
567
623
  const sandbox = stepCtx.sandbox;
@@ -678,11 +734,14 @@ async function* parseSseStream(body) {
678
734
  continue;
679
735
  }
680
736
  if (line === "" && dataLines.length > 0) {
737
+ let data;
681
738
  try {
682
- const data = JSON.parse(dataLines.join(`
739
+ data = JSON.parse(dataLines.join(`
683
740
  `));
684
- yield { id: seq, event, data };
685
- } catch {}
741
+ } catch (err) {
742
+ throw new Error(`Malformed SSE JSON payload for event "${event || "message"}": ${err instanceof Error ? err.message : String(err)}`);
743
+ }
744
+ yield { id: seq, event, data };
686
745
  event = "";
687
746
  seq = 0;
688
747
  dataLines = [];
@@ -706,6 +765,12 @@ function templatePath(factorySlug, ...rest) {
706
765
  return `/api/v1/factories/${encodeURIComponent(factorySlug)}/templates${tail}`;
707
766
  }
708
767
  var PUBLIC_API_HOST = "https://api.agentcompose.ai";
768
+ function steerAnswerPayload(message, actor) {
769
+ if (typeof message !== "string" || message.trim().length === 0) {
770
+ throw new Error("answerSteer: `message` must be a non-empty string");
771
+ }
772
+ return { message, ...actor !== undefined ? { actor } : {} };
773
+ }
709
774
 
710
775
  class AgentComposeClient {
711
776
  fetch;
@@ -791,6 +856,56 @@ class AgentComposeClient {
791
856
  getStatus(runId) {
792
857
  return this.fetch(`/api/v1/workflows/${runId}/status`);
793
858
  }
859
+ async resumePause(runId, pauseId, payload, opts) {
860
+ const headers = {};
861
+ if (opts?.idempotencyKey)
862
+ headers["Idempotency-Key"] = opts.idempotencyKey;
863
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/${encodeURIComponent(pauseId)}/resume`, {
864
+ method: "POST",
865
+ body: { resumePayload: payload },
866
+ ...Object.keys(headers).length ? { headers } : {},
867
+ ...opts?.signal ? { signal: opts.signal } : {}
868
+ });
869
+ }
870
+ async resumePauseByKey(runId, correlationKey, payload, opts) {
871
+ const headers = {};
872
+ if (opts?.idempotencyKey)
873
+ headers["Idempotency-Key"] = opts.idempotencyKey;
874
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/by-key/${encodeURIComponent(correlationKey)}/resume`, {
875
+ method: "POST",
876
+ body: { resumePayload: payload },
877
+ ...Object.keys(headers).length ? { headers } : {},
878
+ ...opts?.signal ? { signal: opts.signal } : {}
879
+ });
880
+ }
881
+ async answerSteerByKey(runId, correlationKey, message, opts) {
882
+ return this.resumePauseByKey(runId, correlationKey, steerAnswerPayload(message, opts?.actor), opts);
883
+ }
884
+ async answerSteer(runId, pauseId, message, opts) {
885
+ return this.resumePause(runId, pauseId, steerAnswerPayload(message, opts?.actor), opts);
886
+ }
887
+ async requestAgentPause(runId, agentId, opts) {
888
+ const body = {};
889
+ if (opts?.reason !== undefined)
890
+ body.reason = opts.reason;
891
+ if (opts?.correlationKey !== undefined)
892
+ body.correlationKey = opts.correlationKey;
893
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/pause`, {
894
+ method: "POST",
895
+ body,
896
+ ...opts?.signal ? { signal: opts.signal } : {}
897
+ });
898
+ }
899
+ async sendAgentMessage(runId, agentId, message, opts) {
900
+ const body = { message };
901
+ if (opts?.senderName !== undefined)
902
+ body.senderName = opts.senderName;
903
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/messages`, {
904
+ method: "POST",
905
+ body,
906
+ ...opts?.signal ? { signal: opts.signal } : {}
907
+ });
908
+ }
794
909
  getRun(runId) {
795
910
  return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}`);
796
911
  }
@@ -930,10 +1045,6 @@ class AgentComposeClient {
930
1045
  }
931
1046
  }
932
1047
  }
933
- // src/utils/discovery.ts
934
- function discoverRuntimeName(source) {
935
- return source.match(/runtime:\s*["']([^"']+)["']/)?.[1] ?? null;
936
- }
937
1048
  // src/utils/bundler.ts
938
1049
  import { createHash } from "node:crypto";
939
1050
  import { parse as babelParse } from "@babel/parser";
@@ -986,8 +1097,8 @@ async function extractFromBundle(source, label, pick) {
986
1097
  } finally {
987
1098
  await loaded.cleanup();
988
1099
  }
989
- } catch {
990
- return;
1100
+ } catch (err) {
1101
+ throw new WorkflowSourceValidationError(`Bundled ${label} could not be evaluated: ${err instanceof Error ? err.message : String(err)}`);
991
1102
  }
992
1103
  }
993
1104
  var DEFINE_WORKFLOW_CALLEE = /^(?:defineWorkflow|defineSandboxEnvironment)\d*$/;
@@ -1080,10 +1191,10 @@ async function bundleWorkflow(workflowPath, overrides) {
1080
1191
  function extractIOSchema(zodSchema) {
1081
1192
  let json;
1082
1193
  try {
1083
- const z3 = __require("zod");
1084
- if (!z3.toJSONSchema)
1194
+ const z4 = __require("zod");
1195
+ if (!z4.toJSONSchema)
1085
1196
  return;
1086
- json = z3.toJSONSchema(zodSchema);
1197
+ json = z4.toJSONSchema(zodSchema);
1087
1198
  } catch {
1088
1199
  return;
1089
1200
  }
@@ -1116,24 +1227,26 @@ function extractIOSchema(zodSchema) {
1116
1227
  return out;
1117
1228
  }
1118
1229
  // 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()
1230
+ import { z as z4 } from "zod";
1231
+ var AgentStatusSchema = z4.object({
1232
+ summary: z4.string(),
1233
+ completed: z4.array(z4.string()),
1234
+ blockers: z4.array(z4.string()),
1235
+ exit_signal: z4.boolean(),
1236
+ needs_input: z4.boolean().optional(),
1237
+ question: z4.string().optional()
1125
1238
  });
1126
1239
  // src/runtimes/claude.ts
1127
1240
  import { query } from "@anthropic-ai/claude-agent-sdk";
1128
1241
 
1129
1242
  // src/agent/agent-loop.ts
1130
- import { z as z5 } from "zod";
1243
+ import { z as z9 } from "zod";
1131
1244
 
1132
1245
  // 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()
1246
+ import { z as z5 } from "zod";
1247
+ var AgentMessageSchema = z5.object({
1248
+ type: z5.enum(["init", "text", "thinking", "tool_use", "tool_result", "done", "error", "usage"]),
1249
+ timestamp: z5.string()
1137
1250
  }).passthrough();
1138
1251
  function parseAgentResponse(text) {
1139
1252
  const match = text.match(/<response>([\s\S]*?)<\/response>/);
@@ -1148,6 +1261,557 @@ function parseAgentResponse(text) {
1148
1261
 
1149
1262
  // src/agent/agent-loop.ts
1150
1263
  import { randomUUID } from "node:crypto";
1264
+
1265
+ // src/pause/manager.ts
1266
+ import { z as z6 } from "zod";
1267
+
1268
+ // src/pause/state-dir.ts
1269
+ import { promises as fs } from "node:fs";
1270
+ import { join as join2 } from "node:path";
1271
+ import { randomBytes } from "node:crypto";
1272
+ function getStateDir() {
1273
+ return process.env.AGENT_COMPOSE_STATE_DIR ?? "/tmp/wf/state";
1274
+ }
1275
+ var checkpointsDir = () => join2(getStateDir(), "checkpoints");
1276
+ var pausesDir = () => join2(getStateDir(), "pauses");
1277
+ var agentStatePath = (agentInstanceId) => join2(getStateDir(), `agent-${agentInstanceId}.json`);
1278
+ var runtimeStatePath = (agentInstanceId) => join2(getStateDir(), `runtime-${agentInstanceId}.json`);
1279
+ var checkpointPath = (name) => join2(checkpointsDir(), `${name}.json`);
1280
+ var pausePath = (pauseId) => join2(pausesDir(), `${pauseId}.json`);
1281
+ var NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/;
1282
+ function assertSafeName(label, name) {
1283
+ if (!NAME_RE.test(name)) {
1284
+ throw new Error(`${label} name must match ${NAME_RE} (got ${JSON.stringify(name)})`);
1285
+ }
1286
+ }
1287
+ function assertSafeCheckpointName(name) {
1288
+ assertSafeName("checkpoint", name);
1289
+ }
1290
+ async function ensureDirs() {
1291
+ await fs.mkdir(checkpointsDir(), { recursive: true });
1292
+ await fs.mkdir(pausesDir(), { recursive: true });
1293
+ }
1294
+ async function atomicWriteJSON(path, value) {
1295
+ await ensureDirs();
1296
+ const tmp = `${path}.tmp.${randomBytes(6).toString("hex")}`;
1297
+ const payload = JSON.stringify(value);
1298
+ if (payload === undefined) {
1299
+ throw new Error("pause state values must be JSON-serialisable");
1300
+ }
1301
+ const handle = await fs.open(tmp, "w");
1302
+ try {
1303
+ await handle.writeFile(payload, { encoding: "utf8" });
1304
+ await handle.sync();
1305
+ } finally {
1306
+ await handle.close();
1307
+ }
1308
+ await fs.rename(tmp, path);
1309
+ }
1310
+ async function readJSON(path) {
1311
+ try {
1312
+ const raw = await fs.readFile(path, "utf8");
1313
+ return JSON.parse(raw);
1314
+ } catch (err) {
1315
+ if (err.code === "ENOENT")
1316
+ return null;
1317
+ throw err;
1318
+ }
1319
+ }
1320
+ async function readJSONWithPresence(path) {
1321
+ try {
1322
+ const raw = await fs.readFile(path, "utf8");
1323
+ return { found: true, value: JSON.parse(raw) };
1324
+ } catch (err) {
1325
+ if (err.code === "ENOENT")
1326
+ return { found: false };
1327
+ throw err;
1328
+ }
1329
+ }
1330
+ async function readAgentState(agentInstanceId) {
1331
+ assertSafeName("agent", agentInstanceId);
1332
+ return readJSON(agentStatePath(agentInstanceId));
1333
+ }
1334
+ async function writeAgentState(agentInstanceId, state) {
1335
+ assertSafeName("agent", agentInstanceId);
1336
+ await atomicWriteJSON(agentStatePath(agentInstanceId), state);
1337
+ }
1338
+ async function readRuntimeCheckpoint(agentInstanceId) {
1339
+ assertSafeName("runtime", agentInstanceId);
1340
+ return readJSON(runtimeStatePath(agentInstanceId));
1341
+ }
1342
+ async function writeRuntimeCheckpoint(agentInstanceId, blob) {
1343
+ assertSafeName("runtime", agentInstanceId);
1344
+ await atomicWriteJSON(runtimeStatePath(agentInstanceId), blob);
1345
+ }
1346
+ async function readCheckpoint(name) {
1347
+ assertSafeCheckpointName(name);
1348
+ return readJSONWithPresence(checkpointPath(name));
1349
+ }
1350
+ async function writeCheckpoint(name, value) {
1351
+ assertSafeCheckpointName(name);
1352
+ await atomicWriteJSON(checkpointPath(name), value);
1353
+ }
1354
+ function hasOwn(obj, key) {
1355
+ return Object.prototype.hasOwnProperty.call(obj, key);
1356
+ }
1357
+ function validatePauseRecord(value, expectedPauseId) {
1358
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1359
+ throw new Error(`pause record ${expectedPauseId} is malformed: expected object`);
1360
+ }
1361
+ const record = value;
1362
+ if (record.pauseId !== undefined && record.pauseId !== expectedPauseId) {
1363
+ throw new Error(`pause record ${expectedPauseId} is malformed: pauseId mismatch`);
1364
+ }
1365
+ if (record.resolved === true) {
1366
+ if (typeof record.expired !== "boolean") {
1367
+ throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require boolean expired`);
1368
+ }
1369
+ if (!hasOwn(record, "resumePayload") || record.resumePayload === undefined) {
1370
+ throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require resumePayload`);
1371
+ }
1372
+ return {
1373
+ pauseId: expectedPauseId,
1374
+ request: hasOwn(record, "request") ? record.request : null,
1375
+ ...record
1376
+ };
1377
+ }
1378
+ if (record.pauseId !== expectedPauseId) {
1379
+ throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require matching pauseId`);
1380
+ }
1381
+ if (!hasOwn(record, "request") || record.request === undefined) {
1382
+ throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require request`);
1383
+ }
1384
+ if (record.resolved !== undefined && record.resolved !== false) {
1385
+ throw new Error(`pause record ${expectedPauseId} is malformed: resolved must be true, false, or absent`);
1386
+ }
1387
+ if (hasOwn(record, "expired") || hasOwn(record, "resumePayload")) {
1388
+ throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records must not include resolution fields`);
1389
+ }
1390
+ return record;
1391
+ }
1392
+ async function readPauseRecord(pauseId) {
1393
+ assertSafeName("pause", pauseId);
1394
+ const record = await readJSON(pausePath(pauseId));
1395
+ return record === null ? null : validatePauseRecord(record, pauseId);
1396
+ }
1397
+ async function writePauseRecord(record) {
1398
+ assertSafeName("pause", record.pauseId);
1399
+ validatePauseRecord(record, record.pauseId);
1400
+ await atomicWriteJSON(pausePath(record.pauseId), record);
1401
+ }
1402
+
1403
+ // src/pause/manager.ts
1404
+ var AGENT_LOOP_STATE_SCHEMA_VERSION = 2;
1405
+ var SUPPORTED_AGENT_LOOP_STATE_VERSIONS = z6.union([z6.literal(1), z6.literal(2)]);
1406
+ var RunningAgentLoopStateSchema = z6.object({
1407
+ schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1408
+ phase: z6.literal("running"),
1409
+ iteration: z6.number().int().nonnegative(),
1410
+ completedIterations: z6.number().int().nonnegative(),
1411
+ lastSessionId: z6.string(),
1412
+ lastStatus: AgentStatusSchema.nullable(),
1413
+ lastResponseText: z6.string(),
1414
+ lastResponseValidationError: z6.string(),
1415
+ iterationsWithoutStatus: z6.number().int().nonnegative(),
1416
+ blockerStreak: z6.object({ key: z6.string(), count: z6.number().int().nonnegative() }).nullable(),
1417
+ pendingSteerPause: z6.object({ reason: z6.string(), correlationKey: z6.string().nullable(), at: z6.number().int() }).nullable().default(null)
1418
+ });
1419
+ var SettledAgentLoopStateSchema = z6.object({
1420
+ schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1421
+ phase: z6.literal("settled"),
1422
+ result: z6.object({
1423
+ sessionId: z6.string(),
1424
+ lastStatus: AgentStatusSchema.nullable(),
1425
+ iterations: z6.number().int().nonnegative(),
1426
+ response: z6.unknown().optional()
1427
+ })
1428
+ });
1429
+ var AgentLoopStateSchema = z6.discriminatedUnion("phase", [
1430
+ RunningAgentLoopStateSchema,
1431
+ SettledAgentLoopStateSchema
1432
+ ]);
1433
+ function toProgressState(state) {
1434
+ return {
1435
+ iteration: state.iteration,
1436
+ completedIterations: state.completedIterations,
1437
+ lastSessionId: state.lastSessionId,
1438
+ lastStatus: state.lastStatus,
1439
+ lastResponseText: state.lastResponseText,
1440
+ lastResponseValidationError: state.lastResponseValidationError,
1441
+ iterationsWithoutStatus: state.iterationsWithoutStatus,
1442
+ blockerStreak: state.blockerStreak,
1443
+ pendingSteerPause: state.pendingSteerPause
1444
+ };
1445
+ }
1446
+
1447
+ class PauseManager {
1448
+ agentId;
1449
+ constructor(agentId) {
1450
+ this.agentId = agentId;
1451
+ }
1452
+ async restoreAgentLoop(runtime) {
1453
+ const priorState = await readAgentState(this.agentId);
1454
+ if (priorState === null)
1455
+ return { kind: "fresh" };
1456
+ const parsed = AgentLoopStateSchema.safeParse(priorState);
1457
+ if (!parsed.success)
1458
+ return { kind: "invalid", error: parsed.error.message };
1459
+ const state = parsed.data;
1460
+ if (state.phase === "settled") {
1461
+ return {
1462
+ kind: "settled",
1463
+ result: {
1464
+ sessionId: state.result.sessionId,
1465
+ lastStatus: state.result.lastStatus,
1466
+ iterations: state.result.iterations,
1467
+ ...Object.prototype.hasOwnProperty.call(state.result, "response") ? { response: state.result.response } : {}
1468
+ }
1469
+ };
1470
+ }
1471
+ const runtimeBlob = await readRuntimeCheckpoint(this.agentId);
1472
+ if (runtimeBlob !== null && !runtime.restoreCheckpoint) {
1473
+ throw new Error(`runtime checkpoint exists for resumed agentId=${this.agentId}, but runtime cannot restore it`);
1474
+ }
1475
+ if (runtime.restoreCheckpoint && runtimeBlob === null) {
1476
+ throw new Error(`missing runtime checkpoint for resumed agentId=${this.agentId}`);
1477
+ }
1478
+ if (runtimeBlob !== null)
1479
+ runtime.restoreCheckpoint?.(runtimeBlob);
1480
+ return { kind: "running", state: toProgressState(state) };
1481
+ }
1482
+ async saveRunningAgentLoop(state, runtime) {
1483
+ await writeAgentState(this.agentId, {
1484
+ schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1485
+ phase: "running",
1486
+ ...state
1487
+ });
1488
+ if (runtime.captureCheckpoint) {
1489
+ const blob = runtime.captureCheckpoint();
1490
+ if (blob !== undefined)
1491
+ await writeRuntimeCheckpoint(this.agentId, blob);
1492
+ }
1493
+ }
1494
+ async saveSettledAgentLoop(result) {
1495
+ await writeAgentState(this.agentId, {
1496
+ schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1497
+ phase: "settled",
1498
+ result: {
1499
+ sessionId: result.sessionId,
1500
+ lastStatus: result.lastStatus,
1501
+ iterations: result.iterations,
1502
+ ...result.response !== undefined ? { response: result.response } : {}
1503
+ }
1504
+ });
1505
+ }
1506
+ }
1507
+
1508
+ // src/pause/pause-core.ts
1509
+ import { createHash as createHash2 } from "node:crypto";
1510
+
1511
+ // src/step-invocation/types.ts
1512
+ import { z as z7 } from "zod";
1513
+ var StepPauseRequestSchema = z7.object({
1514
+ reason: z7.string().min(1),
1515
+ kind: z7.enum(["decision", "sleep", "event", "custom"]),
1516
+ payload: z7.record(z7.string(), z7.unknown()).optional(),
1517
+ ttlMs: z7.number().int().positive().optional(),
1518
+ correlationKey: z7.string().min(1).optional(),
1519
+ snapshot: z7.boolean().optional()
1520
+ });
1521
+
1522
+ class StepExecutionError extends Error {
1523
+ kind;
1524
+ exitCode;
1525
+ constructor(error, stepLabel) {
1526
+ super(`[${error.kind}] ${stepLabel}: ${error.message}`);
1527
+ this.name = "StepExecutionError";
1528
+ this.kind = error.kind;
1529
+ this.exitCode = error.kind === "runner-exit" ? error.exitCode : undefined;
1530
+ }
1531
+ }
1532
+
1533
+ // src/active-step.ts
1534
+ import { AsyncLocalStorage } from "node:async_hooks";
1535
+ var activeStepStorage = new AsyncLocalStorage;
1536
+ var fallbackActive = null;
1537
+ var BRIDGE_KEY = Symbol.for("agent-compose.activeStep.bridge");
1538
+ function bridgeSlot() {
1539
+ const g = globalThis;
1540
+ return g[BRIDGE_KEY] ??= { state: null };
1541
+ }
1542
+ function makeState(step) {
1543
+ return {
1544
+ stepIndex: step.stepIndex,
1545
+ nextAgentCallIndex: 0,
1546
+ pauseOrdinalByScope: new Map
1547
+ };
1548
+ }
1549
+ function currentState() {
1550
+ return activeStepStorage.getStore() ?? bridgeSlot().state ?? fallbackActive;
1551
+ }
1552
+ async function runWithActiveStep(step, fn) {
1553
+ return activeStepStorage.run(makeState(step), fn);
1554
+ }
1555
+ function setActiveStepBridge(step) {
1556
+ const slot = bridgeSlot();
1557
+ const prev = { state: slot.state };
1558
+ slot.state = step ? makeState(step) : null;
1559
+ return prev;
1560
+ }
1561
+ function restoreActiveStepBridge(prev) {
1562
+ bridgeSlot().state = prev.state;
1563
+ }
1564
+ function getActiveStep() {
1565
+ const state = currentState();
1566
+ return state ? { stepIndex: state.stepIndex } : null;
1567
+ }
1568
+ function nextAgentCallInActiveStep() {
1569
+ const state = currentState();
1570
+ if (!state)
1571
+ return null;
1572
+ const callIndex = state.nextAgentCallIndex;
1573
+ state.nextAgentCallIndex += 1;
1574
+ return { stepIndex: state.stepIndex, callIndex };
1575
+ }
1576
+ function nextPauseOrdinalInActiveStep(scope) {
1577
+ const state = currentState();
1578
+ if (!state)
1579
+ return null;
1580
+ const next = state.pauseOrdinalByScope.get(scope) ?? 0;
1581
+ state.pauseOrdinalByScope.set(scope, next + 1);
1582
+ return next;
1583
+ }
1584
+
1585
+ // src/pause/errors.ts
1586
+ class PauseError extends Error {
1587
+ constructor(message) {
1588
+ super(message);
1589
+ this.name = new.target.name;
1590
+ }
1591
+ }
1592
+
1593
+ class PauseExpiredError extends PauseError {
1594
+ code = "pause_expired";
1595
+ constructor(message = "pause expired before it was resolved") {
1596
+ super(message);
1597
+ }
1598
+ }
1599
+
1600
+ class PauseSchemaError extends PauseError {
1601
+ code = "pause_schema";
1602
+ issues;
1603
+ constructor(message, issues) {
1604
+ super(message);
1605
+ this.issues = issues;
1606
+ }
1607
+ }
1608
+
1609
+ class PauseRequestError extends PauseError {
1610
+ code = "pause_request";
1611
+ constructor(message = "pause request is invalid") {
1612
+ super(message);
1613
+ }
1614
+ }
1615
+
1616
+ // src/pause/pause-core.ts
1617
+ var PAUSE_SIGNAL_BRAND = Symbol.for("agent-compose.PauseSignal");
1618
+
1619
+ class PauseSignal {
1620
+ pauseId;
1621
+ pauseRequest;
1622
+ observability;
1623
+ [PAUSE_SIGNAL_BRAND] = true;
1624
+ constructor(pauseId, pauseRequest, observability) {
1625
+ this.pauseId = pauseId;
1626
+ this.pauseRequest = pauseRequest;
1627
+ this.observability = observability;
1628
+ }
1629
+ }
1630
+ function isPauseSignal(err) {
1631
+ return typeof err === "object" && err !== null && err[PAUSE_SIGNAL_BRAND] === true;
1632
+ }
1633
+ var PAUSE_NAMESPACE = "5f3e2b8a-0c41-4e9d-9a7b-6d2c1f8e3a40";
1634
+ function uuidToBytes(uuid) {
1635
+ return Buffer.from(uuid.replace(/-/g, ""), "hex");
1636
+ }
1637
+ function bytesToUuid(b) {
1638
+ const h = b.toString("hex");
1639
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
1640
+ }
1641
+ function uuidv5(name, namespace) {
1642
+ const hash = createHash2("sha1").update(uuidToBytes(namespace)).update(Buffer.from(name, "utf8")).digest();
1643
+ const bytes = Buffer.from(hash.subarray(0, 16));
1644
+ bytes[6] = bytes[6] & 15 | 80;
1645
+ bytes[8] = bytes[8] & 63 | 128;
1646
+ return bytesToUuid(bytes);
1647
+ }
1648
+ function scopeOf(coord) {
1649
+ return coord.agentScope ? `${coord.runId}:${coord.stepIndex}:${coord.agentScope.agentId}:${coord.agentScope.iteration}` : `${coord.runId}:${coord.stepIndex}:bare`;
1650
+ }
1651
+ var pauseOrdinalByScope = new Map;
1652
+ function nextPauseOrdinal(scope) {
1653
+ const activeOrdinal = nextPauseOrdinalInActiveStep(scope);
1654
+ if (activeOrdinal !== null)
1655
+ return activeOrdinal;
1656
+ const next = pauseOrdinalByScope.get(scope) ?? 0;
1657
+ pauseOrdinalByScope.set(scope, next + 1);
1658
+ return next;
1659
+ }
1660
+ function nextPauseId(coord) {
1661
+ const scope = scopeOf(coord);
1662
+ const ordinal = nextPauseOrdinal(scope);
1663
+ return uuidv5(`${scope}:${ordinal}`, PAUSE_NAMESPACE);
1664
+ }
1665
+ function nonJsonPath(value, path = "$", seen = new Set) {
1666
+ if (value === null)
1667
+ return null;
1668
+ const kind = typeof value;
1669
+ if (kind === "string" || kind === "boolean")
1670
+ return null;
1671
+ if (kind === "number")
1672
+ return Number.isFinite(value) ? null : path;
1673
+ if (kind === "undefined" || kind === "bigint" || kind === "function" || kind === "symbol")
1674
+ return path;
1675
+ if (kind !== "object")
1676
+ return path;
1677
+ const obj = value;
1678
+ if (seen.has(obj))
1679
+ return path;
1680
+ seen.add(obj);
1681
+ if (Array.isArray(value)) {
1682
+ for (let i = 0;i < value.length; i++) {
1683
+ const child = nonJsonPath(value[i], `${path}[${i}]`, seen);
1684
+ if (child)
1685
+ return child;
1686
+ }
1687
+ seen.delete(obj);
1688
+ return null;
1689
+ }
1690
+ const proto = Object.getPrototypeOf(obj);
1691
+ if (proto !== Object.prototype && proto !== null)
1692
+ return path;
1693
+ for (const key of Reflect.ownKeys(obj)) {
1694
+ if (typeof key !== "string")
1695
+ return `${path}.${String(key)}`;
1696
+ const desc = Object.getOwnPropertyDescriptor(obj, key);
1697
+ if (!desc?.enumerable)
1698
+ return `${path}.${key}`;
1699
+ const child = nonJsonPath(value[key], `${path}.${key}`, seen);
1700
+ if (child)
1701
+ return child;
1702
+ }
1703
+ seen.delete(obj);
1704
+ return null;
1705
+ }
1706
+ function toWireRequest(req, kind) {
1707
+ const wire = {
1708
+ reason: req.reason,
1709
+ kind,
1710
+ ...req.payload !== undefined ? { payload: req.payload } : {},
1711
+ ...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {},
1712
+ ...req.correlationKey !== undefined ? { correlationKey: req.correlationKey } : {},
1713
+ ...req.snapshot !== undefined ? { snapshot: req.snapshot } : {}
1714
+ };
1715
+ const parsed = StepPauseRequestSchema.safeParse(wire);
1716
+ if (!parsed.success) {
1717
+ throw new PauseRequestError(`pause request failed validation: ${parsed.error.message}`);
1718
+ }
1719
+ const badPath = nonJsonPath(parsed.data);
1720
+ if (badPath) {
1721
+ throw new PauseRequestError(`pause request must be JSON-serializable (invalid value at ${badPath})`);
1722
+ }
1723
+ return parsed.data;
1724
+ }
1725
+ async function corePause(req, coord, kind = "custom") {
1726
+ const wire = toWireRequest(req, kind);
1727
+ const pauseId = nextPauseId(coord);
1728
+ const record = await readPauseRecord(pauseId);
1729
+ if (record === null || record.resolved !== true) {
1730
+ await writePauseRecord({ pauseId, request: wire });
1731
+ throw new PauseSignal(pauseId, wire);
1732
+ }
1733
+ if (record.expired === true) {
1734
+ const onExpiry = req.onExpiry ?? { mode: "throw" };
1735
+ if (onExpiry.mode === "resolve")
1736
+ return onExpiry.value;
1737
+ throw new PauseExpiredError(`pause "${req.reason}" expired before it was resolved`);
1738
+ }
1739
+ const payload = record.resumePayload ?? null;
1740
+ if (req.schema) {
1741
+ const parsed = req.schema.safeParse(payload);
1742
+ if (!parsed.success) {
1743
+ throw new PauseSchemaError(`resume payload for pause "${req.reason}" failed schema validation: ${parsed.error.message}`, parsed.error.issues);
1744
+ }
1745
+ return parsed.data;
1746
+ }
1747
+ return payload;
1748
+ }
1749
+
1750
+ // src/agent/steer-control.ts
1751
+ import { z as z8 } from "zod";
1752
+ var SteerDecisionSchema = z8.object({
1753
+ message: z8.string().min(1),
1754
+ actor: z8.string().nullish()
1755
+ });
1756
+ var steerPending = new Map;
1757
+ function signalSteerPending(agentId, payload) {
1758
+ const queue = steerPending.get(agentId) ?? [];
1759
+ const existing = queue.findIndex((p) => p.correlationKey === payload.correlationKey);
1760
+ if (existing >= 0)
1761
+ queue[existing] = payload;
1762
+ else
1763
+ queue.push(payload);
1764
+ steerPending.set(agentId, queue);
1765
+ }
1766
+ function consumeSteerPending(agentId) {
1767
+ const queue = steerPending.get(agentId);
1768
+ if (queue === undefined || queue.length === 0)
1769
+ return null;
1770
+ const next = queue.shift();
1771
+ if (queue.length === 0)
1772
+ steerPending.delete(agentId);
1773
+ return next;
1774
+ }
1775
+ async function runControlPoller(opts) {
1776
+ const headers = { authorization: `Bearer ${opts.token}`, accept: "application/json" };
1777
+ const url = `${opts.baseUrl.replace(/\/+$/, "")}/api/v1/internal/runs/${opts.runId}/control`;
1778
+ let backoffMs = 0;
1779
+ while (!opts.signal.aborted) {
1780
+ if (backoffMs > 0) {
1781
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
1782
+ if (opts.signal.aborted)
1783
+ return;
1784
+ }
1785
+ try {
1786
+ const res = await fetch(url, { method: "GET", headers, signal: opts.signal });
1787
+ if (!res.ok) {
1788
+ if (res.status === 401 || res.status === 403)
1789
+ return;
1790
+ backoffMs = Math.min(8000, (backoffMs || 500) * 2);
1791
+ continue;
1792
+ }
1793
+ backoffMs = 0;
1794
+ const body = await res.json();
1795
+ const ctrl = body.control;
1796
+ if (ctrl && ctrl.kind === "steer_pause" && (ctrl.agentId == null || ctrl.agentId === opts.agentId)) {
1797
+ signalSteerPending(opts.agentId, {
1798
+ reason: ctrl.reason ?? null,
1799
+ correlationKey: ctrl.correlationKey ?? null
1800
+ });
1801
+ }
1802
+ } catch (err) {
1803
+ if (opts.signal.aborted)
1804
+ return;
1805
+ backoffMs = Math.min(8000, (backoffMs || 500) * 2);
1806
+ if (backoffMs >= 4000) {
1807
+ process.stderr.write(`[control-poller] poll failed, backing off ${backoffMs}ms: ${err instanceof Error ? err.message : String(err)}
1808
+ `);
1809
+ }
1810
+ }
1811
+ }
1812
+ }
1813
+
1814
+ // src/agent/agent-loop.ts
1151
1815
  var DEFAULT_CLAUDE_MODEL = "claude-opus-4-7";
1152
1816
  var SAME_BLOCKER_ITERATIONS = 3;
1153
1817
  var STALL_ITERATIONS = 3;
@@ -1183,7 +1847,7 @@ function summarizeAgentMessage(msg) {
1183
1847
  case "thinking":
1184
1848
  return { type: "thinking", text: msg.text };
1185
1849
  case "tool_use":
1186
- return { type: "tool_use", toolName: msg.toolName, toolUseId: msg.toolUseId, toolInputPreview: preview(msg.toolInput) };
1850
+ return { type: "tool_use", toolName: msg.toolName, toolUseId: msg.toolUseId, toolInput: msg.toolInput, toolInputPreview: preview(msg.toolInput) };
1187
1851
  case "tool_result":
1188
1852
  return { type: "tool_result", toolUseId: msg.toolUseId, output: truncate(msg.output), isError: msg.isError };
1189
1853
  case "usage":
@@ -1236,7 +1900,7 @@ async function agentLoop(opts) {
1236
1900
  processors,
1237
1901
  requestContext,
1238
1902
  agentId,
1239
- ...opts.responseSchema ? { outputFormat: { type: "json_schema", schema: z5.toJSONSchema(opts.responseSchema) } } : {}
1903
+ ...opts.responseSchema ? { outputFormat: { type: "json_schema", schema: z9.toJSONSchema(opts.responseSchema) } } : {}
1240
1904
  });
1241
1905
  if (processors.some((p) => p.processToolCall) && !client.supportsToolCallProcessor) {
1242
1906
  process.stderr.write(`${logLabel} processToolCall registered but runtime does not support pre-tool-use gating — hook is dormant
@@ -1249,11 +1913,101 @@ async function agentLoop(opts) {
1249
1913
  let completedIterations = 0;
1250
1914
  let lastResponseText = "";
1251
1915
  let lastResponseValidationError = "";
1252
- opts.onAgentLifecycleEvent?.({ event: "agent.spawned", at: startedAt, agentId, label, allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS });
1916
+ let startIteration = 0;
1917
+ let resumed = false;
1918
+ let pendingSteerPause = null;
1919
+ const pauseManager = new PauseManager(agentId);
1920
+ const restore = await pauseManager.restoreAgentLoop(client);
1921
+ if (restore.kind === "settled") {
1922
+ process.stdout.write(`${logLabel} restored settled agent result from checkpoint after ${restore.result.iterations} iterations
1923
+ `);
1924
+ return { agentId, label, ...restore.result };
1925
+ }
1926
+ if (restore.kind === "running") {
1927
+ const s = restore.state;
1928
+ startIteration = s.iteration;
1929
+ completedIterations = s.completedIterations;
1930
+ lastSessionId = s.lastSessionId;
1931
+ lastStatus = s.lastStatus;
1932
+ lastResponseText = s.lastResponseText;
1933
+ lastResponseValidationError = s.lastResponseValidationError;
1934
+ iterationsWithoutStatus = s.iterationsWithoutStatus;
1935
+ blockerStreak = s.blockerStreak;
1936
+ pendingSteerPause = s.pendingSteerPause ?? null;
1937
+ resumed = true;
1938
+ process.stdout.write(`${logLabel} resumed from agent-state checkpoint at iteration ${startIteration}/${maxIterations}
1939
+ `);
1940
+ }
1941
+ if (restore.kind === "invalid") {
1942
+ process.stderr.write(`${logLabel} agent-state checkpoint at agentId=${agentId} is unrecognised — starting fresh. ` + `Schema version mismatch or corrupt file: ${restore.error}
1943
+ `);
1944
+ }
1945
+ if (!resumed) {
1946
+ opts.onAgentLifecycleEvent?.({
1947
+ event: "agent.spawned",
1948
+ at: startedAt,
1949
+ agentId,
1950
+ label,
1951
+ allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS,
1952
+ ...client.model != null ? { model: client.model } : {},
1953
+ ...client.kind != null ? { runtimeKind: client.kind } : {}
1954
+ });
1955
+ }
1956
+ const flushState = async (currentIteration) => {
1957
+ await pauseManager.saveRunningAgentLoop({
1958
+ iteration: currentIteration,
1959
+ completedIterations,
1960
+ lastSessionId,
1961
+ lastStatus,
1962
+ lastResponseText,
1963
+ lastResponseValidationError,
1964
+ iterationsWithoutStatus,
1965
+ blockerStreak,
1966
+ pendingSteerPause
1967
+ }, client);
1968
+ };
1969
+ const writeSettledState = async (result) => {
1970
+ await pauseManager.saveSettledAgentLoop({
1971
+ sessionId: result.sessionId,
1972
+ lastStatus: result.lastStatus,
1973
+ iterations: result.iterations,
1974
+ ...result.response !== undefined ? { response: result.response } : {}
1975
+ });
1976
+ };
1253
1977
  try {
1254
- for (let iteration = 0;iteration < maxIterations; iteration++) {
1978
+ for (let iteration = startIteration;iteration < maxIterations || !!opts.pause && pendingSteerPause !== null; iteration++) {
1979
+ await flushState(iteration);
1980
+ let steerDecision = null;
1981
+ if (opts.pause) {
1982
+ if (pendingSteerPause === null) {
1983
+ const steer = opts.consumeSteerPending?.() ?? null;
1984
+ if (steer) {
1985
+ pendingSteerPause = {
1986
+ reason: steer.reason ?? "human steer",
1987
+ correlationKey: steer.correlationKey,
1988
+ at: Date.now()
1989
+ };
1990
+ }
1991
+ }
1992
+ if (pendingSteerPause !== null) {
1993
+ await flushState(iteration);
1994
+ steerDecision = await opts.pause({
1995
+ reason: pendingSteerPause.reason,
1996
+ ...pendingSteerPause.correlationKey !== null ? { correlationKey: pendingSteerPause.correlationKey } : {},
1997
+ schema: SteerDecisionSchema
1998
+ }, { agentId, iteration });
1999
+ pendingSteerPause = null;
2000
+ await flushState(iteration);
2001
+ }
2002
+ }
1255
2003
  const procCtx = buildProcCtx(iteration + 1);
1256
- const initialPrompt = opts.buildPrompt(lastStatus, iteration);
2004
+ let initialPrompt = opts.buildPrompt(lastStatus, iteration);
2005
+ if (steerDecision) {
2006
+ const who = steerDecision.actor ? ` from ${steerDecision.actor}` : "";
2007
+ initialPrompt = `${initialPrompt}
2008
+
2009
+ [human steer${who}]: ${steerDecision.message}`;
2010
+ }
1257
2011
  const inputVerdict = await runProcessorChain(processors, (p) => p.processInput, initialPrompt, procCtx);
1258
2012
  if (inputVerdict.kind !== "continue") {
1259
2013
  loopAbort.abort();
@@ -1263,7 +2017,12 @@ async function agentLoop(opts) {
1263
2017
  process.stdout.write(`${logLabel} iteration ${iteration + 1}/${maxIterations} · ${turnsPerIteration} turns
1264
2018
  `);
1265
2019
  let responseText = "";
1266
- for await (const rawMsg of client.sendMessage({ prompt, sessionId: iteration > 0 ? lastSessionId : undefined, iteration: iteration + 1 })) {
2020
+ for await (const rawMsg of client.sendMessage({
2021
+ prompt,
2022
+ sessionId: iteration > 0 ? lastSessionId : undefined,
2023
+ iteration: iteration + 1,
2024
+ ...opts.inbox ? { inboxStream: opts.inbox } : {}
2025
+ })) {
1267
2026
  const outputVerdict = await runProcessorChain(processors, (p) => p.processOutput, rawMsg, procCtx);
1268
2027
  if (outputVerdict.kind === "abort") {
1269
2028
  loopAbort.abort();
@@ -1287,16 +2046,23 @@ async function agentLoop(opts) {
1287
2046
  lastResponseText = responseText;
1288
2047
  let status = parseAgentStatus(responseText);
1289
2048
  const rawResponse = opts.responseSchema ? parseAgentResponse(responseText) ?? parseRawJsonResponse(responseText) : null;
2049
+ const validateResponse = (target) => {
2050
+ const parsed = opts.responseSchema.safeParse(target);
2051
+ if (!parsed.success)
2052
+ lastResponseValidationError = parsed.error.message;
2053
+ return parsed;
2054
+ };
1290
2055
  if (opts.responseSchema && rawResponse !== null) {
1291
- const parsed = opts.responseSchema.safeParse(rawResponse);
2056
+ const parsed = validateResponse(rawResponse);
1292
2057
  if (parsed.success) {
1293
2058
  const successStatus = status ?? { summary: "structured response completed", completed: [], blockers: [], exit_signal: true };
1294
2059
  process.stdout.write(`${logLabel} structured response validated after ${iteration + 1}/${maxIterations} iterations
1295
2060
  `);
2061
+ const result2 = { agentId, label, sessionId: lastSessionId, lastStatus: successStatus, iterations: iteration + 1, response: parsed.data };
2062
+ await writeSettledState(result2);
1296
2063
  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 };
2064
+ return result2;
1298
2065
  }
1299
- lastResponseValidationError = parsed.error.message;
1300
2066
  }
1301
2067
  completedIterations = iteration + 1;
1302
2068
  process.stdout.write(`${logLabel} iteration ${iteration + 1} status: exit_signal=${status?.exit_signal ?? "(no status)"} blockers=${JSON.stringify(status?.blockers ?? [])}
@@ -1314,9 +2080,8 @@ async function agentLoop(opts) {
1314
2080
  opts.onIteration?.(iteration + 1, status);
1315
2081
  continue;
1316
2082
  }
1317
- const parsed = opts.responseSchema.safeParse({ ...status, ...rawResponse });
2083
+ const parsed = validateResponse({ ...status, ...rawResponse });
1318
2084
  if (!parsed.success) {
1319
- lastResponseValidationError = parsed.error.message;
1320
2085
  process.stderr.write(`${logLabel} <response> SCHEMA FAILED: ${parsed.error.message}
1321
2086
  raw: ${JSON.stringify(rawResponse).slice(0, 400)}
1322
2087
  `);
@@ -1328,8 +2093,19 @@ raw: ${JSON.stringify(rawResponse).slice(0, 400)}
1328
2093
  }
1329
2094
  process.stdout.write(`${logLabel} done after ${iteration + 1}/${maxIterations} iterations
1330
2095
  `);
2096
+ const result2 = { agentId, label, sessionId: lastSessionId, lastStatus: status, iterations: iteration + 1, response };
2097
+ await writeSettledState(result2);
1331
2098
  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 };
2099
+ return result2;
2100
+ }
2101
+ if (opts.pause && opts.mode === "hitl" && status?.needs_input && pendingSteerPause === null) {
2102
+ pendingSteerPause = {
2103
+ reason: status.question?.trim() || "the agent requested human input",
2104
+ correlationKey: null,
2105
+ at: Date.now()
2106
+ };
2107
+ blockerStreak = null;
2108
+ continue;
1333
2109
  }
1334
2110
  if (!status) {
1335
2111
  if (++iterationsWithoutStatus >= STALL_ITERATIONS)
@@ -1356,9 +2132,13 @@ raw: ${JSON.stringify(rawResponse).slice(0, 400)}
1356
2132
  throw new Error(`${logLabel} did not produce a valid <response> after ${maxIterations} iterations${lastResponseValidationError ? `: ${lastResponseValidationError}` : ""}. Response tail: ${lastResponseText.slice(-600)}`);
1357
2133
  process.stderr.write(`${logLabel} exhausted ${maxIterations} iterations, proceeding with available work
1358
2134
  `);
2135
+ const result = { agentId, label, sessionId: lastSessionId, lastStatus, iterations: maxIterations };
2136
+ await writeSettledState(result);
1359
2137
  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 };
2138
+ return result;
1361
2139
  } catch (err) {
2140
+ if (isPauseSignal(err))
2141
+ throw err;
1362
2142
  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
2143
  throw err;
1364
2144
  }
@@ -1374,6 +2154,50 @@ function parseRawJsonResponse(text) {
1374
2154
  }
1375
2155
  }
1376
2156
 
2157
+ // src/agent/async-queue.ts
2158
+ class AsyncQueue {
2159
+ buffer = [];
2160
+ resolvers = [];
2161
+ done = false;
2162
+ push(value) {
2163
+ if (this.done)
2164
+ return;
2165
+ const resolve = this.resolvers.shift();
2166
+ if (resolve) {
2167
+ resolve({ value, done: false });
2168
+ } else {
2169
+ this.buffer.push(value);
2170
+ }
2171
+ }
2172
+ close() {
2173
+ if (this.done)
2174
+ return;
2175
+ this.done = true;
2176
+ while (this.resolvers.length > 0) {
2177
+ const resolve = this.resolvers.shift();
2178
+ resolve({ value: undefined, done: true });
2179
+ }
2180
+ }
2181
+ [Symbol.asyncIterator]() {
2182
+ return {
2183
+ next: () => {
2184
+ const buffered = this.buffer.shift();
2185
+ if (buffered !== undefined)
2186
+ return Promise.resolve({ value: buffered, done: false });
2187
+ if (this.done)
2188
+ return Promise.resolve({ value: undefined, done: true });
2189
+ return new Promise((resolve) => {
2190
+ this.resolvers.push(resolve);
2191
+ });
2192
+ },
2193
+ return: () => {
2194
+ this.close();
2195
+ return Promise.resolve({ value: undefined, done: true });
2196
+ }
2197
+ };
2198
+ }
2199
+ }
2200
+
1377
2201
  // src/runtimes/claude.ts
1378
2202
  function now2() {
1379
2203
  return new Date().toISOString();
@@ -1393,6 +2217,8 @@ function translateMessage(message) {
1393
2217
  msgs.push({ type: "text", text: String(b.text ?? ""), timestamp: ts });
1394
2218
  if (b.type === "thinking")
1395
2219
  msgs.push({ type: "thinking", text: String(b.thinking ?? ""), timestamp: ts });
2220
+ if (b.type === "redacted_thinking")
2221
+ msgs.push({ type: "thinking", text: "(redacted)", timestamp: ts });
1396
2222
  if (b.type === "tool_use")
1397
2223
  msgs.push({ type: "tool_use", toolName: String(b.name ?? ""), toolInput: b.input ?? {}, toolUseId: String(b.id ?? ""), timestamp: ts });
1398
2224
  }
@@ -1431,15 +2257,20 @@ function translateMessage(message) {
1431
2257
  }
1432
2258
  return msgs;
1433
2259
  }
2260
+ var DEFAULT_CLAUDE_PATH = "/usr/local/bin/claude";
1434
2261
 
1435
2262
  class ClaudeRunner {
1436
2263
  options;
1437
2264
  config;
1438
2265
  supportsToolCallProcessor = true;
2266
+ kind = "claude";
1439
2267
  constructor(_sandbox, options = {}, config = {}) {
1440
2268
  this.options = options;
1441
2269
  this.config = config;
1442
2270
  }
2271
+ get model() {
2272
+ return this.config.model ?? this.options.model ?? DEFAULT_CLAUDE_MODEL;
2273
+ }
1443
2274
  async gateToolCall(call, ctx) {
1444
2275
  const verdict = await runProcessorChain(this.options.processors ?? [], (p) => p.processToolCall, call, ctx);
1445
2276
  if (verdict.kind === "continue")
@@ -1475,10 +2306,33 @@ class ClaudeRunner {
1475
2306
  return { hookSpecificOutput: { hookEventName: pre.hook_event_name, permissionDecision: "deny", permissionDecisionReason: gate.reason } };
1476
2307
  return { continue: false, systemMessage: gate.reason };
1477
2308
  };
2309
+ const inboxQueue = opts.inboxStream ? new AsyncQueue : null;
2310
+ if (inboxQueue) {
2311
+ inboxQueue.push({
2312
+ type: "user",
2313
+ message: { role: "user", content: opts.prompt },
2314
+ parent_tool_use_id: null
2315
+ });
2316
+ (async () => {
2317
+ try {
2318
+ for await (const ev of opts.inboxStream) {
2319
+ const tag = ev.senderName ? `[message from ${ev.senderName}]
2320
+ ` : `[user message]
2321
+ `;
2322
+ inboxQueue.push({
2323
+ type: "user",
2324
+ message: { role: "user", content: tag + ev.text },
2325
+ parent_tool_use_id: null
2326
+ });
2327
+ }
2328
+ } catch {}
2329
+ })();
2330
+ }
2331
+ const promptForQuery = inboxQueue ?? opts.prompt;
1478
2332
  try {
1479
2333
  let emittedAssistantText = false;
1480
2334
  for await (const message of query({
1481
- prompt: opts.prompt,
2335
+ prompt: promptForQuery,
1482
2336
  options: {
1483
2337
  tools: this.options.allowedTools,
1484
2338
  allowedTools: this.options.allowedTools,
@@ -1489,7 +2343,7 @@ class ClaudeRunner {
1489
2343
  effort: this.config.effort,
1490
2344
  cwd: this.options.cwd,
1491
2345
  env: { ...process.env, ...this.config.env ?? {} },
1492
- pathToClaudeCodeExecutable: this.config.pathToClaudeCodeExecutable ?? process.env.CLAUDE_CODE_EXECUTABLE ?? "claude",
2346
+ pathToClaudeCodeExecutable: this.config.pathToClaudeCodeExecutable ?? process.env.CLAUDE_CODE_EXECUTABLE ?? DEFAULT_CLAUDE_PATH,
1493
2347
  ...this.config.claudeMdContent ? { systemPrompt: { type: "preset", preset: "claude_code", append: this.config.claudeMdContent } } : {},
1494
2348
  resume: opts.sessionId,
1495
2349
  mcpServers: this.config.mcpServers,
@@ -1506,9 +2360,13 @@ class ClaudeRunner {
1506
2360
  emittedAssistantText = true;
1507
2361
  yield msg;
1508
2362
  }
2363
+ if (raw.type === "result" && inboxQueue)
2364
+ inboxQueue.close();
1509
2365
  }
1510
2366
  } catch (err) {
1511
2367
  yield { type: "error", text: formatError(err), timestamp: now2() };
2368
+ } finally {
2369
+ inboxQueue?.close();
1512
2370
  }
1513
2371
  }
1514
2372
  }
@@ -1518,8 +2376,241 @@ function createClaudeRuntime(config = {}) {
1518
2376
  });
1519
2377
  }
1520
2378
  var claude_default = createClaudeRuntime();
2379
+ // src/runtimes/_cli-agent.ts
2380
+ function now3() {
2381
+ return new Date().toISOString();
2382
+ }
2383
+ function shellQuote(value) {
2384
+ return `'${value.replace(/'/g, `'\\''`)}'`;
2385
+ }
2386
+
2387
+ class CliAgentRunner {
2388
+ sandbox;
2389
+ options;
2390
+ spec;
2391
+ configModel;
2392
+ kind;
2393
+ constructor(sandbox, options, spec, configModel) {
2394
+ this.sandbox = sandbox;
2395
+ this.options = options;
2396
+ this.spec = spec;
2397
+ this.configModel = configModel;
2398
+ this.kind = spec.kind;
2399
+ }
2400
+ get model() {
2401
+ return this.configModel ?? this.options.model ?? this.spec.defaultModel;
2402
+ }
2403
+ async* sendMessage(opts) {
2404
+ const promptPath = `/tmp/ac-${this.spec.kind}-${this.options.agentId ?? "agent"}-${opts.iteration ?? 0}.in`;
2405
+ await this.sandbox.files.write(promptPath, this.spec.promptPayload(opts.prompt));
2406
+ const cmd = this.spec.buildCommand({
2407
+ promptPath,
2408
+ sessionId: opts.sessionId,
2409
+ model: this.model,
2410
+ cwd: this.options.cwd
2411
+ });
2412
+ const lines = new AsyncQueue;
2413
+ let buf = "";
2414
+ const onStdout = (data) => {
2415
+ buf += data;
2416
+ let nl;
2417
+ while ((nl = buf.indexOf(`
2418
+ `)) >= 0) {
2419
+ const line = buf.slice(0, nl).trim();
2420
+ buf = buf.slice(nl + 1);
2421
+ if (line)
2422
+ lines.push(line);
2423
+ }
2424
+ };
2425
+ const runPromise = this.sandbox.commands.run(cmd, {
2426
+ ...this.options.cwd ? { cwd: this.options.cwd } : {},
2427
+ onStdout
2428
+ }).then((res) => {
2429
+ const tail = buf.trim();
2430
+ if (tail)
2431
+ lines.push(tail);
2432
+ lines.close();
2433
+ return res;
2434
+ }, (err) => {
2435
+ lines.close();
2436
+ throw err;
2437
+ });
2438
+ yield { type: "init", sessionId: opts.sessionId ?? "", timestamp: now3() };
2439
+ let sessionId = opts.sessionId;
2440
+ let sawError = false;
2441
+ try {
2442
+ for await (const line of lines) {
2443
+ let parsed;
2444
+ try {
2445
+ parsed = JSON.parse(line);
2446
+ } catch {
2447
+ continue;
2448
+ }
2449
+ const sid = this.spec.extractSessionId(parsed);
2450
+ if (sid)
2451
+ sessionId = sid;
2452
+ for (const msg of this.spec.mapEvent(parsed)) {
2453
+ if (msg.type === "error")
2454
+ sawError = true;
2455
+ yield msg;
2456
+ }
2457
+ }
2458
+ const res = await runPromise;
2459
+ if (res.exitCode !== 0 && !sawError) {
2460
+ const tail = (res.stderr ?? "").slice(-2000);
2461
+ yield { type: "error", text: `${this.spec.kind} exited with code ${res.exitCode}${tail ? `: ${tail}` : ""}`, timestamp: now3() };
2462
+ return;
2463
+ }
2464
+ if (!sawError)
2465
+ yield { type: "done", sessionId: sessionId ?? "", timestamp: now3() };
2466
+ } catch (err) {
2467
+ yield { type: "error", text: formatError(err), timestamp: now3() };
2468
+ }
2469
+ }
2470
+ }
2471
+ function createCliAgentRuntime(spec, configModel) {
2472
+ return defineRuntime({
2473
+ create: (sandbox, opts) => new CliAgentRunner(sandbox, opts, spec, configModel)
2474
+ });
2475
+ }
2476
+
2477
+ // src/runtimes/codex.ts
2478
+ function now4() {
2479
+ return new Date().toISOString();
2480
+ }
2481
+ var codexSpec = {
2482
+ kind: "codex",
2483
+ authEnv: "CODEX_API_KEY",
2484
+ promptPayload: (prompt) => prompt,
2485
+ buildCommand: ({ promptPath, sessionId, model, cwd }) => {
2486
+ const flags = [
2487
+ "--json",
2488
+ "--skip-git-repo-check",
2489
+ "--dangerously-bypass-approvals-and-sandbox",
2490
+ ...model ? ["-m", shellQuote(model)] : [],
2491
+ ...cwd ? ["-C", shellQuote(cwd)] : []
2492
+ ].join(" ");
2493
+ const exec = sessionId ? `codex exec resume ${shellQuote(sessionId)} ${flags}` : `codex exec ${flags}`;
2494
+ return `${exec} - < ${shellQuote(promptPath)}`;
2495
+ },
2496
+ extractSessionId: (p) => p.type === "thread.started" && typeof p.thread_id === "string" ? p.thread_id : undefined,
2497
+ mapEvent: (p) => {
2498
+ const ts = now4();
2499
+ switch (p.type) {
2500
+ case "item.started":
2501
+ case "item.completed": {
2502
+ const item = p.item;
2503
+ if (!item)
2504
+ return [];
2505
+ const itype = String(item.type ?? "");
2506
+ if (itype === "agent_message") {
2507
+ return p.type === "item.completed" ? [{ type: "text", text: String(item.text ?? ""), timestamp: ts }] : [];
2508
+ }
2509
+ if (itype === "reasoning") {
2510
+ return p.type === "item.completed" ? [{ type: "thinking", text: String(item.text ?? ""), timestamp: ts }] : [];
2511
+ }
2512
+ if (itype === "command_execution") {
2513
+ const id = String(item.id ?? "");
2514
+ if (p.type === "item.started") {
2515
+ return [{ type: "tool_use", toolName: "shell", toolInput: { command: String(item.command ?? "") }, toolUseId: id, timestamp: ts }];
2516
+ }
2517
+ const failed = item.status === "failed" || typeof item.exit_code === "number" && item.exit_code !== 0;
2518
+ return [{ type: "tool_result", toolUseId: id, output: String(item.aggregated_output ?? item.output ?? ""), isError: failed, timestamp: ts }];
2519
+ }
2520
+ if (p.type === "item.completed") {
2521
+ return [{ type: "tool_use", toolName: itype || "item", toolInput: item, toolUseId: String(item.id ?? ""), timestamp: ts }];
2522
+ }
2523
+ return [];
2524
+ }
2525
+ case "turn.completed": {
2526
+ const u = p.usage;
2527
+ if (!u)
2528
+ return [];
2529
+ return [{
2530
+ type: "usage",
2531
+ inputTokens: u.input_tokens ?? 0,
2532
+ outputTokens: u.output_tokens ?? 0,
2533
+ cacheReadTokens: u.cached_input_tokens ?? 0,
2534
+ cacheCreationTokens: 0,
2535
+ durationMs: 0,
2536
+ numTurns: 1,
2537
+ timestamp: ts
2538
+ }];
2539
+ }
2540
+ case "turn.failed":
2541
+ case "error":
2542
+ return [{ type: "error", text: formatError(p.error ?? p.message ?? p), timestamp: ts }];
2543
+ default:
2544
+ return [];
2545
+ }
2546
+ }
2547
+ };
2548
+ function createCodexRuntime(config = {}) {
2549
+ return createCliAgentRuntime(codexSpec, config.model);
2550
+ }
2551
+ var codex_default = createCodexRuntime();
2552
+ // src/runtimes/amp.ts
2553
+ function now5() {
2554
+ return new Date().toISOString();
2555
+ }
2556
+ function blocks(msg) {
2557
+ const inner = msg.message?.content ?? msg.content ?? [];
2558
+ return inner;
2559
+ }
2560
+ var ampSpec = {
2561
+ kind: "amp",
2562
+ authEnv: "AMP_API_KEY",
2563
+ promptPayload: (prompt) => JSON.stringify({ type: "user", message: { role: "user", content: [{ type: "text", text: prompt }] } }) + `
2564
+ `,
2565
+ buildCommand: ({ promptPath, sessionId }) => {
2566
+ const cont = sessionId ? `threads continue ${shellQuote(sessionId)} ` : "";
2567
+ return `amp ${cont}-x --stream-json --stream-json-input < ${shellQuote(promptPath)}`;
2568
+ },
2569
+ extractSessionId: (p) => typeof p.session_id === "string" ? p.session_id : undefined,
2570
+ mapEvent: (p) => {
2571
+ const ts = now5();
2572
+ if (p.type === "assistant") {
2573
+ return blocks(p).flatMap((b) => {
2574
+ if (b.type === "text")
2575
+ return [{ type: "text", text: String(b.text ?? ""), timestamp: ts }];
2576
+ if (b.type === "thinking")
2577
+ return [{ type: "thinking", text: String(b.thinking ?? ""), timestamp: ts }];
2578
+ if (b.type === "tool_use")
2579
+ return [{ type: "tool_use", toolName: String(b.name ?? ""), toolInput: b.input ?? {}, toolUseId: String(b.id ?? ""), timestamp: ts }];
2580
+ return [];
2581
+ });
2582
+ }
2583
+ if (p.type === "user") {
2584
+ return blocks(p).flatMap((b) => b.type === "tool_result" ? [{ type: "tool_result", toolUseId: String(b.tool_use_id ?? ""), output: typeof b.content === "string" ? b.content : JSON.stringify(b.content ?? ""), isError: Boolean(b.is_error), timestamp: ts }] : []);
2585
+ }
2586
+ if (p.type === "result") {
2587
+ const out = [];
2588
+ const u = p.usage;
2589
+ if (u)
2590
+ out.push({
2591
+ type: "usage",
2592
+ inputTokens: u.input_tokens ?? 0,
2593
+ outputTokens: u.output_tokens ?? 0,
2594
+ cacheReadTokens: u.cache_read_input_tokens ?? 0,
2595
+ cacheCreationTokens: u.cache_creation_input_tokens ?? 0,
2596
+ durationMs: Number(p.duration_ms ?? 0),
2597
+ numTurns: Number(p.num_turns ?? 0),
2598
+ timestamp: ts
2599
+ });
2600
+ if (p.is_error || p.subtype === "error") {
2601
+ out.push({ type: "error", text: formatError(p.result ?? p.error), timestamp: ts });
2602
+ }
2603
+ return out;
2604
+ }
2605
+ return [];
2606
+ }
2607
+ };
2608
+ function createAmpRuntime(config = {}) {
2609
+ return createCliAgentRuntime(ampSpec, config.model);
2610
+ }
2611
+ var amp_default = createAmpRuntime();
1521
2612
  // src/sandbox.ts
1522
- import { promises as fs } from "node:fs";
2613
+ import { promises as fs2 } from "node:fs";
1523
2614
  import { dirname as dirname2 } from "node:path";
1524
2615
  import { spawn } from "node:child_process";
1525
2616
  import { Sandbox } from "e2b";
@@ -1528,9 +2619,26 @@ import pRetry from "p-retry";
1528
2619
  var AGENT_COMPOSE_TAG = process.env.AGENT_COMPOSE_TAG ?? `agent-compose-${process.env.AGENT_COMPOSE_ENV ?? "dev"}`;
1529
2620
  var VERCEL_VM_LIFETIME_WINDOW_MS = 6 * 60 * 60 * 1000;
1530
2621
  function makeSandboxProvider(sb) {
2622
+ const commands = sb.commands;
1531
2623
  return {
1532
2624
  sandboxId: sb.sandboxId,
1533
- commands: sb.commands,
2625
+ commands: {
2626
+ async run(cmd, opts) {
2627
+ let stderr = "";
2628
+ const result = await commands.run(cmd, {
2629
+ ...opts,
2630
+ onStderr: (chunk) => {
2631
+ stderr += chunk;
2632
+ opts?.onStderr?.(chunk);
2633
+ }
2634
+ });
2635
+ return {
2636
+ exitCode: result.exitCode,
2637
+ stdout: result.stdout,
2638
+ stderr: typeof result.stderr === "string" ? result.stderr : stderr
2639
+ };
2640
+ }
2641
+ },
1534
2642
  files: {
1535
2643
  async write(path, content) {
1536
2644
  await sb.files.write(path, content);
@@ -1555,7 +2663,7 @@ function makeDesktopSandboxProvider(sb) {
1555
2663
  };
1556
2664
  }
1557
2665
  async function parseSseExecStream(body, opts) {
1558
- let stdout = "", exitCode = 0, exited = false;
2666
+ let stdout = "", stderr = "", exitCode = 0, exited = false;
1559
2667
  const reader = body.getReader(), decoder = new TextDecoder;
1560
2668
  let buf = "";
1561
2669
  while (true) {
@@ -1573,13 +2681,14 @@ async function parseSseExecStream(body, opts) {
1573
2681
  let event;
1574
2682
  try {
1575
2683
  event = JSON.parse(line.slice(6));
1576
- } catch {
1577
- continue;
2684
+ } catch (err) {
2685
+ throw new Error(`Malformed sandbox exec event: ${err instanceof Error ? err.message : String(err)}`);
1578
2686
  }
1579
2687
  if (event.type === "stdout") {
1580
2688
  stdout += event.data ?? "";
1581
2689
  opts?.onStdout?.(event.data ?? "");
1582
2690
  } else if (event.type === "stderr") {
2691
+ stderr += event.data ?? "";
1583
2692
  opts?.onStderr?.(event.data ?? "");
1584
2693
  } else if (event.type === "exit") {
1585
2694
  exitCode = event.exitCode ?? 0;
@@ -1591,7 +2700,9 @@ async function parseSseExecStream(body, opts) {
1591
2700
  if (exited)
1592
2701
  break;
1593
2702
  }
1594
- return { exitCode, stdout };
2703
+ if (!exited)
2704
+ throw new Error("Sandbox exec stream ended without an exit event");
2705
+ return { exitCode, stdout, stderr };
1595
2706
  }
1596
2707
  function makeVercelSandboxProvider(sb, globalEnvs) {
1597
2708
  const mergeEnvs = (cmdEnvs) => globalEnvs ? { ...globalEnvs, ...cmdEnvs } : cmdEnvs;
@@ -1599,27 +2710,28 @@ function makeVercelSandboxProvider(sb, globalEnvs) {
1599
2710
  sandboxId: sb.sandboxId,
1600
2711
  commands: {
1601
2712
  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
2713
  const signal = opts?.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined;
1608
2714
  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 } : {} });
2715
+ let stderr = "";
2716
+ 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
2717
  await pRetry(async (attempt) => {
1611
2718
  const h = attempt === 1 ? handle : await sb.getCommand(handle.cmdId);
2719
+ if (attempt > 1) {
2720
+ stdout = "";
2721
+ stderr = "";
2722
+ }
1612
2723
  for await (const log of h.logs()) {
1613
2724
  if (log.stream === "stdout") {
1614
2725
  stdout += log.data;
1615
2726
  opts?.onStdout?.(log.data);
1616
2727
  } else {
2728
+ stderr += log.data;
1617
2729
  opts?.onStderr?.(log.data);
1618
2730
  }
1619
2731
  }
1620
2732
  }, { retries: 3, minTimeout: 1000, factor: 2 });
1621
2733
  const finished = await handle.wait();
1622
- return { exitCode: finished.exitCode, stdout };
2734
+ return { exitCode: finished.exitCode, stdout, stderr };
1623
2735
  }
1624
2736
  },
1625
2737
  files: {
@@ -1653,6 +2765,7 @@ function makeLocalSandboxProvider() {
1653
2765
  stdio: ["ignore", "pipe", "pipe"]
1654
2766
  });
1655
2767
  let stdout = "";
2768
+ let stderr = "";
1656
2769
  proc.stdout?.setEncoding("utf8");
1657
2770
  proc.stderr?.setEncoding("utf8");
1658
2771
  proc.stdout?.on("data", (chunk) => {
@@ -1660,17 +2773,18 @@ function makeLocalSandboxProvider() {
1660
2773
  opts?.onStdout?.(chunk);
1661
2774
  });
1662
2775
  proc.stderr?.on("data", (chunk) => {
2776
+ stderr += chunk;
1663
2777
  opts?.onStderr?.(chunk);
1664
2778
  });
1665
2779
  proc.on("error", reject);
1666
- proc.on("close", (code) => resolve({ exitCode: code ?? 0, stdout }));
2780
+ proc.on("close", (code) => resolve({ exitCode: code ?? 0, stdout, stderr }));
1667
2781
  });
1668
2782
  }
1669
2783
  },
1670
2784
  files: {
1671
2785
  async write(path, content) {
1672
- await fs.mkdir(dirname2(path), { recursive: true });
1673
- await fs.writeFile(path, content);
2786
+ await fs2.mkdir(dirname2(path), { recursive: true });
2787
+ await fs2.writeFile(path, content);
1674
2788
  }
1675
2789
  },
1676
2790
  async kill() {}
@@ -1845,10 +2959,18 @@ async function killAllSandboxes(onError) {
1845
2959
  }));
1846
2960
  }
1847
2961
  // src/workflow-steps/observability.ts
2962
+ var LIVE_EMIT_DRAIN_TIMEOUT_MS = 1500;
2963
+
1848
2964
  class StepObservabilityCollector {
1849
2965
  metadata = {};
1850
2966
  events = [];
1851
2967
  subSteps = [];
2968
+ liveEmitter;
2969
+ ackedSeqs = new Set;
2970
+ inFlight = new Set;
2971
+ constructor(opts = {}) {
2972
+ this.liveEmitter = opts.liveEmitter;
2973
+ }
1852
2974
  setMetadata = async (data) => {
1853
2975
  Object.assign(this.metadata, data);
1854
2976
  };
@@ -1864,6 +2986,8 @@ class StepObservabilityCollector {
1864
2986
  });
1865
2987
  return result;
1866
2988
  } catch (err) {
2989
+ if (isPauseSignal(err))
2990
+ throw err;
1867
2991
  this.subSteps.push({
1868
2992
  name,
1869
2993
  startedAt,
@@ -1876,26 +3000,92 @@ class StepObservabilityCollector {
1876
3000
  };
1877
3001
  agentEvents = {
1878
3002
  emit: (event) => {
3003
+ const seq = this.events.length;
1879
3004
  this.events.push(event);
3005
+ if (this.liveEmitter) {
3006
+ const tracked = this.liveEmitter(event, seq).then((ok) => {
3007
+ if (ok)
3008
+ this.ackedSeqs.add(seq);
3009
+ }).catch(() => {});
3010
+ this.inFlight.add(tracked);
3011
+ tracked.finally(() => this.inFlight.delete(tracked));
3012
+ }
1880
3013
  }
1881
3014
  };
1882
- snapshot() {
3015
+ async drainInFlight(timeoutMs = LIVE_EMIT_DRAIN_TIMEOUT_MS) {
3016
+ if (this.inFlight.size === 0)
3017
+ return;
3018
+ await Promise.race([
3019
+ Promise.allSettled([...this.inFlight]),
3020
+ new Promise((resolve) => setTimeout(resolve, timeoutMs))
3021
+ ]);
3022
+ }
3023
+ async snapshot() {
3024
+ await this.drainInFlight();
1883
3025
  const hasMetadata = Object.keys(this.metadata).length > 0;
1884
- const hasEvents = this.events.length > 0;
1885
3026
  const hasSubSteps = this.subSteps.length > 0;
3027
+ const remainingEvents = this.events.filter((_, idx) => !this.ackedSeqs.has(idx));
3028
+ const hasEvents = remainingEvents.length > 0;
1886
3029
  if (!hasMetadata && !hasEvents && !hasSubSteps)
1887
3030
  return;
1888
3031
  const result = {};
1889
3032
  if (hasMetadata)
1890
3033
  result.metadata = { ...this.metadata };
1891
3034
  if (hasEvents)
1892
- result.events = [...this.events];
3035
+ result.events = remainingEvents;
1893
3036
  if (hasSubSteps)
1894
3037
  result.subSteps = [...this.subSteps];
1895
3038
  return result;
1896
3039
  }
1897
3040
  }
1898
3041
 
3042
+ // src/pause/checkpoint.ts
3043
+ async function checkpoint(name, fn) {
3044
+ const existing = await readCheckpoint(name);
3045
+ if (existing.found)
3046
+ return existing.value;
3047
+ const value = await fn();
3048
+ await writeCheckpoint(name, value);
3049
+ return value;
3050
+ }
3051
+ function scopedCheckpoint(scope) {
3052
+ assertSafeCheckpointName(scope);
3053
+ return async (name, fn) => {
3054
+ assertSafeCheckpointName(name);
3055
+ return checkpoint(`${scope}.${name}`, fn);
3056
+ };
3057
+ }
3058
+
3059
+ // src/pause/wrappers.ts
3060
+ function buildPauseWrappers(pause) {
3061
+ return {
3062
+ requestDecision(req) {
3063
+ return pause({
3064
+ reason: req.reason,
3065
+ schema: req.schema,
3066
+ ...req.payload !== undefined ? { payload: req.payload } : {},
3067
+ ...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {}
3068
+ }, "decision");
3069
+ },
3070
+ sleep(durationMs) {
3071
+ return pause({
3072
+ reason: `sleep ${durationMs}ms`,
3073
+ ttlMs: durationMs,
3074
+ snapshot: false,
3075
+ onExpiry: { mode: "resolve", value: undefined }
3076
+ }, "sleep");
3077
+ },
3078
+ waitForEvent(req) {
3079
+ return pause({
3080
+ reason: req.reason,
3081
+ correlationKey: req.correlationKey,
3082
+ ...req.schema !== undefined ? { schema: req.schema } : {},
3083
+ ...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {}
3084
+ }, "event");
3085
+ }
3086
+ };
3087
+ }
3088
+
1899
3089
  // src/workflow-steps/runner.ts
1900
3090
  class StepValidationError extends Error {
1901
3091
  stepName;
@@ -1938,7 +3128,14 @@ async function runWorkflowSingleStep(opts) {
1938
3128
  const parsedInput = step.input.safeParse(opts.input);
1939
3129
  if (!parsedInput.success)
1940
3130
  throw new StepValidationError(step.name, "input", parsedInput.error);
1941
- const collector = new StepObservabilityCollector;
3131
+ const collector = new StepObservabilityCollector({ liveEmitter: opts.liveAgentEventEmitter });
3132
+ const coord = { runId: opts.run.id, stepIndex: opts.stepIndex };
3133
+ const pauseWithKind = function(req, kind) {
3134
+ return corePause(req, coord, kind);
3135
+ };
3136
+ const pause = function(req) {
3137
+ return corePause(req, coord);
3138
+ };
1942
3139
  const stepCtx = {
1943
3140
  input: parsedInput.data,
1944
3141
  requestContext: opts.requestContext,
@@ -1951,13 +3148,30 @@ async function runWorkflowSingleStep(opts) {
1951
3148
  }),
1952
3149
  setMetadata: collector.setMetadata,
1953
3150
  step: collector.step,
1954
- agentEvents: collector.agentEvents
3151
+ agentEvents: collector.agentEvents,
3152
+ checkpoint: scopedCheckpoint(`step${opts.stepIndex}`),
3153
+ pause,
3154
+ ...buildPauseWrappers(pauseWithKind)
1955
3155
  };
1956
- const output = await step.run(stepCtx);
3156
+ let output;
3157
+ try {
3158
+ const bridged = setActiveStepBridge({ stepIndex: opts.stepIndex });
3159
+ try {
3160
+ output = await runWithActiveStep({ stepIndex: opts.stepIndex }, () => Promise.resolve(step.run(stepCtx)));
3161
+ } finally {
3162
+ restoreActiveStepBridge(bridged);
3163
+ }
3164
+ } catch (err) {
3165
+ if (isPauseSignal(err)) {
3166
+ const observability2 = await collector.snapshot();
3167
+ throw new PauseSignal(err.pauseId, err.pauseRequest, observability2);
3168
+ }
3169
+ throw err;
3170
+ }
1957
3171
  const parsedOutput = step.output.safeParse(output);
1958
3172
  if (!parsedOutput.success)
1959
3173
  throw new StepValidationError(step.name, "output", parsedOutput.error);
1960
- const observability = collector.snapshot();
3174
+ const observability = await collector.snapshot();
1961
3175
  return observability === undefined ? { output: parsedOutput.data } : { output: parsedOutput.data, observability };
1962
3176
  }
1963
3177
  async function runWorkflowSteps(opts) {
@@ -1991,7 +3205,14 @@ async function runWorkflowSteps(opts) {
1991
3205
  await opts.onStepFailed?.(i, step.name, err, 0);
1992
3206
  throw err;
1993
3207
  }
1994
- const collector = new StepObservabilityCollector;
3208
+ const collector = new StepObservabilityCollector({ liveEmitter: opts.liveAgentEventEmitter });
3209
+ const coord = { runId: run.id, stepIndex: i };
3210
+ const pauseWithKind = function(req, kind) {
3211
+ return corePause(req, coord, kind);
3212
+ };
3213
+ const pause = function(req) {
3214
+ return corePause(req, coord);
3215
+ };
1995
3216
  const stepCtx = {
1996
3217
  input: parsedStepInput.data,
1997
3218
  requestContext,
@@ -2002,16 +3223,23 @@ async function runWorkflowSteps(opts) {
2002
3223
  invokeChild,
2003
3224
  setMetadata: collector.setMetadata,
2004
3225
  step: collector.step,
2005
- agentEvents: collector.agentEvents
3226
+ agentEvents: collector.agentEvents,
3227
+ checkpoint: scopedCheckpoint(`step${i}`),
3228
+ pause,
3229
+ ...buildPauseWrappers(pauseWithKind)
2006
3230
  };
2007
3231
  const startedAt = Date.now();
2008
3232
  let output;
2009
3233
  try {
2010
- output = await step.run(stepCtx);
3234
+ output = await runWithActiveStep({ stepIndex: i }, () => Promise.resolve(step.run(stepCtx)));
2011
3235
  } catch (err) {
3236
+ if (isPauseSignal(err)) {
3237
+ const observability3 = await collector.snapshot();
3238
+ throw new PauseSignal(err.pauseId, err.pauseRequest, observability3);
3239
+ }
2012
3240
  const wrapped = err instanceof Error ? err : new Error(String(err));
2013
3241
  const durationMs2 = Date.now() - startedAt;
2014
- const observability2 = collector.snapshot();
3242
+ const observability2 = await collector.snapshot();
2015
3243
  stepResults.push({
2016
3244
  name: step.name,
2017
3245
  status: "failed",
@@ -2026,7 +3254,7 @@ async function runWorkflowSteps(opts) {
2026
3254
  if (!parsedOutput.success) {
2027
3255
  const err = new StepValidationError(step.name, "output", parsedOutput.error);
2028
3256
  const durationMs2 = Date.now() - startedAt;
2029
- const observability2 = collector.snapshot();
3257
+ const observability2 = await collector.snapshot();
2030
3258
  stepResults.push({
2031
3259
  name: step.name,
2032
3260
  status: "failed",
@@ -2039,7 +3267,7 @@ async function runWorkflowSteps(opts) {
2039
3267
  }
2040
3268
  const durationMs = Date.now() - startedAt;
2041
3269
  current = parsedOutput.data;
2042
- const observability = collector.snapshot();
3270
+ const observability = await collector.snapshot();
2043
3271
  stepResults.push({
2044
3272
  name: step.name,
2045
3273
  status: "completed",
@@ -2164,9 +3392,13 @@ function defineStep(opts) {
2164
3392
  }
2165
3393
  // src/step-invocation/protocol.ts
2166
3394
  var STEP_RESULT_PREFIX = "__AC_STEP_RESULT__";
3395
+ var STEP_PAUSE_PREFIX = "__AC_STEP_PAUSE__";
2167
3396
  function stepResultLinePrefix(token) {
2168
3397
  return `${STEP_RESULT_PREFIX}${token}:`;
2169
3398
  }
3399
+ function stepPauseLinePrefix(token) {
3400
+ return `${STEP_PAUSE_PREFIX}${token}:`;
3401
+ }
2170
3402
  var RUNNER_BUNDLE_PATH = "/tmp/runner.bundle.js";
2171
3403
  var RUNNER_COMMAND = `node ${RUNNER_BUNDLE_PATH}`;
2172
3404
  var STEP_ENV = {
@@ -2175,7 +3407,8 @@ var STEP_ENV = {
2175
3407
  STEP_INDEX: "AC_STEP_INDEX",
2176
3408
  STEP_INPUT_PATH: "AC_STEP_INPUT_PATH",
2177
3409
  REQUEST_CONTEXT_PATH: "AC_REQUEST_CONTEXT_PATH",
2178
- STEP_RESULT_TOKEN: "AC_STEP_RESULT_TOKEN"
3410
+ STEP_RESULT_TOKEN: "AC_STEP_RESULT_TOKEN",
3411
+ STEP_RESUME: "AC_STEP_RESUME"
2179
3412
  };
2180
3413
  function stepInputPath(stepIndex) {
2181
3414
  return `/tmp/wf/step-input-${stepIndex}.json`;
@@ -2184,7 +3417,36 @@ function requestContextPath(stepIndex) {
2184
3417
  return `/tmp/wf/request-context-${stepIndex}.json`;
2185
3418
  }
2186
3419
  // src/step-invocation/invoker.ts
2187
- import { randomBytes } from "node:crypto";
3420
+ import { randomBytes as randomBytes2 } from "node:crypto";
3421
+ import { z as z10 } from "zod";
3422
+ var StepObservabilitySchema = z10.object({
3423
+ metadata: z10.record(z10.string(), z10.unknown()).optional(),
3424
+ events: z10.array(z10.unknown()).optional(),
3425
+ subSteps: z10.array(z10.object({
3426
+ name: z10.string(),
3427
+ startedAt: z10.number(),
3428
+ durationMs: z10.number(),
3429
+ status: z10.enum(["completed", "failed"]),
3430
+ error: z10.string().optional()
3431
+ })).optional()
3432
+ }).passthrough();
3433
+ var StepResultEnvelopeSchema = z10.discriminatedUnion("ok", [
3434
+ z10.object({
3435
+ ok: z10.literal(true),
3436
+ output: z10.unknown().optional(),
3437
+ observability: StepObservabilitySchema.optional()
3438
+ }),
3439
+ z10.object({
3440
+ ok: z10.literal(false),
3441
+ kind: z10.enum(["protocol", "user-step"]),
3442
+ error: z10.string().optional()
3443
+ })
3444
+ ]);
3445
+ var StepPauseEnvelopeSchema = z10.object({
3446
+ pauseId: z10.string().min(1),
3447
+ pauseRequest: StepPauseRequestSchema,
3448
+ observability: StepObservabilitySchema.optional()
3449
+ });
2188
3450
  function buildStepEnvs(args) {
2189
3451
  return {
2190
3452
  [STEP_ENV.RUN_ID]: args.runId,
@@ -2192,30 +3454,64 @@ function buildStepEnvs(args) {
2192
3454
  [STEP_ENV.STEP_INDEX]: String(args.stepIndex),
2193
3455
  [STEP_ENV.STEP_INPUT_PATH]: stepInputPath(args.stepIndex),
2194
3456
  [STEP_ENV.REQUEST_CONTEXT_PATH]: requestContextPath(args.stepIndex),
2195
- [STEP_ENV.STEP_RESULT_TOKEN]: args.resultToken
3457
+ [STEP_ENV.STEP_RESULT_TOKEN]: args.resultToken,
3458
+ [STEP_ENV.STEP_RESUME]: args.isResume ? "1" : "0"
2196
3459
  };
2197
3460
  }
2198
3461
  function parseStepResult(stdout, resultToken) {
2199
- const prefix = stepResultLinePrefix(resultToken);
2200
- const line = stdout.split(/\r?\n/).find((l) => l.startsWith(prefix));
2201
- if (!line)
3462
+ const resultPrefix = stepResultLinePrefix(resultToken);
3463
+ const pausePrefix = stepPauseLinePrefix(resultToken);
3464
+ let resultLine;
3465
+ let pauseLine;
3466
+ for (const line of stdout.split(/\r?\n/)) {
3467
+ if (!pauseLine && line.startsWith(pausePrefix))
3468
+ pauseLine = line;
3469
+ if (!resultLine && line.startsWith(resultPrefix))
3470
+ resultLine = line;
3471
+ if (pauseLine && resultLine)
3472
+ break;
3473
+ }
3474
+ if (pauseLine) {
3475
+ try {
3476
+ const raw = JSON.parse(pauseLine.slice(pausePrefix.length));
3477
+ const parsed2 = StepPauseEnvelopeSchema.safeParse(raw);
3478
+ if (!parsed2.success) {
3479
+ return { ok: false, error: { kind: "protocol", message: `step pause schema validation failed: ${parsed2.error.message}` } };
3480
+ }
3481
+ return {
3482
+ ok: "paused",
3483
+ pauseId: parsed2.data.pauseId,
3484
+ pauseRequest: parsed2.data.pauseRequest,
3485
+ ...parsed2.data.observability ? { observability: parsed2.data.observability } : {}
3486
+ };
3487
+ } catch {
3488
+ return { ok: false, error: { kind: "protocol", message: "step pause JSON parse failed" } };
3489
+ }
3490
+ }
3491
+ if (!resultLine)
2202
3492
  return null;
2203
3493
  let parsed;
2204
3494
  try {
2205
- parsed = JSON.parse(line.slice(prefix.length));
3495
+ const raw = JSON.parse(resultLine.slice(resultPrefix.length));
3496
+ const result = StepResultEnvelopeSchema.safeParse(raw);
3497
+ if (!result.success) {
3498
+ const kind2 = raw && typeof raw === "object" && typeof raw.kind === "string" ? ` (kind=${raw.kind})` : "";
3499
+ return { ok: false, error: { kind: "protocol", message: `step result schema validation failed${kind2}: ${result.error.message}` } };
3500
+ }
3501
+ parsed = result.data;
2206
3502
  } catch {
2207
3503
  return { ok: false, error: { kind: "protocol", message: "step result JSON parse failed" } };
2208
3504
  }
2209
3505
  if (parsed.ok) {
2210
3506
  return parsed.observability ? { ok: true, output: parsed.output, observability: parsed.observability } : { ok: true, output: parsed.output };
2211
3507
  }
2212
- const kind = parsed.kind === "user-step" ? "user-step" : "protocol";
3508
+ const kind = parsed.kind;
2213
3509
  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;
3510
+ const message = baseMessage;
2215
3511
  return { ok: false, error: { kind, message } };
2216
3512
  }
2217
3513
  async function invokeStep(sandbox, request, opts) {
2218
- const resultToken = randomBytes(16).toString("hex");
3514
+ const resultToken = randomBytes2(16).toString("hex");
2219
3515
  await Promise.all([
2220
3516
  sandbox.files.write(stepInputPath(request.stepIndex), JSON.stringify(request.input)),
2221
3517
  sandbox.files.write(requestContextPath(request.stepIndex), JSON.stringify(request.requestContext))
@@ -2225,10 +3521,13 @@ async function invokeStep(sandbox, request, opts) {
2225
3521
  ...buildStepEnvs({
2226
3522
  runId: request.runId,
2227
3523
  stepIndex: request.stepIndex,
2228
- resultToken
3524
+ resultToken,
3525
+ isResume: opts?.isResume
2229
3526
  })
2230
3527
  };
2231
- const sentinelPrefix = stepResultLinePrefix(resultToken);
3528
+ const resultSentinel = stepResultLinePrefix(resultToken);
3529
+ const pauseSentinel = stepPauseLinePrefix(resultToken);
3530
+ const isSentinel = (line) => line.startsWith(resultSentinel) || line.startsWith(pauseSentinel);
2232
3531
  const makeLineSplitter = (sink, filterSentinel) => {
2233
3532
  if (!sink)
2234
3533
  return { onChunk: undefined, flush: () => {} };
@@ -2241,7 +3540,7 @@ async function invokeStep(sandbox, request, opts) {
2241
3540
  `)) !== -1) {
2242
3541
  const line = buf.slice(0, nl);
2243
3542
  buf = buf.slice(nl + 1);
2244
- if (filterSentinel && line.startsWith(sentinelPrefix))
3543
+ if (filterSentinel && isSentinel(line))
2245
3544
  continue;
2246
3545
  sink(line);
2247
3546
  }
@@ -2251,7 +3550,7 @@ async function invokeStep(sandbox, request, opts) {
2251
3550
  return;
2252
3551
  const line = buf;
2253
3552
  buf = "";
2254
- if (filterSentinel && line.startsWith(sentinelPrefix))
3553
+ if (filterSentinel && isSentinel(line))
2255
3554
  return;
2256
3555
  sink(line);
2257
3556
  }
@@ -2271,11 +3570,21 @@ async function invokeStep(sandbox, request, opts) {
2271
3570
  if (parsed)
2272
3571
  return parsed;
2273
3572
  if (result.exitCode !== 0) {
3573
+ const stderrTail = result.stderr.trim().slice(-2000);
3574
+ const stdoutTail = result.stdout.trim().slice(-2000);
3575
+ const details = [
3576
+ stderrTail ? `stderr:
3577
+ ${stderrTail}` : "",
3578
+ stdoutTail ? `stdout:
3579
+ ${stdoutTail}` : ""
3580
+ ].filter(Boolean).join(`
3581
+ `);
2274
3582
  return {
2275
3583
  ok: false,
2276
3584
  error: {
2277
3585
  kind: "runner-exit",
2278
- message: `runner subprocess exited ${result.exitCode} before emitting a step result`,
3586
+ message: `runner subprocess exited ${result.exitCode} before emitting a step result${details ? `
3587
+ ${details}` : ""}`,
2279
3588
  exitCode: result.exitCode
2280
3589
  }
2281
3590
  };
@@ -2287,10 +3596,8 @@ async function invokeStep(sandbox, request, opts) {
2287
3596
  }
2288
3597
  // src/step-invocation/server.ts
2289
3598
  import { readFileSync } from "node:fs";
2290
- function emitResult(token, payload) {
3599
+ function writeSentinelLine(line) {
2291
3600
  return new Promise((resolve, reject) => {
2292
- const line = `${STEP_RESULT_PREFIX}${token}:${JSON.stringify(payload)}
2293
- `;
2294
3601
  process.stdout.write(line, (err) => {
2295
3602
  if (err)
2296
3603
  reject(err);
@@ -2299,13 +3606,27 @@ function emitResult(token, payload) {
2299
3606
  });
2300
3607
  });
2301
3608
  }
3609
+ function emitResult(token, payload) {
3610
+ return writeSentinelLine(`${STEP_RESULT_PREFIX}${token}:${JSON.stringify(payload)}
3611
+ `);
3612
+ }
3613
+ function emitPause(token, signal) {
3614
+ const body = JSON.stringify({
3615
+ pauseId: signal.pauseId,
3616
+ pauseRequest: signal.pauseRequest,
3617
+ ...signal.observability ? { observability: signal.observability } : {}
3618
+ });
3619
+ return writeSentinelLine(`${STEP_PAUSE_PREFIX}${token}:${body}
3620
+ `);
3621
+ }
2302
3622
  function scrubProtocolEnvs() {
2303
3623
  for (const key of [
2304
3624
  STEP_ENV.STEP_MODE,
2305
3625
  STEP_ENV.STEP_INDEX,
2306
3626
  STEP_ENV.STEP_INPUT_PATH,
2307
3627
  STEP_ENV.REQUEST_CONTEXT_PATH,
2308
- STEP_ENV.STEP_RESULT_TOKEN
3628
+ STEP_ENV.STEP_RESULT_TOKEN,
3629
+ STEP_ENV.STEP_RESUME
2309
3630
  ]) {
2310
3631
  delete process.env[key];
2311
3632
  }
@@ -2319,12 +3640,14 @@ async function serveStep(handler) {
2319
3640
  }
2320
3641
  let exitCode = 0;
2321
3642
  let payload;
3643
+ let pauseSignal;
2322
3644
  let setupResult;
2323
3645
  try {
2324
3646
  const runId = process.env[STEP_ENV.RUN_ID];
2325
3647
  const stepIndexEnv = process.env[STEP_ENV.STEP_INDEX];
2326
3648
  const inputPath = process.env[STEP_ENV.STEP_INPUT_PATH];
2327
3649
  const ctxPath = process.env[STEP_ENV.REQUEST_CONTEXT_PATH];
3650
+ const isResume = process.env[STEP_ENV.STEP_RESUME] === "1";
2328
3651
  const stepIndex = stepIndexEnv === undefined ? Number.NaN : Number(stepIndexEnv);
2329
3652
  if (!runId || !inputPath || !ctxPath || !Number.isFinite(stepIndex)) {
2330
3653
  throw new Error(`step-server missing required env: ${[
@@ -2335,8 +3658,8 @@ async function serveStep(handler) {
2335
3658
  ].filter(Boolean).join(", ")}`);
2336
3659
  }
2337
3660
  const input = JSON.parse(readFileSync(inputPath, "utf8"));
2338
- const requestContext = JSON.parse(readFileSync(ctxPath, "utf8"));
2339
- setupResult = { runId, stepIndex, input, requestContext };
3661
+ const requestContext = RequestContextWireSchema.parse(JSON.parse(readFileSync(ctxPath, "utf8")));
3662
+ setupResult = { runId, stepIndex, isResume, input, requestContext };
2340
3663
  } catch (err) {
2341
3664
  exitCode = 1;
2342
3665
  payload = { ok: false, kind: "protocol", error: err instanceof Error ? err.message : String(err) };
@@ -2347,32 +3670,76 @@ async function serveStep(handler) {
2347
3670
  const { output, observability } = await handler(setupResult);
2348
3671
  payload = observability ? { ok: true, output, observability } : { ok: true, output };
2349
3672
  } catch (err) {
2350
- exitCode = 1;
2351
- payload = { ok: false, kind: "user-step", error: err instanceof Error ? err.message : String(err) };
3673
+ if (isPauseSignal(err)) {
3674
+ pauseSignal = err;
3675
+ } else {
3676
+ exitCode = 1;
3677
+ payload = { ok: false, kind: "user-step", error: err instanceof Error ? err.message : String(err) };
3678
+ }
2352
3679
  }
2353
3680
  }
3681
+ if (pauseSignal) {
3682
+ await emitPause(token, pauseSignal);
3683
+ process.exit(0);
3684
+ }
2354
3685
  await emitResult(token, payload);
2355
3686
  process.exit(exitCode);
2356
3687
  }
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
3688
  // src/agent/run-agent.ts
2369
- import { z as z6 } from "zod";
3689
+ import { z as z11 } from "zod";
3690
+ import { randomUUID as randomUUID2 } from "node:crypto";
2370
3691
 
2371
3692
  // src/agent/protocol-suffix.md
2372
3693
  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
3694
 
2374
3695
  // src/agent/run-agent.ts
3696
+ async function runInboxPoller(opts) {
3697
+ let afterSeq = 0;
3698
+ const headers = {
3699
+ authorization: `Bearer ${opts.token}`,
3700
+ accept: "application/json"
3701
+ };
3702
+ let backoffMs = 0;
3703
+ while (!opts.signal.aborted) {
3704
+ if (backoffMs > 0) {
3705
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
3706
+ if (opts.signal.aborted)
3707
+ return;
3708
+ }
3709
+ const url = `${opts.baseUrl.replace(/\/+$/, "")}` + `/api/v1/internal/runs/${opts.runId}/agents/${encodeURIComponent(opts.agentId)}/inbox?afterSeq=${afterSeq}`;
3710
+ try {
3711
+ const res = await fetch(url, { method: "GET", headers, signal: opts.signal });
3712
+ if (!res.ok) {
3713
+ if (res.status === 401 || res.status === 403)
3714
+ return;
3715
+ backoffMs = Math.min(8000, (backoffMs || 500) * 2);
3716
+ continue;
3717
+ }
3718
+ backoffMs = 0;
3719
+ const body = await res.json();
3720
+ for (const m of body.messages ?? []) {
3721
+ opts.inbox.push({ text: m.body, senderName: m.senderName });
3722
+ if (m.seq > afterSeq)
3723
+ afterSeq = m.seq;
3724
+ }
3725
+ } catch (err) {
3726
+ if (opts.signal.aborted)
3727
+ return;
3728
+ backoffMs = Math.min(8000, (backoffMs || 500) * 2);
3729
+ if (backoffMs >= 4000) {
3730
+ process.stderr.write(`[inbox-poller] poll failed, backing off ${backoffMs}ms: ${err instanceof Error ? err.message : String(err)}
3731
+ `);
3732
+ }
3733
+ }
3734
+ }
3735
+ }
2375
3736
  var PROTOCOL_SUFFIX = stripFrontmatter(protocol_suffix_default);
3737
+ var HITL_ASK_INSTRUCTION = [
3738
+ "## Asking a human",
3739
+ "",
3740
+ "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."
3741
+ ].join(`
3742
+ `);
2376
3743
  function stripFrontmatter(content) {
2377
3744
  if (!content.startsWith("---"))
2378
3745
  return content;
@@ -2381,29 +3748,29 @@ function stripFrontmatter(content) {
2381
3748
  return end === -1 ? content : content.slice(end + 4).trimStart();
2382
3749
  }
2383
3750
  function zodTypeName(schema) {
2384
- if (schema instanceof z6.ZodString)
3751
+ if (schema instanceof z11.ZodString)
2385
3752
  return "string";
2386
- if (schema instanceof z6.ZodNumber)
3753
+ if (schema instanceof z11.ZodNumber)
2387
3754
  return "number";
2388
- if (schema instanceof z6.ZodBoolean)
3755
+ if (schema instanceof z11.ZodBoolean)
2389
3756
  return "boolean";
2390
- if (schema instanceof z6.ZodArray)
3757
+ if (schema instanceof z11.ZodArray)
2391
3758
  return `${zodTypeName(schema.element)}[]`;
2392
- if (schema instanceof z6.ZodEnum)
3759
+ if (schema instanceof z11.ZodEnum)
2393
3760
  return schema.options.map((o) => `"${o}"`).join(" | ");
2394
- if (schema instanceof z6.ZodOptional)
3761
+ if (schema instanceof z11.ZodOptional)
2395
3762
  return `${zodTypeName(schema.unwrap())} (optional)`;
2396
- if (schema instanceof z6.ZodNullable)
3763
+ if (schema instanceof z11.ZodNullable)
2397
3764
  return `${zodTypeName(schema.unwrap())} | null`;
2398
- if (schema instanceof z6.ZodLiteral)
3765
+ if (schema instanceof z11.ZodLiteral)
2399
3766
  return `"${String(schema.value)}"`;
2400
- if (schema instanceof z6.ZodObject)
3767
+ if (schema instanceof z11.ZodObject)
2401
3768
  return "object";
2402
3769
  return "any";
2403
3770
  }
2404
3771
  function buildResponseFormatAppendix(schema) {
2405
3772
  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;
3773
+ const shape = schema instanceof z11.ZodObject ? schema.shape : null;
2407
3774
  const body = shape ? (() => {
2408
3775
  const entries = Object.entries(shape);
2409
3776
  const example = Object.fromEntries(entries.map(([k, v]) => [k, `<${zodTypeName(v)}>`]));
@@ -2428,44 +3795,96 @@ function buildResponseFormatAppendix(schema) {
2428
3795
  return [...header, ...body].join(`
2429
3796
  `);
2430
3797
  }
3798
+ function resolveAgentId(explicitId) {
3799
+ if (explicitId)
3800
+ return explicitId;
3801
+ const activeCall = nextAgentCallInActiveStep();
3802
+ if (activeCall === null)
3803
+ return randomUUID2();
3804
+ return `step${activeCall.stepIndex}-agent-${activeCall.callIndex}`;
3805
+ }
2431
3806
  async function agent(opts) {
2432
3807
  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}
3808
+ const agentId = resolveAgentId(opts.agentId);
3809
+ const callbackUrl = process.env.AGENT_COMPOSE_URL;
3810
+ const callbackToken = process.env.AGENT_COMPOSE_RUN_TOKEN;
3811
+ const runId = process.env.RUN_ID;
3812
+ const inbox = callbackUrl && callbackToken && runId ? new AsyncQueue : undefined;
3813
+ const inboxAbort = new AbortController;
3814
+ if (inbox && callbackUrl && callbackToken && runId) {
3815
+ runInboxPoller({
3816
+ baseUrl: callbackUrl,
3817
+ token: callbackToken,
3818
+ runId,
3819
+ agentId,
3820
+ inbox,
3821
+ signal: inboxAbort.signal
3822
+ });
3823
+ }
3824
+ if (callbackUrl && callbackToken && runId) {
3825
+ runControlPoller({
3826
+ baseUrl: callbackUrl,
3827
+ token: callbackToken,
3828
+ runId,
3829
+ agentId,
3830
+ signal: inboxAbort.signal
3831
+ });
3832
+ }
3833
+ const activeStepForPause = getActiveStep();
3834
+ const steerPause = activeStepForPause && runId ? function steerPauseFn(req, agentScope) {
3835
+ return corePause(req, {
3836
+ runId,
3837
+ stepIndex: activeStepForPause.stepIndex,
3838
+ agentScope
3839
+ });
3840
+ } : undefined;
3841
+ const mode = opts.mode ?? "auto";
3842
+ try {
3843
+ return await agentLoop({
3844
+ runtime: (runtimeOpts) => opts.runtime.create(opts.sandbox, runtimeOpts),
3845
+ agentId,
3846
+ ...opts.label !== undefined ? { label: opts.label } : {},
3847
+ ...opts.budget?.turnsPerIteration !== undefined ? { turnsPerIteration: opts.budget.turnsPerIteration } : {},
3848
+ ...opts.budget?.maxIterations !== undefined ? { maxIterations: opts.budget.maxIterations } : {},
3849
+ ...opts.tools !== undefined ? { allowedTools: opts.tools } : {},
3850
+ ...opts.responseSchema !== undefined ? { responseSchema: opts.responseSchema } : {},
3851
+ ...workingDir ? { cwd: workingDir } : {},
3852
+ buildPrompt: (_lastStatus, iteration) => {
3853
+ if (iteration > 0)
3854
+ return PROTOCOL_SUFFIX;
3855
+ const base = stripFrontmatter(opts.prompt);
3856
+ const parts = [base, PROTOCOL_SUFFIX];
3857
+ if (mode === "hitl")
3858
+ parts.push(HITL_ASK_INSTRUCTION);
3859
+ if (opts.responseSchema)
3860
+ parts.push(buildResponseFormatAppendix(opts.responseSchema));
3861
+ return parts.join(`
2451
3862
 
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
- });
3863
+ `);
3864
+ },
3865
+ ...opts.events ? { onAgentLifecycleEvent: (event) => {
3866
+ opts.events?.emit(event);
3867
+ } } : {},
3868
+ ...opts.onAgentEvent ? { onAgentEvent: opts.onAgentEvent } : {},
3869
+ ...opts.onIteration ? { onIteration: opts.onIteration } : {},
3870
+ ...opts.processors?.length ? { processors: opts.processors } : {},
3871
+ requestContext: opts.requestContext ?? RequestContext.fromReserved({
3872
+ teamId: "",
3873
+ runId: "",
3874
+ workflowId: "",
3875
+ factoryId: null,
3876
+ apiKeyScopes: [],
3877
+ parentRunId: null
3878
+ }),
3879
+ ...inbox ? { inbox } : {},
3880
+ mode,
3881
+ consumeSteerPending: () => consumeSteerPending(agentId),
3882
+ ...steerPause ? { pause: steerPause } : {}
3883
+ });
3884
+ } finally {
3885
+ inbox?.close();
3886
+ inboxAbort.abort();
3887
+ }
2469
3888
  }
2470
3889
  // src/runtimes/openai-desktop.ts
2471
3890
  import OpenAI from "openai";
@@ -2478,6 +3897,8 @@ var MAX_TURNS = 40;
2478
3897
 
2479
3898
  class OpenAIDesktopRunner {
2480
3899
  sandbox;
3900
+ kind = "openai-desktop";
3901
+ model = "computer-use-preview";
2481
3902
  openai;
2482
3903
  label;
2483
3904
  constructor(sandbox, opts) {