@agent-compose/sdk 0.5.8 → 0.5.9

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 (70) hide show
  1. package/dist/agent/agent-context.d.ts +1 -1
  2. package/dist/agent/agent-loop.d.ts +22 -12
  3. package/dist/agent/local-pause-request.d.ts +49 -0
  4. package/dist/agent/local-pause-request.test.d.ts +1 -0
  5. package/dist/agent/steer-control.d.ts +22 -6
  6. package/dist/client.d.ts +12 -1
  7. package/dist/index.d.ts +4 -2
  8. package/dist/index.js +1275 -527
  9. package/dist/pause/checkpoint.d.ts +27 -10
  10. package/dist/pause/manager.d.ts +1 -0
  11. package/dist/pause/pause-core.d.ts +23 -0
  12. package/dist/pause/state-dir.d.ts +1 -1
  13. package/dist/processors/builtins.d.ts +20 -1
  14. package/dist/processors/index.d.ts +1 -1
  15. package/dist/processors/processor.d.ts +13 -0
  16. package/dist/runtimes/_acp-client.d.ts +140 -0
  17. package/dist/runtimes/_cli-agent.d.ts +155 -3
  18. package/dist/runtimes/amp.d.ts +2 -2
  19. package/dist/runtimes/cli-agent-acp-live.test.d.ts +30 -0
  20. package/dist/runtimes/cli-agent.test.d.ts +22 -6
  21. package/dist/runtimes/codex.d.ts +7 -2
  22. package/dist/runtimes/openai-desktop.js +1263 -527
  23. package/dist/runtimes/vercel.js +389 -2
  24. package/dist/sandbox.d.ts +113 -19
  25. package/dist/types/__tests__/environment-build-flag.test.d.ts +1 -0
  26. package/dist/types/__tests__/workflow-metadata-provider.test.d.ts +1 -0
  27. package/dist/types/execution-context.d.ts +0 -8
  28. package/dist/types/protocol.d.ts +32 -1
  29. package/dist/types/runtime.d.ts +7 -0
  30. package/dist/types/sandbox-environment.d.ts +6 -1
  31. package/dist/types/sandbox.d.ts +41 -6
  32. package/dist/types/workflow-metadata.d.ts +40 -10
  33. package/dist/types/workflow.d.ts +22 -6
  34. package/dist/utils/bundler.d.ts +5 -1
  35. package/dist/workflow-steps/observability.d.ts +28 -2
  36. package/dist/workflow-steps/types.d.ts +11 -7
  37. package/dist/workflow-steps/workflow.d.ts +3 -2
  38. package/package.json +3 -2
  39. package/src/agent/agent-context.ts +14 -6
  40. package/src/agent/agent-loop.ts +59 -10
  41. package/src/agent/local-pause-request.ts +90 -0
  42. package/src/agent/run-agent.ts +6 -2
  43. package/src/agent/steer-control.ts +21 -7
  44. package/src/client.ts +35 -1
  45. package/src/index.ts +10 -2
  46. package/src/pause/checkpoint.ts +33 -14
  47. package/src/pause/manager.ts +2 -2
  48. package/src/pause/pause-core.ts +35 -0
  49. package/src/pause/state-dir.ts +2 -2
  50. package/src/processors/builtins.ts +44 -1
  51. package/src/processors/index.ts +1 -0
  52. package/src/processors/processor.ts +13 -0
  53. package/src/runtimes/_acp-client.ts +516 -0
  54. package/src/runtimes/_cli-agent.ts +418 -3
  55. package/src/runtimes/claude.ts +27 -3
  56. package/src/runtimes/codex.ts +21 -1
  57. package/src/runtimes/vercel.ts +4 -1
  58. package/src/sandbox.ts +361 -54
  59. package/src/types/execution-context.ts +0 -8
  60. package/src/types/protocol.ts +27 -1
  61. package/src/types/runtime.ts +7 -0
  62. package/src/types/sandbox-environment.ts +12 -1
  63. package/src/types/sandbox.ts +40 -6
  64. package/src/types/workflow-metadata.ts +42 -10
  65. package/src/types/workflow.ts +22 -7
  66. package/src/utils/bundler.ts +6 -1
  67. package/src/workflow-steps/observability.ts +51 -5
  68. package/src/workflow-steps/runner.ts +9 -5
  69. package/src/workflow-steps/types.ts +11 -7
  70. package/src/workflow-steps/workflow.ts +3 -2
package/dist/index.js CHANGED
@@ -330,6 +330,391 @@ class RequestContext {
330
330
  }
331
331
  }
332
332
 
333
+ // src/pause/pause-core.ts
334
+ import { createHash } from "node:crypto";
335
+
336
+ // src/step-invocation/types.ts
337
+ import { z as z3 } from "zod";
338
+ var StepPauseRequestSchema = z3.object({
339
+ reason: z3.string().min(1),
340
+ kind: z3.enum(["decision", "sleep", "event", "custom"]),
341
+ payload: z3.record(z3.string(), z3.unknown()).optional(),
342
+ ttlMs: z3.number().int().positive().optional(),
343
+ correlationKey: z3.string().min(1).optional(),
344
+ snapshot: z3.boolean().optional()
345
+ });
346
+
347
+ class StepExecutionError extends Error {
348
+ kind;
349
+ exitCode;
350
+ constructor(error, stepLabel) {
351
+ super(`[${error.kind}] ${stepLabel}: ${error.message}`);
352
+ this.name = "StepExecutionError";
353
+ this.kind = error.kind;
354
+ this.exitCode = error.kind === "runner-exit" ? error.exitCode : undefined;
355
+ }
356
+ }
357
+
358
+ // src/active-step.ts
359
+ import { AsyncLocalStorage } from "node:async_hooks";
360
+ var activeStepStorage = new AsyncLocalStorage;
361
+ var fallbackActive = null;
362
+ var BRIDGE_KEY = Symbol.for("agent-compose.activeStep.bridge");
363
+ function bridgeSlot() {
364
+ const g = globalThis;
365
+ return g[BRIDGE_KEY] ??= { state: null };
366
+ }
367
+ function makeState(step) {
368
+ return {
369
+ stepIndex: step.stepIndex,
370
+ nextAgentCallIndex: 0,
371
+ pauseOrdinalByScope: new Map
372
+ };
373
+ }
374
+ function currentState() {
375
+ return activeStepStorage.getStore() ?? bridgeSlot().state ?? fallbackActive;
376
+ }
377
+ async function runWithActiveStep(step, fn) {
378
+ return activeStepStorage.run(makeState(step), fn);
379
+ }
380
+ function setActiveStepBridge(step) {
381
+ const slot = bridgeSlot();
382
+ const prev = { state: slot.state };
383
+ slot.state = step ? makeState(step) : null;
384
+ return prev;
385
+ }
386
+ function restoreActiveStepBridge(prev) {
387
+ bridgeSlot().state = prev.state;
388
+ }
389
+ function getActiveStep() {
390
+ const state = currentState();
391
+ return state ? { stepIndex: state.stepIndex } : null;
392
+ }
393
+ function nextAgentCallInActiveStep() {
394
+ const state = currentState();
395
+ if (!state)
396
+ return null;
397
+ const callIndex = state.nextAgentCallIndex;
398
+ state.nextAgentCallIndex += 1;
399
+ return { stepIndex: state.stepIndex, callIndex };
400
+ }
401
+ function nextPauseOrdinalInActiveStep(scope) {
402
+ const state = currentState();
403
+ if (!state)
404
+ return null;
405
+ const next = state.pauseOrdinalByScope.get(scope) ?? 0;
406
+ state.pauseOrdinalByScope.set(scope, next + 1);
407
+ return next;
408
+ }
409
+
410
+ // src/pause/state-dir.ts
411
+ import { promises as fs } from "node:fs";
412
+ import { join as join2 } from "node:path";
413
+ import { randomBytes } from "node:crypto";
414
+ function getStateDir() {
415
+ return process.env.AGENT_COMPOSE_STATE_DIR ?? "/tmp/wf/state";
416
+ }
417
+ var checkpointsDir = () => join2(getStateDir(), "checkpoints");
418
+ var pausesDir = () => join2(getStateDir(), "pauses");
419
+ var agentStatePath = (agentInstanceId) => join2(getStateDir(), `agent-${agentInstanceId}.json`);
420
+ var runtimeStatePath = (agentInstanceId) => join2(getStateDir(), `runtime-${agentInstanceId}.json`);
421
+ var checkpointPath = (name) => join2(checkpointsDir(), `${name}.json`);
422
+ var pausePath = (pauseId) => join2(pausesDir(), `${pauseId}.json`);
423
+ var NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/;
424
+ function assertSafeName(label, name) {
425
+ if (!NAME_RE.test(name)) {
426
+ throw new Error(`${label} name must match ${NAME_RE} (got ${JSON.stringify(name)})`);
427
+ }
428
+ }
429
+ function assertSafeCheckpointName(name) {
430
+ assertSafeName("checkpoint", name);
431
+ }
432
+ async function ensureDirs() {
433
+ await fs.mkdir(checkpointsDir(), { recursive: true });
434
+ await fs.mkdir(pausesDir(), { recursive: true });
435
+ }
436
+ async function atomicWriteJSON(path, value) {
437
+ await ensureDirs();
438
+ const tmp = `${path}.tmp.${randomBytes(6).toString("hex")}`;
439
+ const payload = JSON.stringify(value);
440
+ if (payload === undefined) {
441
+ throw new Error("pause state values must be JSON-serialisable");
442
+ }
443
+ const handle = await fs.open(tmp, "w");
444
+ try {
445
+ await handle.writeFile(payload, { encoding: "utf8" });
446
+ await handle.sync();
447
+ } finally {
448
+ await handle.close();
449
+ }
450
+ await fs.rename(tmp, path);
451
+ }
452
+ async function readJSON(path) {
453
+ try {
454
+ const raw = await fs.readFile(path, "utf8");
455
+ return JSON.parse(raw);
456
+ } catch (err) {
457
+ if (err.code === "ENOENT")
458
+ return null;
459
+ throw err;
460
+ }
461
+ }
462
+ async function readJSONWithPresence(path) {
463
+ try {
464
+ const raw = await fs.readFile(path, "utf8");
465
+ return { found: true, value: JSON.parse(raw) };
466
+ } catch (err) {
467
+ if (err.code === "ENOENT")
468
+ return { found: false };
469
+ throw err;
470
+ }
471
+ }
472
+ async function readAgentState(agentInstanceId) {
473
+ assertSafeName("agent", agentInstanceId);
474
+ return readJSON(agentStatePath(agentInstanceId));
475
+ }
476
+ async function writeAgentState(agentInstanceId, state) {
477
+ assertSafeName("agent", agentInstanceId);
478
+ await atomicWriteJSON(agentStatePath(agentInstanceId), state);
479
+ }
480
+ async function readRuntimeCheckpoint(agentInstanceId) {
481
+ assertSafeName("runtime", agentInstanceId);
482
+ return readJSON(runtimeStatePath(agentInstanceId));
483
+ }
484
+ async function writeRuntimeCheckpoint(agentInstanceId, blob) {
485
+ assertSafeName("runtime", agentInstanceId);
486
+ await atomicWriteJSON(runtimeStatePath(agentInstanceId), blob);
487
+ }
488
+ async function readCheckpoint(name) {
489
+ assertSafeCheckpointName(name);
490
+ return readJSONWithPresence(checkpointPath(name));
491
+ }
492
+ async function writeCheckpoint(name, value) {
493
+ assertSafeCheckpointName(name);
494
+ await atomicWriteJSON(checkpointPath(name), value);
495
+ }
496
+ function hasOwn(obj, key) {
497
+ return Object.prototype.hasOwnProperty.call(obj, key);
498
+ }
499
+ function validatePauseRecord(value, expectedPauseId) {
500
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
501
+ throw new Error(`pause record ${expectedPauseId} is malformed: expected object`);
502
+ }
503
+ const record = value;
504
+ if (record.pauseId !== undefined && record.pauseId !== expectedPauseId) {
505
+ throw new Error(`pause record ${expectedPauseId} is malformed: pauseId mismatch`);
506
+ }
507
+ if (record.resolved === true) {
508
+ if (typeof record.expired !== "boolean") {
509
+ throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require boolean expired`);
510
+ }
511
+ if (!hasOwn(record, "resumePayload") || record.resumePayload === undefined) {
512
+ throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require resumePayload`);
513
+ }
514
+ return {
515
+ pauseId: expectedPauseId,
516
+ request: hasOwn(record, "request") ? record.request : null,
517
+ ...record
518
+ };
519
+ }
520
+ if (record.pauseId !== expectedPauseId) {
521
+ throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require matching pauseId`);
522
+ }
523
+ if (!hasOwn(record, "request") || record.request === undefined) {
524
+ throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require request`);
525
+ }
526
+ if (record.resolved !== undefined && record.resolved !== false) {
527
+ throw new Error(`pause record ${expectedPauseId} is malformed: resolved must be true, false, or absent`);
528
+ }
529
+ if (hasOwn(record, "expired") || hasOwn(record, "resumePayload")) {
530
+ throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records must not include resolution fields`);
531
+ }
532
+ return record;
533
+ }
534
+ async function readPauseRecord(pauseId) {
535
+ assertSafeName("pause", pauseId);
536
+ const record = await readJSON(pausePath(pauseId));
537
+ return record === null ? null : validatePauseRecord(record, pauseId);
538
+ }
539
+ async function writePauseRecord(record) {
540
+ assertSafeName("pause", record.pauseId);
541
+ validatePauseRecord(record, record.pauseId);
542
+ await atomicWriteJSON(pausePath(record.pauseId), record);
543
+ }
544
+
545
+ // src/pause/errors.ts
546
+ class PauseError extends Error {
547
+ constructor(message) {
548
+ super(message);
549
+ this.name = new.target.name;
550
+ }
551
+ }
552
+
553
+ class PauseExpiredError extends PauseError {
554
+ code = "pause_expired";
555
+ constructor(message = "pause expired before it was resolved") {
556
+ super(message);
557
+ }
558
+ }
559
+
560
+ class PauseSchemaError extends PauseError {
561
+ code = "pause_schema";
562
+ issues;
563
+ constructor(message, issues) {
564
+ super(message);
565
+ this.issues = issues;
566
+ }
567
+ }
568
+
569
+ class PauseRequestError extends PauseError {
570
+ code = "pause_request";
571
+ constructor(message = "pause request is invalid") {
572
+ super(message);
573
+ }
574
+ }
575
+
576
+ // src/pause/pause-core.ts
577
+ var PAUSE_SIGNAL_BRAND = Symbol.for("agent-compose.PauseSignal");
578
+
579
+ class PauseSignal {
580
+ pauseId;
581
+ pauseRequest;
582
+ observability;
583
+ [PAUSE_SIGNAL_BRAND] = true;
584
+ constructor(pauseId, pauseRequest, observability) {
585
+ this.pauseId = pauseId;
586
+ this.pauseRequest = pauseRequest;
587
+ this.observability = observability;
588
+ }
589
+ }
590
+ function isPauseSignal(err) {
591
+ return typeof err === "object" && err !== null && err[PAUSE_SIGNAL_BRAND] === true;
592
+ }
593
+ function boundProcessorPause(boundary, scope) {
594
+ return (req) => {
595
+ if (!boundary) {
596
+ return Promise.reject(new PauseRequestError("ctx.pause is unavailable here: no pause boundary is wired (a processor can " + "only pause inside a sandboxed workflow step, not in a local/unit-test run)"));
597
+ }
598
+ return boundary(req, scope);
599
+ };
600
+ }
601
+ var PAUSE_NAMESPACE = "5f3e2b8a-0c41-4e9d-9a7b-6d2c1f8e3a40";
602
+ function uuidToBytes(uuid) {
603
+ return Buffer.from(uuid.replace(/-/g, ""), "hex");
604
+ }
605
+ function bytesToUuid(b) {
606
+ const h = b.toString("hex");
607
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
608
+ }
609
+ function uuidv5(name, namespace) {
610
+ const hash = createHash("sha1").update(uuidToBytes(namespace)).update(Buffer.from(name, "utf8")).digest();
611
+ const bytes = Buffer.from(hash.subarray(0, 16));
612
+ bytes[6] = bytes[6] & 15 | 80;
613
+ bytes[8] = bytes[8] & 63 | 128;
614
+ return bytesToUuid(bytes);
615
+ }
616
+ function scopeOf(coord) {
617
+ return coord.agentScope ? `${coord.runId}:${coord.stepIndex}:${coord.agentScope.agentId}:${coord.agentScope.iteration}` : `${coord.runId}:${coord.stepIndex}:bare`;
618
+ }
619
+ var pauseOrdinalByScope = new Map;
620
+ function nextPauseOrdinal(scope) {
621
+ const activeOrdinal = nextPauseOrdinalInActiveStep(scope);
622
+ if (activeOrdinal !== null)
623
+ return activeOrdinal;
624
+ const next = pauseOrdinalByScope.get(scope) ?? 0;
625
+ pauseOrdinalByScope.set(scope, next + 1);
626
+ return next;
627
+ }
628
+ function nextPauseId(coord) {
629
+ const scope = scopeOf(coord);
630
+ const ordinal = nextPauseOrdinal(scope);
631
+ return uuidv5(`${scope}:${ordinal}`, PAUSE_NAMESPACE);
632
+ }
633
+ function nonJsonPath(value, path = "$", seen = new Set) {
634
+ if (value === null)
635
+ return null;
636
+ const kind = typeof value;
637
+ if (kind === "string" || kind === "boolean")
638
+ return null;
639
+ if (kind === "number")
640
+ return Number.isFinite(value) ? null : path;
641
+ if (kind === "undefined" || kind === "bigint" || kind === "function" || kind === "symbol")
642
+ return path;
643
+ if (kind !== "object")
644
+ return path;
645
+ const obj = value;
646
+ if (seen.has(obj))
647
+ return path;
648
+ seen.add(obj);
649
+ if (Array.isArray(value)) {
650
+ for (let i = 0;i < value.length; i++) {
651
+ const child = nonJsonPath(value[i], `${path}[${i}]`, seen);
652
+ if (child)
653
+ return child;
654
+ }
655
+ seen.delete(obj);
656
+ return null;
657
+ }
658
+ const proto = Object.getPrototypeOf(obj);
659
+ if (proto !== Object.prototype && proto !== null)
660
+ return path;
661
+ for (const key of Reflect.ownKeys(obj)) {
662
+ if (typeof key !== "string")
663
+ return `${path}.${String(key)}`;
664
+ const desc = Object.getOwnPropertyDescriptor(obj, key);
665
+ if (!desc?.enumerable)
666
+ return `${path}.${key}`;
667
+ const child = nonJsonPath(value[key], `${path}.${key}`, seen);
668
+ if (child)
669
+ return child;
670
+ }
671
+ seen.delete(obj);
672
+ return null;
673
+ }
674
+ function toWireRequest(req, kind) {
675
+ const wire = {
676
+ reason: req.reason,
677
+ kind,
678
+ ...req.payload !== undefined ? { payload: req.payload } : {},
679
+ ...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {},
680
+ ...req.correlationKey !== undefined ? { correlationKey: req.correlationKey } : {},
681
+ ...req.snapshot !== undefined ? { snapshot: req.snapshot } : {}
682
+ };
683
+ const parsed = StepPauseRequestSchema.safeParse(wire);
684
+ if (!parsed.success) {
685
+ throw new PauseRequestError(`pause request failed validation: ${parsed.error.message}`);
686
+ }
687
+ const badPath = nonJsonPath(parsed.data);
688
+ if (badPath) {
689
+ throw new PauseRequestError(`pause request must be JSON-serializable (invalid value at ${badPath})`);
690
+ }
691
+ return parsed.data;
692
+ }
693
+ async function corePause(req, coord, kind = "custom") {
694
+ const wire = toWireRequest(req, kind);
695
+ const pauseId = nextPauseId(coord);
696
+ const record = await readPauseRecord(pauseId);
697
+ if (record === null || record.resolved !== true) {
698
+ await writePauseRecord({ pauseId, request: wire });
699
+ throw new PauseSignal(pauseId, wire);
700
+ }
701
+ if (record.expired === true) {
702
+ const onExpiry = req.onExpiry ?? { mode: "throw" };
703
+ if (onExpiry.mode === "resolve")
704
+ return onExpiry.value;
705
+ throw new PauseExpiredError(`pause "${req.reason}" expired before it was resolved`);
706
+ }
707
+ const payload = record.resumePayload ?? null;
708
+ if (req.schema) {
709
+ const parsed = req.schema.safeParse(payload);
710
+ if (!parsed.success) {
711
+ throw new PauseSchemaError(`resume payload for pause "${req.reason}" failed schema validation: ${parsed.error.message}`, parsed.error.issues);
712
+ }
713
+ return parsed.data;
714
+ }
715
+ return payload;
716
+ }
717
+
333
718
  // src/utils/errors.ts
334
719
  function formatError(err) {
335
720
  return err instanceof Error ? err.message : String(err);
@@ -441,12 +826,14 @@ class VercelRunner {
441
826
  apiKeyScopes: [],
442
827
  parentRunId: null
443
828
  });
829
+ const gateAgentId = this.options.agentId ?? "agent";
444
830
  const gate = await this.gateToolCall(call, {
445
831
  requestContext,
446
832
  abortSignal: execOpts.abortSignal ?? signal ?? new AbortController().signal,
447
833
  retryCount: 0,
448
- agentId: this.options.agentId ?? "agent",
449
- iteration
834
+ agentId: gateAgentId,
835
+ iteration,
836
+ pause: boundProcessorPause(this.options.pause, { agentId: gateAgentId, iteration })
450
837
  });
451
838
  if (gate.kind === "deny")
452
839
  return gate.reason;
@@ -520,7 +907,7 @@ async function listVercelRuntimeModels() {
520
907
  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
908
  }
522
909
  // src/types/workflow.ts
523
- import { z as z3 } from "zod";
910
+ import { z as z4 } from "zod";
524
911
 
525
912
  // src/workflow-steps/types.ts
526
913
  var WORKFLOW_BRAND = Symbol.for("@agent-compose/sdk.Workflow");
@@ -550,6 +937,8 @@ function extractMetadata(source) {
550
937
  out.connectorOperation = Object.freeze({ ...source.connectorOperation });
551
938
  if (source.invokePolicy !== undefined)
552
939
  out.invokePolicy = freezeMetadataValue(source.invokePolicy);
940
+ if (source.environmentBuild !== undefined)
941
+ out.environmentBuild = source.environmentBuild;
553
942
  return Object.freeze(out);
554
943
  }
555
944
  function freezeMetadataValue(value) {
@@ -601,7 +990,7 @@ function isWorkflow(value) {
601
990
 
602
991
  // src/types/workflow.ts
603
992
  function compileRunForm(def, metadata) {
604
- const unknownSchema = z3.unknown();
993
+ const unknownSchema = z4.unknown();
605
994
  const inputSchema = def.input ?? unknownSchema;
606
995
  const outputSchema = def.output ?? unknownSchema;
607
996
  const step = {
@@ -617,7 +1006,6 @@ function compileRunForm(def, metadata) {
617
1006
  setMetadata: stepCtx.setMetadata,
618
1007
  step: stepCtx.step,
619
1008
  agentEvents: stepCtx.agentEvents,
620
- checkpoint: stepCtx.checkpoint,
621
1009
  pause: stepCtx.pause,
622
1010
  sleep: stepCtx.sleep,
623
1011
  waitForEvent: stepCtx.waitForEvent,
@@ -651,7 +1039,9 @@ function defineSandboxEnvironment(env) {
651
1039
  }
652
1040
  return defineWorkflow({
653
1041
  ...env.description !== undefined ? { description: env.description } : {},
1042
+ ...env.resources !== undefined ? { resources: env.resources } : {},
654
1043
  snapshots: env.snapshots ?? { saveLatest: true },
1044
+ environmentBuild: true,
655
1045
  run: async (_ctx, sandbox) => env.setup(sandbox)
656
1046
  });
657
1047
  }
@@ -662,6 +1052,7 @@ var Verdict = {
662
1052
  abort: (reason) => ({ kind: "abort", reason })
663
1053
  };
664
1054
  // src/processors/builtins.ts
1055
+ import { z as z5 } from "zod";
665
1056
  function denyTools(names) {
666
1057
  const banned = new Set(names);
667
1058
  return {
@@ -669,6 +1060,26 @@ function denyTools(names) {
669
1060
  processToolCall: (call) => banned.has(call.toolName) ? Verdict.deny(`tool "${call.toolName}" is denied by policy`) : Verdict.continue(call)
670
1061
  };
671
1062
  }
1063
+ var ApprovalDecision = z5.object({
1064
+ approved: z5.boolean(),
1065
+ reason: z5.string().optional()
1066
+ });
1067
+ function humanApproval(opts) {
1068
+ const gated = opts?.tools ? new Set(opts.tools) : null;
1069
+ return {
1070
+ name: "humanApproval",
1071
+ async processToolCall(call, ctx) {
1072
+ if (gated && !gated.has(call.toolName))
1073
+ return Verdict.continue(call);
1074
+ const decision = await ctx.pause({
1075
+ reason: opts?.reason?.(call) ?? `Approve tool call: ${call.toolName}`,
1076
+ payload: { tool: call.toolName, input: call.toolInput, toolUseId: call.toolUseId },
1077
+ schema: ApprovalDecision
1078
+ });
1079
+ return decision.approved ? Verdict.continue(call) : Verdict.deny(decision.reason ?? `tool "${call.toolName}" denied by human reviewer`);
1080
+ }
1081
+ };
1082
+ }
672
1083
  function requireScope(required) {
673
1084
  return {
674
1085
  name: "requireScope",
@@ -1063,6 +1474,22 @@ class AgentComposeClient {
1063
1474
  const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1064
1475
  return this.fetch(templatePath(factorySlug, workflowName, "secrets", key), { method: "DELETE" });
1065
1476
  }
1477
+ setFactorySecret(key, value, opts) {
1478
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1479
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/secrets`, {
1480
+ method: "POST",
1481
+ body: { key, value }
1482
+ });
1483
+ }
1484
+ async listFactorySecrets(opts) {
1485
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1486
+ const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/secrets`);
1487
+ return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
1488
+ }
1489
+ deleteFactorySecret(key, opts) {
1490
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1491
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/secrets/${encodeURIComponent(key)}`, { method: "DELETE" });
1492
+ }
1066
1493
  createApiKey(input) {
1067
1494
  return this.fetch("/api-keys", { method: "POST", body: input });
1068
1495
  }
@@ -1101,7 +1528,7 @@ class AgentComposeClient {
1101
1528
  }
1102
1529
  }
1103
1530
  // src/utils/bundler.ts
1104
- import { createHash } from "node:crypto";
1531
+ import { createHash as createHash2 } from "node:crypto";
1105
1532
  import { parse as babelParse } from "@babel/parser";
1106
1533
 
1107
1534
  // src/utils/source-loader.ts
@@ -1210,7 +1637,7 @@ function findDefaultExportExpression(body) {
1210
1637
  return null;
1211
1638
  }
1212
1639
  function sha256(text) {
1213
- return createHash("sha256").update(text, "utf8").digest("hex");
1640
+ return createHash2("sha256").update(text, "utf8").digest("hex");
1214
1641
  }
1215
1642
  async function bundleWorkflow(workflowPath, overrides) {
1216
1643
  const label = `"${workflowPath}"`;
@@ -1242,16 +1669,17 @@ async function bundleWorkflow(workflowPath, overrides) {
1242
1669
  ...metadata.resources !== undefined ? { resources: metadata.resources } : {},
1243
1670
  ...metadata.connectors !== undefined ? { connectors: metadata.connectors } : {},
1244
1671
  ...metadata.connectorOperation !== undefined ? { connectorOperation: metadata.connectorOperation } : {},
1245
- ...metadata.invokePolicy !== undefined ? { invokePolicy: metadata.invokePolicy } : {}
1672
+ ...metadata.invokePolicy !== undefined ? { invokePolicy: metadata.invokePolicy } : {},
1673
+ ...metadata.environmentBuild !== undefined ? { environmentBuild: metadata.environmentBuild } : {}
1246
1674
  };
1247
1675
  }
1248
1676
  function extractIOSchema(zodSchema) {
1249
1677
  let json;
1250
1678
  try {
1251
- const z4 = __require("zod");
1252
- if (!z4.toJSONSchema)
1679
+ const z6 = __require("zod");
1680
+ if (!z6.toJSONSchema)
1253
1681
  return;
1254
- json = z4.toJSONSchema(zodSchema);
1682
+ json = z6.toJSONSchema(zodSchema);
1255
1683
  } catch {
1256
1684
  return;
1257
1685
  }
@@ -1284,26 +1712,26 @@ function extractIOSchema(zodSchema) {
1284
1712
  return out;
1285
1713
  }
1286
1714
  // src/utils/schemas.ts
1287
- import { z as z4 } from "zod";
1288
- var AgentStatusSchema = z4.object({
1289
- summary: z4.string(),
1290
- completed: z4.array(z4.string()),
1291
- blockers: z4.array(z4.string()),
1292
- exit_signal: z4.boolean(),
1293
- needs_input: z4.boolean().optional(),
1294
- question: z4.string().optional()
1715
+ import { z as z6 } from "zod";
1716
+ var AgentStatusSchema = z6.object({
1717
+ summary: z6.string(),
1718
+ completed: z6.array(z6.string()),
1719
+ blockers: z6.array(z6.string()),
1720
+ exit_signal: z6.boolean(),
1721
+ needs_input: z6.boolean().optional(),
1722
+ question: z6.string().optional()
1295
1723
  });
1296
1724
  // src/runtimes/claude.ts
1297
1725
  import { query } from "@anthropic-ai/claude-agent-sdk";
1298
1726
 
1299
1727
  // src/agent/agent-loop.ts
1300
- import { z as z9 } from "zod";
1728
+ import { z as z10 } from "zod";
1301
1729
 
1302
1730
  // src/agent/protocol.ts
1303
- import { z as z5 } from "zod";
1304
- var AgentMessageSchema = z5.object({
1305
- type: z5.enum(["init", "text", "thinking", "tool_use", "tool_result", "done", "error", "usage"]),
1306
- timestamp: z5.string()
1731
+ import { z as z7 } from "zod";
1732
+ var AgentMessageSchema = z7.object({
1733
+ type: z7.enum(["init", "text", "thinking", "tool_use", "tool_result", "done", "error", "usage"]),
1734
+ timestamp: z7.string()
1307
1735
  }).passthrough();
1308
1736
  function parseAgentResponse(text) {
1309
1737
  const match = text.match(/<response>([\s\S]*?)<\/response>/);
@@ -1320,170 +1748,33 @@ function parseAgentResponse(text) {
1320
1748
  import { randomUUID } from "node:crypto";
1321
1749
 
1322
1750
  // src/pause/manager.ts
1323
- import { z as z6 } from "zod";
1324
-
1325
- // src/pause/state-dir.ts
1326
- import { promises as fs } from "node:fs";
1327
- import { join as join2 } from "node:path";
1328
- import { randomBytes } from "node:crypto";
1329
- function getStateDir() {
1330
- return process.env.AGENT_COMPOSE_STATE_DIR ?? "/tmp/wf/state";
1331
- }
1332
- var checkpointsDir = () => join2(getStateDir(), "checkpoints");
1333
- var pausesDir = () => join2(getStateDir(), "pauses");
1334
- var agentStatePath = (agentInstanceId) => join2(getStateDir(), `agent-${agentInstanceId}.json`);
1335
- var runtimeStatePath = (agentInstanceId) => join2(getStateDir(), `runtime-${agentInstanceId}.json`);
1336
- var checkpointPath = (name) => join2(checkpointsDir(), `${name}.json`);
1337
- var pausePath = (pauseId) => join2(pausesDir(), `${pauseId}.json`);
1338
- var NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/;
1339
- function assertSafeName(label, name) {
1340
- if (!NAME_RE.test(name)) {
1341
- throw new Error(`${label} name must match ${NAME_RE} (got ${JSON.stringify(name)})`);
1342
- }
1343
- }
1344
- function assertSafeCheckpointName(name) {
1345
- assertSafeName("checkpoint", name);
1346
- }
1347
- async function ensureDirs() {
1348
- await fs.mkdir(checkpointsDir(), { recursive: true });
1349
- await fs.mkdir(pausesDir(), { recursive: true });
1350
- }
1351
- async function atomicWriteJSON(path, value) {
1352
- await ensureDirs();
1353
- const tmp = `${path}.tmp.${randomBytes(6).toString("hex")}`;
1354
- const payload = JSON.stringify(value);
1355
- if (payload === undefined) {
1356
- throw new Error("pause state values must be JSON-serialisable");
1357
- }
1358
- const handle = await fs.open(tmp, "w");
1359
- try {
1360
- await handle.writeFile(payload, { encoding: "utf8" });
1361
- await handle.sync();
1362
- } finally {
1363
- await handle.close();
1364
- }
1365
- await fs.rename(tmp, path);
1366
- }
1367
- async function readJSON(path) {
1368
- try {
1369
- const raw = await fs.readFile(path, "utf8");
1370
- return JSON.parse(raw);
1371
- } catch (err) {
1372
- if (err.code === "ENOENT")
1373
- return null;
1374
- throw err;
1375
- }
1376
- }
1377
- async function readJSONWithPresence(path) {
1378
- try {
1379
- const raw = await fs.readFile(path, "utf8");
1380
- return { found: true, value: JSON.parse(raw) };
1381
- } catch (err) {
1382
- if (err.code === "ENOENT")
1383
- return { found: false };
1384
- throw err;
1385
- }
1386
- }
1387
- async function readAgentState(agentInstanceId) {
1388
- assertSafeName("agent", agentInstanceId);
1389
- return readJSON(agentStatePath(agentInstanceId));
1390
- }
1391
- async function writeAgentState(agentInstanceId, state) {
1392
- assertSafeName("agent", agentInstanceId);
1393
- await atomicWriteJSON(agentStatePath(agentInstanceId), state);
1394
- }
1395
- async function readRuntimeCheckpoint(agentInstanceId) {
1396
- assertSafeName("runtime", agentInstanceId);
1397
- return readJSON(runtimeStatePath(agentInstanceId));
1398
- }
1399
- async function writeRuntimeCheckpoint(agentInstanceId, blob) {
1400
- assertSafeName("runtime", agentInstanceId);
1401
- await atomicWriteJSON(runtimeStatePath(agentInstanceId), blob);
1402
- }
1403
- async function readCheckpoint(name) {
1404
- assertSafeCheckpointName(name);
1405
- return readJSONWithPresence(checkpointPath(name));
1406
- }
1407
- async function writeCheckpoint(name, value) {
1408
- assertSafeCheckpointName(name);
1409
- await atomicWriteJSON(checkpointPath(name), value);
1410
- }
1411
- function hasOwn(obj, key) {
1412
- return Object.prototype.hasOwnProperty.call(obj, key);
1413
- }
1414
- function validatePauseRecord(value, expectedPauseId) {
1415
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
1416
- throw new Error(`pause record ${expectedPauseId} is malformed: expected object`);
1417
- }
1418
- const record = value;
1419
- if (record.pauseId !== undefined && record.pauseId !== expectedPauseId) {
1420
- throw new Error(`pause record ${expectedPauseId} is malformed: pauseId mismatch`);
1421
- }
1422
- if (record.resolved === true) {
1423
- if (typeof record.expired !== "boolean") {
1424
- throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require boolean expired`);
1425
- }
1426
- if (!hasOwn(record, "resumePayload") || record.resumePayload === undefined) {
1427
- throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require resumePayload`);
1428
- }
1429
- return {
1430
- pauseId: expectedPauseId,
1431
- request: hasOwn(record, "request") ? record.request : null,
1432
- ...record
1433
- };
1434
- }
1435
- if (record.pauseId !== expectedPauseId) {
1436
- throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require matching pauseId`);
1437
- }
1438
- if (!hasOwn(record, "request") || record.request === undefined) {
1439
- throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require request`);
1440
- }
1441
- if (record.resolved !== undefined && record.resolved !== false) {
1442
- throw new Error(`pause record ${expectedPauseId} is malformed: resolved must be true, false, or absent`);
1443
- }
1444
- if (hasOwn(record, "expired") || hasOwn(record, "resumePayload")) {
1445
- throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records must not include resolution fields`);
1446
- }
1447
- return record;
1448
- }
1449
- async function readPauseRecord(pauseId) {
1450
- assertSafeName("pause", pauseId);
1451
- const record = await readJSON(pausePath(pauseId));
1452
- return record === null ? null : validatePauseRecord(record, pauseId);
1453
- }
1454
- async function writePauseRecord(record) {
1455
- assertSafeName("pause", record.pauseId);
1456
- validatePauseRecord(record, record.pauseId);
1457
- await atomicWriteJSON(pausePath(record.pauseId), record);
1458
- }
1459
-
1460
- // src/pause/manager.ts
1751
+ import { z as z8 } from "zod";
1461
1752
  var AGENT_LOOP_STATE_SCHEMA_VERSION = 2;
1462
- var SUPPORTED_AGENT_LOOP_STATE_VERSIONS = z6.union([z6.literal(1), z6.literal(2)]);
1463
- var RunningAgentLoopStateSchema = z6.object({
1753
+ var SUPPORTED_AGENT_LOOP_STATE_VERSIONS = z8.union([z8.literal(1), z8.literal(2)]);
1754
+ var RunningAgentLoopStateSchema = z8.object({
1464
1755
  schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1465
- phase: z6.literal("running"),
1466
- iteration: z6.number().int().nonnegative(),
1467
- completedIterations: z6.number().int().nonnegative(),
1468
- lastSessionId: z6.string(),
1756
+ phase: z8.literal("running"),
1757
+ iteration: z8.number().int().nonnegative(),
1758
+ completedIterations: z8.number().int().nonnegative(),
1759
+ lastSessionId: z8.string(),
1469
1760
  lastStatus: AgentStatusSchema.nullable(),
1470
- lastResponseText: z6.string(),
1471
- lastResponseValidationError: z6.string(),
1472
- iterationsWithoutStatus: z6.number().int().nonnegative(),
1473
- blockerStreak: z6.object({ key: z6.string(), count: z6.number().int().nonnegative() }).nullable(),
1474
- pendingSteerPause: z6.object({ reason: z6.string(), correlationKey: z6.string().nullable(), at: z6.number().int() }).nullable().default(null)
1761
+ lastResponseText: z8.string(),
1762
+ lastResponseValidationError: z8.string(),
1763
+ iterationsWithoutStatus: z8.number().int().nonnegative(),
1764
+ blockerStreak: z8.object({ key: z8.string(), count: z8.number().int().nonnegative() }).nullable(),
1765
+ pendingSteerPause: z8.object({ reason: z8.string(), correlationKey: z8.string().nullable(), at: z8.number().int(), payload: z8.record(z8.string(), z8.unknown()).optional() }).nullable().default(null)
1475
1766
  });
1476
- var SettledAgentLoopStateSchema = z6.object({
1767
+ var SettledAgentLoopStateSchema = z8.object({
1477
1768
  schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1478
- phase: z6.literal("settled"),
1479
- result: z6.object({
1480
- sessionId: z6.string(),
1769
+ phase: z8.literal("settled"),
1770
+ result: z8.object({
1771
+ sessionId: z8.string(),
1481
1772
  lastStatus: AgentStatusSchema.nullable(),
1482
- iterations: z6.number().int().nonnegative(),
1483
- response: z6.unknown().optional()
1773
+ iterations: z8.number().int().nonnegative(),
1774
+ response: z8.unknown().optional()
1484
1775
  })
1485
1776
  });
1486
- var AgentLoopStateSchema = z6.discriminatedUnion("phase", [
1777
+ var AgentLoopStateSchema = z8.discriminatedUnion("phase", [
1487
1778
  RunningAgentLoopStateSchema,
1488
1779
  SettledAgentLoopStateSchema
1489
1780
  ]);
@@ -1528,287 +1819,48 @@ class PauseManager {
1528
1819
  const runtimeBlob = await readRuntimeCheckpoint(this.agentId);
1529
1820
  if (runtimeBlob !== null && !runtime.restoreCheckpoint) {
1530
1821
  throw new Error(`runtime checkpoint exists for resumed agentId=${this.agentId}, but runtime cannot restore it`);
1531
- }
1532
- if (runtime.restoreCheckpoint && runtimeBlob === null) {
1533
- throw new Error(`missing runtime checkpoint for resumed agentId=${this.agentId}`);
1534
- }
1535
- if (runtimeBlob !== null)
1536
- runtime.restoreCheckpoint?.(runtimeBlob);
1537
- return { kind: "running", state: toProgressState(state) };
1538
- }
1539
- async saveRunningAgentLoop(state, runtime) {
1540
- await writeAgentState(this.agentId, {
1541
- schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1542
- phase: "running",
1543
- ...state
1544
- });
1545
- if (runtime.captureCheckpoint) {
1546
- const blob = runtime.captureCheckpoint();
1547
- if (blob !== undefined)
1548
- await writeRuntimeCheckpoint(this.agentId, blob);
1549
- }
1550
- }
1551
- async saveSettledAgentLoop(result) {
1552
- await writeAgentState(this.agentId, {
1553
- schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1554
- phase: "settled",
1555
- result: {
1556
- sessionId: result.sessionId,
1557
- lastStatus: result.lastStatus,
1558
- iterations: result.iterations,
1559
- ...result.response !== undefined ? { response: result.response } : {}
1560
- }
1561
- });
1562
- }
1563
- }
1564
-
1565
- // src/pause/pause-core.ts
1566
- import { createHash as createHash2 } from "node:crypto";
1567
-
1568
- // src/step-invocation/types.ts
1569
- import { z as z7 } from "zod";
1570
- var StepPauseRequestSchema = z7.object({
1571
- reason: z7.string().min(1),
1572
- kind: z7.enum(["decision", "sleep", "event", "custom"]),
1573
- payload: z7.record(z7.string(), z7.unknown()).optional(),
1574
- ttlMs: z7.number().int().positive().optional(),
1575
- correlationKey: z7.string().min(1).optional(),
1576
- snapshot: z7.boolean().optional()
1577
- });
1578
-
1579
- class StepExecutionError extends Error {
1580
- kind;
1581
- exitCode;
1582
- constructor(error, stepLabel) {
1583
- super(`[${error.kind}] ${stepLabel}: ${error.message}`);
1584
- this.name = "StepExecutionError";
1585
- this.kind = error.kind;
1586
- this.exitCode = error.kind === "runner-exit" ? error.exitCode : undefined;
1587
- }
1588
- }
1589
-
1590
- // src/active-step.ts
1591
- import { AsyncLocalStorage } from "node:async_hooks";
1592
- var activeStepStorage = new AsyncLocalStorage;
1593
- var fallbackActive = null;
1594
- var BRIDGE_KEY = Symbol.for("agent-compose.activeStep.bridge");
1595
- function bridgeSlot() {
1596
- const g = globalThis;
1597
- return g[BRIDGE_KEY] ??= { state: null };
1598
- }
1599
- function makeState(step) {
1600
- return {
1601
- stepIndex: step.stepIndex,
1602
- nextAgentCallIndex: 0,
1603
- pauseOrdinalByScope: new Map
1604
- };
1605
- }
1606
- function currentState() {
1607
- return activeStepStorage.getStore() ?? bridgeSlot().state ?? fallbackActive;
1608
- }
1609
- async function runWithActiveStep(step, fn) {
1610
- return activeStepStorage.run(makeState(step), fn);
1611
- }
1612
- function setActiveStepBridge(step) {
1613
- const slot = bridgeSlot();
1614
- const prev = { state: slot.state };
1615
- slot.state = step ? makeState(step) : null;
1616
- return prev;
1617
- }
1618
- function restoreActiveStepBridge(prev) {
1619
- bridgeSlot().state = prev.state;
1620
- }
1621
- function getActiveStep() {
1622
- const state = currentState();
1623
- return state ? { stepIndex: state.stepIndex } : null;
1624
- }
1625
- function nextAgentCallInActiveStep() {
1626
- const state = currentState();
1627
- if (!state)
1628
- return null;
1629
- const callIndex = state.nextAgentCallIndex;
1630
- state.nextAgentCallIndex += 1;
1631
- return { stepIndex: state.stepIndex, callIndex };
1632
- }
1633
- function nextPauseOrdinalInActiveStep(scope) {
1634
- const state = currentState();
1635
- if (!state)
1636
- return null;
1637
- const next = state.pauseOrdinalByScope.get(scope) ?? 0;
1638
- state.pauseOrdinalByScope.set(scope, next + 1);
1639
- return next;
1640
- }
1641
-
1642
- // src/pause/errors.ts
1643
- class PauseError extends Error {
1644
- constructor(message) {
1645
- super(message);
1646
- this.name = new.target.name;
1647
- }
1648
- }
1649
-
1650
- class PauseExpiredError extends PauseError {
1651
- code = "pause_expired";
1652
- constructor(message = "pause expired before it was resolved") {
1653
- super(message);
1654
- }
1655
- }
1656
-
1657
- class PauseSchemaError extends PauseError {
1658
- code = "pause_schema";
1659
- issues;
1660
- constructor(message, issues) {
1661
- super(message);
1662
- this.issues = issues;
1663
- }
1664
- }
1665
-
1666
- class PauseRequestError extends PauseError {
1667
- code = "pause_request";
1668
- constructor(message = "pause request is invalid") {
1669
- super(message);
1670
- }
1671
- }
1672
-
1673
- // src/pause/pause-core.ts
1674
- var PAUSE_SIGNAL_BRAND = Symbol.for("agent-compose.PauseSignal");
1675
-
1676
- class PauseSignal {
1677
- pauseId;
1678
- pauseRequest;
1679
- observability;
1680
- [PAUSE_SIGNAL_BRAND] = true;
1681
- constructor(pauseId, pauseRequest, observability) {
1682
- this.pauseId = pauseId;
1683
- this.pauseRequest = pauseRequest;
1684
- this.observability = observability;
1685
- }
1686
- }
1687
- function isPauseSignal(err) {
1688
- return typeof err === "object" && err !== null && err[PAUSE_SIGNAL_BRAND] === true;
1689
- }
1690
- var PAUSE_NAMESPACE = "5f3e2b8a-0c41-4e9d-9a7b-6d2c1f8e3a40";
1691
- function uuidToBytes(uuid) {
1692
- return Buffer.from(uuid.replace(/-/g, ""), "hex");
1693
- }
1694
- function bytesToUuid(b) {
1695
- const h = b.toString("hex");
1696
- return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
1697
- }
1698
- function uuidv5(name, namespace) {
1699
- const hash = createHash2("sha1").update(uuidToBytes(namespace)).update(Buffer.from(name, "utf8")).digest();
1700
- const bytes = Buffer.from(hash.subarray(0, 16));
1701
- bytes[6] = bytes[6] & 15 | 80;
1702
- bytes[8] = bytes[8] & 63 | 128;
1703
- return bytesToUuid(bytes);
1704
- }
1705
- function scopeOf(coord) {
1706
- return coord.agentScope ? `${coord.runId}:${coord.stepIndex}:${coord.agentScope.agentId}:${coord.agentScope.iteration}` : `${coord.runId}:${coord.stepIndex}:bare`;
1707
- }
1708
- var pauseOrdinalByScope = new Map;
1709
- function nextPauseOrdinal(scope) {
1710
- const activeOrdinal = nextPauseOrdinalInActiveStep(scope);
1711
- if (activeOrdinal !== null)
1712
- return activeOrdinal;
1713
- const next = pauseOrdinalByScope.get(scope) ?? 0;
1714
- pauseOrdinalByScope.set(scope, next + 1);
1715
- return next;
1716
- }
1717
- function nextPauseId(coord) {
1718
- const scope = scopeOf(coord);
1719
- const ordinal = nextPauseOrdinal(scope);
1720
- return uuidv5(`${scope}:${ordinal}`, PAUSE_NAMESPACE);
1721
- }
1722
- function nonJsonPath(value, path = "$", seen = new Set) {
1723
- if (value === null)
1724
- return null;
1725
- const kind = typeof value;
1726
- if (kind === "string" || kind === "boolean")
1727
- return null;
1728
- if (kind === "number")
1729
- return Number.isFinite(value) ? null : path;
1730
- if (kind === "undefined" || kind === "bigint" || kind === "function" || kind === "symbol")
1731
- return path;
1732
- if (kind !== "object")
1733
- return path;
1734
- const obj = value;
1735
- if (seen.has(obj))
1736
- return path;
1737
- seen.add(obj);
1738
- if (Array.isArray(value)) {
1739
- for (let i = 0;i < value.length; i++) {
1740
- const child = nonJsonPath(value[i], `${path}[${i}]`, seen);
1741
- if (child)
1742
- return child;
1743
- }
1744
- seen.delete(obj);
1745
- return null;
1746
- }
1747
- const proto = Object.getPrototypeOf(obj);
1748
- if (proto !== Object.prototype && proto !== null)
1749
- return path;
1750
- for (const key of Reflect.ownKeys(obj)) {
1751
- if (typeof key !== "string")
1752
- return `${path}.${String(key)}`;
1753
- const desc = Object.getOwnPropertyDescriptor(obj, key);
1754
- if (!desc?.enumerable)
1755
- return `${path}.${key}`;
1756
- const child = nonJsonPath(value[key], `${path}.${key}`, seen);
1757
- if (child)
1758
- return child;
1759
- }
1760
- seen.delete(obj);
1761
- return null;
1762
- }
1763
- function toWireRequest(req, kind) {
1764
- const wire = {
1765
- reason: req.reason,
1766
- kind,
1767
- ...req.payload !== undefined ? { payload: req.payload } : {},
1768
- ...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {},
1769
- ...req.correlationKey !== undefined ? { correlationKey: req.correlationKey } : {},
1770
- ...req.snapshot !== undefined ? { snapshot: req.snapshot } : {}
1771
- };
1772
- const parsed = StepPauseRequestSchema.safeParse(wire);
1773
- if (!parsed.success) {
1774
- throw new PauseRequestError(`pause request failed validation: ${parsed.error.message}`);
1775
- }
1776
- const badPath = nonJsonPath(parsed.data);
1777
- if (badPath) {
1778
- throw new PauseRequestError(`pause request must be JSON-serializable (invalid value at ${badPath})`);
1779
- }
1780
- return parsed.data;
1781
- }
1782
- async function corePause(req, coord, kind = "custom") {
1783
- const wire = toWireRequest(req, kind);
1784
- const pauseId = nextPauseId(coord);
1785
- const record = await readPauseRecord(pauseId);
1786
- if (record === null || record.resolved !== true) {
1787
- await writePauseRecord({ pauseId, request: wire });
1788
- throw new PauseSignal(pauseId, wire);
1789
- }
1790
- if (record.expired === true) {
1791
- const onExpiry = req.onExpiry ?? { mode: "throw" };
1792
- if (onExpiry.mode === "resolve")
1793
- return onExpiry.value;
1794
- throw new PauseExpiredError(`pause "${req.reason}" expired before it was resolved`);
1822
+ }
1823
+ if (runtime.restoreCheckpoint && runtimeBlob === null) {
1824
+ throw new Error(`missing runtime checkpoint for resumed agentId=${this.agentId}`);
1825
+ }
1826
+ if (runtimeBlob !== null)
1827
+ runtime.restoreCheckpoint?.(runtimeBlob);
1828
+ return { kind: "running", state: toProgressState(state) };
1795
1829
  }
1796
- const payload = record.resumePayload ?? null;
1797
- if (req.schema) {
1798
- const parsed = req.schema.safeParse(payload);
1799
- if (!parsed.success) {
1800
- throw new PauseSchemaError(`resume payload for pause "${req.reason}" failed schema validation: ${parsed.error.message}`, parsed.error.issues);
1830
+ async saveRunningAgentLoop(state, runtime) {
1831
+ await writeAgentState(this.agentId, {
1832
+ schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1833
+ phase: "running",
1834
+ ...state
1835
+ });
1836
+ if (runtime.captureCheckpoint) {
1837
+ const blob = runtime.captureCheckpoint();
1838
+ if (blob !== undefined)
1839
+ await writeRuntimeCheckpoint(this.agentId, blob);
1801
1840
  }
1802
- return parsed.data;
1803
1841
  }
1804
- return payload;
1842
+ async saveSettledAgentLoop(result) {
1843
+ await writeAgentState(this.agentId, {
1844
+ schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1845
+ phase: "settled",
1846
+ result: {
1847
+ sessionId: result.sessionId,
1848
+ lastStatus: result.lastStatus,
1849
+ iterations: result.iterations,
1850
+ ...result.response !== undefined ? { response: result.response } : {}
1851
+ }
1852
+ });
1853
+ }
1805
1854
  }
1806
1855
 
1807
1856
  // src/agent/steer-control.ts
1808
- import { z as z8 } from "zod";
1809
- var SteerDecisionSchema = z8.object({
1810
- message: z8.string().min(1),
1811
- actor: z8.string().nullish()
1857
+ import { z as z9 } from "zod";
1858
+ var SteerDecisionSchema = z9.object({
1859
+ message: z9.string().min(1).optional(),
1860
+ decision: z9.string().min(1).optional(),
1861
+ actor: z9.string().nullish()
1862
+ }).transform((d) => ({ message: (d.message ?? d.decision ?? "").trim(), actor: d.actor ?? null })).refine((d) => d.message.length > 0, {
1863
+ message: "a steer/resume answer needs a non-empty `message` or `decision`"
1812
1864
  });
1813
1865
  var steerPending = new Map;
1814
1866
  function signalSteerPending(agentId, payload) {
@@ -1921,6 +1973,8 @@ function summarizeAgentMessage(msg) {
1921
1973
  return { type: "done", sessionId: msg.sessionId };
1922
1974
  case "error":
1923
1975
  return { type: "error", text: truncate(msg.text) };
1976
+ case "plan":
1977
+ return { type: "plan", entries: msg.entries };
1924
1978
  }
1925
1979
  }
1926
1980
  async function agentLoop(opts) {
@@ -1946,7 +2000,8 @@ async function agentLoop(opts) {
1946
2000
  abortSignal: loopAbort.signal,
1947
2001
  retryCount: 0,
1948
2002
  agentId,
1949
- iteration
2003
+ iteration,
2004
+ pause: boundProcessorPause(opts.pause, { agentId, iteration })
1950
2005
  });
1951
2006
  if (!opts.runtime)
1952
2007
  throw new Error("agentLoop: opts.runtime is required");
@@ -1958,7 +2013,8 @@ async function agentLoop(opts) {
1958
2013
  processors,
1959
2014
  requestContext,
1960
2015
  agentId,
1961
- ...opts.responseSchema ? { outputFormat: { type: "json_schema", schema: z9.toJSONSchema(opts.responseSchema) } } : {}
2016
+ ...opts.pause ? { pause: opts.pause } : {},
2017
+ ...opts.responseSchema ? { outputFormat: { type: "json_schema", schema: z10.toJSONSchema(opts.responseSchema) } } : {}
1962
2018
  });
1963
2019
  if (processors.some((p) => p.processToolCall) && !client.supportsToolCallProcessor) {
1964
2020
  process.stderr.write(`${logLabel} processToolCall registered but runtime does not support pre-tool-use gating — hook is dormant
@@ -2052,6 +2108,7 @@ async function agentLoop(opts) {
2052
2108
  steerDecision = await opts.pause({
2053
2109
  reason: pendingSteerPause.reason,
2054
2110
  ...pendingSteerPause.correlationKey !== null ? { correlationKey: pendingSteerPause.correlationKey } : {},
2111
+ ...pendingSteerPause.payload !== undefined ? { payload: pendingSteerPause.payload } : {},
2055
2112
  schema: SteerDecisionSchema
2056
2113
  }, { agentId, iteration });
2057
2114
  pendingSteerPause = null;
@@ -2088,6 +2145,7 @@ Re-emit the COMPLETE corrected <response> JSON now: every required field present
2088
2145
  prompt,
2089
2146
  sessionId: lastSessionId ?? undefined,
2090
2147
  iteration: iteration + 1,
2148
+ signal: loopAbort.signal,
2091
2149
  ...opts.inbox ? { inboxStream: opts.inbox } : {}
2092
2150
  })) {
2093
2151
  const outputVerdict = await runProcessorChain(processors, (p) => p.processOutput, rawMsg, procCtx);
@@ -2120,6 +2178,19 @@ Re-emit the COMPLETE corrected <response> JSON now: every required field present
2120
2178
  }
2121
2179
  lastResponseText = responseText;
2122
2180
  let status = parseAgentStatus(responseText);
2181
+ if (opts.pause && pendingSteerPause === null) {
2182
+ const localPause = opts.consumeLocalPauseRequest?.() ?? null;
2183
+ if (localPause) {
2184
+ pendingSteerPause = {
2185
+ reason: localPause.reason,
2186
+ correlationKey: null,
2187
+ ...localPause.options && localPause.options.length > 0 ? { payload: { options: localPause.options } } : {},
2188
+ at: Date.now()
2189
+ };
2190
+ blockerStreak = null;
2191
+ continue;
2192
+ }
2193
+ }
2123
2194
  const rawResponse = opts.responseSchema ? parseAgentResponse(responseText) ?? parseRawJsonResponse(responseText) : null;
2124
2195
  const validateResponse = (target) => {
2125
2196
  const parsed = opts.responseSchema.safeParse(target);
@@ -2405,12 +2476,15 @@ class ClaudeRunner {
2405
2476
  toolInput: pre.tool_input ?? {},
2406
2477
  toolUseId: toolUseId ?? `${pre.tool_name}-${Date.now()}`
2407
2478
  };
2479
+ const gateAgentId = this.options.agentId ?? "agent";
2480
+ const gateIteration = opts.iteration ?? 1;
2408
2481
  const gate = await this.gateToolCall(call, {
2409
2482
  requestContext,
2410
2483
  abortSignal: signal ?? opts.signal ?? new AbortController().signal,
2411
2484
  retryCount: 0,
2412
- agentId: this.options.agentId ?? "agent",
2413
- iteration: opts.iteration ?? 1
2485
+ agentId: gateAgentId,
2486
+ iteration: gateIteration,
2487
+ pause: boundProcessorPause(this.options.pause, { agentId: gateAgentId, iteration: gateIteration })
2414
2488
  });
2415
2489
  if (gate.kind === "allow")
2416
2490
  return { hookSpecificOutput: { hookEventName: pre.hook_event_name, permissionDecision: "allow", updatedInput: gate.call.toolInput } };
@@ -2441,6 +2515,14 @@ class ClaudeRunner {
2441
2515
  })();
2442
2516
  }
2443
2517
  const promptForQuery = inboxQueue ?? opts.prompt;
2518
+ const queryAbort = new AbortController;
2519
+ const onLoopAbort = () => queryAbort.abort();
2520
+ if (opts.signal) {
2521
+ if (opts.signal.aborted)
2522
+ queryAbort.abort();
2523
+ else
2524
+ opts.signal.addEventListener("abort", onLoopAbort, { once: true });
2525
+ }
2444
2526
  try {
2445
2527
  let emittedAssistantText = false;
2446
2528
  for await (const message of query({
@@ -2454,7 +2536,8 @@ class ClaudeRunner {
2454
2536
  thinking: resolveThinking(this.model, this.config.thinking),
2455
2537
  effort: this.config.effort,
2456
2538
  cwd: this.options.cwd,
2457
- env: { ...process.env, ...this.config.env ?? {} },
2539
+ abortController: queryAbort,
2540
+ env: { ...process.env, ...this.config.env ?? {}, ...this.options.agentId ? { AGENT_COMPOSE_AGENT_ID: this.options.agentId } : {} },
2458
2541
  pathToClaudeCodeExecutable: this.config.pathToClaudeCodeExecutable ?? process.env.CLAUDE_CODE_EXECUTABLE ?? DEFAULT_CLAUDE_PATH,
2459
2542
  ...this.config.claudeMdContent ? { systemPrompt: { type: "preset", preset: "claude_code", append: this.config.claudeMdContent } } : {},
2460
2543
  skills: this.config.skills ?? "all",
@@ -2485,6 +2568,7 @@ class ClaudeRunner {
2485
2568
  }
2486
2569
  } finally {
2487
2570
  inboxQueue?.close();
2571
+ opts.signal?.removeEventListener("abort", onLoopAbort);
2488
2572
  }
2489
2573
  }
2490
2574
  }
@@ -2495,12 +2579,295 @@ function createClaudeRuntime(config = {}) {
2495
2579
  }
2496
2580
  var claude_default = createClaudeRuntime();
2497
2581
  // src/runtimes/_cli-agent.ts
2582
+ import { ndJsonStream } from "@agentclientprotocol/sdk";
2583
+
2584
+ // src/runtimes/_acp-client.ts
2585
+ import {
2586
+ ClientSideConnection,
2587
+ RequestError
2588
+ } from "@agentclientprotocol/sdk";
2498
2589
  function now3() {
2499
2590
  return new Date().toISOString();
2500
2591
  }
2592
+ var ACP_PROTOCOL_VERSION = 1;
2593
+ var OPTION_KIND_FOR_VERDICT = {
2594
+ allow: ["allow_once", "allow_always"],
2595
+ deny: ["reject_once", "reject_always"]
2596
+ };
2597
+ function contentBlockText(block) {
2598
+ if (!block)
2599
+ return "";
2600
+ return block.type === "text" ? block.text : "";
2601
+ }
2602
+ function renderToolContent(content) {
2603
+ const parts = [];
2604
+ const diffs = [];
2605
+ for (const block of content ?? []) {
2606
+ if (block.type === "content") {
2607
+ parts.push(contentBlockText(block.content));
2608
+ } else if (block.type === "diff") {
2609
+ diffs.push({ path: block.path, oldText: block.oldText ?? null, newText: block.newText });
2610
+ const header = block.oldText == null ? `--- (new file)
2611
+ +++ ${block.path}` : `--- ${block.path}
2612
+ +++ ${block.path}`;
2613
+ parts.push(`${header}
2614
+ ${block.newText}`);
2615
+ } else if (block.type === "terminal") {
2616
+ parts.push(`[terminal ${block.terminalId}]`);
2617
+ }
2618
+ }
2619
+ return { output: parts.filter((p) => p !== "").join(`
2620
+ `), diffs };
2621
+ }
2622
+ function mapLocations(locations) {
2623
+ if (!locations || locations.length === 0)
2624
+ return;
2625
+ return locations.map((l) => l.line != null ? { path: l.path, line: l.line } : { path: l.path });
2626
+ }
2627
+ function terminalToolResult(args) {
2628
+ const { output, diffs } = renderToolContent(args.content);
2629
+ const locations = mapLocations(args.locations);
2630
+ return {
2631
+ type: "tool_result",
2632
+ toolUseId: args.toolCallId,
2633
+ output,
2634
+ isError: args.status === "failed",
2635
+ timestamp: args.timestamp,
2636
+ ...diffs.length > 0 ? { diffs } : {},
2637
+ ...locations ? { locations } : {}
2638
+ };
2639
+ }
2640
+ function normalizeSessionUpdate(update) {
2641
+ const ts = now3();
2642
+ switch (update.sessionUpdate) {
2643
+ case "agent_message_chunk":
2644
+ return [{ type: "text", text: contentBlockText(update.content), timestamp: ts }];
2645
+ case "agent_thought_chunk":
2646
+ return [{ type: "thinking", text: contentBlockText(update.content), timestamp: ts }];
2647
+ case "tool_call": {
2648
+ const messages = [{
2649
+ type: "tool_use",
2650
+ toolName: update.title || update.kind || "tool",
2651
+ toolInput: update.rawInput ?? {},
2652
+ toolUseId: update.toolCallId,
2653
+ timestamp: ts
2654
+ }];
2655
+ if (update.status === "completed" || update.status === "failed") {
2656
+ messages.push(terminalToolResult({
2657
+ toolCallId: update.toolCallId,
2658
+ status: update.status,
2659
+ content: update.content,
2660
+ locations: update.locations,
2661
+ timestamp: ts
2662
+ }));
2663
+ }
2664
+ return messages;
2665
+ }
2666
+ case "tool_call_update": {
2667
+ if (update.status !== "completed" && update.status !== "failed")
2668
+ return [];
2669
+ return [terminalToolResult({
2670
+ toolCallId: update.toolCallId,
2671
+ status: update.status,
2672
+ content: update.content,
2673
+ locations: update.locations,
2674
+ timestamp: ts
2675
+ })];
2676
+ }
2677
+ case "plan":
2678
+ return [{
2679
+ type: "plan",
2680
+ entries: update.entries.map((e) => ({ content: e.content, priority: e.priority, status: e.status })),
2681
+ timestamp: ts
2682
+ }];
2683
+ case "user_message_chunk":
2684
+ case "available_commands_update":
2685
+ case "current_mode_update":
2686
+ case "usage_update":
2687
+ case "plan_update":
2688
+ case "plan_removed":
2689
+ case "config_option_update":
2690
+ case "session_info_update":
2691
+ return [];
2692
+ default:
2693
+ return [];
2694
+ }
2695
+ }
2696
+
2697
+ class AcpClientPeer {
2698
+ deps;
2699
+ conn;
2700
+ updates = new AsyncQueue;
2701
+ sessionId;
2702
+ pendingPause = null;
2703
+ cancelled = false;
2704
+ abortPrompt = null;
2705
+ constructor(deps) {
2706
+ this.deps = deps;
2707
+ const handler = {
2708
+ sessionUpdate: (params) => {
2709
+ for (const msg of normalizeSessionUpdate(params.update))
2710
+ this.updates.push(msg);
2711
+ },
2712
+ requestPermission: (params) => this.onRequestPermission(params)
2713
+ };
2714
+ this.conn = new ClientSideConnection((_agent) => handler, this.deps.stream);
2715
+ if (this.deps.signal) {
2716
+ if (this.deps.signal.aborted)
2717
+ this.cancel();
2718
+ else
2719
+ this.deps.signal.addEventListener("abort", () => this.cancel(), { once: true });
2720
+ }
2721
+ }
2722
+ async initialize() {
2723
+ const res = await this.conn.initialize({
2724
+ protocolVersion: ACP_PROTOCOL_VERSION,
2725
+ clientCapabilities: {
2726
+ fs: { readTextFile: false, writeTextFile: false },
2727
+ terminal: false
2728
+ }
2729
+ });
2730
+ return { protocolVersion: res.protocolVersion };
2731
+ }
2732
+ async startSession(cwd) {
2733
+ const res = await this.conn.newSession({ cwd, mcpServers: [] });
2734
+ this.sessionId = res.sessionId;
2735
+ return res.sessionId;
2736
+ }
2737
+ async* prompt(text) {
2738
+ if (!this.sessionId)
2739
+ throw new Error("AcpClientPeer.prompt called before startSession");
2740
+ const updates = new AsyncQueue;
2741
+ this.updates = updates;
2742
+ const aborted = new Promise((resolve) => {
2743
+ this.abortPrompt = () => {
2744
+ resolve();
2745
+ updates.close();
2746
+ };
2747
+ });
2748
+ if (this.cancelled)
2749
+ this.abortPrompt();
2750
+ const promptPromise = this.conn.prompt({ sessionId: this.sessionId, prompt: [{ type: "text", text }] }).then((res) => {
2751
+ updates.close();
2752
+ return { ok: true, res };
2753
+ }, (err) => {
2754
+ updates.close();
2755
+ return { ok: false, err };
2756
+ });
2757
+ for await (const msg of updates) {
2758
+ yield msg;
2759
+ }
2760
+ if (this.pendingPause !== null) {
2761
+ const pause = this.pendingPause;
2762
+ this.pendingPause = null;
2763
+ this.abortPrompt = null;
2764
+ throw pause;
2765
+ }
2766
+ const settled = await Promise.race([
2767
+ promptPromise.then((o) => ({ kind: "prompt", o })),
2768
+ aborted.then(() => ({ kind: "aborted" }))
2769
+ ]);
2770
+ this.abortPrompt = null;
2771
+ if (settled.kind === "aborted")
2772
+ return;
2773
+ const outcome = settled.o;
2774
+ if (!outcome.ok) {
2775
+ yield { type: "error", text: formatError(outcome.err), timestamp: now3() };
2776
+ return;
2777
+ }
2778
+ const stopReason = outcome.res.stopReason;
2779
+ if (stopReason === "refusal") {
2780
+ yield { type: "error", text: "agent refused the request", timestamp: now3() };
2781
+ return;
2782
+ }
2783
+ const usage = outcome.res.usage;
2784
+ if (usage) {
2785
+ yield {
2786
+ type: "usage",
2787
+ inputTokens: usage.inputTokens,
2788
+ outputTokens: usage.outputTokens,
2789
+ cacheReadTokens: usage.cachedReadTokens ?? 0,
2790
+ cacheCreationTokens: usage.cachedWriteTokens ?? 0,
2791
+ durationMs: 0,
2792
+ numTurns: 1,
2793
+ timestamp: now3()
2794
+ };
2795
+ }
2796
+ }
2797
+ get currentSessionId() {
2798
+ return this.sessionId;
2799
+ }
2800
+ cancel() {
2801
+ if (this.cancelled) {
2802
+ this.abortPrompt?.();
2803
+ return;
2804
+ }
2805
+ this.cancelled = true;
2806
+ if (this.sessionId) {
2807
+ this.conn.cancel({ sessionId: this.sessionId }).catch(() => {});
2808
+ }
2809
+ this.abortPrompt?.();
2810
+ }
2811
+ close() {
2812
+ this.abortPrompt?.();
2813
+ this.updates.close();
2814
+ }
2815
+ async onRequestPermission(params) {
2816
+ if (this.cancelled || this.deps.signal?.aborted) {
2817
+ return { outcome: { outcome: "cancelled" } };
2818
+ }
2819
+ const tc = params.toolCall;
2820
+ const call = {
2821
+ toolName: tc.title || tc.kind || "tool",
2822
+ toolInput: tc.rawInput ?? {},
2823
+ toolUseId: tc.toolCallId
2824
+ };
2825
+ let verdict;
2826
+ try {
2827
+ verdict = await this.deps.gateToolCall(call, this.deps.procCtx());
2828
+ } catch (err) {
2829
+ if (isPauseSignal(err)) {
2830
+ this.pendingPause = err;
2831
+ this.cancel();
2832
+ return { outcome: { outcome: "cancelled" } };
2833
+ }
2834
+ throw err;
2835
+ }
2836
+ if (verdict.kind === "abort") {
2837
+ this.cancel();
2838
+ return { outcome: { outcome: "cancelled" } };
2839
+ }
2840
+ const wantedKinds = verdict.kind === "allow" ? OPTION_KIND_FOR_VERDICT.allow : OPTION_KIND_FOR_VERDICT.deny;
2841
+ const option = params.options.find((o) => wantedKinds.includes(o.kind)) ?? params.options[0];
2842
+ if (!option) {
2843
+ return { outcome: { outcome: "cancelled" } };
2844
+ }
2845
+ return { outcome: { outcome: "selected", optionId: option.optionId } };
2846
+ }
2847
+ }
2848
+
2849
+ // src/runtimes/_cli-agent.ts
2850
+ function now4() {
2851
+ return new Date().toISOString();
2852
+ }
2501
2853
  function shellQuote(value) {
2502
2854
  return `'${value.replace(/'/g, `'\\''`)}'`;
2503
2855
  }
2856
+ var ACP_FALLBACK = Symbol("acp-fallback");
2857
+ var ACP_HANDSHAKE_TIMEOUT_MS = Number(process.env.AC_ACP_HANDSHAKE_TIMEOUT_MS) || 1e4;
2858
+ var ACP_TRANSPORT_READY = true;
2859
+ async function withHandshakeTimeout(p, ms) {
2860
+ let timer;
2861
+ const timeout = new Promise((_resolve, reject) => {
2862
+ timer = setTimeout(() => reject(new Error(`ACP handshake timed out after ${ms}ms`)), ms);
2863
+ });
2864
+ try {
2865
+ return await Promise.race([p, timeout]);
2866
+ } finally {
2867
+ if (timer)
2868
+ clearTimeout(timer);
2869
+ }
2870
+ }
2504
2871
 
2505
2872
  class CliAgentRunner {
2506
2873
  sandbox;
@@ -2508,6 +2875,16 @@ class CliAgentRunner {
2508
2875
  spec;
2509
2876
  configModel;
2510
2877
  kind;
2878
+ get acpCapable() {
2879
+ return this.spec.acp !== undefined && this.acpTransportReady && typeof this.sandbox.commands.spawnDuplex === "function";
2880
+ }
2881
+ get supportsToolCallProcessor() {
2882
+ return this.acpCapable;
2883
+ }
2884
+ acpDecision;
2885
+ acpSession;
2886
+ currentAcpProcCtx;
2887
+ acpTransportReady = ACP_TRANSPORT_READY;
2511
2888
  constructor(sandbox, options, spec, configModel) {
2512
2889
  this.sandbox = sandbox;
2513
2890
  this.options = options;
@@ -2515,9 +2892,37 @@ class CliAgentRunner {
2515
2892
  this.configModel = configModel;
2516
2893
  this.kind = spec.kind;
2517
2894
  }
2895
+ enableAcpForTesting() {
2896
+ this.acpTransportReady = true;
2897
+ }
2518
2898
  get model() {
2519
2899
  return this.configModel ?? this.options.model ?? this.spec.defaultModel;
2520
2900
  }
2901
+ async gateToolCall(call, ctx) {
2902
+ const verdict = await runProcessorChain(this.options.processors ?? [], (p) => p.processToolCall, call, ctx);
2903
+ if (verdict.kind === "continue")
2904
+ return { kind: "allow", call: verdict.value };
2905
+ return verdict;
2906
+ }
2907
+ buildProcCtx(signal, iteration) {
2908
+ const agentId = this.options.agentId ?? "agent";
2909
+ const iter = iteration ?? 1;
2910
+ return {
2911
+ requestContext: this.options.requestContext ?? RequestContext.fromReserved({
2912
+ teamId: "",
2913
+ runId: "",
2914
+ workflowId: "",
2915
+ factoryId: null,
2916
+ apiKeyScopes: [],
2917
+ parentRunId: null
2918
+ }),
2919
+ abortSignal: signal ?? new AbortController().signal,
2920
+ retryCount: 0,
2921
+ agentId,
2922
+ iteration: iter,
2923
+ pause: boundProcessorPause(this.options.pause, { agentId, iteration: iter })
2924
+ };
2925
+ }
2521
2926
  installed = false;
2522
2927
  async ensureInstalled() {
2523
2928
  if (this.installed)
@@ -2534,7 +2939,133 @@ class CliAgentRunner {
2534
2939
  this.installed = true;
2535
2940
  }
2536
2941
  async* sendMessage(opts) {
2537
- yield { type: "init", sessionId: opts.sessionId ?? "", timestamp: now3() };
2942
+ yield { type: "init", sessionId: opts.sessionId ?? "", timestamp: now4() };
2943
+ if (this.acpCapable && this.acpDecision !== "jsonl") {
2944
+ let fellBack = false;
2945
+ try {
2946
+ for await (const ev of this.sendMessageAcp(opts)) {
2947
+ if (ev === ACP_FALLBACK) {
2948
+ fellBack = true;
2949
+ break;
2950
+ }
2951
+ yield ev;
2952
+ }
2953
+ } catch (err) {
2954
+ if (isPauseSignal(err))
2955
+ throw err;
2956
+ yield { type: "error", text: formatError(err), timestamp: now4() };
2957
+ return;
2958
+ }
2959
+ if (!fellBack) {
2960
+ this.acpDecision = "acp";
2961
+ return;
2962
+ }
2963
+ this.acpDecision = "jsonl";
2964
+ }
2965
+ yield* this.sendMessageJsonl(opts);
2966
+ }
2967
+ async teardownAcp(proc, peer) {
2968
+ peer.close();
2969
+ try {
2970
+ await proc.stream.writable.close();
2971
+ } catch {}
2972
+ proc.kill();
2973
+ proc.exited.catch(() => {});
2974
+ }
2975
+ async ensureAcpSession(opts) {
2976
+ if (this.acpSession)
2977
+ return this.acpSession;
2978
+ await this.ensureInstalled();
2979
+ const acp = this.spec.acp;
2980
+ const proc = this.spawnAcpProcess(acp, opts.signal);
2981
+ const peer = new AcpClientPeer({
2982
+ stream: proc.stream,
2983
+ gateToolCall: (call, ctx) => this.gateToolCall(call, ctx),
2984
+ procCtx: () => this.currentAcpProcCtx ?? this.buildProcCtx(opts.signal, opts.iteration),
2985
+ ...opts.signal ? { signal: opts.signal } : {}
2986
+ });
2987
+ let negotiated;
2988
+ try {
2989
+ ({ protocolVersion: negotiated } = await withHandshakeTimeout(peer.initialize(), ACP_HANDSHAKE_TIMEOUT_MS));
2990
+ } catch {
2991
+ await this.teardownAcp(proc, peer);
2992
+ return null;
2993
+ }
2994
+ if (negotiated !== ACP_PROTOCOL_VERSION) {
2995
+ await this.teardownAcp(proc, peer);
2996
+ return null;
2997
+ }
2998
+ let sessionId;
2999
+ try {
3000
+ sessionId = await withHandshakeTimeout(peer.startSession(this.options.cwd ?? "/"), ACP_HANDSHAKE_TIMEOUT_MS);
3001
+ } catch {
3002
+ await this.teardownAcp(proc, peer);
3003
+ return null;
3004
+ }
3005
+ this.acpSession = { proc, peer, sessionId };
3006
+ return this.acpSession;
3007
+ }
3008
+ async* sendMessageAcp(opts) {
3009
+ this.currentAcpProcCtx = this.buildProcCtx(opts.signal, opts.iteration);
3010
+ const session = await this.ensureAcpSession(opts);
3011
+ if (!session) {
3012
+ yield ACP_FALLBACK;
3013
+ return;
3014
+ }
3015
+ const { proc, peer, sessionId } = session;
3016
+ let sawError = false;
3017
+ let stalled = false;
3018
+ let watchdog;
3019
+ const armWatchdog = () => {
3020
+ if (watchdog)
3021
+ clearTimeout(watchdog);
3022
+ watchdog = setTimeout(() => {
3023
+ stalled = true;
3024
+ peer.cancel();
3025
+ }, ACP_HANDSHAKE_TIMEOUT_MS);
3026
+ };
3027
+ try {
3028
+ armWatchdog();
3029
+ for await (const msg of peer.prompt(opts.prompt)) {
3030
+ armWatchdog();
3031
+ if (msg.type === "error")
3032
+ sawError = true;
3033
+ yield msg;
3034
+ }
3035
+ } finally {
3036
+ if (watchdog)
3037
+ clearTimeout(watchdog);
3038
+ }
3039
+ if (stalled && !sawError) {
3040
+ await this.teardownAcp(proc, peer);
3041
+ this.acpSession = undefined;
3042
+ yield { type: "error", text: `${this.spec.kind} prompt turn stalled (no activity for ${ACP_HANDSHAKE_TIMEOUT_MS}ms)`, timestamp: now4() };
3043
+ return;
3044
+ }
3045
+ if (!sawError)
3046
+ yield { type: "done", sessionId: peer.currentSessionId ?? sessionId, timestamp: now4() };
3047
+ }
3048
+ spawnAcpProcess(acp, signal) {
3049
+ const spawnDuplex = this.sandbox.commands.spawnDuplex;
3050
+ if (!spawnDuplex) {
3051
+ throw new Error("spawnAcpProcess called on a provider without commands.spawnDuplex");
3052
+ }
3053
+ const cmd = `${acp.command} ${acp.args.map(shellQuote).join(" ")}`.trim();
3054
+ const agentEnv = { ...acp.env, ...this.options.agentId ? { AGENT_COMPOSE_AGENT_ID: this.options.agentId } : {} };
3055
+ const proc = spawnDuplex.call(this.sandbox.commands, cmd, {
3056
+ ...this.options.cwd ? { cwd: this.options.cwd } : {},
3057
+ ...Object.keys(agentEnv).length > 0 ? { envs: agentEnv } : {}
3058
+ });
3059
+ const kill = () => proc.kill();
3060
+ if (signal) {
3061
+ if (signal.aborted)
3062
+ kill();
3063
+ else
3064
+ signal.addEventListener("abort", kill, { once: true });
3065
+ }
3066
+ return { stream: ndJsonStream(proc.stdin, proc.stdout), exited: proc.exited, kill };
3067
+ }
3068
+ async* sendMessageJsonl(opts) {
2538
3069
  let sessionId = opts.sessionId;
2539
3070
  let sawError = false;
2540
3071
  try {
@@ -2592,13 +3123,13 @@ class CliAgentRunner {
2592
3123
  const res = await runPromise;
2593
3124
  if (res.exitCode !== 0 && !sawError) {
2594
3125
  const tail = (res.stderr ?? "").slice(-2000);
2595
- yield { type: "error", text: `${this.spec.kind} exited with code ${res.exitCode}${tail ? `: ${tail}` : ""}`, timestamp: now3() };
3126
+ yield { type: "error", text: `${this.spec.kind} exited with code ${res.exitCode}${tail ? `: ${tail}` : ""}`, timestamp: now4() };
2596
3127
  return;
2597
3128
  }
2598
3129
  if (!sawError)
2599
- yield { type: "done", sessionId: sessionId ?? "", timestamp: now3() };
3130
+ yield { type: "done", sessionId: sessionId ?? "", timestamp: now4() };
2600
3131
  } catch (err) {
2601
- yield { type: "error", text: formatError(err), timestamp: now3() };
3132
+ yield { type: "error", text: formatError(err), timestamp: now4() };
2602
3133
  }
2603
3134
  }
2604
3135
  }
@@ -2609,13 +3140,19 @@ function createCliAgentRuntime(spec, configModel) {
2609
3140
  }
2610
3141
 
2611
3142
  // src/runtimes/codex.ts
2612
- function now4() {
3143
+ function now5() {
2613
3144
  return new Date().toISOString();
2614
3145
  }
3146
+ var CODEX_ACP_ADAPTER = "@agentclientprotocol/codex-acp@0.1.0";
2615
3147
  var codexSpec = {
2616
3148
  kind: "codex",
2617
3149
  authEnv: "CODEX_API_KEY",
2618
3150
  bin: "codex",
3151
+ acp: {
3152
+ command: "npx",
3153
+ args: ["-y", CODEX_ACP_ADAPTER],
3154
+ ...process.env.CODEX_PATH ? { env: { CODEX_PATH: process.env.CODEX_PATH } } : {}
3155
+ },
2619
3156
  install: 'sudo npm install -g @openai/codex && (command -v codex >/dev/null 2>&1 || sudo ln -sf "$(npm prefix -g)/bin/codex" /usr/local/bin/codex)',
2620
3157
  promptPayload: (prompt) => prompt,
2621
3158
  buildCommand: ({ promptPath, sessionId, model, cwd }) => {
@@ -2631,7 +3168,7 @@ var codexSpec = {
2631
3168
  },
2632
3169
  extractSessionId: (p) => p.type === "thread.started" && typeof p.thread_id === "string" ? p.thread_id : undefined,
2633
3170
  mapEvent: (p) => {
2634
- const ts = now4();
3171
+ const ts = now5();
2635
3172
  switch (p.type) {
2636
3173
  case "item.started":
2637
3174
  case "item.completed": {
@@ -2686,7 +3223,7 @@ function createCodexRuntime(config = {}) {
2686
3223
  }
2687
3224
  var codex_default = createCodexRuntime();
2688
3225
  // src/runtimes/amp.ts
2689
- function now5() {
3226
+ function now6() {
2690
3227
  return new Date().toISOString();
2691
3228
  }
2692
3229
  function blocks(msg) {
@@ -2706,7 +3243,7 @@ var ampSpec = {
2706
3243
  },
2707
3244
  extractSessionId: (p) => typeof p.session_id === "string" ? p.session_id : undefined,
2708
3245
  mapEvent: (p) => {
2709
- const ts = now5();
3246
+ const ts = now6();
2710
3247
  if (p.type === "assistant") {
2711
3248
  return blocks(p).flatMap((b) => {
2712
3249
  if (b.type === "text")
@@ -2751,6 +3288,7 @@ var amp_default = createAmpRuntime();
2751
3288
  import { promises as fs2 } from "node:fs";
2752
3289
  import { dirname as dirname2 } from "node:path";
2753
3290
  import { spawn } from "node:child_process";
3291
+ import { Readable, Writable } from "node:stream";
2754
3292
  import { Sandbox, SandboxNotFoundError, RateLimitError } from "e2b";
2755
3293
  import { Sandbox as Desktop } from "@e2b/desktop";
2756
3294
  import pRetry from "p-retry";
@@ -2779,6 +3317,26 @@ var SANDBOX_VCPUS = {
2779
3317
  "32vcpu-64gb": 32
2780
3318
  };
2781
3319
  var DEFAULT_SANDBOX_SIZE = "2vcpu-4gb";
3320
+ var E2B_TEMPLATE_SIZES = [
3321
+ "2vcpu-4gb",
3322
+ "4vcpu-8gb"
3323
+ ];
3324
+ function isE2bSupportedSize(size) {
3325
+ return E2B_TEMPLATE_SIZES.includes(size);
3326
+ }
3327
+ function e2bMachineSpec(size) {
3328
+ const cpuCount = SANDBOX_VCPUS[size];
3329
+ return { cpuCount, memoryMB: cpuCount * 2048 };
3330
+ }
3331
+ function e2bBaseTemplate(size) {
3332
+ return `agent-compose-base-${size}`;
3333
+ }
3334
+ function e2bAgentEnvTemplate(size) {
3335
+ return `agent-env-${size}`;
3336
+ }
3337
+ function isPlatformE2bTemplateAlias(id) {
3338
+ return E2B_TEMPLATE_SIZES.some((size) => id === e2bBaseTemplate(size) || id === e2bAgentEnvTemplate(size));
3339
+ }
2782
3340
  function makeSandboxProvider(sb) {
2783
3341
  const commands = sb.commands;
2784
3342
  return {
@@ -2786,18 +3344,33 @@ function makeSandboxProvider(sb) {
2786
3344
  commands: {
2787
3345
  async run(cmd, opts) {
2788
3346
  let stderr = "";
2789
- const result = await commands.run(cmd, {
2790
- ...opts,
3347
+ const { sudo, ...rest } = opts ?? {};
3348
+ const runOpts = {
3349
+ ...rest,
3350
+ ...sudo ? { user: "root" } : {},
2791
3351
  onStderr: (chunk) => {
2792
3352
  stderr += chunk;
2793
3353
  opts?.onStderr?.(chunk);
2794
3354
  }
2795
- });
2796
- return {
2797
- exitCode: result.exitCode,
2798
- stdout: result.stdout,
2799
- stderr: typeof result.stderr === "string" ? result.stderr : stderr
2800
3355
  };
3356
+ try {
3357
+ const result = await commands.run(cmd, runOpts);
3358
+ return {
3359
+ exitCode: result.exitCode,
3360
+ stdout: result.stdout,
3361
+ stderr: typeof result.stderr === "string" ? result.stderr : stderr
3362
+ };
3363
+ } catch (e) {
3364
+ const ce = e;
3365
+ if (typeof ce.exitCode === "number") {
3366
+ return {
3367
+ exitCode: ce.exitCode,
3368
+ stdout: typeof ce.stdout === "string" ? ce.stdout : "",
3369
+ stderr: typeof ce.stderr === "string" ? ce.stderr : stderr
3370
+ };
3371
+ }
3372
+ throw e;
3373
+ }
2801
3374
  }
2802
3375
  },
2803
3376
  files: {
@@ -2805,7 +3378,9 @@ function makeSandboxProvider(sb) {
2805
3378
  await sb.files.write(path, content);
2806
3379
  }
2807
3380
  },
2808
- kill: () => sb.kill()
3381
+ async kill() {
3382
+ await sb.kill();
3383
+ }
2809
3384
  };
2810
3385
  }
2811
3386
  function e2bMaxSandboxMs() {
@@ -2832,6 +3407,9 @@ function makeE2bSandboxProvider(sb) {
2832
3407
  async snapshot() {
2833
3408
  const { snapshotId } = await sb.createSnapshot();
2834
3409
  return { snapshotId };
3410
+ },
3411
+ async updateNetworkPolicy(policy) {
3412
+ await sb.updateNetwork(toE2bNetwork(policy));
2835
3413
  }
2836
3414
  };
2837
3415
  }
@@ -2923,6 +3501,31 @@ function toVercelNetworkPolicy(policy) {
2923
3501
  }
2924
3502
  return { ...policy, allow };
2925
3503
  }
3504
+ function toE2bNetwork(policy) {
3505
+ if (policy === "allow-all")
3506
+ return {};
3507
+ if (policy === "deny-all")
3508
+ return { denyOut: ({ allTraffic }) => [allTraffic] };
3509
+ const allowHosts = Array.isArray(policy.allow) ? [...policy.allow] : Object.keys(policy.allow ?? {});
3510
+ const subnetAllow = policy.subnets?.allow ?? [];
3511
+ const allowOut = [...new Set([...allowHosts, ...subnetAllow])];
3512
+ const network = {
3513
+ denyOut: ({ allTraffic }) => [allTraffic]
3514
+ };
3515
+ if (allowOut.length > 0)
3516
+ network.allowOut = allowOut;
3517
+ if (!Array.isArray(policy.allow) && policy.allow) {
3518
+ const rules = {};
3519
+ for (const [host, hostRules] of Object.entries(policy.allow)) {
3520
+ const hostTransforms = hostRules.flatMap((rule) => (rule.transform ?? []).filter((t) => t.headers && Object.keys(t.headers).length > 0).map((t) => ({ transform: { headers: t.headers } })));
3521
+ if (hostTransforms.length > 0)
3522
+ rules[host] = hostTransforms;
3523
+ }
3524
+ if (Object.keys(rules).length > 0)
3525
+ network.rules = rules;
3526
+ }
3527
+ return network;
3528
+ }
2926
3529
  var VERCEL_MAX_TAGS = 5;
2927
3530
  function buildVercelTags(metadata) {
2928
3531
  const tags = { ...metadata, executor: AGENT_COMPOSE_TAG };
@@ -3031,6 +3634,38 @@ function makeLocalSandboxProvider() {
3031
3634
  proc.on("error", reject);
3032
3635
  proc.on("close", (code) => resolve({ exitCode: code ?? 0, stdout, stderr }));
3033
3636
  });
3637
+ },
3638
+ spawnDuplex(cmd, opts) {
3639
+ const proc = spawn("sh", ["-c", cmd], {
3640
+ ...opts?.cwd ? { cwd: opts.cwd } : {},
3641
+ env: { ...process.env, ...opts?.envs ?? {} },
3642
+ stdio: ["pipe", "pipe", "pipe"],
3643
+ detached: true
3644
+ });
3645
+ let stderr = "";
3646
+ proc.stderr.setEncoding("utf8");
3647
+ proc.stderr.on("data", (chunk) => {
3648
+ stderr += chunk;
3649
+ });
3650
+ const exited = new Promise((resolve, reject) => {
3651
+ proc.on("error", reject);
3652
+ proc.on("close", (code) => resolve({ exitCode: code ?? 0, stderr }));
3653
+ });
3654
+ return {
3655
+ stdin: Writable.toWeb(proc.stdin),
3656
+ stdout: Readable.toWeb(proc.stdout),
3657
+ exited,
3658
+ kill() {
3659
+ if (proc.pid === undefined)
3660
+ return;
3661
+ try {
3662
+ process.kill(-proc.pid, "SIGTERM");
3663
+ } catch (err) {
3664
+ if (err.code !== "ESRCH")
3665
+ throw err;
3666
+ }
3667
+ }
3668
+ };
3034
3669
  }
3035
3670
  },
3036
3671
  files: {
@@ -3098,17 +3733,36 @@ var SANDBOX_PROVIDERS = {
3098
3733
  const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
3099
3734
  const snap = await Snapshot.get({ snapshotId, ...creds });
3100
3735
  await snap.delete();
3736
+ },
3737
+ snapshotExists: async (snapshotId, env) => {
3738
+ const { Snapshot } = await import("@vercel/sandbox");
3739
+ const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
3740
+ try {
3741
+ await Snapshot.get({ snapshotId, ...creds });
3742
+ return true;
3743
+ } catch (err) {
3744
+ const message = err instanceof Error ? err.message : String(err ?? "");
3745
+ const status = err?.status ?? err?.statusCode;
3746
+ if (status === 404 || /\b404\b|not[\s_-]?found|no such snapshot/i.test(message))
3747
+ return false;
3748
+ throw err;
3749
+ }
3101
3750
  }
3102
3751
  },
3103
3752
  e2b: {
3104
3753
  requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
3105
- create: async ({ template, timeoutMs, networkPolicy: _np, size: _size, ...rest }) => {
3754
+ create: async ({ template, timeoutMs, networkPolicy, size, ...rest }) => {
3106
3755
  const maxMs = e2bMaxSandboxMs();
3107
3756
  if (timeoutMs > maxMs) {
3108
3757
  console.warn(`[sandbox] e2b create timeout clamped: requested ${timeoutMs}ms exceeds the plan cap ${maxMs}ms — ` + `the sandbox dies at the cap, not the requested deadline. Raise E2B_MAX_SANDBOX_MS on plans that allow more.`);
3109
3758
  }
3110
- const sandboxOpts = { ...rest, timeoutMs: Math.min(timeoutMs, maxMs) };
3111
- const tmpl = template ?? process.env.E2B_DEFAULT_TEMPLATE;
3759
+ const sandboxOpts = {
3760
+ ...rest,
3761
+ timeoutMs: Math.min(timeoutMs, maxMs),
3762
+ ...networkPolicy ? { network: toE2bNetwork(networkPolicy) } : {}
3763
+ };
3764
+ const resolvedSize = size ?? DEFAULT_SANDBOX_SIZE;
3765
+ const tmpl = template ?? (isE2bSupportedSize(resolvedSize) ? e2bBaseTemplate(resolvedSize) : undefined) ?? process.env.E2B_DEFAULT_TEMPLATE;
3112
3766
  return makeE2bSandboxProvider(await (tmpl ? Sandbox.create(tmpl, sandboxOpts) : Sandbox.create(sandboxOpts)));
3113
3767
  },
3114
3768
  reconnect: async (sandboxId) => makeE2bSandboxProvider(await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY ?? "", timeoutMs: 3600000 })),
@@ -3144,10 +3798,16 @@ var SANDBOX_PROVIDERS = {
3144
3798
  },
3145
3799
  "e2b-desktop": {
3146
3800
  requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
3147
- create: async ({ template, timeoutMs, networkPolicy: _np, size: _size, ...rest }) => {
3801
+ create: async ({ template, timeoutMs, networkPolicy, size: _size, ...rest }) => {
3148
3802
  if (!template)
3149
3803
  throw new Error("E2B Desktop provider requires an explicit `template` (Dockerfile-based — no default base image)");
3150
- return makeDesktopSandboxProvider(await Desktop.create(template, { ...rest, timeoutMs: Math.min(timeoutMs, e2bMaxSandboxMs()) }));
3804
+ if (networkPolicy && networkPolicy !== "allow-all") {
3805
+ throw new Error("E2B Desktop provider cannot enforce an egress network policy — `@e2b/desktop` bundles an e2b version that predates the native firewall. " + "e2b-desktop is registry-only and must not run with a confined policy.");
3806
+ }
3807
+ return makeDesktopSandboxProvider(await Desktop.create(template, {
3808
+ ...rest,
3809
+ timeoutMs: Math.min(timeoutMs, e2bMaxSandboxMs())
3810
+ }));
3151
3811
  }
3152
3812
  }
3153
3813
  };
@@ -3180,6 +3840,17 @@ ${missing.join(`
3180
3840
  `)}`);
3181
3841
  return def.deleteSnapshot(snapshotId, Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k]])));
3182
3842
  }
3843
+ async function snapshotResolves(provider, snapshotId) {
3844
+ const def = SANDBOX_PROVIDERS[provider];
3845
+ if (!def?.snapshotExists)
3846
+ throw new Error(`Provider "${provider}" does not support snapshotExists`);
3847
+ const missing = Object.entries(def.requiredEnv).filter(([k]) => !process.env[k]).map(([k, desc]) => ` ${k} — ${desc}`);
3848
+ if (missing.length > 0)
3849
+ throw new Error(`Sandbox provider "${provider}" requires env vars:
3850
+ ${missing.join(`
3851
+ `)}`);
3852
+ return def.snapshotExists(snapshotId, Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k]])));
3853
+ }
3183
3854
  async function getSandboxQuotas() {
3184
3855
  const out = {};
3185
3856
  await Promise.all(Object.entries(SANDBOX_PROVIDERS).map(async ([name, def]) => {
@@ -3235,17 +3906,34 @@ class StepObservabilityCollector {
3235
3906
  events = [];
3236
3907
  subSteps = [];
3237
3908
  liveEmitter;
3909
+ memoize;
3910
+ usedStepNames = new Set;
3238
3911
  ackedSeqs = new Set;
3239
3912
  inFlight = new Set;
3240
3913
  constructor(opts = {}) {
3241
3914
  this.liveEmitter = opts.liveEmitter;
3915
+ this.memoize = opts.memoize;
3242
3916
  }
3243
3917
  setMetadata = async (data) => {
3244
3918
  Object.assign(this.metadata, data);
3245
3919
  };
3246
3920
  step = async (name, fn) => {
3921
+ if (this.usedStepNames.has(name)) {
3922
+ throw new Error(`ctx.step("${name}", …) reuses a step name already used in this step body. ` + `Durable step names must be unique — they key the resume-time memoise file.`);
3923
+ }
3924
+ this.usedStepNames.add(name);
3247
3925
  const startedAt = Date.now();
3248
3926
  try {
3927
+ if (this.memoize) {
3928
+ const { value, restored } = await this.memoize(name, fn);
3929
+ this.subSteps.push({
3930
+ name,
3931
+ startedAt,
3932
+ durationMs: restored ? 0 : Date.now() - startedAt,
3933
+ status: restored ? "restored" : "completed"
3934
+ });
3935
+ return value;
3936
+ }
3249
3937
  const result = await fn();
3250
3938
  this.subSteps.push({
3251
3939
  name,
@@ -3309,19 +3997,19 @@ class StepObservabilityCollector {
3309
3997
  }
3310
3998
 
3311
3999
  // src/pause/checkpoint.ts
3312
- async function checkpoint(name, fn) {
4000
+ async function memoize(name, fn) {
3313
4001
  const existing = await readCheckpoint(name);
3314
4002
  if (existing.found)
3315
- return existing.value;
4003
+ return { value: existing.value, restored: true };
3316
4004
  const value = await fn();
3317
4005
  await writeCheckpoint(name, value);
3318
- return value;
4006
+ return { value, restored: false };
3319
4007
  }
3320
- function scopedCheckpoint(scope) {
4008
+ function scopedMemoize(scope) {
3321
4009
  assertSafeCheckpointName(scope);
3322
4010
  return async (name, fn) => {
3323
4011
  assertSafeCheckpointName(name);
3324
- return checkpoint(`${scope}.${name}`, fn);
4012
+ return memoize(`${scope}.${name}`, fn);
3325
4013
  };
3326
4014
  }
3327
4015
 
@@ -3389,7 +4077,10 @@ async function runWorkflowSingleStep(opts) {
3389
4077
  const parsedInput = step.input.safeParse(opts.input);
3390
4078
  if (!parsedInput.success)
3391
4079
  throw new StepValidationError(step.name, "input", parsedInput.error);
3392
- const collector = new StepObservabilityCollector({ liveEmitter: opts.liveAgentEventEmitter });
4080
+ const collector = new StepObservabilityCollector({
4081
+ liveEmitter: opts.liveAgentEventEmitter,
4082
+ memoize: scopedMemoize(`step${opts.stepIndex}`)
4083
+ });
3393
4084
  const coord = { runId: opts.run.id, stepIndex: opts.stepIndex };
3394
4085
  const pauseWithKind = function(req, kind) {
3395
4086
  return corePause(req, coord, kind);
@@ -3410,7 +4101,6 @@ async function runWorkflowSingleStep(opts) {
3410
4101
  setMetadata: collector.setMetadata,
3411
4102
  step: collector.step,
3412
4103
  agentEvents: collector.agentEvents,
3413
- checkpoint: scopedCheckpoint(`step${opts.stepIndex}`),
3414
4104
  pause,
3415
4105
  ...buildPauseWrappers(pauseWithKind)
3416
4106
  };
@@ -3466,7 +4156,10 @@ async function runWorkflowSteps(opts) {
3466
4156
  await opts.onStepFailed?.(i, step.name, err, 0);
3467
4157
  throw err;
3468
4158
  }
3469
- const collector = new StepObservabilityCollector({ liveEmitter: opts.liveAgentEventEmitter });
4159
+ const collector = new StepObservabilityCollector({
4160
+ liveEmitter: opts.liveAgentEventEmitter,
4161
+ memoize: scopedMemoize(`step${i}`)
4162
+ });
3470
4163
  const coord = { runId: run.id, stepIndex: i };
3471
4164
  const pauseWithKind = function(req, kind) {
3472
4165
  return corePause(req, coord, kind);
@@ -3485,7 +4178,6 @@ async function runWorkflowSteps(opts) {
3485
4178
  setMetadata: collector.setMetadata,
3486
4179
  step: collector.step,
3487
4180
  agentEvents: collector.agentEvents,
3488
- checkpoint: scopedCheckpoint(`step${i}`),
3489
4181
  pause,
3490
4182
  ...buildPauseWrappers(pauseWithKind)
3491
4183
  };
@@ -3682,32 +4374,32 @@ function stepResultFilePath(token) {
3682
4374
  }
3683
4375
  // src/step-invocation/invoker.ts
3684
4376
  import { randomBytes as randomBytes2 } from "node:crypto";
3685
- import { z as z10 } from "zod";
3686
- var StepObservabilitySchema = z10.object({
3687
- metadata: z10.record(z10.string(), z10.unknown()).optional(),
3688
- events: z10.array(z10.unknown()).optional(),
3689
- subSteps: z10.array(z10.object({
3690
- name: z10.string(),
3691
- startedAt: z10.number(),
3692
- durationMs: z10.number(),
3693
- status: z10.enum(["completed", "failed"]),
3694
- error: z10.string().optional()
4377
+ import { z as z11 } from "zod";
4378
+ var StepObservabilitySchema = z11.object({
4379
+ metadata: z11.record(z11.string(), z11.unknown()).optional(),
4380
+ events: z11.array(z11.unknown()).optional(),
4381
+ subSteps: z11.array(z11.object({
4382
+ name: z11.string(),
4383
+ startedAt: z11.number(),
4384
+ durationMs: z11.number(),
4385
+ status: z11.enum(["completed", "failed"]),
4386
+ error: z11.string().optional()
3695
4387
  })).optional()
3696
4388
  }).passthrough();
3697
- var StepResultEnvelopeSchema = z10.discriminatedUnion("ok", [
3698
- z10.object({
3699
- ok: z10.literal(true),
3700
- output: z10.unknown().optional(),
4389
+ var StepResultEnvelopeSchema = z11.discriminatedUnion("ok", [
4390
+ z11.object({
4391
+ ok: z11.literal(true),
4392
+ output: z11.unknown().optional(),
3701
4393
  observability: StepObservabilitySchema.optional()
3702
4394
  }),
3703
- z10.object({
3704
- ok: z10.literal(false),
3705
- kind: z10.enum(["protocol", "user-step"]),
3706
- error: z10.string().optional()
4395
+ z11.object({
4396
+ ok: z11.literal(false),
4397
+ kind: z11.enum(["protocol", "user-step"]),
4398
+ error: z11.string().optional()
3707
4399
  })
3708
4400
  ]);
3709
- var StepPauseEnvelopeSchema = z10.object({
3710
- pauseId: z10.string().min(1),
4401
+ var StepPauseEnvelopeSchema = z11.object({
4402
+ pauseId: z11.string().min(1),
3711
4403
  pauseRequest: StepPauseRequestSchema,
3712
4404
  observability: StepObservabilitySchema.optional()
3713
4405
  });
@@ -4000,8 +4692,43 @@ async function serveStep(handler) {
4000
4692
  await emitResult(token, payload);
4001
4693
  process.exit(exitCode);
4002
4694
  }
4695
+ // src/agent/local-pause-request.ts
4696
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
4697
+ import { join as join3 } from "node:path";
4698
+ import { randomBytes as randomBytes3 } from "node:crypto";
4699
+ var UNSCOPED = "_unscoped";
4700
+ var requestsDir = () => join3(getStateDir(), "pause-requests");
4701
+ var markerPath = (agentId) => join3(requestsDir(), `${slug(agentId) || UNSCOPED}.json`);
4702
+ function slug(agentId) {
4703
+ return (agentId ?? "").replace(/[^a-zA-Z0-9_.-]/g, "_");
4704
+ }
4705
+ function writeLocalPauseRequest(agentId, req) {
4706
+ mkdirSync2(requestsDir(), { recursive: true });
4707
+ const final = markerPath(agentId);
4708
+ const tmp = `${final}.${randomBytes3(6).toString("hex")}.tmp`;
4709
+ writeFileSync2(tmp, JSON.stringify(req), "utf8");
4710
+ renameSync(tmp, final);
4711
+ }
4712
+ function consumeLocalPauseRequest(agentId) {
4713
+ const paths = [...new Set([markerPath(agentId), markerPath(null)])];
4714
+ for (const path of paths) {
4715
+ if (!existsSync(path))
4716
+ continue;
4717
+ try {
4718
+ const raw = JSON.parse(readFileSync2(path, "utf8"));
4719
+ rmSync(path, { force: true });
4720
+ if (raw && typeof raw === "object" && typeof raw.reason === "string" && raw.reason.trim().length > 0) {
4721
+ const r = raw;
4722
+ return { reason: r.reason.trim(), ...Array.isArray(r.options) && r.options.length > 0 ? { options: r.options } : {} };
4723
+ }
4724
+ } catch {
4725
+ rmSync(path, { force: true });
4726
+ }
4727
+ }
4728
+ return null;
4729
+ }
4003
4730
  // src/agent/run-agent.ts
4004
- import { z as z11 } from "zod";
4731
+ import { z as z12 } from "zod";
4005
4732
  import { randomUUID as randomUUID2 } from "node:crypto";
4006
4733
 
4007
4734
  // src/agent/agent-context.ts
@@ -4065,11 +4792,19 @@ Use \`/ac:generate-workflow\` / \`/ac:generate-agent\` to scaffold, then
4065
4792
 
4066
4793
  ## Pausing to ask the human — \`agentc pause\`
4067
4794
 
4068
- When you can't or shouldn't proceed without a human, run \`agentc pause\`. It
4069
- blocks until they answer on the dashboard, then prints their answer to stdout:
4795
+ When you can't or shouldn't proceed without a human, run \`agentc pause\`, then
4796
+ **END YOUR TURN**:
4797
+
4798
+ agentc pause --reason "Notion returned 401 — connect Notion to continue" \\
4799
+ --option retry --option skip
4070
4800
 
4071
- ANSWER=$(agentc pause --reason "Notion returned 401 — connect Notion to continue" \\
4072
- --option retry --option skip)
4801
+ \`agentc pause\` does NOT block and does NOT print the answer. It records your
4802
+ question and returns immediately. The moment you end your turn, the run pauses
4803
+ (your sandbox is snapshotted and compute stops while the human decides) and the
4804
+ human's answer is delivered to you as your **next message** — you pick up
4805
+ exactly where you left off, with the answer in hand. So: ask, end your turn,
4806
+ and wait. Do NOT keep working, do NOT call more tools, and do NOT mark the task
4807
+ complete after pausing.
4073
4808
 
4074
4809
  Reach for it the moment you hit — or foresee — any of these:
4075
4810
  - **A wall only a human can clear:** a 401/403, a missing credential, an
@@ -4083,8 +4818,8 @@ Reach for it the moment you hit — or foresee — any of these:
4083
4818
  several valid paths, a conflict with existing state, missing input only they have.
4084
4819
 
4085
4820
  You compose the \`--reason\` (the ask) yourself; pass \`--option\` choices when
4086
- there are clear ones, omit them for a free-form answer. Read the printed answer
4087
- and act on it. Each agent pauses independently — pausing doesn't stop the others.
4821
+ there are clear ones, omit them for a free-form answer. Each agent pauses
4822
+ independently — pausing doesn't stop the others.
4088
4823
 
4089
4824
  ## Credentials
4090
4825
 
@@ -4206,29 +4941,29 @@ function stripFrontmatter(content) {
4206
4941
  return end === -1 ? content : content.slice(end + 4).trimStart();
4207
4942
  }
4208
4943
  function zodTypeName(schema) {
4209
- if (schema instanceof z11.ZodString)
4944
+ if (schema instanceof z12.ZodString)
4210
4945
  return "string";
4211
- if (schema instanceof z11.ZodNumber)
4946
+ if (schema instanceof z12.ZodNumber)
4212
4947
  return "number";
4213
- if (schema instanceof z11.ZodBoolean)
4948
+ if (schema instanceof z12.ZodBoolean)
4214
4949
  return "boolean";
4215
- if (schema instanceof z11.ZodArray)
4950
+ if (schema instanceof z12.ZodArray)
4216
4951
  return `${zodTypeName(schema.element)}[]`;
4217
- if (schema instanceof z11.ZodEnum)
4952
+ if (schema instanceof z12.ZodEnum)
4218
4953
  return schema.options.map((o) => `"${o}"`).join(" | ");
4219
- if (schema instanceof z11.ZodOptional)
4954
+ if (schema instanceof z12.ZodOptional)
4220
4955
  return `${zodTypeName(schema.unwrap())} (optional)`;
4221
- if (schema instanceof z11.ZodNullable)
4956
+ if (schema instanceof z12.ZodNullable)
4222
4957
  return `${zodTypeName(schema.unwrap())} | null`;
4223
- if (schema instanceof z11.ZodLiteral)
4958
+ if (schema instanceof z12.ZodLiteral)
4224
4959
  return `"${String(schema.value)}"`;
4225
- if (schema instanceof z11.ZodObject)
4960
+ if (schema instanceof z12.ZodObject)
4226
4961
  return "object";
4227
4962
  return "any";
4228
4963
  }
4229
4964
  function buildResponseFormatAppendix(schema) {
4230
4965
  const header = ["## Response Format", "", "When you emit `exit_signal: true` in your `<status>` block, also include a `<response>` block containing JSON immediately after `<status>`:", ""];
4231
- const shape = schema instanceof z11.ZodObject ? schema.shape : null;
4966
+ const shape = schema instanceof z12.ZodObject ? schema.shape : null;
4232
4967
  const body = shape ? (() => {
4233
4968
  const entries = Object.entries(shape);
4234
4969
  const example = Object.fromEntries(entries.map(([k, v]) => [k, `<${zodTypeName(v)}>`]));
@@ -4358,6 +5093,7 @@ async function agent(opts) {
4358
5093
  ...inbox ? { inbox } : {},
4359
5094
  mode,
4360
5095
  consumeSteerPending: () => consumeSteerPending(agentId),
5096
+ consumeLocalPauseRequest: () => consumeLocalPauseRequest(agentId),
4361
5097
  ...steerPause ? { pause: steerPause } : {}
4362
5098
  });
4363
5099
  } finally {
@@ -4367,8 +5103,10 @@ async function agent(opts) {
4367
5103
  }
4368
5104
  export {
4369
5105
  writeTool,
5106
+ writeLocalPauseRequest,
4370
5107
  writeAgentContext,
4371
5108
  stepInputPath,
5109
+ snapshotResolves,
4372
5110
  serveStep,
4373
5111
  runWorkflowSteps,
4374
5112
  runWorkflowSingleStep,
@@ -4392,11 +5130,17 @@ export {
4392
5130
  killSandboxById,
4393
5131
  killAllSandboxes,
4394
5132
  isWorkflow,
5133
+ isPlatformE2bTemplateAlias,
5134
+ isE2bSupportedSize,
4395
5135
  invokeStep,
4396
5136
  importSourceModule,
5137
+ humanApproval,
4397
5138
  getSandboxQuotas,
4398
5139
  formatError,
4399
5140
  editTool,
5141
+ e2bMachineSpec,
5142
+ e2bBaseTemplate,
5143
+ e2bAgentEnvTemplate,
4400
5144
  denyTools,
4401
5145
  deleteSandboxSnapshot,
4402
5146
  defineWorkflow,
@@ -4408,6 +5152,7 @@ export {
4408
5152
  createCodexRuntime,
4409
5153
  createClaudeRuntime,
4410
5154
  createAmpRuntime,
5155
+ consumeLocalPauseRequest,
4411
5156
  codingTools,
4412
5157
  codex_default as codexRuntime,
4413
5158
  claude_default as claudeRuntime,
@@ -4434,6 +5179,7 @@ export {
4434
5179
  STEP_RESULT_PREFIX,
4435
5180
  STEP_PAUSE_PREFIX,
4436
5181
  STEP_ENV,
5182
+ SANDBOX_VCPUS,
4437
5183
  SANDBOX_UNAVAILABLE_PREFIX,
4438
5184
  ReservedKeyError,
4439
5185
  RequestContext,
@@ -4446,6 +5192,8 @@ export {
4446
5192
  NonSerialisableValueError,
4447
5193
  LATEST_VERSION,
4448
5194
  EngineError,
5195
+ E2B_TEMPLATE_SIZES,
5196
+ DEFAULT_SANDBOX_SIZE,
4449
5197
  DEFAULT_CLAUDE_MODEL,
4450
5198
  ClaudeRunner,
4451
5199
  BUNDLER_VERSION,