@agent-compose/sdk 0.5.7 → 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 (76) hide show
  1. package/dist/agent/__tests__/run-agent-liveness.test.d.ts +17 -0
  2. package/dist/agent/agent-context.d.ts +67 -0
  3. package/dist/agent/agent-loop.d.ts +23 -12
  4. package/dist/agent/local-pause-request.d.ts +49 -0
  5. package/dist/agent/local-pause-request.test.d.ts +1 -0
  6. package/dist/agent/steer-control.d.ts +22 -6
  7. package/dist/client.d.ts +76 -2
  8. package/dist/index.d.ts +10 -5
  9. package/dist/index.js +2409 -1457
  10. package/dist/pause/checkpoint.d.ts +27 -10
  11. package/dist/pause/manager.d.ts +1 -0
  12. package/dist/pause/pause-core.d.ts +23 -0
  13. package/dist/pause/state-dir.d.ts +1 -1
  14. package/dist/pause/wrappers.d.ts +7 -11
  15. package/dist/processors/builtins.d.ts +20 -1
  16. package/dist/processors/index.d.ts +1 -1
  17. package/dist/processors/processor.d.ts +13 -0
  18. package/dist/runtimes/_acp-client.d.ts +140 -0
  19. package/dist/runtimes/_cli-agent.d.ts +155 -3
  20. package/dist/runtimes/amp.d.ts +2 -2
  21. package/dist/runtimes/cli-agent-acp-live.test.d.ts +30 -0
  22. package/dist/runtimes/cli-agent.test.d.ts +22 -6
  23. package/dist/runtimes/codex.d.ts +7 -2
  24. package/dist/runtimes/openai-desktop.js +2394 -1457
  25. package/dist/runtimes/vercel.js +389 -2
  26. package/dist/sandbox.d.ts +132 -14
  27. package/dist/step-invocation/types.d.ts +1 -1
  28. package/dist/types/__tests__/environment-build-flag.test.d.ts +1 -0
  29. package/dist/types/__tests__/workflow-metadata-provider.test.d.ts +1 -0
  30. package/dist/types/execution-context.d.ts +1 -11
  31. package/dist/types/protocol.d.ts +32 -1
  32. package/dist/types/runtime.d.ts +7 -0
  33. package/dist/types/sandbox-environment.d.ts +6 -1
  34. package/dist/types/sandbox.d.ts +41 -6
  35. package/dist/types/workflow-metadata.d.ts +47 -6
  36. package/dist/types/workflow.d.ts +27 -4
  37. package/dist/utils/bundler.d.ts +7 -1
  38. package/dist/workflow-steps/observability.d.ts +28 -2
  39. package/dist/workflow-steps/types.d.ts +11 -7
  40. package/dist/workflow-steps/workflow.d.ts +5 -1
  41. package/package.json +3 -2
  42. package/src/agent/agent-context.ts +220 -0
  43. package/src/agent/agent-loop.ts +90 -22
  44. package/src/agent/local-pause-request.ts +90 -0
  45. package/src/agent/run-agent.ts +43 -3
  46. package/src/agent/steer-control.ts +21 -7
  47. package/src/client.ts +123 -2
  48. package/src/index.ts +16 -4
  49. package/src/pause/checkpoint.ts +33 -14
  50. package/src/pause/manager.ts +2 -2
  51. package/src/pause/pause-core.ts +35 -0
  52. package/src/pause/state-dir.ts +2 -2
  53. package/src/pause/wrappers.ts +7 -21
  54. package/src/processors/builtins.ts +44 -1
  55. package/src/processors/index.ts +1 -0
  56. package/src/processors/processor.ts +13 -0
  57. package/src/runtimes/_acp-client.ts +516 -0
  58. package/src/runtimes/_cli-agent.ts +418 -3
  59. package/src/runtimes/claude.ts +27 -3
  60. package/src/runtimes/codex.ts +21 -1
  61. package/src/runtimes/vercel.ts +4 -1
  62. package/src/sandbox.ts +429 -67
  63. package/src/step-invocation/types.ts +1 -1
  64. package/src/types/execution-context.ts +1 -11
  65. package/src/types/protocol.ts +27 -1
  66. package/src/types/runtime.ts +7 -0
  67. package/src/types/sandbox-environment.ts +12 -1
  68. package/src/types/sandbox.ts +40 -6
  69. package/src/types/workflow-metadata.ts +51 -6
  70. package/src/types/workflow.ts +27 -6
  71. package/src/utils/bundler.ts +9 -1
  72. package/src/workflow-steps/observability.ts +51 -5
  73. package/src/workflow-steps/runner.ts +9 -5
  74. package/src/workflow-steps/types.ts +11 -7
  75. package/src/workflow-steps/workflow.ts +5 -1
  76. package/src/workflows/invoke-child.ts +7 -1
package/dist/index.js CHANGED
@@ -330,1467 +330,1537 @@ class RequestContext {
330
330
  }
331
331
  }
332
332
 
333
- // src/utils/errors.ts
334
- function formatError(err) {
335
- return err instanceof Error ? err.message : String(err);
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
+ }
336
356
  }
337
357
 
338
- // src/runtimes/vercel.ts
339
- function now() {
340
- return new Date().toISOString();
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 };
341
366
  }
342
- function serialiseOutput(value) {
343
- return typeof value === "string" ? value : JSON.stringify(value ?? "");
367
+ function makeState(step) {
368
+ return {
369
+ stepIndex: step.stepIndex,
370
+ nextAgentCallIndex: 0,
371
+ pauseOrdinalByScope: new Map
372
+ };
344
373
  }
345
- function toAgentMessages(part) {
346
- const ts = now();
347
- switch (part.type) {
348
- case "text-delta":
349
- case "text":
350
- return [{ type: "text", text: String(part.text ?? ""), timestamp: ts }];
351
- case "reasoning-delta":
352
- case "reasoning":
353
- return [{ type: "thinking", text: String(part.text ?? ""), timestamp: ts }];
354
- case "tool-call":
355
- return [{
356
- type: "tool_use",
357
- toolName: String(part.toolName ?? ""),
358
- toolInput: part.input ?? {},
359
- toolUseId: String(part.toolCallId ?? ""),
360
- timestamp: ts
361
- }];
362
- case "tool-result":
363
- return [{
364
- type: "tool_result",
365
- toolUseId: String(part.toolCallId ?? ""),
366
- output: serialiseOutput(part.output),
367
- isError: false,
368
- timestamp: ts
369
- }];
370
- case "tool-error":
371
- return [{
372
- type: "tool_result",
373
- toolUseId: String(part.toolCallId ?? ""),
374
- output: formatError(part.error),
375
- isError: true,
376
- timestamp: ts
377
- }];
378
- case "error":
379
- return [{ type: "error", text: formatError(part.error), timestamp: ts }];
380
- default:
381
- return [];
382
- }
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;
383
408
  }
384
409
 
385
- class VercelRunner {
386
- sandbox;
387
- options;
388
- config;
389
- supportsToolCallProcessor = true;
390
- kind;
391
- model;
392
- tools;
393
- messages = [];
394
- constructor(sandbox, options, config) {
395
- this.sandbox = sandbox;
396
- this.options = options;
397
- this.config = config;
398
- this.tools = config.tools ?? codingTools;
399
- this.kind = config.kind;
400
- this.model = config.modelId;
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)})`);
401
427
  }
402
- async gateToolCall(call, ctx) {
403
- const verdict = await runProcessorChain(this.options.processors ?? [], (p) => p.processToolCall, call, ctx);
404
- if (verdict.kind === "continue")
405
- return { kind: "allow", call: verdict.value };
406
- return verdict;
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");
407
442
  }
408
- captureCheckpoint() {
409
- return { messages: [...this.messages] };
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();
410
449
  }
411
- restoreCheckpoint(blob) {
412
- if (!blob || typeof blob !== "object") {
413
- throw new Error("Vercel runtime checkpoint is invalid: expected an object with messages[]");
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`);
414
510
  }
415
- const incoming = blob.messages;
416
- if (!Array.isArray(incoming)) {
417
- throw new Error("Vercel runtime checkpoint is invalid: expected messages[]");
511
+ if (!hasOwn(record, "resumePayload") || record.resumePayload === undefined) {
512
+ throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require resumePayload`);
418
513
  }
419
- this.messages.length = 0;
420
- this.messages.push(...incoming);
514
+ return {
515
+ pauseId: expectedPauseId,
516
+ request: hasOwn(record, "request") ? record.request : null,
517
+ ...record
518
+ };
421
519
  }
422
- buildTools(iteration, signal) {
423
- const set = {};
424
- for (const t of this.tools) {
425
- if (this.options.allowedTools && !this.options.allowedTools.includes(t.name))
426
- continue;
427
- set[t.name] = tool({
428
- description: t.description,
429
- inputSchema: t.inputSchema,
430
- execute: async (input, execOpts) => {
431
- const call = {
432
- toolName: t.name,
433
- toolInput: input,
434
- toolUseId: execOpts.toolCallId ?? `${t.name}-${Date.now()}`
435
- };
436
- const requestContext = this.options.requestContext ?? RequestContext.fromReserved({
437
- teamId: "",
438
- runId: "",
439
- workflowId: "",
440
- factoryId: null,
441
- apiKeyScopes: [],
442
- parentRunId: null
443
- });
444
- const gate = await this.gateToolCall(call, {
445
- requestContext,
446
- abortSignal: execOpts.abortSignal ?? signal ?? new AbortController().signal,
447
- retryCount: 0,
448
- agentId: this.options.agentId ?? "agent",
449
- iteration
450
- });
451
- if (gate.kind === "deny")
452
- return gate.reason;
453
- if (gate.kind === "abort")
454
- throw new Error(gate.reason);
455
- return t.execute(gate.call.toolInput, {
456
- sandbox: this.sandbox,
457
- cwd: this.options.cwd,
458
- abortSignal: execOpts.abortSignal ?? signal
459
- });
460
- }
461
- });
462
- }
463
- return set;
520
+ if (record.pauseId !== expectedPauseId) {
521
+ throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require matching pauseId`);
464
522
  }
465
- async* sendMessage(opts) {
466
- const sessionId = opts.sessionId ?? `vercel-${Date.now()}`;
467
- yield { type: "init", sessionId, timestamp: now() };
468
- const inputMessages = [...this.messages, { role: "user", content: opts.prompt }];
469
- const result = streamText({
470
- model: this.config.model,
471
- ...this.config.system ? { system: this.config.system } : {},
472
- messages: inputMessages,
473
- tools: this.buildTools(opts.iteration ?? 1, opts.signal),
474
- stopWhen: stepCountIs(this.options.maxTurns ?? 40),
475
- abortSignal: opts.signal
476
- });
477
- try {
478
- for await (const part of result.fullStream) {
479
- for (const msg of toAgentMessages(part))
480
- yield msg;
481
- }
482
- const response = await result.response;
483
- const responseMessages = response.messages;
484
- if (responseMessages) {
485
- this.messages.length = 0;
486
- this.messages.push(...responseMessages);
487
- }
488
- let usage;
489
- try {
490
- usage = await result.usage;
491
- } catch {
492
- usage = undefined;
493
- }
494
- if (usage) {
495
- yield {
496
- type: "usage",
497
- inputTokens: usage.inputTokens ?? 0,
498
- outputTokens: usage.outputTokens ?? 0,
499
- cacheReadTokens: usage.inputTokenDetails?.cacheReadTokens ?? 0,
500
- cacheCreationTokens: usage.inputTokenDetails?.cacheWriteTokens ?? 0,
501
- durationMs: 0,
502
- numTurns: opts.iteration ?? 1,
503
- timestamp: now()
504
- };
505
- }
506
- } catch (err) {
507
- yield { type: "error", text: formatError(err), timestamp: now() };
508
- return;
509
- }
510
- yield { type: "done", sessionId, timestamp: now() };
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`);
511
531
  }
532
+ return record;
512
533
  }
513
- function createVercelRuntime(config) {
514
- return defineRuntime({
515
- create: (sandbox, opts) => new VercelRunner(sandbox, opts, config)
516
- });
534
+ async function readPauseRecord(pauseId) {
535
+ assertSafeName("pause", pauseId);
536
+ const record = await readJSON(pausePath(pauseId));
537
+ return record === null ? null : validatePauseRecord(record, pauseId);
517
538
  }
518
- async function listVercelRuntimeModels() {
519
- const { models } = await gateway.getAvailableModels();
520
- return models.filter((m) => m.modelType == null || m.modelType === "language").map((m) => ({ id: m.id, name: m.name })).sort((a, b) => a.id.localeCompare(b.id));
539
+ async function writePauseRecord(record) {
540
+ assertSafeName("pause", record.pauseId);
541
+ validatePauseRecord(record, record.pauseId);
542
+ await atomicWriteJSON(pausePath(record.pauseId), record);
521
543
  }
522
- // src/types/workflow.ts
523
- import { z as z3 } from "zod";
524
544
 
525
- // src/workflow-steps/types.ts
526
- var WORKFLOW_BRAND = Symbol.for("@agent-compose/sdk.Workflow");
545
+ // src/pause/errors.ts
546
+ class PauseError extends Error {
547
+ constructor(message) {
548
+ super(message);
549
+ this.name = new.target.name;
550
+ }
551
+ }
527
552
 
528
- // src/types/workflow-metadata.ts
529
- function extractMetadata(source) {
530
- const out = {};
531
- if (source.description !== undefined)
532
- out.description = source.description;
533
- if (source.networkPolicy !== undefined)
534
- out.networkPolicy = freezeMetadataValue(source.networkPolicy);
535
- if (source.placeholders !== undefined)
536
- out.placeholders = Object.freeze({ ...source.placeholders });
537
- if (source.inputSchema !== undefined)
538
- out.inputSchema = freezeMetadataValue(source.inputSchema);
539
- if (source.outputSchema !== undefined)
540
- out.outputSchema = freezeMetadataValue(source.outputSchema);
541
- if (source.snapshots !== undefined)
542
- out.snapshots = Object.freeze({ ...source.snapshots });
543
- if (source.processors !== undefined)
544
- out.processors = Object.freeze([...source.processors]);
545
- if (source.connectors !== undefined)
546
- out.connectors = freezeMetadataValue(source.connectors);
547
- if (source.connectorOperation !== undefined)
548
- out.connectorOperation = Object.freeze({ ...source.connectorOperation });
549
- if (source.invokePolicy !== undefined)
550
- out.invokePolicy = freezeMetadataValue(source.invokePolicy);
551
- return Object.freeze(out);
553
+ class PauseExpiredError extends PauseError {
554
+ code = "pause_expired";
555
+ constructor(message = "pause expired before it was resolved") {
556
+ super(message);
557
+ }
552
558
  }
553
- function freezeMetadataValue(value) {
554
- if (value === null || typeof value !== "object")
555
- return value;
556
- if (Array.isArray(value)) {
557
- for (const v of value)
558
- freezeMetadataValue(v);
559
- return Object.freeze(value);
559
+
560
+ class PauseSchemaError extends PauseError {
561
+ code = "pause_schema";
562
+ issues;
563
+ constructor(message, issues) {
564
+ super(message);
565
+ this.issues = issues;
560
566
  }
561
- for (const v of Object.values(value)) {
562
- freezeMetadataValue(v);
567
+ }
568
+
569
+ class PauseRequestError extends PauseError {
570
+ code = "pause_request";
571
+ constructor(message = "pause request is invalid") {
572
+ super(message);
563
573
  }
564
- return Object.freeze(value);
565
574
  }
566
575
 
567
- // src/workflow-steps/workflow.ts
568
- function createStepWorkflow(opts) {
569
- if (!opts.id)
570
- throw new Error("defineWorkflow: 'id' is required for step-based workflows");
571
- const build = (steps) => ({
572
- step(step) {
573
- return build([...steps, step]);
574
- },
575
- build() {
576
- if (steps.length === 0) {
577
- throw new Error(`defineWorkflow("${opts.id}").build: no steps registered — add at least one with .step(step)`);
578
- }
579
- const finalStep = steps[steps.length - 1];
580
- if (finalStep.output !== opts.output) {
581
- throw new Error(`defineWorkflow("${opts.id}").build: final step "${finalStep.name}" output schema is not the same Zod schema instance as the workflow's declared output schema. ` + `Pass the same schema reference to both the final step and defineWorkflow.`);
582
- }
583
- const workflow = {
584
- id: opts.id,
585
- input: opts.input,
586
- output: opts.output,
587
- steps: Object.freeze([...steps]),
588
- metadata: extractMetadata(opts)
589
- };
590
- Object.defineProperty(workflow, WORKFLOW_BRAND, { value: true, enumerable: false });
591
- return Object.freeze(workflow);
592
- }
593
- });
594
- return build([]);
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
+ }
595
589
  }
596
- function isWorkflow(value) {
597
- return typeof value === "object" && value !== null && value[WORKFLOW_BRAND] === true;
590
+ function isPauseSignal(err) {
591
+ return typeof err === "object" && err !== null && err[PAUSE_SIGNAL_BRAND] === true;
598
592
  }
599
-
600
- // src/types/workflow.ts
601
- function compileRunForm(def, metadata) {
602
- const unknownSchema = z3.unknown();
603
- const inputSchema = def.input ?? unknownSchema;
604
- const outputSchema = def.output ?? unknownSchema;
605
- const step = {
606
- name: "run",
607
- input: inputSchema,
608
- output: outputSchema,
609
- run: async (stepCtx) => {
610
- const workflowCtx = {
611
- input: stepCtx.input,
612
- run: stepCtx.run,
613
- requestContext: stepCtx.requestContext,
614
- invokeChild: stepCtx.invokeChild,
615
- setMetadata: stepCtx.setMetadata,
616
- step: stepCtx.step,
617
- agentEvents: stepCtx.agentEvents,
618
- checkpoint: stepCtx.checkpoint,
619
- pause: stepCtx.pause,
620
- requestDecision: stepCtx.requestDecision,
621
- sleep: stepCtx.sleep,
622
- waitForEvent: stepCtx.waitForEvent,
623
- processors: metadata.processors ?? []
624
- };
625
- const sandbox = stepCtx.sandbox;
626
- if (!sandbox)
627
- throw new Error("legacy run-form workflow requires a sandbox in StepContext");
628
- return def.run(workflowCtx, sandbox);
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)"));
629
597
  }
598
+ return boundary(req, scope);
630
599
  };
631
- const workflow = {
632
- id: "@run-form",
633
- input: inputSchema,
634
- output: outputSchema,
635
- steps: Object.freeze([step]),
636
- metadata
637
- };
638
- Object.defineProperty(workflow, WORKFLOW_BRAND, { value: true, enumerable: false });
639
- return Object.freeze(workflow);
640
600
  }
641
- function defineWorkflow(def) {
642
- if ("run" in def)
643
- return compileRunForm(def, extractMetadata(def));
644
- return createStepWorkflow(def);
601
+ var PAUSE_NAMESPACE = "5f3e2b8a-0c41-4e9d-9a7b-6d2c1f8e3a40";
602
+ function uuidToBytes(uuid) {
603
+ return Buffer.from(uuid.replace(/-/g, ""), "hex");
645
604
  }
646
- // src/types/sandbox-environment.ts
647
- function defineSandboxEnvironment(env) {
648
- if (typeof env.setup !== "function") {
649
- throw new Error(`defineSandboxEnvironment(${env.name}): 'setup' must be a function`);
650
- }
651
- return defineWorkflow({
652
- ...env.description !== undefined ? { description: env.description } : {},
653
- snapshots: env.snapshots ?? { saveLatest: true },
654
- run: async (_ctx, sandbox) => env.setup(sandbox)
655
- });
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)}`;
656
608
  }
657
- // src/processors/processor.ts
658
- var Verdict = {
659
- continue: (value) => ({ kind: "continue", value }),
660
- deny: (reason) => ({ kind: "deny", reason }),
661
- abort: (reason) => ({ kind: "abort", reason })
662
- };
663
- // src/processors/builtins.ts
664
- function denyTools(names) {
665
- const banned = new Set(names);
666
- return {
667
- name: "denyTools",
668
- processToolCall: (call) => banned.has(call.toolName) ? Verdict.deny(`tool "${call.toolName}" is denied by policy`) : Verdict.continue(call)
669
- };
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);
670
615
  }
671
- function requireScope(required) {
672
- return {
673
- name: "requireScope",
674
- processInput: (prompt, ctx) => {
675
- const missing = required.filter((s) => !ctx.requestContext.hasScope(s));
676
- return missing.length === 0 ? Verdict.continue(prompt) : Verdict.abort(`API key is missing required scopes: ${missing.join(", ")}`);
677
- }
678
- };
616
+ function scopeOf(coord) {
617
+ return coord.agentScope ? `${coord.runId}:${coord.stepIndex}:${coord.agentScope.agentId}:${coord.agentScope.iteration}` : `${coord.runId}:${coord.stepIndex}:bare`;
679
618
  }
680
- function redactPattern(pattern, replacement = "[REDACTED]") {
681
- return {
682
- name: "redactPattern",
683
- processOutput: (message) => {
684
- if (message.type === "text" || message.type === "thinking") {
685
- const redacted = message.text.replace(pattern, replacement);
686
- if (redacted !== message.text) {
687
- return Verdict.continue({ ...message, text: redacted });
688
- }
689
- }
690
- return Verdict.continue(message);
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;
691
654
  }
692
- };
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;
693
673
  }
694
- // src/client.ts
695
- import { ofetch } from "ofetch";
696
-
697
- // src/errors.ts
698
- class AgentComposeError extends Error {
699
- status;
700
- constructor(status, message) {
701
- super(message);
702
- this.status = status;
703
- this.name = "AgentComposeError";
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})`);
704
690
  }
691
+ return parsed.data;
705
692
  }
706
-
707
- // src/sse.ts
708
- async function* parseSseStream(body) {
709
- const reader = body.getReader();
710
- const decoder = new TextDecoder;
711
- let buffer = "";
712
- let event = "";
713
- let seq = 0;
714
- let dataLines = [];
715
- try {
716
- while (true) {
717
- const { done, value } = await reader.read();
718
- if (done)
719
- break;
720
- buffer += decoder.decode(value, { stream: true });
721
- const lines = buffer.split(`
722
- `);
723
- buffer = lines.pop() ?? "";
724
- for (const line of lines) {
725
- if (line.startsWith("id:")) {
726
- seq = Number(line.slice(3).trim());
727
- continue;
728
- }
729
- if (line.startsWith("event:")) {
730
- event = line.slice(6).trim();
731
- continue;
732
- }
733
- if (line.startsWith("data:")) {
734
- dataLines.push(line.slice(5).trim());
735
- continue;
736
- }
737
- if (line === "" && dataLines.length > 0) {
738
- let data;
739
- try {
740
- data = JSON.parse(dataLines.join(`
741
- `));
742
- } catch (err) {
743
- throw new Error(`Malformed SSE JSON payload for event "${event || "message"}": ${err instanceof Error ? err.message : String(err)}`);
744
- }
745
- yield { id: seq, event, data };
746
- event = "";
747
- seq = 0;
748
- dataLines = [];
749
- }
750
- }
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);
751
712
  }
752
- } finally {
753
- reader.releaseLock();
713
+ return parsed.data;
754
714
  }
715
+ return payload;
755
716
  }
756
717
 
757
- // src/client.ts
758
- var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
759
- var DEFAULT_FACTORY = "default";
760
- function detectAmbientParentRunId() {
761
- const envRunId = typeof process !== "undefined" ? process.env?.RUN_ID : undefined;
762
- return envRunId && UUID_REGEX.test(envRunId) ? envRunId : null;
718
+ // src/utils/errors.ts
719
+ function formatError(err) {
720
+ return err instanceof Error ? err.message : String(err);
763
721
  }
764
- function templatePath(factorySlug, ...rest) {
765
- const tail = rest.length > 0 ? "/" + rest.map(encodeURIComponent).join("/") : "";
766
- return `/api/v1/factories/${encodeURIComponent(factorySlug)}/templates${tail}`;
722
+
723
+ // src/runtimes/vercel.ts
724
+ function now() {
725
+ return new Date().toISOString();
767
726
  }
768
- var PUBLIC_API_HOST = "https://api.agentcompose.ai";
769
- function steerAnswerPayload(message, actor) {
770
- if (typeof message !== "string" || message.trim().length === 0) {
771
- throw new Error("answerSteer: `message` must be a non-empty string");
772
- }
773
- return { message, ...actor !== undefined ? { actor } : {} };
727
+ function serialiseOutput(value) {
728
+ return typeof value === "string" ? value : JSON.stringify(value ?? "");
774
729
  }
775
-
776
- class AgentComposeClient {
777
- fetch;
778
- baseUrl;
779
- apiKey;
780
- constructor(options = {}) {
781
- const apiKey = options.apiKey ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_API_KEY : undefined) ?? "";
782
- if (!apiKey) {
783
- throw new Error("AgentComposeClient: `apiKey` is required. Pass it as `{ apiKey }` " + "or set the AGENT_COMPOSE_API_KEY env var.");
784
- }
785
- const baseUrl = options.baseUrl ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_URL : undefined) ?? PUBLIC_API_HOST;
786
- this.baseUrl = baseUrl.replace(/\/$/, "");
787
- this.apiKey = apiKey;
788
- this.fetch = ofetch.create({
789
- baseURL: this.baseUrl,
790
- headers: { Authorization: `Bearer ${apiKey}` },
791
- async onResponseError({ response }) {
792
- const body = response._data;
793
- throw new AgentComposeError(response.status, body?.error ?? response.statusText);
794
- }
795
- });
796
- }
797
- register(payload) {
798
- const { factorySlug = DEFAULT_FACTORY, ...body } = payload;
799
- return this.fetch(templatePath(factorySlug), { method: "POST", body });
800
- }
801
- invoke(name, input, opts) {
802
- const parentRunId = opts?.parentRunId === undefined ? detectAmbientParentRunId() : opts.parentRunId;
803
- const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
804
- const headers = {};
805
- if (opts?.idempotencyKey)
806
- headers["Idempotency-Key"] = opts.idempotencyKey;
807
- return this.fetch(templatePath(factorySlug, name, "invoke"), {
808
- method: "POST",
809
- ...opts?.idempotencyKey ? { headers } : {},
810
- body: {
811
- input,
812
- ...opts?.snapshots !== undefined ? { snapshots: opts.snapshots } : {},
813
- ...opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {},
814
- ...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {},
815
- ...parentRunId ? { parentRunId } : {},
816
- ...opts?.agentId ? { agentId: opts.agentId } : {}
817
- }
818
- });
819
- }
820
- async invokeAndWait(name, input, opts) {
821
- const timeoutMs = opts?.timeoutMs ?? 30 * 60 * 1000;
822
- const pollMs = opts?.pollIntervalMs ?? 1000;
823
- const { id: runId } = await this.invoke(name, input, opts);
824
- const deadline = Date.now() + timeoutMs;
825
- while (Date.now() < deadline) {
826
- const status = await this.getStatus(runId);
827
- if (status.status === "success" || status.status === "failed" || status.status === "abandoned") {
828
- return status;
829
- }
830
- await new Promise((r) => setTimeout(r, pollMs));
831
- }
832
- throw new AgentComposeError(504, `invokeAndWait: run ${runId} did not settle within ${timeoutMs}ms`);
833
- }
834
- async listSnapshots(opts) {
835
- const body = await this.listSnapshotsPage(opts);
836
- return body.data;
837
- }
838
- async listSnapshotsPage(opts) {
839
- const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
840
- const q2 = new URLSearchParams;
841
- if (opts?.workflow)
842
- q2.set("workflow", opts.workflow);
843
- if (opts?.limit != null)
844
- q2.set("limit", String(opts.limit));
845
- if (opts?.before)
846
- q2.set("before", opts.before);
847
- return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/snapshots${q2.toString() ? `?${q2}` : ""}`);
848
- }
849
- deleteSnapshot(runId) {
850
- return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshot`, { method: "DELETE" });
851
- }
852
- async listRunSnapshots(runId) {
853
- const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshots`);
854
- return body.data;
855
- }
856
- deleteRunSnapshot(runId, snapshotId) {
857
- return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshots/${encodeURIComponent(snapshotId)}`, { method: "DELETE" });
858
- }
859
- getStatus(runId) {
860
- return this.fetch(`/api/v1/workflows/${runId}/status`);
730
+ function toAgentMessages(part) {
731
+ const ts = now();
732
+ switch (part.type) {
733
+ case "text-delta":
734
+ case "text":
735
+ return [{ type: "text", text: String(part.text ?? ""), timestamp: ts }];
736
+ case "reasoning-delta":
737
+ case "reasoning":
738
+ return [{ type: "thinking", text: String(part.text ?? ""), timestamp: ts }];
739
+ case "tool-call":
740
+ return [{
741
+ type: "tool_use",
742
+ toolName: String(part.toolName ?? ""),
743
+ toolInput: part.input ?? {},
744
+ toolUseId: String(part.toolCallId ?? ""),
745
+ timestamp: ts
746
+ }];
747
+ case "tool-result":
748
+ return [{
749
+ type: "tool_result",
750
+ toolUseId: String(part.toolCallId ?? ""),
751
+ output: serialiseOutput(part.output),
752
+ isError: false,
753
+ timestamp: ts
754
+ }];
755
+ case "tool-error":
756
+ return [{
757
+ type: "tool_result",
758
+ toolUseId: String(part.toolCallId ?? ""),
759
+ output: formatError(part.error),
760
+ isError: true,
761
+ timestamp: ts
762
+ }];
763
+ case "error":
764
+ return [{ type: "error", text: formatError(part.error), timestamp: ts }];
765
+ default:
766
+ return [];
861
767
  }
862
- async resumePause(runId, pauseId, payload, opts) {
863
- const headers = {};
864
- if (opts?.idempotencyKey)
865
- headers["Idempotency-Key"] = opts.idempotencyKey;
866
- return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/${encodeURIComponent(pauseId)}/resume`, {
867
- method: "POST",
868
- body: { resumePayload: payload },
869
- ...Object.keys(headers).length ? { headers } : {},
870
- ...opts?.signal ? { signal: opts.signal } : {}
871
- });
768
+ }
769
+
770
+ class VercelRunner {
771
+ sandbox;
772
+ options;
773
+ config;
774
+ supportsToolCallProcessor = true;
775
+ kind;
776
+ model;
777
+ tools;
778
+ messages = [];
779
+ constructor(sandbox, options, config) {
780
+ this.sandbox = sandbox;
781
+ this.options = options;
782
+ this.config = config;
783
+ this.tools = config.tools ?? codingTools;
784
+ this.kind = config.kind;
785
+ this.model = config.modelId;
872
786
  }
873
- async resumePauseByKey(runId, correlationKey, payload, opts) {
874
- const headers = {};
875
- if (opts?.idempotencyKey)
876
- headers["Idempotency-Key"] = opts.idempotencyKey;
877
- return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/by-key/${encodeURIComponent(correlationKey)}/resume`, {
878
- method: "POST",
879
- body: { resumePayload: payload },
880
- ...Object.keys(headers).length ? { headers } : {},
881
- ...opts?.signal ? { signal: opts.signal } : {}
882
- });
787
+ async gateToolCall(call, ctx) {
788
+ const verdict = await runProcessorChain(this.options.processors ?? [], (p) => p.processToolCall, call, ctx);
789
+ if (verdict.kind === "continue")
790
+ return { kind: "allow", call: verdict.value };
791
+ return verdict;
883
792
  }
884
- async answerSteerByKey(runId, correlationKey, message, opts) {
885
- return this.resumePauseByKey(runId, correlationKey, steerAnswerPayload(message, opts?.actor), opts);
793
+ captureCheckpoint() {
794
+ return { messages: [...this.messages] };
886
795
  }
887
- async answerSteer(runId, pauseId, message, opts) {
888
- return this.resumePause(runId, pauseId, steerAnswerPayload(message, opts?.actor), opts);
796
+ restoreCheckpoint(blob) {
797
+ if (!blob || typeof blob !== "object") {
798
+ throw new Error("Vercel runtime checkpoint is invalid: expected an object with messages[]");
799
+ }
800
+ const incoming = blob.messages;
801
+ if (!Array.isArray(incoming)) {
802
+ throw new Error("Vercel runtime checkpoint is invalid: expected messages[]");
803
+ }
804
+ this.messages.length = 0;
805
+ this.messages.push(...incoming);
889
806
  }
890
- async requestAgentPause(runId, agentId, opts) {
891
- const body = {};
892
- if (opts?.reason !== undefined)
893
- body.reason = opts.reason;
894
- if (opts?.correlationKey !== undefined)
895
- body.correlationKey = opts.correlationKey;
896
- return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/pause`, {
897
- method: "POST",
898
- body,
899
- ...opts?.signal ? { signal: opts.signal } : {}
900
- });
807
+ buildTools(iteration, signal) {
808
+ const set = {};
809
+ for (const t of this.tools) {
810
+ if (this.options.allowedTools && !this.options.allowedTools.includes(t.name))
811
+ continue;
812
+ set[t.name] = tool({
813
+ description: t.description,
814
+ inputSchema: t.inputSchema,
815
+ execute: async (input, execOpts) => {
816
+ const call = {
817
+ toolName: t.name,
818
+ toolInput: input,
819
+ toolUseId: execOpts.toolCallId ?? `${t.name}-${Date.now()}`
820
+ };
821
+ const requestContext = this.options.requestContext ?? RequestContext.fromReserved({
822
+ teamId: "",
823
+ runId: "",
824
+ workflowId: "",
825
+ factoryId: null,
826
+ apiKeyScopes: [],
827
+ parentRunId: null
828
+ });
829
+ const gateAgentId = this.options.agentId ?? "agent";
830
+ const gate = await this.gateToolCall(call, {
831
+ requestContext,
832
+ abortSignal: execOpts.abortSignal ?? signal ?? new AbortController().signal,
833
+ retryCount: 0,
834
+ agentId: gateAgentId,
835
+ iteration,
836
+ pause: boundProcessorPause(this.options.pause, { agentId: gateAgentId, iteration })
837
+ });
838
+ if (gate.kind === "deny")
839
+ return gate.reason;
840
+ if (gate.kind === "abort")
841
+ throw new Error(gate.reason);
842
+ return t.execute(gate.call.toolInput, {
843
+ sandbox: this.sandbox,
844
+ cwd: this.options.cwd,
845
+ abortSignal: execOpts.abortSignal ?? signal
846
+ });
847
+ }
848
+ });
849
+ }
850
+ return set;
901
851
  }
902
- async sendAgentMessage(runId, agentId, message, opts) {
903
- const body = { message };
904
- if (opts?.senderName !== undefined)
905
- body.senderName = opts.senderName;
906
- return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/messages`, {
907
- method: "POST",
908
- body,
909
- ...opts?.signal ? { signal: opts.signal } : {}
852
+ async* sendMessage(opts) {
853
+ const sessionId = opts.sessionId ?? `vercel-${Date.now()}`;
854
+ yield { type: "init", sessionId, timestamp: now() };
855
+ const inputMessages = [...this.messages, { role: "user", content: opts.prompt }];
856
+ const result = streamText({
857
+ model: this.config.model,
858
+ ...this.config.system ? { system: this.config.system } : {},
859
+ messages: inputMessages,
860
+ tools: this.buildTools(opts.iteration ?? 1, opts.signal),
861
+ stopWhen: stepCountIs(this.options.maxTurns ?? 40),
862
+ abortSignal: opts.signal
910
863
  });
864
+ try {
865
+ for await (const part of result.fullStream) {
866
+ for (const msg of toAgentMessages(part))
867
+ yield msg;
868
+ }
869
+ const response = await result.response;
870
+ const responseMessages = response.messages;
871
+ if (responseMessages) {
872
+ this.messages.length = 0;
873
+ this.messages.push(...responseMessages);
874
+ }
875
+ let usage;
876
+ try {
877
+ usage = await result.usage;
878
+ } catch {
879
+ usage = undefined;
880
+ }
881
+ if (usage) {
882
+ yield {
883
+ type: "usage",
884
+ inputTokens: usage.inputTokens ?? 0,
885
+ outputTokens: usage.outputTokens ?? 0,
886
+ cacheReadTokens: usage.inputTokenDetails?.cacheReadTokens ?? 0,
887
+ cacheCreationTokens: usage.inputTokenDetails?.cacheWriteTokens ?? 0,
888
+ durationMs: 0,
889
+ numTurns: opts.iteration ?? 1,
890
+ timestamp: now()
891
+ };
892
+ }
893
+ } catch (err) {
894
+ yield { type: "error", text: formatError(err), timestamp: now() };
895
+ return;
896
+ }
897
+ yield { type: "done", sessionId, timestamp: now() };
911
898
  }
912
- getRun(runId) {
913
- return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}`);
914
- }
915
- async getRunTimeline(runId, opts) {
916
- const q2 = new URLSearchParams;
917
- if (opts?.limit !== undefined)
918
- q2.set("limit", String(opts.limit));
919
- if (opts?.offset !== undefined)
920
- q2.set("offset", String(opts.offset));
921
- const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/timeline${q2.toString() ? `?${q2}` : ""}`);
922
- return body.events;
923
- }
924
- async reportEvent(runId, input) {
925
- const body = {
926
- ...input,
927
- ...input.timestamp instanceof Date ? { timestamp: input.timestamp.toISOString() } : {}
928
- };
929
- const response = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/events`, {
930
- method: "POST",
931
- body
932
- });
933
- return response.event;
934
- }
935
- async listRunEvents(runId) {
936
- const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/events`);
937
- return body.events;
899
+ }
900
+ function createVercelRuntime(config) {
901
+ return defineRuntime({
902
+ create: (sandbox, opts) => new VercelRunner(sandbox, opts, config)
903
+ });
904
+ }
905
+ async function listVercelRuntimeModels() {
906
+ const { models } = await gateway.getAvailableModels();
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));
908
+ }
909
+ // src/types/workflow.ts
910
+ import { z as z4 } from "zod";
911
+
912
+ // src/workflow-steps/types.ts
913
+ var WORKFLOW_BRAND = Symbol.for("@agent-compose/sdk.Workflow");
914
+
915
+ // src/types/workflow-metadata.ts
916
+ function extractMetadata(source) {
917
+ const out = {};
918
+ if (source.description !== undefined)
919
+ out.description = source.description;
920
+ if (source.networkPolicy !== undefined)
921
+ out.networkPolicy = freezeMetadataValue(source.networkPolicy);
922
+ if (source.placeholders !== undefined)
923
+ out.placeholders = Object.freeze({ ...source.placeholders });
924
+ if (source.inputSchema !== undefined)
925
+ out.inputSchema = freezeMetadataValue(source.inputSchema);
926
+ if (source.outputSchema !== undefined)
927
+ out.outputSchema = freezeMetadataValue(source.outputSchema);
928
+ if (source.snapshots !== undefined)
929
+ out.snapshots = Object.freeze({ ...source.snapshots });
930
+ if (source.resources !== undefined)
931
+ out.resources = Object.freeze({ ...source.resources });
932
+ if (source.processors !== undefined)
933
+ out.processors = Object.freeze([...source.processors]);
934
+ if (source.connectors !== undefined)
935
+ out.connectors = freezeMetadataValue(source.connectors);
936
+ if (source.connectorOperation !== undefined)
937
+ out.connectorOperation = Object.freeze({ ...source.connectorOperation });
938
+ if (source.invokePolicy !== undefined)
939
+ out.invokePolicy = freezeMetadataValue(source.invokePolicy);
940
+ if (source.environmentBuild !== undefined)
941
+ out.environmentBuild = source.environmentBuild;
942
+ return Object.freeze(out);
943
+ }
944
+ function freezeMetadataValue(value) {
945
+ if (value === null || typeof value !== "object")
946
+ return value;
947
+ if (Array.isArray(value)) {
948
+ for (const v of value)
949
+ freezeMetadataValue(v);
950
+ return Object.freeze(value);
938
951
  }
939
- async listRunArtifacts(runId) {
940
- const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/artifacts`);
941
- return body.artifacts.map((a) => ({
942
- path: a.path,
943
- factorySlug: a.factory_slug,
944
- sizeBytes: a.size_bytes,
945
- lastWriteAt: a.last_write_at,
946
- preview: a.preview
947
- }));
952
+ for (const v of Object.values(value)) {
953
+ freezeMetadataValue(v);
948
954
  }
949
- async putFactoryFile(path, content, opts) {
950
- const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
951
- const runToken = typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_RUN_TOKEN : undefined;
952
- const wire = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?path=${encodeURIComponent(path)}`, {
953
- method: "PUT",
954
- body: content,
955
- headers: {
956
- "content-type": opts?.contentType ?? "text/plain; charset=utf-8",
957
- ...runToken ? { "x-run-token": runToken } : {}
955
+ return Object.freeze(value);
956
+ }
957
+
958
+ // src/workflow-steps/workflow.ts
959
+ function createStepWorkflow(opts) {
960
+ if (!opts.id)
961
+ throw new Error("defineWorkflow: 'id' is required for step-based workflows");
962
+ const build = (steps) => ({
963
+ step(step) {
964
+ return build([...steps, step]);
965
+ },
966
+ build() {
967
+ if (steps.length === 0) {
968
+ throw new Error(`defineWorkflow("${opts.id}").build: no steps registered — add at least one with .step(step)`);
958
969
  }
959
- });
960
- return {
961
- path: wire.path,
962
- contentHash: wire.content_hash,
963
- sizeBytes: wire.size_bytes,
964
- created: wire.created
965
- };
966
- }
967
- async getFactoryFile(path, opts) {
968
- const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
969
- const q2 = new URLSearchParams({ path });
970
- if (opts?.revision !== undefined)
971
- q2.set("revision", String(opts.revision));
972
- return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?${q2}`, { responseType: "text" });
973
- }
974
- async listFactoryEvents(opts) {
975
- const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
976
- const q2 = new URLSearchParams;
977
- if (opts?.limit != null)
978
- q2.set("limit", String(opts.limit));
979
- if (opts?.name)
980
- q2.set("name", opts.name);
981
- if (opts?.from)
982
- q2.set("from", opts.from);
983
- if (opts?.before)
984
- q2.set("before", opts.before);
985
- return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/events${q2.toString() ? `?${q2}` : ""}`);
986
- }
987
- async listRunLogs(runId, opts) {
988
- const q2 = new URLSearchParams;
989
- if (opts?.afterId !== undefined)
990
- q2.set("afterId", String(opts.afterId));
991
- if (opts?.limit !== undefined)
992
- q2.set("limit", String(opts.limit));
993
- if (opts?.direction !== undefined)
994
- q2.set("direction", opts.direction);
995
- const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/logs${q2.toString() ? `?${q2}` : ""}`);
996
- return body.logs;
997
- }
998
- async listTemplates(opts) {
999
- const path = opts?.factorySlug ? templatePath(opts.factorySlug) : "/api/v1/templates";
1000
- const body = await this.fetch(path);
1001
- return body.templates;
1002
- }
1003
- async listFactories() {
1004
- const body = await this.fetch("/api/v1/factories");
1005
- return body.factories;
1006
- }
1007
- createFactory(payload) {
1008
- return this.fetch("/api/v1/factories", { method: "POST", body: payload });
1009
- }
1010
- getFactory(slug) {
1011
- return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`);
970
+ const finalStep = steps[steps.length - 1];
971
+ if (finalStep.output !== opts.output) {
972
+ throw new Error(`defineWorkflow("${opts.id}").build: final step "${finalStep.name}" output schema is not the same Zod schema instance as the workflow's declared output schema. ` + `Pass the same schema reference to both the final step and defineWorkflow.`);
973
+ }
974
+ const workflow = {
975
+ id: opts.id,
976
+ input: opts.input,
977
+ output: opts.output,
978
+ steps: Object.freeze([...steps]),
979
+ metadata: extractMetadata(opts)
980
+ };
981
+ Object.defineProperty(workflow, WORKFLOW_BRAND, { value: true, enumerable: false });
982
+ return Object.freeze(workflow);
983
+ }
984
+ });
985
+ return build([]);
986
+ }
987
+ function isWorkflow(value) {
988
+ return typeof value === "object" && value !== null && value[WORKFLOW_BRAND] === true;
989
+ }
990
+
991
+ // src/types/workflow.ts
992
+ function compileRunForm(def, metadata) {
993
+ const unknownSchema = z4.unknown();
994
+ const inputSchema = def.input ?? unknownSchema;
995
+ const outputSchema = def.output ?? unknownSchema;
996
+ const step = {
997
+ name: "run",
998
+ input: inputSchema,
999
+ output: outputSchema,
1000
+ run: async (stepCtx) => {
1001
+ const workflowCtx = {
1002
+ input: stepCtx.input,
1003
+ run: stepCtx.run,
1004
+ requestContext: stepCtx.requestContext,
1005
+ invokeChild: stepCtx.invokeChild,
1006
+ setMetadata: stepCtx.setMetadata,
1007
+ step: stepCtx.step,
1008
+ agentEvents: stepCtx.agentEvents,
1009
+ pause: stepCtx.pause,
1010
+ sleep: stepCtx.sleep,
1011
+ waitForEvent: stepCtx.waitForEvent,
1012
+ processors: metadata.processors ?? []
1013
+ };
1014
+ const sandbox = stepCtx.sandbox;
1015
+ if (!sandbox)
1016
+ throw new Error("legacy run-form workflow requires a sandbox in StepContext");
1017
+ return def.run(workflowCtx, sandbox);
1018
+ }
1019
+ };
1020
+ const workflow = {
1021
+ id: "@run-form",
1022
+ input: inputSchema,
1023
+ output: outputSchema,
1024
+ steps: Object.freeze([step]),
1025
+ metadata
1026
+ };
1027
+ Object.defineProperty(workflow, WORKFLOW_BRAND, { value: true, enumerable: false });
1028
+ return Object.freeze(workflow);
1029
+ }
1030
+ function defineWorkflow(def) {
1031
+ if ("run" in def)
1032
+ return compileRunForm(def, extractMetadata(def));
1033
+ return createStepWorkflow(def);
1034
+ }
1035
+ // src/types/sandbox-environment.ts
1036
+ function defineSandboxEnvironment(env) {
1037
+ if (typeof env.setup !== "function") {
1038
+ throw new Error(`defineSandboxEnvironment(${env.name}): 'setup' must be a function`);
1012
1039
  }
1013
- updateFactory(slug, updates) {
1014
- return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "PATCH", body: updates });
1040
+ return defineWorkflow({
1041
+ ...env.description !== undefined ? { description: env.description } : {},
1042
+ ...env.resources !== undefined ? { resources: env.resources } : {},
1043
+ snapshots: env.snapshots ?? { saveLatest: true },
1044
+ environmentBuild: true,
1045
+ run: async (_ctx, sandbox) => env.setup(sandbox)
1046
+ });
1047
+ }
1048
+ // src/processors/processor.ts
1049
+ var Verdict = {
1050
+ continue: (value) => ({ kind: "continue", value }),
1051
+ deny: (reason) => ({ kind: "deny", reason }),
1052
+ abort: (reason) => ({ kind: "abort", reason })
1053
+ };
1054
+ // src/processors/builtins.ts
1055
+ import { z as z5 } from "zod";
1056
+ function denyTools(names) {
1057
+ const banned = new Set(names);
1058
+ return {
1059
+ name: "denyTools",
1060
+ processToolCall: (call) => banned.has(call.toolName) ? Verdict.deny(`tool "${call.toolName}" is denied by policy`) : Verdict.continue(call)
1061
+ };
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
+ }
1083
+ function requireScope(required) {
1084
+ return {
1085
+ name: "requireScope",
1086
+ processInput: (prompt, ctx) => {
1087
+ const missing = required.filter((s) => !ctx.requestContext.hasScope(s));
1088
+ return missing.length === 0 ? Verdict.continue(prompt) : Verdict.abort(`API key is missing required scopes: ${missing.join(", ")}`);
1089
+ }
1090
+ };
1091
+ }
1092
+ function redactPattern(pattern, replacement = "[REDACTED]") {
1093
+ return {
1094
+ name: "redactPattern",
1095
+ processOutput: (message) => {
1096
+ if (message.type === "text" || message.type === "thinking") {
1097
+ const redacted = message.text.replace(pattern, replacement);
1098
+ if (redacted !== message.text) {
1099
+ return Verdict.continue({ ...message, text: redacted });
1100
+ }
1101
+ }
1102
+ return Verdict.continue(message);
1103
+ }
1104
+ };
1105
+ }
1106
+ // src/client.ts
1107
+ import { ofetch } from "ofetch";
1108
+
1109
+ // src/errors.ts
1110
+ class AgentComposeError extends Error {
1111
+ status;
1112
+ constructor(status, message) {
1113
+ super(message);
1114
+ this.status = status;
1115
+ this.name = "AgentComposeError";
1015
1116
  }
1016
- deleteFactory(slug) {
1017
- return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "DELETE" });
1117
+ }
1118
+
1119
+ // src/sse.ts
1120
+ async function* parseSseStream(body) {
1121
+ const reader = body.getReader();
1122
+ const decoder = new TextDecoder;
1123
+ let buffer = "";
1124
+ let event = "";
1125
+ let seq = 0;
1126
+ let dataLines = [];
1127
+ try {
1128
+ while (true) {
1129
+ const { done, value } = await reader.read();
1130
+ if (done)
1131
+ break;
1132
+ buffer += decoder.decode(value, { stream: true });
1133
+ const lines = buffer.split(`
1134
+ `);
1135
+ buffer = lines.pop() ?? "";
1136
+ for (const line of lines) {
1137
+ if (line.startsWith("id:")) {
1138
+ seq = Number(line.slice(3).trim());
1139
+ continue;
1140
+ }
1141
+ if (line.startsWith("event:")) {
1142
+ event = line.slice(6).trim();
1143
+ continue;
1144
+ }
1145
+ if (line.startsWith("data:")) {
1146
+ dataLines.push(line.slice(5).trim());
1147
+ continue;
1148
+ }
1149
+ if (line === "" && dataLines.length > 0) {
1150
+ let data;
1151
+ try {
1152
+ data = JSON.parse(dataLines.join(`
1153
+ `));
1154
+ } catch (err) {
1155
+ throw new Error(`Malformed SSE JSON payload for event "${event || "message"}": ${err instanceof Error ? err.message : String(err)}`);
1156
+ }
1157
+ yield { id: seq, event, data };
1158
+ event = "";
1159
+ seq = 0;
1160
+ dataLines = [];
1161
+ }
1162
+ }
1163
+ }
1164
+ } finally {
1165
+ reader.releaseLock();
1018
1166
  }
1019
- async listSchedules(factorySlug = "default") {
1020
- const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules`);
1021
- return body.schedules;
1167
+ }
1168
+
1169
+ // src/client.ts
1170
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1171
+ var DEFAULT_FACTORY = "default";
1172
+ function detectAmbientParentRunId() {
1173
+ const envRunId = typeof process !== "undefined" ? process.env?.RUN_ID : undefined;
1174
+ return envRunId && UUID_REGEX.test(envRunId) ? envRunId : null;
1175
+ }
1176
+ function templatePath(factorySlug, ...rest) {
1177
+ const tail = rest.length > 0 ? "/" + rest.map(encodeURIComponent).join("/") : "";
1178
+ return `/api/v1/factories/${encodeURIComponent(factorySlug)}/templates${tail}`;
1179
+ }
1180
+ var PUBLIC_API_HOST = "https://api.agentcompose.ai";
1181
+ function steerAnswerPayload(message, actor) {
1182
+ if (typeof message !== "string" || message.trim().length === 0) {
1183
+ throw new Error("answerSteer: `message` must be a non-empty string");
1022
1184
  }
1023
- createSchedule(payload) {
1024
- const factorySlug = payload.factorySlug ?? "default";
1025
- return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules`, {
1026
- method: "POST",
1027
- body: { name: payload.name, workflow: payload.workflow, cron: payload.cron }
1185
+ return { message, ...actor !== undefined ? { actor } : {} };
1186
+ }
1187
+
1188
+ class AgentComposeClient {
1189
+ fetch;
1190
+ baseUrl;
1191
+ apiKey;
1192
+ constructor(options = {}) {
1193
+ const apiKey = options.apiKey ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_API_KEY : undefined) ?? "";
1194
+ if (!apiKey) {
1195
+ throw new Error("AgentComposeClient: `apiKey` is required. Pass it as `{ apiKey }` " + "or set the AGENT_COMPOSE_API_KEY env var.");
1196
+ }
1197
+ const baseUrl = options.baseUrl ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_URL : undefined) ?? PUBLIC_API_HOST;
1198
+ this.baseUrl = baseUrl.replace(/\/$/, "");
1199
+ this.apiKey = apiKey;
1200
+ this.fetch = ofetch.create({
1201
+ baseURL: this.baseUrl,
1202
+ headers: { Authorization: `Bearer ${apiKey}` },
1203
+ async onResponseError({ response }) {
1204
+ const body = response._data;
1205
+ throw new AgentComposeError(response.status, body?.error ?? response.statusText);
1206
+ }
1028
1207
  });
1029
1208
  }
1030
- deleteSchedule(id, factorySlug = "default") {
1031
- return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules/${encodeURIComponent(id)}`, { method: "DELETE" });
1209
+ register(payload) {
1210
+ const { factorySlug = DEFAULT_FACTORY, ...body } = payload;
1211
+ return this.fetch(templatePath(factorySlug), { method: "POST", body });
1032
1212
  }
1033
- setSecret(workflowName, key, value, opts) {
1213
+ invoke(name, input, opts) {
1214
+ const parentRunId = opts?.parentRunId === undefined ? detectAmbientParentRunId() : opts.parentRunId;
1034
1215
  const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1035
- return this.fetch(templatePath(factorySlug, workflowName, "secrets"), {
1216
+ const headers = {};
1217
+ if (opts?.idempotencyKey)
1218
+ headers["Idempotency-Key"] = opts.idempotencyKey;
1219
+ return this.fetch(templatePath(factorySlug, name, "invoke"), {
1036
1220
  method: "POST",
1037
- body: { key, value }
1221
+ ...opts?.idempotencyKey ? { headers } : {},
1222
+ body: {
1223
+ input,
1224
+ ...opts?.snapshots !== undefined ? { snapshots: opts.snapshots } : {},
1225
+ ...opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {},
1226
+ ...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {},
1227
+ ...opts?.size !== undefined ? { size: opts.size } : {},
1228
+ ...parentRunId ? { parentRunId } : {},
1229
+ ...opts?.agentId ? { agentId: opts.agentId } : {}
1230
+ }
1038
1231
  });
1039
1232
  }
1040
- async listSecrets(workflowName, opts) {
1041
- const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1042
- const body = await this.fetch(templatePath(factorySlug, workflowName, "secrets"));
1043
- return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
1233
+ async invokeAndWait(name, input, opts) {
1234
+ const timeoutMs = opts?.timeoutMs ?? 30 * 60 * 1000;
1235
+ const pollMs = opts?.pollIntervalMs ?? 1000;
1236
+ const { id: runId } = await this.invoke(name, input, opts);
1237
+ const deadline = Date.now() + timeoutMs;
1238
+ while (Date.now() < deadline) {
1239
+ const status = await this.getStatus(runId);
1240
+ if (status.status === "success" || status.status === "failed" || status.status === "abandoned") {
1241
+ return status;
1242
+ }
1243
+ await new Promise((r) => setTimeout(r, pollMs));
1244
+ }
1245
+ throw new AgentComposeError(504, `invokeAndWait: run ${runId} did not settle within ${timeoutMs}ms`);
1044
1246
  }
1045
- deleteSecret(workflowName, key, opts) {
1247
+ async listSnapshots(opts) {
1248
+ const body = await this.listSnapshotsPage(opts);
1249
+ return body.data;
1250
+ }
1251
+ async listSnapshotsPage(opts) {
1046
1252
  const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1047
- return this.fetch(templatePath(factorySlug, workflowName, "secrets", key), { method: "DELETE" });
1253
+ const q2 = new URLSearchParams;
1254
+ if (opts?.workflow)
1255
+ q2.set("workflow", opts.workflow);
1256
+ if (opts?.limit != null)
1257
+ q2.set("limit", String(opts.limit));
1258
+ if (opts?.before)
1259
+ q2.set("before", opts.before);
1260
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/snapshots${q2.toString() ? `?${q2}` : ""}`);
1048
1261
  }
1049
- createApiKey(input) {
1050
- return this.fetch("/api-keys", { method: "POST", body: input });
1262
+ deleteSnapshot(runId) {
1263
+ return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshot`, { method: "DELETE" });
1051
1264
  }
1052
- async listApiKeys() {
1053
- const body = await this.fetch("/api-keys");
1265
+ async listRunSnapshots(runId) {
1266
+ const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshots`);
1054
1267
  return body.data;
1055
1268
  }
1056
- getUsage(from, to) {
1057
- const qs = `?from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`;
1058
- return this.fetch(`/api/v1/usage${qs}`);
1269
+ deleteRunSnapshot(runId, snapshotId) {
1270
+ return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshots/${encodeURIComponent(snapshotId)}`, { method: "DELETE" });
1059
1271
  }
1060
- cancelRun(runId) {
1061
- return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
1272
+ getStatus(runId) {
1273
+ return this.fetch(`/api/v1/workflows/${runId}/status`);
1062
1274
  }
1063
- async* streamRunLogs(runId, opts) {
1064
- const headers = { Authorization: `Bearer ${this.apiKey}` };
1065
- if (opts?.lastEventId && opts.lastEventId > 0) {
1066
- headers["Last-Event-ID"] = String(opts.lastEventId);
1067
- }
1068
- const res = await fetch(`${this.baseUrl}/api/v1/workflows/${encodeURIComponent(runId)}/stream`, {
1069
- headers,
1275
+ async resumePause(runId, pauseId, payload, opts) {
1276
+ const headers = {};
1277
+ if (opts?.idempotencyKey)
1278
+ headers["Idempotency-Key"] = opts.idempotencyKey;
1279
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/${encodeURIComponent(pauseId)}/resume`, {
1280
+ method: "POST",
1281
+ body: { resumePayload: payload },
1282
+ ...Object.keys(headers).length ? { headers } : {},
1070
1283
  ...opts?.signal ? { signal: opts.signal } : {}
1071
1284
  });
1072
- if (!res.ok || !res.body) {
1073
- let message = res.statusText;
1074
- try {
1075
- const body = await res.json();
1076
- if (body.error)
1077
- message = body.error;
1078
- } catch {}
1079
- throw new AgentComposeError(res.status, message);
1080
- }
1081
- for await (const ev of parseSseStream(res.body)) {
1082
- yield ev.data;
1083
- }
1084
- }
1085
- }
1086
- // src/utils/bundler.ts
1087
- import { createHash } from "node:crypto";
1088
- import { parse as babelParse } from "@babel/parser";
1089
-
1090
- // src/utils/source-loader.ts
1091
- import { dirname } from "path";
1092
- import { writeFile, rm, mkdir } from "node:fs/promises";
1093
- var TMP_DIR = process.env.TMP_DIR ?? "/tmp/agent-compose";
1094
- var LATEST_VERSION = "$LATEST";
1095
- async function importSourceModule(source, tmpPath) {
1096
- await mkdir(dirname(tmpPath), { recursive: true });
1097
- await writeFile(tmpPath, source, "utf8");
1098
- const mod = await import(tmpPath);
1099
- const cleanup = () => rm(tmpPath, { force: true });
1100
- return { mod, cleanup };
1101
- }
1102
-
1103
- // src/types/workflow-plan.ts
1104
- function workflowPlan(steps) {
1105
- return { steps };
1106
- }
1107
-
1108
- // src/utils/bundler.ts
1109
- var BUNDLER_VERSION = 2;
1110
-
1111
- class WorkflowSourceValidationError extends Error {
1112
- constructor(message) {
1113
- super(message);
1114
- this.name = "WorkflowSourceValidationError";
1115
- }
1116
- }
1117
- async function bundle(path, label) {
1118
- const bun = globalThis;
1119
- if (!bun.Bun?.build)
1120
- throw new Error("bundleWorkflow requires the Bun runtime (Bun.build)");
1121
- const result = await bun.Bun.build({ entrypoints: [path], format: "esm", target: "node" });
1122
- if (!result.success) {
1123
- throw new Error(`Failed to bundle ${label}:
1124
- ${result.logs.map((l) => l.message).join(`
1125
- `)}`);
1126
- }
1127
- return result.outputs[0].text();
1128
- }
1129
- async function extractFromBundle(source, label, pick) {
1130
- const tmpPath = `/tmp/agentc-${label}-${Date.now()}-${Math.random().toString(36).slice(2)}.js`;
1131
- try {
1132
- const loaded = await importSourceModule(source, tmpPath);
1133
- try {
1134
- return pick(loaded.mod.default);
1135
- } finally {
1136
- await loaded.cleanup();
1137
- }
1138
- } catch (err) {
1139
- throw new WorkflowSourceValidationError(`Bundled ${label} could not be evaluated: ${err instanceof Error ? err.message : String(err)}`);
1140
- }
1141
- }
1142
- var DEFINE_WORKFLOW_CALLEE = /^(?:defineWorkflow|defineSandboxEnvironment)\d*$/;
1143
- function isDefineWorkflowCall(node) {
1144
- if (node.callee.type === "Identifier") {
1145
- return DEFINE_WORKFLOW_CALLEE.test(node.callee.name);
1146
- }
1147
- if (node.callee.type === "MemberExpression" && node.callee.object.type === "CallExpression") {
1148
- return isDefineWorkflowCall(node.callee.object);
1149
1285
  }
1150
- return false;
1151
- }
1152
- function assertDefaultExportIsDefineWorkflow(source, label) {
1153
- let ast;
1154
- try {
1155
- ast = babelParse(source, {
1156
- sourceType: "module",
1157
- plugins: [],
1158
- errorRecovery: false
1286
+ async resumePauseByKey(runId, correlationKey, payload, opts) {
1287
+ const headers = {};
1288
+ if (opts?.idempotencyKey)
1289
+ headers["Idempotency-Key"] = opts.idempotencyKey;
1290
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/by-key/${encodeURIComponent(correlationKey)}/resume`, {
1291
+ method: "POST",
1292
+ body: { resumePayload: payload },
1293
+ ...Object.keys(headers).length ? { headers } : {},
1294
+ ...opts?.signal ? { signal: opts.signal } : {}
1159
1295
  });
1160
- } catch (err) {
1161
- const msg = err instanceof Error ? err.message : String(err);
1162
- throw new WorkflowSourceValidationError(`Bundled workflow ${label} failed to parse as JavaScript: ${msg}. ` + `If you registered raw .ts source, switch to \`agentc register <file.ts>\` so the source is bundled first.`);
1163
1296
  }
1164
- const defaultExpr = findDefaultExportExpression(ast.program.body);
1165
- if (!defaultExpr) {
1166
- throw new WorkflowSourceValidationError(`Bundled workflow ${label} has no default export. ` + `Use \`export default defineWorkflow({ run: ... })\`.`);
1297
+ async answerSteerByKey(runId, correlationKey, message, opts) {
1298
+ return this.resumePauseByKey(runId, correlationKey, steerAnswerPayload(message, opts?.actor), opts);
1167
1299
  }
1168
- if (defaultExpr.type !== "CallExpression" || !isDefineWorkflowCall(defaultExpr)) {
1169
- throw new WorkflowSourceValidationError(`Bundled workflow ${label} default export must be a \`defineWorkflow({...})\` call ` + `(or a \`defineWorkflow(...).step(...).build()\` chain). ` + `Got: ${defaultExpr.type}. Wrap your workflow with \`defineWorkflow\` from "@agent-compose/sdk".`);
1300
+ async answerSteer(runId, pauseId, message, opts) {
1301
+ return this.resumePause(runId, pauseId, steerAnswerPayload(message, opts?.actor), opts);
1170
1302
  }
1171
- }
1172
- function findDefaultExportExpression(body) {
1173
- const defaultDecl = body.find((node) => node.type === "ExportDefaultDeclaration");
1174
- if (defaultDecl) {
1175
- return defaultDecl.declaration;
1303
+ async requestAgentPause(runId, agentId, opts) {
1304
+ const body = {};
1305
+ if (opts?.reason !== undefined)
1306
+ body.reason = opts.reason;
1307
+ if (opts?.correlationKey !== undefined)
1308
+ body.correlationKey = opts.correlationKey;
1309
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/pause`, {
1310
+ method: "POST",
1311
+ body,
1312
+ ...opts?.signal ? { signal: opts.signal } : {}
1313
+ });
1176
1314
  }
1177
- const exportNamed = body.find((node) => node.type === "ExportNamedDeclaration" && node.specifiers.some((spec2) => spec2.type === "ExportSpecifier" && spec2.exported.type === "Identifier" && spec2.exported.name === "default"));
1178
- if (!exportNamed || exportNamed.type !== "ExportNamedDeclaration")
1179
- return null;
1180
- const spec = exportNamed.specifiers.find((s) => s.type === "ExportSpecifier" && s.exported.type === "Identifier" && s.exported.name === "default");
1181
- if (!spec || spec.type !== "ExportSpecifier" || spec.local.type !== "Identifier")
1182
- return null;
1183
- const localName = spec.local.name;
1184
- for (const stmt of body) {
1185
- if (stmt.type !== "VariableDeclaration")
1186
- continue;
1187
- for (const decl of stmt.declarations) {
1188
- if (decl.id.type === "Identifier" && decl.id.name === localName && decl.init && decl.init.type !== "TSSatisfiesExpression") {
1189
- return decl.init;
1190
- }
1191
- }
1315
+ async sendAgentMessage(runId, agentId, message, opts) {
1316
+ const body = { message };
1317
+ if (opts?.senderName !== undefined)
1318
+ body.senderName = opts.senderName;
1319
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/messages`, {
1320
+ method: "POST",
1321
+ body,
1322
+ ...opts?.signal ? { signal: opts.signal } : {}
1323
+ });
1192
1324
  }
1193
- return null;
1194
- }
1195
- function sha256(text) {
1196
- return createHash("sha256").update(text, "utf8").digest("hex");
1197
- }
1198
- async function bundleWorkflow(workflowPath, overrides) {
1199
- const label = `"${workflowPath}"`;
1200
- const source = await bundle(workflowPath, `workflow ${label}`);
1201
- assertDefaultExportIsDefineWorkflow(source, label);
1202
- const workflow = await extractFromBundle(source, "workflow", (wf) => isWorkflow(wf) ? wf : undefined);
1203
- if (!workflow) {
1204
- throw new WorkflowSourceValidationError(`Bundled workflow ${label} default export is not a Workflow. ` + `Wrap your workflow with \`defineWorkflow\` from "@agent-compose/sdk" — ` + `raw \`export default async function\` is no longer supported.`);
1325
+ getRun(runId) {
1326
+ return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}`);
1205
1327
  }
1206
- const { metadata } = workflow;
1207
- const plan = workflowPlan(workflow.steps.map((step, index) => ({ index, name: step.name })));
1208
- const manifest = {
1209
- definitionBrand: true,
1210
- sourceHash: sha256(source),
1211
- bundlerVersion: BUNDLER_VERSION
1212
- };
1213
- const inputSchema = extractIOSchema(workflow.input);
1214
- const outputSchema = extractIOSchema(workflow.output);
1215
- return {
1216
- source,
1217
- manifest,
1218
- workflowPlan: plan,
1219
- networkPolicy: overrides?.networkPolicy ?? metadata.networkPolicy,
1220
- placeholders: overrides?.placeholders ?? metadata.placeholders,
1221
- ...metadata.description !== undefined ? { description: metadata.description } : {},
1222
- ...inputSchema !== undefined ? { inputSchema } : {},
1223
- ...outputSchema !== undefined ? { outputSchema } : {},
1224
- ...metadata.snapshots !== undefined ? { snapshots: metadata.snapshots } : {},
1225
- ...metadata.connectors !== undefined ? { connectors: metadata.connectors } : {},
1226
- ...metadata.connectorOperation !== undefined ? { connectorOperation: metadata.connectorOperation } : {},
1227
- ...metadata.invokePolicy !== undefined ? { invokePolicy: metadata.invokePolicy } : {}
1228
- };
1229
- }
1230
- function extractIOSchema(zodSchema) {
1231
- let json;
1232
- try {
1233
- const z4 = __require("zod");
1234
- if (!z4.toJSONSchema)
1235
- return;
1236
- json = z4.toJSONSchema(zodSchema);
1237
- } catch {
1238
- return;
1328
+ async getRunTimeline(runId, opts) {
1329
+ const q2 = new URLSearchParams;
1330
+ if (opts?.limit !== undefined)
1331
+ q2.set("limit", String(opts.limit));
1332
+ if (opts?.offset !== undefined)
1333
+ q2.set("offset", String(opts.offset));
1334
+ const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/timeline${q2.toString() ? `?${q2}` : ""}`);
1335
+ return body.events;
1239
1336
  }
1240
- if (!json || typeof json !== "object")
1241
- return;
1242
- if (Object.keys(json).filter((k) => k !== "$schema").length === 0)
1243
- return;
1244
- const out = {};
1245
- if (json.type !== undefined)
1246
- out.type = json.type;
1247
- if (json.description !== undefined)
1248
- out.description = String(json.description);
1249
- if (json.properties && typeof json.properties === "object") {
1250
- const props = {};
1251
- for (const [key, raw] of Object.entries(json.properties)) {
1252
- if (raw && typeof raw === "object") {
1253
- const r = raw;
1254
- props[key] = {};
1255
- if (r.type !== undefined)
1256
- props[key].type = r.type;
1257
- if (r.description !== undefined)
1258
- props[key].description = String(r.description);
1259
- }
1260
- }
1261
- if (Object.keys(props).length > 0)
1262
- out.properties = props;
1337
+ async reportEvent(runId, input) {
1338
+ const body = {
1339
+ ...input,
1340
+ ...input.timestamp instanceof Date ? { timestamp: input.timestamp.toISOString() } : {}
1341
+ };
1342
+ const response = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/events`, {
1343
+ method: "POST",
1344
+ body
1345
+ });
1346
+ return response.event;
1263
1347
  }
1264
- if (Array.isArray(json.required))
1265
- out.required = json.required.map(String);
1266
- return out;
1267
- }
1268
- // src/utils/schemas.ts
1269
- import { z as z4 } from "zod";
1270
- var AgentStatusSchema = z4.object({
1271
- summary: z4.string(),
1272
- completed: z4.array(z4.string()),
1273
- blockers: z4.array(z4.string()),
1274
- exit_signal: z4.boolean(),
1275
- needs_input: z4.boolean().optional(),
1276
- question: z4.string().optional()
1277
- });
1278
- // src/runtimes/claude.ts
1279
- import { query } from "@anthropic-ai/claude-agent-sdk";
1280
-
1281
- // src/agent/agent-loop.ts
1282
- import { z as z9 } from "zod";
1283
-
1284
- // src/agent/protocol.ts
1285
- import { z as z5 } from "zod";
1286
- var AgentMessageSchema = z5.object({
1287
- type: z5.enum(["init", "text", "thinking", "tool_use", "tool_result", "done", "error", "usage"]),
1288
- timestamp: z5.string()
1289
- }).passthrough();
1290
- function parseAgentResponse(text) {
1291
- const match = text.match(/<response>([\s\S]*?)<\/response>/);
1292
- if (!match)
1293
- return null;
1294
- try {
1295
- return JSON.parse(match[1].trim());
1296
- } catch {
1297
- return null;
1348
+ async listRunEvents(runId) {
1349
+ const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/events`);
1350
+ return body.events;
1298
1351
  }
1299
- }
1300
-
1301
- // src/agent/agent-loop.ts
1302
- import { randomUUID } from "node:crypto";
1303
-
1304
- // src/pause/manager.ts
1305
- import { z as z6 } from "zod";
1306
-
1307
- // src/pause/state-dir.ts
1308
- import { promises as fs } from "node:fs";
1309
- import { join as join2 } from "node:path";
1310
- import { randomBytes } from "node:crypto";
1311
- function getStateDir() {
1312
- return process.env.AGENT_COMPOSE_STATE_DIR ?? "/tmp/wf/state";
1313
- }
1314
- var checkpointsDir = () => join2(getStateDir(), "checkpoints");
1315
- var pausesDir = () => join2(getStateDir(), "pauses");
1316
- var agentStatePath = (agentInstanceId) => join2(getStateDir(), `agent-${agentInstanceId}.json`);
1317
- var runtimeStatePath = (agentInstanceId) => join2(getStateDir(), `runtime-${agentInstanceId}.json`);
1318
- var checkpointPath = (name) => join2(checkpointsDir(), `${name}.json`);
1319
- var pausePath = (pauseId) => join2(pausesDir(), `${pauseId}.json`);
1320
- var NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/;
1321
- function assertSafeName(label, name) {
1322
- if (!NAME_RE.test(name)) {
1323
- throw new Error(`${label} name must match ${NAME_RE} (got ${JSON.stringify(name)})`);
1352
+ async listRunArtifacts(runId) {
1353
+ const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/artifacts`);
1354
+ return body.artifacts.map((a) => ({
1355
+ path: a.path,
1356
+ factorySlug: a.factory_slug,
1357
+ sizeBytes: a.size_bytes,
1358
+ lastWriteAt: a.last_write_at,
1359
+ preview: a.preview
1360
+ }));
1324
1361
  }
1325
- }
1326
- function assertSafeCheckpointName(name) {
1327
- assertSafeName("checkpoint", name);
1328
- }
1329
- async function ensureDirs() {
1330
- await fs.mkdir(checkpointsDir(), { recursive: true });
1331
- await fs.mkdir(pausesDir(), { recursive: true });
1332
- }
1333
- async function atomicWriteJSON(path, value) {
1334
- await ensureDirs();
1335
- const tmp = `${path}.tmp.${randomBytes(6).toString("hex")}`;
1336
- const payload = JSON.stringify(value);
1337
- if (payload === undefined) {
1338
- throw new Error("pause state values must be JSON-serialisable");
1362
+ async putFactoryFile(path, content, opts) {
1363
+ const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
1364
+ const runToken = typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_RUN_TOKEN : undefined;
1365
+ const wire = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?path=${encodeURIComponent(path)}`, {
1366
+ method: "PUT",
1367
+ body: content,
1368
+ headers: {
1369
+ "content-type": opts?.contentType ?? "text/plain; charset=utf-8",
1370
+ ...runToken ? { "x-run-token": runToken } : {}
1371
+ }
1372
+ });
1373
+ return {
1374
+ path: wire.path,
1375
+ contentHash: wire.content_hash,
1376
+ sizeBytes: wire.size_bytes,
1377
+ created: wire.created
1378
+ };
1339
1379
  }
1340
- const handle = await fs.open(tmp, "w");
1341
- try {
1342
- await handle.writeFile(payload, { encoding: "utf8" });
1343
- await handle.sync();
1344
- } finally {
1345
- await handle.close();
1380
+ async getFactoryFile(path, opts) {
1381
+ const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
1382
+ const q2 = new URLSearchParams({ path });
1383
+ if (opts?.revision !== undefined)
1384
+ q2.set("revision", String(opts.revision));
1385
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?${q2}`, { responseType: "text" });
1346
1386
  }
1347
- await fs.rename(tmp, path);
1348
- }
1349
- async function readJSON(path) {
1350
- try {
1351
- const raw = await fs.readFile(path, "utf8");
1352
- return JSON.parse(raw);
1353
- } catch (err) {
1354
- if (err.code === "ENOENT")
1355
- return null;
1356
- throw err;
1387
+ async listMembers() {
1388
+ const body = await this.fetch("/api/v1/team/members");
1389
+ return body.members;
1357
1390
  }
1358
- }
1359
- async function readJSONWithPresence(path) {
1360
- try {
1361
- const raw = await fs.readFile(path, "utf8");
1362
- return { found: true, value: JSON.parse(raw) };
1363
- } catch (err) {
1364
- if (err.code === "ENOENT")
1365
- return { found: false };
1366
- throw err;
1391
+ async createMentions(input) {
1392
+ const factorySlug = input.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
1393
+ const runToken = typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_RUN_TOKEN : undefined;
1394
+ const { factorySlug: _omit, ...payload } = input;
1395
+ const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/mentions`, {
1396
+ method: "POST",
1397
+ body: payload,
1398
+ ...runToken ? { headers: { "x-run-token": runToken } } : {}
1399
+ });
1400
+ return body.mentions;
1367
1401
  }
1368
- }
1369
- async function readAgentState(agentInstanceId) {
1370
- assertSafeName("agent", agentInstanceId);
1371
- return readJSON(agentStatePath(agentInstanceId));
1372
- }
1373
- async function writeAgentState(agentInstanceId, state) {
1374
- assertSafeName("agent", agentInstanceId);
1375
- await atomicWriteJSON(agentStatePath(agentInstanceId), state);
1376
- }
1377
- async function readRuntimeCheckpoint(agentInstanceId) {
1378
- assertSafeName("runtime", agentInstanceId);
1379
- return readJSON(runtimeStatePath(agentInstanceId));
1380
- }
1381
- async function writeRuntimeCheckpoint(agentInstanceId, blob) {
1382
- assertSafeName("runtime", agentInstanceId);
1383
- await atomicWriteJSON(runtimeStatePath(agentInstanceId), blob);
1384
- }
1385
- async function readCheckpoint(name) {
1386
- assertSafeCheckpointName(name);
1387
- return readJSONWithPresence(checkpointPath(name));
1388
- }
1389
- async function writeCheckpoint(name, value) {
1390
- assertSafeCheckpointName(name);
1391
- await atomicWriteJSON(checkpointPath(name), value);
1392
- }
1393
- function hasOwn(obj, key) {
1394
- return Object.prototype.hasOwnProperty.call(obj, key);
1395
- }
1396
- function validatePauseRecord(value, expectedPauseId) {
1397
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
1398
- throw new Error(`pause record ${expectedPauseId} is malformed: expected object`);
1402
+ async listFactoryEvents(opts) {
1403
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1404
+ const q2 = new URLSearchParams;
1405
+ if (opts?.limit != null)
1406
+ q2.set("limit", String(opts.limit));
1407
+ if (opts?.name)
1408
+ q2.set("name", opts.name);
1409
+ if (opts?.from)
1410
+ q2.set("from", opts.from);
1411
+ if (opts?.before)
1412
+ q2.set("before", opts.before);
1413
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/events${q2.toString() ? `?${q2}` : ""}`);
1399
1414
  }
1400
- const record = value;
1401
- if (record.pauseId !== undefined && record.pauseId !== expectedPauseId) {
1402
- throw new Error(`pause record ${expectedPauseId} is malformed: pauseId mismatch`);
1415
+ async listRunLogs(runId, opts) {
1416
+ const q2 = new URLSearchParams;
1417
+ if (opts?.afterId !== undefined)
1418
+ q2.set("afterId", String(opts.afterId));
1419
+ if (opts?.limit !== undefined)
1420
+ q2.set("limit", String(opts.limit));
1421
+ if (opts?.direction !== undefined)
1422
+ q2.set("direction", opts.direction);
1423
+ const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/logs${q2.toString() ? `?${q2}` : ""}`);
1424
+ return body.logs;
1425
+ }
1426
+ async listTemplates(opts) {
1427
+ const path = opts?.factorySlug ? templatePath(opts.factorySlug) : "/api/v1/templates";
1428
+ const body = await this.fetch(path);
1429
+ return body.templates;
1403
1430
  }
1404
- if (record.resolved === true) {
1405
- if (typeof record.expired !== "boolean") {
1406
- throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require boolean expired`);
1407
- }
1408
- if (!hasOwn(record, "resumePayload") || record.resumePayload === undefined) {
1409
- throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require resumePayload`);
1410
- }
1411
- return {
1412
- pauseId: expectedPauseId,
1413
- request: hasOwn(record, "request") ? record.request : null,
1414
- ...record
1415
- };
1431
+ async listFactories() {
1432
+ const body = await this.fetch("/api/v1/factories");
1433
+ return body.factories;
1416
1434
  }
1417
- if (record.pauseId !== expectedPauseId) {
1418
- throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require matching pauseId`);
1435
+ createFactory(payload) {
1436
+ return this.fetch("/api/v1/factories", { method: "POST", body: payload });
1419
1437
  }
1420
- if (!hasOwn(record, "request") || record.request === undefined) {
1421
- throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require request`);
1438
+ getFactory(slug) {
1439
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`);
1422
1440
  }
1423
- if (record.resolved !== undefined && record.resolved !== false) {
1424
- throw new Error(`pause record ${expectedPauseId} is malformed: resolved must be true, false, or absent`);
1441
+ updateFactory(slug, updates) {
1442
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "PATCH", body: updates });
1425
1443
  }
1426
- if (hasOwn(record, "expired") || hasOwn(record, "resumePayload")) {
1427
- throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records must not include resolution fields`);
1444
+ deleteFactory(slug) {
1445
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "DELETE" });
1428
1446
  }
1429
- return record;
1430
- }
1431
- async function readPauseRecord(pauseId) {
1432
- assertSafeName("pause", pauseId);
1433
- const record = await readJSON(pausePath(pauseId));
1434
- return record === null ? null : validatePauseRecord(record, pauseId);
1435
- }
1436
- async function writePauseRecord(record) {
1437
- assertSafeName("pause", record.pauseId);
1438
- validatePauseRecord(record, record.pauseId);
1439
- await atomicWriteJSON(pausePath(record.pauseId), record);
1440
- }
1441
-
1442
- // src/pause/manager.ts
1443
- var AGENT_LOOP_STATE_SCHEMA_VERSION = 2;
1444
- var SUPPORTED_AGENT_LOOP_STATE_VERSIONS = z6.union([z6.literal(1), z6.literal(2)]);
1445
- var RunningAgentLoopStateSchema = z6.object({
1446
- schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1447
- phase: z6.literal("running"),
1448
- iteration: z6.number().int().nonnegative(),
1449
- completedIterations: z6.number().int().nonnegative(),
1450
- lastSessionId: z6.string(),
1451
- lastStatus: AgentStatusSchema.nullable(),
1452
- lastResponseText: z6.string(),
1453
- lastResponseValidationError: z6.string(),
1454
- iterationsWithoutStatus: z6.number().int().nonnegative(),
1455
- blockerStreak: z6.object({ key: z6.string(), count: z6.number().int().nonnegative() }).nullable(),
1456
- pendingSteerPause: z6.object({ reason: z6.string(), correlationKey: z6.string().nullable(), at: z6.number().int() }).nullable().default(null)
1457
- });
1458
- var SettledAgentLoopStateSchema = z6.object({
1459
- schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1460
- phase: z6.literal("settled"),
1461
- result: z6.object({
1462
- sessionId: z6.string(),
1463
- lastStatus: AgentStatusSchema.nullable(),
1464
- iterations: z6.number().int().nonnegative(),
1465
- response: z6.unknown().optional()
1466
- })
1467
- });
1468
- var AgentLoopStateSchema = z6.discriminatedUnion("phase", [
1469
- RunningAgentLoopStateSchema,
1470
- SettledAgentLoopStateSchema
1471
- ]);
1472
- function toProgressState(state) {
1473
- return {
1474
- iteration: state.iteration,
1475
- completedIterations: state.completedIterations,
1476
- lastSessionId: state.lastSessionId,
1477
- lastStatus: state.lastStatus,
1478
- lastResponseText: state.lastResponseText,
1479
- lastResponseValidationError: state.lastResponseValidationError,
1480
- iterationsWithoutStatus: state.iterationsWithoutStatus,
1481
- blockerStreak: state.blockerStreak,
1482
- pendingSteerPause: state.pendingSteerPause
1483
- };
1484
- }
1485
-
1486
- class PauseManager {
1487
- agentId;
1488
- constructor(agentId) {
1489
- this.agentId = agentId;
1447
+ async listSchedules(factorySlug = "default") {
1448
+ const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules`);
1449
+ return body.schedules;
1490
1450
  }
1491
- async restoreAgentLoop(runtime) {
1492
- const priorState = await readAgentState(this.agentId);
1493
- if (priorState === null)
1494
- return { kind: "fresh" };
1495
- const parsed = AgentLoopStateSchema.safeParse(priorState);
1496
- if (!parsed.success)
1497
- return { kind: "invalid", error: parsed.error.message };
1498
- const state = parsed.data;
1499
- if (state.phase === "settled") {
1500
- return {
1501
- kind: "settled",
1502
- result: {
1503
- sessionId: state.result.sessionId,
1504
- lastStatus: state.result.lastStatus,
1505
- iterations: state.result.iterations,
1506
- ...Object.prototype.hasOwnProperty.call(state.result, "response") ? { response: state.result.response } : {}
1507
- }
1508
- };
1509
- }
1510
- const runtimeBlob = await readRuntimeCheckpoint(this.agentId);
1511
- if (runtimeBlob !== null && !runtime.restoreCheckpoint) {
1512
- throw new Error(`runtime checkpoint exists for resumed agentId=${this.agentId}, but runtime cannot restore it`);
1513
- }
1514
- if (runtime.restoreCheckpoint && runtimeBlob === null) {
1515
- throw new Error(`missing runtime checkpoint for resumed agentId=${this.agentId}`);
1516
- }
1517
- if (runtimeBlob !== null)
1518
- runtime.restoreCheckpoint?.(runtimeBlob);
1519
- return { kind: "running", state: toProgressState(state) };
1451
+ createSchedule(payload) {
1452
+ const factorySlug = payload.factorySlug ?? "default";
1453
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules`, {
1454
+ method: "POST",
1455
+ body: { name: payload.name, workflow: payload.workflow, cron: payload.cron }
1456
+ });
1520
1457
  }
1521
- async saveRunningAgentLoop(state, runtime) {
1522
- await writeAgentState(this.agentId, {
1523
- schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1524
- phase: "running",
1525
- ...state
1458
+ deleteSchedule(id, factorySlug = "default") {
1459
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules/${encodeURIComponent(id)}`, { method: "DELETE" });
1460
+ }
1461
+ setSecret(workflowName, key, value, opts) {
1462
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1463
+ return this.fetch(templatePath(factorySlug, workflowName, "secrets"), {
1464
+ method: "POST",
1465
+ body: { key, value }
1526
1466
  });
1527
- if (runtime.captureCheckpoint) {
1528
- const blob = runtime.captureCheckpoint();
1529
- if (blob !== undefined)
1530
- await writeRuntimeCheckpoint(this.agentId, blob);
1531
- }
1532
1467
  }
1533
- async saveSettledAgentLoop(result) {
1534
- await writeAgentState(this.agentId, {
1535
- schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1536
- phase: "settled",
1537
- result: {
1538
- sessionId: result.sessionId,
1539
- lastStatus: result.lastStatus,
1540
- iterations: result.iterations,
1541
- ...result.response !== undefined ? { response: result.response } : {}
1542
- }
1468
+ async listSecrets(workflowName, opts) {
1469
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1470
+ const body = await this.fetch(templatePath(factorySlug, workflowName, "secrets"));
1471
+ return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
1472
+ }
1473
+ deleteSecret(workflowName, key, opts) {
1474
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1475
+ return this.fetch(templatePath(factorySlug, workflowName, "secrets", key), { method: "DELETE" });
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 }
1543
1482
  });
1544
1483
  }
1545
- }
1546
-
1547
- // src/pause/pause-core.ts
1548
- import { createHash as createHash2 } from "node:crypto";
1549
-
1550
- // src/step-invocation/types.ts
1551
- import { z as z7 } from "zod";
1552
- var StepPauseRequestSchema = z7.object({
1553
- reason: z7.string().min(1),
1554
- kind: z7.enum(["decision", "sleep", "event", "custom"]),
1555
- payload: z7.record(z7.string(), z7.unknown()).optional(),
1556
- ttlMs: z7.number().int().positive().optional(),
1557
- correlationKey: z7.string().min(1).optional(),
1558
- snapshot: z7.boolean().optional()
1559
- });
1560
-
1561
- class StepExecutionError extends Error {
1562
- kind;
1563
- exitCode;
1564
- constructor(error, stepLabel) {
1565
- super(`[${error.kind}] ${stepLabel}: ${error.message}`);
1566
- this.name = "StepExecutionError";
1567
- this.kind = error.kind;
1568
- this.exitCode = error.kind === "runner-exit" ? error.exitCode : undefined;
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 }));
1569
1488
  }
1570
- }
1571
-
1572
- // src/active-step.ts
1573
- import { AsyncLocalStorage } from "node:async_hooks";
1574
- var activeStepStorage = new AsyncLocalStorage;
1575
- var fallbackActive = null;
1576
- var BRIDGE_KEY = Symbol.for("agent-compose.activeStep.bridge");
1577
- function bridgeSlot() {
1578
- const g = globalThis;
1579
- return g[BRIDGE_KEY] ??= { state: null };
1580
- }
1581
- function makeState(step) {
1582
- return {
1583
- stepIndex: step.stepIndex,
1584
- nextAgentCallIndex: 0,
1585
- pauseOrdinalByScope: new Map
1586
- };
1587
- }
1588
- function currentState() {
1589
- return activeStepStorage.getStore() ?? bridgeSlot().state ?? fallbackActive;
1590
- }
1591
- async function runWithActiveStep(step, fn) {
1592
- return activeStepStorage.run(makeState(step), fn);
1593
- }
1594
- function setActiveStepBridge(step) {
1595
- const slot = bridgeSlot();
1596
- const prev = { state: slot.state };
1597
- slot.state = step ? makeState(step) : null;
1598
- return prev;
1599
- }
1600
- function restoreActiveStepBridge(prev) {
1601
- bridgeSlot().state = prev.state;
1602
- }
1603
- function getActiveStep() {
1604
- const state = currentState();
1605
- return state ? { stepIndex: state.stepIndex } : null;
1606
- }
1607
- function nextAgentCallInActiveStep() {
1608
- const state = currentState();
1609
- if (!state)
1610
- return null;
1611
- const callIndex = state.nextAgentCallIndex;
1612
- state.nextAgentCallIndex += 1;
1613
- return { stepIndex: state.stepIndex, callIndex };
1614
- }
1615
- function nextPauseOrdinalInActiveStep(scope) {
1616
- const state = currentState();
1617
- if (!state)
1618
- return null;
1619
- const next = state.pauseOrdinalByScope.get(scope) ?? 0;
1620
- state.pauseOrdinalByScope.set(scope, next + 1);
1621
- return next;
1622
- }
1623
-
1624
- // src/pause/errors.ts
1625
- class PauseError extends Error {
1626
- constructor(message) {
1627
- super(message);
1628
- this.name = new.target.name;
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
+ }
1493
+ createApiKey(input) {
1494
+ return this.fetch("/api-keys", { method: "POST", body: input });
1495
+ }
1496
+ async listApiKeys() {
1497
+ const body = await this.fetch("/api-keys");
1498
+ return body.data;
1499
+ }
1500
+ getUsage(from, to) {
1501
+ const qs = `?from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`;
1502
+ return this.fetch(`/api/v1/usage${qs}`);
1503
+ }
1504
+ cancelRun(runId) {
1505
+ return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
1506
+ }
1507
+ async* streamRunLogs(runId, opts) {
1508
+ const headers = { Authorization: `Bearer ${this.apiKey}` };
1509
+ if (opts?.lastEventId && opts.lastEventId > 0) {
1510
+ headers["Last-Event-ID"] = String(opts.lastEventId);
1511
+ }
1512
+ const res = await fetch(`${this.baseUrl}/api/v1/workflows/${encodeURIComponent(runId)}/stream`, {
1513
+ headers,
1514
+ ...opts?.signal ? { signal: opts.signal } : {}
1515
+ });
1516
+ if (!res.ok || !res.body) {
1517
+ let message = res.statusText;
1518
+ try {
1519
+ const body = await res.json();
1520
+ if (body.error)
1521
+ message = body.error;
1522
+ } catch {}
1523
+ throw new AgentComposeError(res.status, message);
1524
+ }
1525
+ for await (const ev of parseSseStream(res.body)) {
1526
+ yield ev.data;
1527
+ }
1629
1528
  }
1630
1529
  }
1530
+ // src/utils/bundler.ts
1531
+ import { createHash as createHash2 } from "node:crypto";
1532
+ import { parse as babelParse } from "@babel/parser";
1631
1533
 
1632
- class PauseExpiredError extends PauseError {
1633
- code = "pause_expired";
1634
- constructor(message = "pause expired before it was resolved") {
1635
- super(message);
1636
- }
1534
+ // src/utils/source-loader.ts
1535
+ import { dirname } from "path";
1536
+ import { writeFile, rm, mkdir } from "node:fs/promises";
1537
+ var TMP_DIR = process.env.TMP_DIR ?? "/tmp/agent-compose";
1538
+ var LATEST_VERSION = "$LATEST";
1539
+ async function importSourceModule(source, tmpPath) {
1540
+ await mkdir(dirname(tmpPath), { recursive: true });
1541
+ await writeFile(tmpPath, source, "utf8");
1542
+ const mod = await import(tmpPath);
1543
+ const cleanup = () => rm(tmpPath, { force: true });
1544
+ return { mod, cleanup };
1637
1545
  }
1638
1546
 
1639
- class PauseSchemaError extends PauseError {
1640
- code = "pause_schema";
1641
- issues;
1642
- constructor(message, issues) {
1643
- super(message);
1644
- this.issues = issues;
1645
- }
1547
+ // src/types/workflow-plan.ts
1548
+ function workflowPlan(steps) {
1549
+ return { steps };
1646
1550
  }
1647
1551
 
1648
- class PauseRequestError extends PauseError {
1649
- code = "pause_request";
1650
- constructor(message = "pause request is invalid") {
1552
+ // src/utils/bundler.ts
1553
+ var BUNDLER_VERSION = 2;
1554
+
1555
+ class WorkflowSourceValidationError extends Error {
1556
+ constructor(message) {
1651
1557
  super(message);
1558
+ this.name = "WorkflowSourceValidationError";
1652
1559
  }
1653
1560
  }
1654
-
1655
- // src/pause/pause-core.ts
1656
- var PAUSE_SIGNAL_BRAND = Symbol.for("agent-compose.PauseSignal");
1657
-
1658
- class PauseSignal {
1659
- pauseId;
1660
- pauseRequest;
1661
- observability;
1662
- [PAUSE_SIGNAL_BRAND] = true;
1663
- constructor(pauseId, pauseRequest, observability) {
1664
- this.pauseId = pauseId;
1665
- this.pauseRequest = pauseRequest;
1666
- this.observability = observability;
1561
+ async function bundle(path, label) {
1562
+ const bun = globalThis;
1563
+ if (!bun.Bun?.build)
1564
+ throw new Error("bundleWorkflow requires the Bun runtime (Bun.build)");
1565
+ const result = await bun.Bun.build({ entrypoints: [path], format: "esm", target: "node" });
1566
+ if (!result.success) {
1567
+ throw new Error(`Failed to bundle ${label}:
1568
+ ${result.logs.map((l) => l.message).join(`
1569
+ `)}`);
1667
1570
  }
1571
+ return result.outputs[0].text();
1668
1572
  }
1669
- function isPauseSignal(err) {
1670
- return typeof err === "object" && err !== null && err[PAUSE_SIGNAL_BRAND] === true;
1671
- }
1672
- var PAUSE_NAMESPACE = "5f3e2b8a-0c41-4e9d-9a7b-6d2c1f8e3a40";
1673
- function uuidToBytes(uuid) {
1674
- return Buffer.from(uuid.replace(/-/g, ""), "hex");
1573
+ async function extractFromBundle(source, label, pick) {
1574
+ const tmpPath = `/tmp/agentc-${label}-${Date.now()}-${Math.random().toString(36).slice(2)}.js`;
1575
+ try {
1576
+ const loaded = await importSourceModule(source, tmpPath);
1577
+ try {
1578
+ return pick(loaded.mod.default);
1579
+ } finally {
1580
+ await loaded.cleanup();
1581
+ }
1582
+ } catch (err) {
1583
+ throw new WorkflowSourceValidationError(`Bundled ${label} could not be evaluated: ${err instanceof Error ? err.message : String(err)}`);
1584
+ }
1675
1585
  }
1676
- function bytesToUuid(b) {
1677
- const h = b.toString("hex");
1678
- return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
1586
+ var DEFINE_WORKFLOW_CALLEE = /^(?:defineWorkflow|defineSandboxEnvironment)\d*$/;
1587
+ function isDefineWorkflowCall(node) {
1588
+ if (node.callee.type === "Identifier") {
1589
+ return DEFINE_WORKFLOW_CALLEE.test(node.callee.name);
1590
+ }
1591
+ if (node.callee.type === "MemberExpression" && node.callee.object.type === "CallExpression") {
1592
+ return isDefineWorkflowCall(node.callee.object);
1593
+ }
1594
+ return false;
1679
1595
  }
1680
- function uuidv5(name, namespace) {
1681
- const hash = createHash2("sha1").update(uuidToBytes(namespace)).update(Buffer.from(name, "utf8")).digest();
1682
- const bytes = Buffer.from(hash.subarray(0, 16));
1683
- bytes[6] = bytes[6] & 15 | 80;
1684
- bytes[8] = bytes[8] & 63 | 128;
1685
- return bytesToUuid(bytes);
1596
+ function assertDefaultExportIsDefineWorkflow(source, label) {
1597
+ let ast;
1598
+ try {
1599
+ ast = babelParse(source, {
1600
+ sourceType: "module",
1601
+ plugins: [],
1602
+ errorRecovery: false
1603
+ });
1604
+ } catch (err) {
1605
+ const msg = err instanceof Error ? err.message : String(err);
1606
+ throw new WorkflowSourceValidationError(`Bundled workflow ${label} failed to parse as JavaScript: ${msg}. ` + `If you registered raw .ts source, switch to \`agentc register <file.ts>\` so the source is bundled first.`);
1607
+ }
1608
+ const defaultExpr = findDefaultExportExpression(ast.program.body);
1609
+ if (!defaultExpr) {
1610
+ throw new WorkflowSourceValidationError(`Bundled workflow ${label} has no default export. ` + `Use \`export default defineWorkflow({ run: ... })\`.`);
1611
+ }
1612
+ if (defaultExpr.type !== "CallExpression" || !isDefineWorkflowCall(defaultExpr)) {
1613
+ throw new WorkflowSourceValidationError(`Bundled workflow ${label} default export must be a \`defineWorkflow({...})\` call ` + `(or a \`defineWorkflow(...).step(...).build()\` chain). ` + `Got: ${defaultExpr.type}. Wrap your workflow with \`defineWorkflow\` from "@agent-compose/sdk".`);
1614
+ }
1686
1615
  }
1687
- function scopeOf(coord) {
1688
- return coord.agentScope ? `${coord.runId}:${coord.stepIndex}:${coord.agentScope.agentId}:${coord.agentScope.iteration}` : `${coord.runId}:${coord.stepIndex}:bare`;
1616
+ function findDefaultExportExpression(body) {
1617
+ const defaultDecl = body.find((node) => node.type === "ExportDefaultDeclaration");
1618
+ if (defaultDecl) {
1619
+ return defaultDecl.declaration;
1620
+ }
1621
+ const exportNamed = body.find((node) => node.type === "ExportNamedDeclaration" && node.specifiers.some((spec2) => spec2.type === "ExportSpecifier" && spec2.exported.type === "Identifier" && spec2.exported.name === "default"));
1622
+ if (!exportNamed || exportNamed.type !== "ExportNamedDeclaration")
1623
+ return null;
1624
+ const spec = exportNamed.specifiers.find((s) => s.type === "ExportSpecifier" && s.exported.type === "Identifier" && s.exported.name === "default");
1625
+ if (!spec || spec.type !== "ExportSpecifier" || spec.local.type !== "Identifier")
1626
+ return null;
1627
+ const localName = spec.local.name;
1628
+ for (const stmt of body) {
1629
+ if (stmt.type !== "VariableDeclaration")
1630
+ continue;
1631
+ for (const decl of stmt.declarations) {
1632
+ if (decl.id.type === "Identifier" && decl.id.name === localName && decl.init && decl.init.type !== "TSSatisfiesExpression") {
1633
+ return decl.init;
1634
+ }
1635
+ }
1636
+ }
1637
+ return null;
1689
1638
  }
1690
- var pauseOrdinalByScope = new Map;
1691
- function nextPauseOrdinal(scope) {
1692
- const activeOrdinal = nextPauseOrdinalInActiveStep(scope);
1693
- if (activeOrdinal !== null)
1694
- return activeOrdinal;
1695
- const next = pauseOrdinalByScope.get(scope) ?? 0;
1696
- pauseOrdinalByScope.set(scope, next + 1);
1697
- return next;
1639
+ function sha256(text) {
1640
+ return createHash2("sha256").update(text, "utf8").digest("hex");
1698
1641
  }
1699
- function nextPauseId(coord) {
1700
- const scope = scopeOf(coord);
1701
- const ordinal = nextPauseOrdinal(scope);
1702
- return uuidv5(`${scope}:${ordinal}`, PAUSE_NAMESPACE);
1642
+ async function bundleWorkflow(workflowPath, overrides) {
1643
+ const label = `"${workflowPath}"`;
1644
+ const source = await bundle(workflowPath, `workflow ${label}`);
1645
+ assertDefaultExportIsDefineWorkflow(source, label);
1646
+ const workflow = await extractFromBundle(source, "workflow", (wf) => isWorkflow(wf) ? wf : undefined);
1647
+ if (!workflow) {
1648
+ throw new WorkflowSourceValidationError(`Bundled workflow ${label} default export is not a Workflow. ` + `Wrap your workflow with \`defineWorkflow\` from "@agent-compose/sdk" — ` + `raw \`export default async function\` is no longer supported.`);
1649
+ }
1650
+ const { metadata } = workflow;
1651
+ const plan = workflowPlan(workflow.steps.map((step, index) => ({ index, name: step.name })));
1652
+ const manifest = {
1653
+ definitionBrand: true,
1654
+ sourceHash: sha256(source),
1655
+ bundlerVersion: BUNDLER_VERSION
1656
+ };
1657
+ const inputSchema = extractIOSchema(workflow.input);
1658
+ const outputSchema = extractIOSchema(workflow.output);
1659
+ return {
1660
+ source,
1661
+ manifest,
1662
+ workflowPlan: plan,
1663
+ networkPolicy: overrides?.networkPolicy ?? metadata.networkPolicy,
1664
+ placeholders: overrides?.placeholders ?? metadata.placeholders,
1665
+ ...metadata.description !== undefined ? { description: metadata.description } : {},
1666
+ ...inputSchema !== undefined ? { inputSchema } : {},
1667
+ ...outputSchema !== undefined ? { outputSchema } : {},
1668
+ ...metadata.snapshots !== undefined ? { snapshots: metadata.snapshots } : {},
1669
+ ...metadata.resources !== undefined ? { resources: metadata.resources } : {},
1670
+ ...metadata.connectors !== undefined ? { connectors: metadata.connectors } : {},
1671
+ ...metadata.connectorOperation !== undefined ? { connectorOperation: metadata.connectorOperation } : {},
1672
+ ...metadata.invokePolicy !== undefined ? { invokePolicy: metadata.invokePolicy } : {},
1673
+ ...metadata.environmentBuild !== undefined ? { environmentBuild: metadata.environmentBuild } : {}
1674
+ };
1703
1675
  }
1704
- function nonJsonPath(value, path = "$", seen = new Set) {
1705
- if (value === null)
1706
- return null;
1707
- const kind = typeof value;
1708
- if (kind === "string" || kind === "boolean")
1709
- return null;
1710
- if (kind === "number")
1711
- return Number.isFinite(value) ? null : path;
1712
- if (kind === "undefined" || kind === "bigint" || kind === "function" || kind === "symbol")
1713
- return path;
1714
- if (kind !== "object")
1715
- return path;
1716
- const obj = value;
1717
- if (seen.has(obj))
1718
- return path;
1719
- seen.add(obj);
1720
- if (Array.isArray(value)) {
1721
- for (let i = 0;i < value.length; i++) {
1722
- const child = nonJsonPath(value[i], `${path}[${i}]`, seen);
1723
- if (child)
1724
- return child;
1676
+ function extractIOSchema(zodSchema) {
1677
+ let json;
1678
+ try {
1679
+ const z6 = __require("zod");
1680
+ if (!z6.toJSONSchema)
1681
+ return;
1682
+ json = z6.toJSONSchema(zodSchema);
1683
+ } catch {
1684
+ return;
1685
+ }
1686
+ if (!json || typeof json !== "object")
1687
+ return;
1688
+ if (Object.keys(json).filter((k) => k !== "$schema").length === 0)
1689
+ return;
1690
+ const out = {};
1691
+ if (json.type !== undefined)
1692
+ out.type = json.type;
1693
+ if (json.description !== undefined)
1694
+ out.description = String(json.description);
1695
+ if (json.properties && typeof json.properties === "object") {
1696
+ const props = {};
1697
+ for (const [key, raw] of Object.entries(json.properties)) {
1698
+ if (raw && typeof raw === "object") {
1699
+ const r = raw;
1700
+ props[key] = {};
1701
+ if (r.type !== undefined)
1702
+ props[key].type = r.type;
1703
+ if (r.description !== undefined)
1704
+ props[key].description = String(r.description);
1705
+ }
1725
1706
  }
1726
- seen.delete(obj);
1707
+ if (Object.keys(props).length > 0)
1708
+ out.properties = props;
1709
+ }
1710
+ if (Array.isArray(json.required))
1711
+ out.required = json.required.map(String);
1712
+ return out;
1713
+ }
1714
+ // src/utils/schemas.ts
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()
1723
+ });
1724
+ // src/runtimes/claude.ts
1725
+ import { query } from "@anthropic-ai/claude-agent-sdk";
1726
+
1727
+ // src/agent/agent-loop.ts
1728
+ import { z as z10 } from "zod";
1729
+
1730
+ // src/agent/protocol.ts
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()
1735
+ }).passthrough();
1736
+ function parseAgentResponse(text) {
1737
+ const match = text.match(/<response>([\s\S]*?)<\/response>/);
1738
+ if (!match)
1739
+ return null;
1740
+ try {
1741
+ return JSON.parse(match[1].trim());
1742
+ } catch {
1727
1743
  return null;
1728
1744
  }
1729
- const proto = Object.getPrototypeOf(obj);
1730
- if (proto !== Object.prototype && proto !== null)
1731
- return path;
1732
- for (const key of Reflect.ownKeys(obj)) {
1733
- if (typeof key !== "string")
1734
- return `${path}.${String(key)}`;
1735
- const desc = Object.getOwnPropertyDescriptor(obj, key);
1736
- if (!desc?.enumerable)
1737
- return `${path}.${key}`;
1738
- const child = nonJsonPath(value[key], `${path}.${key}`, seen);
1739
- if (child)
1740
- return child;
1741
- }
1742
- seen.delete(obj);
1743
- return null;
1744
1745
  }
1745
- function toWireRequest(req, kind) {
1746
- const wire = {
1747
- reason: req.reason,
1748
- kind,
1749
- ...req.payload !== undefined ? { payload: req.payload } : {},
1750
- ...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {},
1751
- ...req.correlationKey !== undefined ? { correlationKey: req.correlationKey } : {},
1752
- ...req.snapshot !== undefined ? { snapshot: req.snapshot } : {}
1746
+
1747
+ // src/agent/agent-loop.ts
1748
+ import { randomUUID } from "node:crypto";
1749
+
1750
+ // src/pause/manager.ts
1751
+ import { z as z8 } from "zod";
1752
+ var AGENT_LOOP_STATE_SCHEMA_VERSION = 2;
1753
+ var SUPPORTED_AGENT_LOOP_STATE_VERSIONS = z8.union([z8.literal(1), z8.literal(2)]);
1754
+ var RunningAgentLoopStateSchema = z8.object({
1755
+ schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1756
+ phase: z8.literal("running"),
1757
+ iteration: z8.number().int().nonnegative(),
1758
+ completedIterations: z8.number().int().nonnegative(),
1759
+ lastSessionId: z8.string(),
1760
+ lastStatus: AgentStatusSchema.nullable(),
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)
1766
+ });
1767
+ var SettledAgentLoopStateSchema = z8.object({
1768
+ schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1769
+ phase: z8.literal("settled"),
1770
+ result: z8.object({
1771
+ sessionId: z8.string(),
1772
+ lastStatus: AgentStatusSchema.nullable(),
1773
+ iterations: z8.number().int().nonnegative(),
1774
+ response: z8.unknown().optional()
1775
+ })
1776
+ });
1777
+ var AgentLoopStateSchema = z8.discriminatedUnion("phase", [
1778
+ RunningAgentLoopStateSchema,
1779
+ SettledAgentLoopStateSchema
1780
+ ]);
1781
+ function toProgressState(state) {
1782
+ return {
1783
+ iteration: state.iteration,
1784
+ completedIterations: state.completedIterations,
1785
+ lastSessionId: state.lastSessionId,
1786
+ lastStatus: state.lastStatus,
1787
+ lastResponseText: state.lastResponseText,
1788
+ lastResponseValidationError: state.lastResponseValidationError,
1789
+ iterationsWithoutStatus: state.iterationsWithoutStatus,
1790
+ blockerStreak: state.blockerStreak,
1791
+ pendingSteerPause: state.pendingSteerPause
1753
1792
  };
1754
- const parsed = StepPauseRequestSchema.safeParse(wire);
1755
- if (!parsed.success) {
1756
- throw new PauseRequestError(`pause request failed validation: ${parsed.error.message}`);
1757
- }
1758
- const badPath = nonJsonPath(parsed.data);
1759
- if (badPath) {
1760
- throw new PauseRequestError(`pause request must be JSON-serializable (invalid value at ${badPath})`);
1761
- }
1762
- return parsed.data;
1763
1793
  }
1764
- async function corePause(req, coord, kind = "custom") {
1765
- const wire = toWireRequest(req, kind);
1766
- const pauseId = nextPauseId(coord);
1767
- const record = await readPauseRecord(pauseId);
1768
- if (record === null || record.resolved !== true) {
1769
- await writePauseRecord({ pauseId, request: wire });
1770
- throw new PauseSignal(pauseId, wire);
1794
+
1795
+ class PauseManager {
1796
+ agentId;
1797
+ constructor(agentId) {
1798
+ this.agentId = agentId;
1771
1799
  }
1772
- if (record.expired === true) {
1773
- const onExpiry = req.onExpiry ?? { mode: "throw" };
1774
- if (onExpiry.mode === "resolve")
1775
- return onExpiry.value;
1776
- throw new PauseExpiredError(`pause "${req.reason}" expired before it was resolved`);
1800
+ async restoreAgentLoop(runtime) {
1801
+ const priorState = await readAgentState(this.agentId);
1802
+ if (priorState === null)
1803
+ return { kind: "fresh" };
1804
+ const parsed = AgentLoopStateSchema.safeParse(priorState);
1805
+ if (!parsed.success)
1806
+ return { kind: "invalid", error: parsed.error.message };
1807
+ const state = parsed.data;
1808
+ if (state.phase === "settled") {
1809
+ return {
1810
+ kind: "settled",
1811
+ result: {
1812
+ sessionId: state.result.sessionId,
1813
+ lastStatus: state.result.lastStatus,
1814
+ iterations: state.result.iterations,
1815
+ ...Object.prototype.hasOwnProperty.call(state.result, "response") ? { response: state.result.response } : {}
1816
+ }
1817
+ };
1818
+ }
1819
+ const runtimeBlob = await readRuntimeCheckpoint(this.agentId);
1820
+ if (runtimeBlob !== null && !runtime.restoreCheckpoint) {
1821
+ throw new Error(`runtime checkpoint exists for resumed agentId=${this.agentId}, but runtime cannot restore it`);
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) };
1777
1829
  }
1778
- const payload = record.resumePayload ?? null;
1779
- if (req.schema) {
1780
- const parsed = req.schema.safeParse(payload);
1781
- if (!parsed.success) {
1782
- 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);
1783
1840
  }
1784
- return parsed.data;
1785
1841
  }
1786
- 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
+ }
1787
1854
  }
1788
1855
 
1789
1856
  // src/agent/steer-control.ts
1790
- import { z as z8 } from "zod";
1791
- var SteerDecisionSchema = z8.object({
1792
- message: z8.string().min(1),
1793
- 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`"
1794
1864
  });
1795
1865
  var steerPending = new Map;
1796
1866
  function signalSteerPending(agentId, payload) {
@@ -1853,7 +1923,7 @@ async function runControlPoller(opts) {
1853
1923
  // src/agent/agent-loop.ts
1854
1924
  var DEFAULT_CLAUDE_MODEL = "claude-fable-5";
1855
1925
  var SAME_BLOCKER_ITERATIONS = 3;
1856
- var STALL_ITERATIONS = 3;
1926
+ var STALL_ITERATIONS = 6;
1857
1927
  var MESSAGE_PREVIEW_CHARS = 400;
1858
1928
  function parseAgentStatus(text) {
1859
1929
  const match = text.match(/<status>([\s\S]*?)<\/status>/);
@@ -1903,6 +1973,8 @@ function summarizeAgentMessage(msg) {
1903
1973
  return { type: "done", sessionId: msg.sessionId };
1904
1974
  case "error":
1905
1975
  return { type: "error", text: truncate(msg.text) };
1976
+ case "plan":
1977
+ return { type: "plan", entries: msg.entries };
1906
1978
  }
1907
1979
  }
1908
1980
  async function agentLoop(opts) {
@@ -1928,7 +2000,8 @@ async function agentLoop(opts) {
1928
2000
  abortSignal: loopAbort.signal,
1929
2001
  retryCount: 0,
1930
2002
  agentId,
1931
- iteration
2003
+ iteration,
2004
+ pause: boundProcessorPause(opts.pause, { agentId, iteration })
1932
2005
  });
1933
2006
  if (!opts.runtime)
1934
2007
  throw new Error("agentLoop: opts.runtime is required");
@@ -1940,7 +2013,8 @@ async function agentLoop(opts) {
1940
2013
  processors,
1941
2014
  requestContext,
1942
2015
  agentId,
1943
- ...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) } } : {}
1944
2018
  });
1945
2019
  if (processors.some((p) => p.processToolCall) && !client.supportsToolCallProcessor) {
1946
2020
  process.stderr.write(`${logLabel} processToolCall registered but runtime does not support pre-tool-use gating — hook is dormant
@@ -2034,6 +2108,7 @@ async function agentLoop(opts) {
2034
2108
  steerDecision = await opts.pause({
2035
2109
  reason: pendingSteerPause.reason,
2036
2110
  ...pendingSteerPause.correlationKey !== null ? { correlationKey: pendingSteerPause.correlationKey } : {},
2111
+ ...pendingSteerPause.payload !== undefined ? { payload: pendingSteerPause.payload } : {},
2037
2112
  schema: SteerDecisionSchema
2038
2113
  }, { agentId, iteration });
2039
2114
  pendingSteerPause = null;
@@ -2070,6 +2145,7 @@ Re-emit the COMPLETE corrected <response> JSON now: every required field present
2070
2145
  prompt,
2071
2146
  sessionId: lastSessionId ?? undefined,
2072
2147
  iteration: iteration + 1,
2148
+ signal: loopAbort.signal,
2073
2149
  ...opts.inbox ? { inboxStream: opts.inbox } : {}
2074
2150
  })) {
2075
2151
  const outputVerdict = await runProcessorChain(processors, (p) => p.processOutput, rawMsg, procCtx);
@@ -2084,7 +2160,15 @@ Re-emit the COMPLETE corrected <response> JSON now: every required field present
2084
2160
  }
2085
2161
  const msg = outputVerdict.value;
2086
2162
  opts.onAgentEvent?.(iteration, msg);
2087
- opts.onAgentLifecycleEvent?.({ event: "agent.message", at: Date.now(), agentId, label, iteration: iteration + 1, message: summarizeAgentMessage(msg) });
2163
+ const summary = summarizeAgentMessage(msg);
2164
+ opts.onAgentLifecycleEvent?.({
2165
+ event: "agent.message",
2166
+ at: Date.now(),
2167
+ agentId,
2168
+ label,
2169
+ iteration: iteration + 1,
2170
+ message: summary.type === "usage" && client.model != null ? { ...summary, model: client.model } : summary
2171
+ });
2088
2172
  if (msg.type === "init")
2089
2173
  lastSessionId = msg.sessionId;
2090
2174
  if (msg.type === "text")
@@ -2094,6 +2178,19 @@ Re-emit the COMPLETE corrected <response> JSON now: every required field present
2094
2178
  }
2095
2179
  lastResponseText = responseText;
2096
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
+ }
2097
2194
  const rawResponse = opts.responseSchema ? parseAgentResponse(responseText) ?? parseRawJsonResponse(responseText) : null;
2098
2195
  const validateResponse = (target) => {
2099
2196
  const parsed = opts.responseSchema.safeParse(target);
@@ -2163,9 +2260,9 @@ raw: ${JSON.stringify(rawResponse).slice(0, 400)}
2163
2260
  if (!status && rawResponse === null) {
2164
2261
  if (++iterationsWithoutStatus >= STALL_ITERATIONS)
2165
2262
  throw new Error(`${logLabel} stalled: no <status> block after ${iterationsWithoutStatus} iterations`);
2166
- if (opts.responseSchema && schemaRetriesLeft-- > 0) {
2167
- lastResponseValidationError = "no <response> block found — the turn ended with neither a <status> nor a <response> block; emit the complete <response> JSON";
2168
- process.stdout.write(`${logLabel} no <status>/<response> emitted — corrective re-prompt (${schemaRetriesLeft} retries left)
2263
+ if (schemaRetriesLeft-- > 0) {
2264
+ lastResponseValidationError = "Your turn ended with no <status> block" + (opts.responseSchema ? " (and no <response> block)" : "") + ". " + "If you launched a background job (`… &`) and are waiting to be notified when it finishes — STOP: nothing will notify you here. " + "Poll it NOW (read its output / wait for it synchronously to completion), then emit your <status>" + (opts.responseSchema ? " and the complete <response> JSON." : ".");
2265
+ process.stdout.write(`${logLabel} empty turn (no <status>) — corrective re-prompt (${schemaRetriesLeft} retries left)
2169
2266
  `);
2170
2267
  iteration--;
2171
2268
  continue;
@@ -2379,12 +2476,15 @@ class ClaudeRunner {
2379
2476
  toolInput: pre.tool_input ?? {},
2380
2477
  toolUseId: toolUseId ?? `${pre.tool_name}-${Date.now()}`
2381
2478
  };
2479
+ const gateAgentId = this.options.agentId ?? "agent";
2480
+ const gateIteration = opts.iteration ?? 1;
2382
2481
  const gate = await this.gateToolCall(call, {
2383
2482
  requestContext,
2384
2483
  abortSignal: signal ?? opts.signal ?? new AbortController().signal,
2385
2484
  retryCount: 0,
2386
- agentId: this.options.agentId ?? "agent",
2387
- iteration: opts.iteration ?? 1
2485
+ agentId: gateAgentId,
2486
+ iteration: gateIteration,
2487
+ pause: boundProcessorPause(this.options.pause, { agentId: gateAgentId, iteration: gateIteration })
2388
2488
  });
2389
2489
  if (gate.kind === "allow")
2390
2490
  return { hookSpecificOutput: { hookEventName: pre.hook_event_name, permissionDecision: "allow", updatedInput: gate.call.toolInput } };
@@ -2414,67 +2514,360 @@ class ClaudeRunner {
2414
2514
  } catch {}
2415
2515
  })();
2416
2516
  }
2417
- const promptForQuery = inboxQueue ?? opts.prompt;
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
+ }
2526
+ try {
2527
+ let emittedAssistantText = false;
2528
+ for await (const message of query({
2529
+ prompt: promptForQuery,
2530
+ options: {
2531
+ tools: this.options.allowedTools,
2532
+ allowedTools: this.options.allowedTools,
2533
+ maxTurns: this.options.maxTurns,
2534
+ model: this.model,
2535
+ outputFormat: this.options.outputFormat,
2536
+ thinking: resolveThinking(this.model, this.config.thinking),
2537
+ effort: this.config.effort,
2538
+ cwd: this.options.cwd,
2539
+ abortController: queryAbort,
2540
+ env: { ...process.env, ...this.config.env ?? {}, ...this.options.agentId ? { AGENT_COMPOSE_AGENT_ID: this.options.agentId } : {} },
2541
+ pathToClaudeCodeExecutable: this.config.pathToClaudeCodeExecutable ?? process.env.CLAUDE_CODE_EXECUTABLE ?? DEFAULT_CLAUDE_PATH,
2542
+ ...this.config.claudeMdContent ? { systemPrompt: { type: "preset", preset: "claude_code", append: this.config.claudeMdContent } } : {},
2543
+ skills: this.config.skills ?? "all",
2544
+ resume: opts.sessionId,
2545
+ mcpServers: this.config.mcpServers,
2546
+ permissionMode: "acceptEdits",
2547
+ hooks: { PreToolUse: [{ hooks: [preToolHook] }] }
2548
+ }
2549
+ })) {
2550
+ const raw = message;
2551
+ const translated = translateMessage(raw);
2552
+ for (const msg of translated) {
2553
+ if (raw.type === "result" && msg.type === "text" && emittedAssistantText)
2554
+ continue;
2555
+ if (msg.type === "text" && raw.type === "assistant")
2556
+ emittedAssistantText = true;
2557
+ yield msg;
2558
+ }
2559
+ if (raw.type === "result" && inboxQueue)
2560
+ inboxQueue.close();
2561
+ }
2562
+ } catch (err) {
2563
+ const text = formatError(err);
2564
+ if (/maximum number of turns/i.test(text)) {
2565
+ yield { type: "done", sessionId: opts.sessionId ?? "", timestamp: now2() };
2566
+ } else {
2567
+ yield { type: "error", text, timestamp: now2() };
2568
+ }
2569
+ } finally {
2570
+ inboxQueue?.close();
2571
+ opts.signal?.removeEventListener("abort", onLoopAbort);
2572
+ }
2573
+ }
2574
+ }
2575
+ function createClaudeRuntime(config = {}) {
2576
+ return defineRuntime({
2577
+ create: (sandbox, opts) => new ClaudeRunner(sandbox, opts, config)
2578
+ });
2579
+ }
2580
+ var claude_default = createClaudeRuntime();
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";
2589
+ function now3() {
2590
+ return new Date().toISOString();
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;
2418
2826
  try {
2419
- let emittedAssistantText = false;
2420
- for await (const message of query({
2421
- prompt: promptForQuery,
2422
- options: {
2423
- tools: this.options.allowedTools,
2424
- allowedTools: this.options.allowedTools,
2425
- maxTurns: this.options.maxTurns,
2426
- model: this.model,
2427
- outputFormat: this.options.outputFormat,
2428
- thinking: resolveThinking(this.model, this.config.thinking),
2429
- effort: this.config.effort,
2430
- cwd: this.options.cwd,
2431
- env: { ...process.env, ...this.config.env ?? {} },
2432
- pathToClaudeCodeExecutable: this.config.pathToClaudeCodeExecutable ?? process.env.CLAUDE_CODE_EXECUTABLE ?? DEFAULT_CLAUDE_PATH,
2433
- ...this.config.claudeMdContent ? { systemPrompt: { type: "preset", preset: "claude_code", append: this.config.claudeMdContent } } : {},
2434
- skills: this.config.skills ?? "all",
2435
- resume: opts.sessionId,
2436
- mcpServers: this.config.mcpServers,
2437
- permissionMode: "acceptEdits",
2438
- hooks: { PreToolUse: [{ hooks: [preToolHook] }] }
2439
- }
2440
- })) {
2441
- const raw = message;
2442
- const translated = translateMessage(raw);
2443
- for (const msg of translated) {
2444
- if (raw.type === "result" && msg.type === "text" && emittedAssistantText)
2445
- continue;
2446
- if (msg.type === "text" && raw.type === "assistant")
2447
- emittedAssistantText = true;
2448
- yield msg;
2449
- }
2450
- if (raw.type === "result" && inboxQueue)
2451
- inboxQueue.close();
2452
- }
2827
+ verdict = await this.deps.gateToolCall(call, this.deps.procCtx());
2453
2828
  } catch (err) {
2454
- const text = formatError(err);
2455
- if (/maximum number of turns/i.test(text)) {
2456
- yield { type: "done", sessionId: opts.sessionId ?? "", timestamp: now2() };
2457
- } else {
2458
- yield { type: "error", text, timestamp: now2() };
2829
+ if (isPauseSignal(err)) {
2830
+ this.pendingPause = err;
2831
+ this.cancel();
2832
+ return { outcome: { outcome: "cancelled" } };
2459
2833
  }
2460
- } finally {
2461
- inboxQueue?.close();
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" } };
2462
2844
  }
2845
+ return { outcome: { outcome: "selected", optionId: option.optionId } };
2463
2846
  }
2464
2847
  }
2465
- function createClaudeRuntime(config = {}) {
2466
- return defineRuntime({
2467
- create: (sandbox, opts) => new ClaudeRunner(sandbox, opts, config)
2468
- });
2469
- }
2470
- var claude_default = createClaudeRuntime();
2848
+
2471
2849
  // src/runtimes/_cli-agent.ts
2472
- function now3() {
2850
+ function now4() {
2473
2851
  return new Date().toISOString();
2474
2852
  }
2475
2853
  function shellQuote(value) {
2476
2854
  return `'${value.replace(/'/g, `'\\''`)}'`;
2477
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
+ }
2478
2871
 
2479
2872
  class CliAgentRunner {
2480
2873
  sandbox;
@@ -2482,6 +2875,16 @@ class CliAgentRunner {
2482
2875
  spec;
2483
2876
  configModel;
2484
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;
2485
2888
  constructor(sandbox, options, spec, configModel) {
2486
2889
  this.sandbox = sandbox;
2487
2890
  this.options = options;
@@ -2489,9 +2892,37 @@ class CliAgentRunner {
2489
2892
  this.configModel = configModel;
2490
2893
  this.kind = spec.kind;
2491
2894
  }
2895
+ enableAcpForTesting() {
2896
+ this.acpTransportReady = true;
2897
+ }
2492
2898
  get model() {
2493
2899
  return this.configModel ?? this.options.model ?? this.spec.defaultModel;
2494
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
+ }
2495
2926
  installed = false;
2496
2927
  async ensureInstalled() {
2497
2928
  if (this.installed)
@@ -2508,7 +2939,133 @@ class CliAgentRunner {
2508
2939
  this.installed = true;
2509
2940
  }
2510
2941
  async* sendMessage(opts) {
2511
- 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) {
2512
3069
  let sessionId = opts.sessionId;
2513
3070
  let sawError = false;
2514
3071
  try {
@@ -2566,13 +3123,13 @@ class CliAgentRunner {
2566
3123
  const res = await runPromise;
2567
3124
  if (res.exitCode !== 0 && !sawError) {
2568
3125
  const tail = (res.stderr ?? "").slice(-2000);
2569
- 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() };
2570
3127
  return;
2571
3128
  }
2572
3129
  if (!sawError)
2573
- yield { type: "done", sessionId: sessionId ?? "", timestamp: now3() };
3130
+ yield { type: "done", sessionId: sessionId ?? "", timestamp: now4() };
2574
3131
  } catch (err) {
2575
- yield { type: "error", text: formatError(err), timestamp: now3() };
3132
+ yield { type: "error", text: formatError(err), timestamp: now4() };
2576
3133
  }
2577
3134
  }
2578
3135
  }
@@ -2583,13 +3140,19 @@ function createCliAgentRuntime(spec, configModel) {
2583
3140
  }
2584
3141
 
2585
3142
  // src/runtimes/codex.ts
2586
- function now4() {
3143
+ function now5() {
2587
3144
  return new Date().toISOString();
2588
3145
  }
3146
+ var CODEX_ACP_ADAPTER = "@agentclientprotocol/codex-acp@0.1.0";
2589
3147
  var codexSpec = {
2590
3148
  kind: "codex",
2591
3149
  authEnv: "CODEX_API_KEY",
2592
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
+ },
2593
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)',
2594
3157
  promptPayload: (prompt) => prompt,
2595
3158
  buildCommand: ({ promptPath, sessionId, model, cwd }) => {
@@ -2605,7 +3168,7 @@ var codexSpec = {
2605
3168
  },
2606
3169
  extractSessionId: (p) => p.type === "thread.started" && typeof p.thread_id === "string" ? p.thread_id : undefined,
2607
3170
  mapEvent: (p) => {
2608
- const ts = now4();
3171
+ const ts = now5();
2609
3172
  switch (p.type) {
2610
3173
  case "item.started":
2611
3174
  case "item.completed": {
@@ -2660,7 +3223,7 @@ function createCodexRuntime(config = {}) {
2660
3223
  }
2661
3224
  var codex_default = createCodexRuntime();
2662
3225
  // src/runtimes/amp.ts
2663
- function now5() {
3226
+ function now6() {
2664
3227
  return new Date().toISOString();
2665
3228
  }
2666
3229
  function blocks(msg) {
@@ -2680,7 +3243,7 @@ var ampSpec = {
2680
3243
  },
2681
3244
  extractSessionId: (p) => typeof p.session_id === "string" ? p.session_id : undefined,
2682
3245
  mapEvent: (p) => {
2683
- const ts = now5();
3246
+ const ts = now6();
2684
3247
  if (p.type === "assistant") {
2685
3248
  return blocks(p).flatMap((b) => {
2686
3249
  if (b.type === "text")
@@ -2725,6 +3288,7 @@ var amp_default = createAmpRuntime();
2725
3288
  import { promises as fs2 } from "node:fs";
2726
3289
  import { dirname as dirname2 } from "node:path";
2727
3290
  import { spawn } from "node:child_process";
3291
+ import { Readable, Writable } from "node:stream";
2728
3292
  import { Sandbox, SandboxNotFoundError, RateLimitError } from "e2b";
2729
3293
  import { Sandbox as Desktop } from "@e2b/desktop";
2730
3294
  import pRetry from "p-retry";
@@ -2746,6 +3310,33 @@ class SandboxUnavailableError extends Error {
2746
3310
  // src/sandbox.ts
2747
3311
  var AGENT_COMPOSE_TAG = process.env.AGENT_COMPOSE_TAG ?? `agent-compose-${process.env.AGENT_COMPOSE_ENV ?? "dev"}`;
2748
3312
  var VERCEL_VM_LIFETIME_WINDOW_MS = 6 * 60 * 60 * 1000;
3313
+ var SANDBOX_VCPUS = {
3314
+ "2vcpu-4gb": 2,
3315
+ "4vcpu-8gb": 4,
3316
+ "8vcpu-16gb": 8,
3317
+ "32vcpu-64gb": 32
3318
+ };
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
+ }
2749
3340
  function makeSandboxProvider(sb) {
2750
3341
  const commands = sb.commands;
2751
3342
  return {
@@ -2753,18 +3344,33 @@ function makeSandboxProvider(sb) {
2753
3344
  commands: {
2754
3345
  async run(cmd, opts) {
2755
3346
  let stderr = "";
2756
- const result = await commands.run(cmd, {
2757
- ...opts,
3347
+ const { sudo, ...rest } = opts ?? {};
3348
+ const runOpts = {
3349
+ ...rest,
3350
+ ...sudo ? { user: "root" } : {},
2758
3351
  onStderr: (chunk) => {
2759
3352
  stderr += chunk;
2760
3353
  opts?.onStderr?.(chunk);
2761
3354
  }
2762
- });
2763
- return {
2764
- exitCode: result.exitCode,
2765
- stdout: result.stdout,
2766
- stderr: typeof result.stderr === "string" ? result.stderr : stderr
2767
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
+ }
2768
3374
  }
2769
3375
  },
2770
3376
  files: {
@@ -2772,7 +3378,9 @@ function makeSandboxProvider(sb) {
2772
3378
  await sb.files.write(path, content);
2773
3379
  }
2774
3380
  },
2775
- kill: () => sb.kill()
3381
+ async kill() {
3382
+ await sb.kill();
3383
+ }
2776
3384
  };
2777
3385
  }
2778
3386
  function e2bMaxSandboxMs() {
@@ -2799,6 +3407,9 @@ function makeE2bSandboxProvider(sb) {
2799
3407
  async snapshot() {
2800
3408
  const { snapshotId } = await sb.createSnapshot();
2801
3409
  return { snapshotId };
3410
+ },
3411
+ async updateNetworkPolicy(policy) {
3412
+ await sb.updateNetwork(toE2bNetwork(policy));
2802
3413
  }
2803
3414
  };
2804
3415
  }
@@ -2890,6 +3501,31 @@ function toVercelNetworkPolicy(policy) {
2890
3501
  }
2891
3502
  return { ...policy, allow };
2892
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
+ }
2893
3529
  var VERCEL_MAX_TAGS = 5;
2894
3530
  function buildVercelTags(metadata) {
2895
3531
  const tags = { ...metadata, executor: AGENT_COMPOSE_TAG };
@@ -2911,7 +3547,7 @@ function makeVercelSandboxProvider(sb, globalEnvs) {
2911
3547
  let stdout = "";
2912
3548
  let stderr = "";
2913
3549
  const handle = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal, ...opts?.sudo ? { sudo: true } : {} }).catch((err) => asUnavailable(err, true));
2914
- try {
3550
+ const collect = async () => {
2915
3551
  await pRetry(async (attempt) => {
2916
3552
  const h = attempt === 1 ? handle : await sb.getCommand(handle.cmdId);
2917
3553
  if (attempt > 1) {
@@ -2930,11 +3566,22 @@ function makeVercelSandboxProvider(sb, globalEnvs) {
2930
3566
  }, { retries: 3, minTimeout: 1000, factor: 2 });
2931
3567
  const finished = await handle.wait();
2932
3568
  return { exitCode: finished.exitCode, stdout, stderr };
3569
+ };
3570
+ let timer;
3571
+ const deadline = opts?.timeoutMs ? new Promise((_, reject) => {
3572
+ timer = setTimeout(() => reject(new Error(`command timed out after ${opts.timeoutMs}ms: ${cmd.slice(0, 200)}`)), opts.timeoutMs);
3573
+ }) : undefined;
3574
+ try {
3575
+ return await (deadline ? Promise.race([collect(), deadline]) : collect());
2933
3576
  } catch (err) {
2934
- if (signal?.aborted) {
3577
+ if (err instanceof Error && err.message.startsWith("command timed out after"))
3578
+ throw err;
3579
+ if (signal?.aborted)
2935
3580
  throw new Error(`command timed out after ${opts?.timeoutMs}ms: ${cmd.slice(0, 200)}`);
2936
- }
2937
3581
  return asUnavailable(err, false);
3582
+ } finally {
3583
+ if (timer)
3584
+ clearTimeout(timer);
2938
3585
  }
2939
3586
  }
2940
3587
  },
@@ -2987,6 +3634,38 @@ function makeLocalSandboxProvider() {
2987
3634
  proc.on("error", reject);
2988
3635
  proc.on("close", (code) => resolve({ exitCode: code ?? 0, stdout, stderr }));
2989
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
+ };
2990
3669
  }
2991
3670
  },
2992
3671
  files: {
@@ -3011,7 +3690,8 @@ var SANDBOX_PROVIDERS = {
3011
3690
  const np = opts.networkPolicy ? { networkPolicy: toVercelNetworkPolicy(opts.networkPolicy) } : {};
3012
3691
  const tags = buildVercelTags(opts.metadata);
3013
3692
  const tmpl = opts.template ?? process.env.VERCEL_DEFAULT_SNAPSHOT;
3014
- const sb = await VercelSandbox.create(tmpl ? { source: { type: "snapshot", snapshotId: tmpl }, timeout: opts.timeoutMs, env: opts.envs, tags, persistent: false, ...np, ...creds } : { runtime: "node24", timeout: opts.timeoutMs, env: opts.envs, tags, persistent: false, ...np, ...creds });
3693
+ const resources = { vcpus: SANDBOX_VCPUS[opts.size ?? DEFAULT_SANDBOX_SIZE] };
3694
+ const sb = await VercelSandbox.create(tmpl ? { source: { type: "snapshot", snapshotId: tmpl }, timeout: opts.timeoutMs, env: opts.envs, tags, persistent: false, resources, ...np, ...creds } : { runtime: "node24", timeout: opts.timeoutMs, env: opts.envs, tags, persistent: false, resources, ...np, ...creds });
3015
3695
  return makeVercelSandboxProvider(sb, opts.envs);
3016
3696
  },
3017
3697
  reconnect: async (sandboxId) => {
@@ -3053,17 +3733,36 @@ var SANDBOX_PROVIDERS = {
3053
3733
  const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
3054
3734
  const snap = await Snapshot.get({ snapshotId, ...creds });
3055
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
+ }
3056
3750
  }
3057
3751
  },
3058
3752
  e2b: {
3059
3753
  requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
3060
- create: async ({ template, timeoutMs, networkPolicy: _np, ...rest }) => {
3754
+ create: async ({ template, timeoutMs, networkPolicy, size, ...rest }) => {
3061
3755
  const maxMs = e2bMaxSandboxMs();
3062
3756
  if (timeoutMs > maxMs) {
3063
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.`);
3064
3758
  }
3065
- const sandboxOpts = { ...rest, timeoutMs: Math.min(timeoutMs, maxMs) };
3066
- 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;
3067
3766
  return makeE2bSandboxProvider(await (tmpl ? Sandbox.create(tmpl, sandboxOpts) : Sandbox.create(sandboxOpts)));
3068
3767
  },
3069
3768
  reconnect: async (sandboxId) => makeE2bSandboxProvider(await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY ?? "", timeoutMs: 3600000 })),
@@ -3099,10 +3798,16 @@ var SANDBOX_PROVIDERS = {
3099
3798
  },
3100
3799
  "e2b-desktop": {
3101
3800
  requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
3102
- create: async ({ template, timeoutMs, networkPolicy: _np, ...rest }) => {
3801
+ create: async ({ template, timeoutMs, networkPolicy, size: _size, ...rest }) => {
3103
3802
  if (!template)
3104
3803
  throw new Error("E2B Desktop provider requires an explicit `template` (Dockerfile-based — no default base image)");
3105
- 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
+ }));
3106
3811
  }
3107
3812
  }
3108
3813
  };
@@ -3135,6 +3840,17 @@ ${missing.join(`
3135
3840
  `)}`);
3136
3841
  return def.deleteSnapshot(snapshotId, Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k]])));
3137
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
+ }
3138
3854
  async function getSandboxQuotas() {
3139
3855
  const out = {};
3140
3856
  await Promise.all(Object.entries(SANDBOX_PROVIDERS).map(async ([name, def]) => {
@@ -3190,17 +3906,34 @@ class StepObservabilityCollector {
3190
3906
  events = [];
3191
3907
  subSteps = [];
3192
3908
  liveEmitter;
3909
+ memoize;
3910
+ usedStepNames = new Set;
3193
3911
  ackedSeqs = new Set;
3194
3912
  inFlight = new Set;
3195
3913
  constructor(opts = {}) {
3196
3914
  this.liveEmitter = opts.liveEmitter;
3915
+ this.memoize = opts.memoize;
3197
3916
  }
3198
3917
  setMetadata = async (data) => {
3199
3918
  Object.assign(this.metadata, data);
3200
3919
  };
3201
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);
3202
3925
  const startedAt = Date.now();
3203
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
+ }
3204
3937
  const result = await fn();
3205
3938
  this.subSteps.push({
3206
3939
  name,
@@ -3264,33 +3997,25 @@ class StepObservabilityCollector {
3264
3997
  }
3265
3998
 
3266
3999
  // src/pause/checkpoint.ts
3267
- async function checkpoint(name, fn) {
4000
+ async function memoize(name, fn) {
3268
4001
  const existing = await readCheckpoint(name);
3269
4002
  if (existing.found)
3270
- return existing.value;
4003
+ return { value: existing.value, restored: true };
3271
4004
  const value = await fn();
3272
4005
  await writeCheckpoint(name, value);
3273
- return value;
4006
+ return { value, restored: false };
3274
4007
  }
3275
- function scopedCheckpoint(scope) {
4008
+ function scopedMemoize(scope) {
3276
4009
  assertSafeCheckpointName(scope);
3277
4010
  return async (name, fn) => {
3278
4011
  assertSafeCheckpointName(name);
3279
- return checkpoint(`${scope}.${name}`, fn);
4012
+ return memoize(`${scope}.${name}`, fn);
3280
4013
  };
3281
4014
  }
3282
4015
 
3283
4016
  // src/pause/wrappers.ts
3284
4017
  function buildPauseWrappers(pause) {
3285
4018
  return {
3286
- requestDecision(req) {
3287
- return pause({
3288
- reason: req.reason,
3289
- schema: req.schema,
3290
- ...req.payload !== undefined ? { payload: req.payload } : {},
3291
- ...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {}
3292
- }, "decision");
3293
- },
3294
4019
  sleep(durationMs) {
3295
4020
  return pause({
3296
4021
  reason: `sleep ${durationMs}ms`,
@@ -3352,7 +4077,10 @@ async function runWorkflowSingleStep(opts) {
3352
4077
  const parsedInput = step.input.safeParse(opts.input);
3353
4078
  if (!parsedInput.success)
3354
4079
  throw new StepValidationError(step.name, "input", parsedInput.error);
3355
- const collector = new StepObservabilityCollector({ liveEmitter: opts.liveAgentEventEmitter });
4080
+ const collector = new StepObservabilityCollector({
4081
+ liveEmitter: opts.liveAgentEventEmitter,
4082
+ memoize: scopedMemoize(`step${opts.stepIndex}`)
4083
+ });
3356
4084
  const coord = { runId: opts.run.id, stepIndex: opts.stepIndex };
3357
4085
  const pauseWithKind = function(req, kind) {
3358
4086
  return corePause(req, coord, kind);
@@ -3373,7 +4101,6 @@ async function runWorkflowSingleStep(opts) {
3373
4101
  setMetadata: collector.setMetadata,
3374
4102
  step: collector.step,
3375
4103
  agentEvents: collector.agentEvents,
3376
- checkpoint: scopedCheckpoint(`step${opts.stepIndex}`),
3377
4104
  pause,
3378
4105
  ...buildPauseWrappers(pauseWithKind)
3379
4106
  };
@@ -3429,7 +4156,10 @@ async function runWorkflowSteps(opts) {
3429
4156
  await opts.onStepFailed?.(i, step.name, err, 0);
3430
4157
  throw err;
3431
4158
  }
3432
- const collector = new StepObservabilityCollector({ liveEmitter: opts.liveAgentEventEmitter });
4159
+ const collector = new StepObservabilityCollector({
4160
+ liveEmitter: opts.liveAgentEventEmitter,
4161
+ memoize: scopedMemoize(`step${i}`)
4162
+ });
3433
4163
  const coord = { runId: run.id, stepIndex: i };
3434
4164
  const pauseWithKind = function(req, kind) {
3435
4165
  return corePause(req, coord, kind);
@@ -3448,7 +4178,6 @@ async function runWorkflowSteps(opts) {
3448
4178
  setMetadata: collector.setMetadata,
3449
4179
  step: collector.step,
3450
4180
  agentEvents: collector.agentEvents,
3451
- checkpoint: scopedCheckpoint(`step${i}`),
3452
4181
  pause,
3453
4182
  ...buildPauseWrappers(pauseWithKind)
3454
4183
  };
@@ -3600,7 +4329,7 @@ function buildInvokeChild(runId, opts = {}) {
3600
4329
  return (name, input, childOpts) => getChildClient().invokeAndWait(name, input, {
3601
4330
  ...childOpts,
3602
4331
  parentRunId: runId,
3603
- factorySlug: childOpts?.factorySlug ?? opts.defaultFactorySlug ?? process.env.AGENT_COMPOSE_FACTORY_SLUG ?? "default"
4332
+ factorySlug: childOpts?.factorySlug ?? opts.defaultFactorySlug ?? process.env.AGENT_COMPOSE_FACTORY ?? process.env.AGENT_COMPOSE_FACTORY_SLUG ?? "default"
3604
4333
  });
3605
4334
  }
3606
4335
  // src/workflow-steps/step.ts
@@ -3645,32 +4374,32 @@ function stepResultFilePath(token) {
3645
4374
  }
3646
4375
  // src/step-invocation/invoker.ts
3647
4376
  import { randomBytes as randomBytes2 } from "node:crypto";
3648
- import { z as z10 } from "zod";
3649
- var StepObservabilitySchema = z10.object({
3650
- metadata: z10.record(z10.string(), z10.unknown()).optional(),
3651
- events: z10.array(z10.unknown()).optional(),
3652
- subSteps: z10.array(z10.object({
3653
- name: z10.string(),
3654
- startedAt: z10.number(),
3655
- durationMs: z10.number(),
3656
- status: z10.enum(["completed", "failed"]),
3657
- 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()
3658
4387
  })).optional()
3659
4388
  }).passthrough();
3660
- var StepResultEnvelopeSchema = z10.discriminatedUnion("ok", [
3661
- z10.object({
3662
- ok: z10.literal(true),
3663
- output: z10.unknown().optional(),
4389
+ var StepResultEnvelopeSchema = z11.discriminatedUnion("ok", [
4390
+ z11.object({
4391
+ ok: z11.literal(true),
4392
+ output: z11.unknown().optional(),
3664
4393
  observability: StepObservabilitySchema.optional()
3665
4394
  }),
3666
- z10.object({
3667
- ok: z10.literal(false),
3668
- kind: z10.enum(["protocol", "user-step"]),
3669
- error: z10.string().optional()
4395
+ z11.object({
4396
+ ok: z11.literal(false),
4397
+ kind: z11.enum(["protocol", "user-step"]),
4398
+ error: z11.string().optional()
3670
4399
  })
3671
4400
  ]);
3672
- var StepPauseEnvelopeSchema = z10.object({
3673
- pauseId: z10.string().min(1),
4401
+ var StepPauseEnvelopeSchema = z11.object({
4402
+ pauseId: z11.string().min(1),
3674
4403
  pauseRequest: StepPauseRequestSchema,
3675
4404
  observability: StepObservabilitySchema.optional()
3676
4405
  });
@@ -3963,10 +4692,196 @@ async function serveStep(handler) {
3963
4692
  await emitResult(token, payload);
3964
4693
  process.exit(exitCode);
3965
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
+ }
3966
4730
  // src/agent/run-agent.ts
3967
- import { z as z11 } from "zod";
4731
+ import { z as z12 } from "zod";
3968
4732
  import { randomUUID as randomUUID2 } from "node:crypto";
3969
4733
 
4734
+ // src/agent/agent-context.ts
4735
+ var AGENT_COMPOSE_MANUAL = `# Working inside an Agent Compose sandbox
4736
+
4737
+ You are an agent running in a per-run sandbox on the Agent Compose platform.
4738
+ Use the **\`agentc\` CLI** and the **\`@agent-compose/sdk\`** for everything below —
4739
+ do NOT hand-roll raw HTTP/curl calls against the platform API. The CLI is on
4740
+ your PATH and already authenticated from the environment
4741
+ (\`AGENT_COMPOSE_URL\` / \`AGENT_COMPOSE_API_KEY\` / \`AGENT_COMPOSE_FACTORY\` are
4742
+ injected for this run), so commands just work — no login, no keys to manage.
4743
+
4744
+ The \`/ac:*\` skills are installed as Claude Code slash commands (\`/ac:invoke\`,
4745
+ \`/ac:events\`, \`/ac:logs\`, \`/ac:register\`, …) — reach for them too.
4746
+
4747
+ ## Files — your outputs persist by default
4748
+
4749
+ Your working directory defaults to **\`$AGENT_COMPOSE_RUN_DIR\`** — a per-run
4750
+ directory on the shared factory drive
4751
+ (\`$AGENT_COMPOSE_FACTORY_DIR/<workflow>/<version>/<run-id>/\`) the platform
4752
+ creates and attributes to this run. **Files you write here persist by
4753
+ default** — they show up in the dashboard's Files tab and the run's Artifacts
4754
+ card, with no API calls to save them. The dir already exists and is writable.
4755
+
4756
+ Need throwaway scratch — heavy build output, package caches, temp files?
4757
+ \`cd /tmp\` (or any path outside \`/factory\`): anything off the factory drive is
4758
+ ephemeral and discarded when the sandbox ends. In short: **stay in your working
4759
+ dir to keep something, \`cd\` out to throw it away.**
4760
+
4761
+ The whole shared drive is POSIX-mounted at \`/factory\`; the dashboard-visible
4762
+ root is \`$AGENT_COMPOSE_FACTORY_DIR\` (\`/factory/files\`). Earlier versions and
4763
+ runs live in sibling dirs under
4764
+ \`$AGENT_COMPOSE_FACTORY_DIR/$AGENT_COMPOSE_WORKFLOW/\` — read them for prior
4765
+ context. Other workflows' dirs are present but not your concern.
4766
+
4767
+ ## Events — the factory timeline
4768
+
4769
+ Record something on the run/factory timeline (the dashboard renders these)
4770
+ with the CLI — your run id is \`$RUN_ID\`:
4771
+
4772
+ agentc events send "$RUN_ID" <name> --summary "<one line>" [--body '<json>']
4773
+
4774
+ Names like \`note.created\` / \`brief.posted\` surface in the Workbench;
4775
+ \`agentc events list\` reads them back. \`/ac:events\` is the skill equivalent.
4776
+
4777
+ ## Runs
4778
+
4779
+ agentc list # registered workflows (/ac:list)
4780
+ agentc logs "$RUN_ID" # a run's logs (/ac:logs)
4781
+ agentc invoke <workflow> -i '<json>' # dispatch a workflow (/ac:invoke)
4782
+
4783
+ ## Writing workflow / agent code — the SDK
4784
+
4785
+ \`@agent-compose/sdk\` is installed in \`/workspace\` — import it from any script
4786
+ you write there:
4787
+
4788
+ import { defineWorkflow, agent, AgentComposeClient } from "@agent-compose/sdk";
4789
+
4790
+ Use \`/ac:generate-workflow\` / \`/ac:generate-agent\` to scaffold, then
4791
+ \`agentc register <file.ts>\` (or \`/ac:register\`).
4792
+
4793
+ ## Pausing to ask the human — \`agentc pause\`
4794
+
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
4800
+
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.
4808
+
4809
+ Reach for it the moment you hit — or foresee — any of these:
4810
+ - **A wall only a human can clear:** a 401/403, a missing credential, an
4811
+ unconnected provider, a host the network refuses. Do NOT retry blindly or try
4812
+ to work around it — pause and say what needs enabling.
4813
+ - **A durable or outward-facing action that needs sign-off:** registering a
4814
+ workflow, deploying, sending email/messages, deleting or overwriting shared
4815
+ data, spending money. Prepare everything, then pause for approval BEFORE you
4816
+ commit it.
4817
+ - **A judgment call only the human can settle:** an under-specified request,
4818
+ several valid paths, a conflict with existing state, missing input only they have.
4819
+
4820
+ You compose the \`--reason\` (the ask) yourself; pass \`--option\` choices when
4821
+ there are clear ones, omit them for a free-form answer. Each agent pauses
4822
+ independently — pausing doesn't stop the others.
4823
+
4824
+ ## Credentials
4825
+
4826
+ Connector credentials (Google, GitHub, …) are NEVER in your environment.
4827
+ They're injected at the network layer when you call an allowed host — make the
4828
+ request **without** an Authorization header and the platform adds it. Don't try
4829
+ to read or exfiltrate tokens; they aren't here. The "Connectors & access"
4830
+ section below (when present) lists exactly which providers this run can reach.
4831
+
4832
+ ## Tools in this environment
4833
+
4834
+ - \`agentc\` — Agent Compose CLI (your primary interface; authed from env)
4835
+ - \`@agent-compose/sdk\` — installed in /workspace for writing workflows
4836
+ - \`/ac:*\` Claude Code skills — slash commands for the above
4837
+ - \`archil\` (factory drive), \`rtk\`, \`bun\`
4838
+ - A world-writable \`/workspace\` working directory`;
4839
+ function renderConnectorsSection(connectors) {
4840
+ if (connectors.length === 0)
4841
+ return "";
4842
+ const rows = connectors.map((c) => {
4843
+ const host = c.hosts?.length ? c.hosts.join(", ") : "(host set by the platform)";
4844
+ const verbs = c.methods?.length ? c.methods.join("/") : "any method";
4845
+ const paths = c.pathPrefixes?.length ? ` under ${c.pathPrefixes.join(", ")}` : "";
4846
+ const repo = c.repository ? ` — repo \`${c.repository}\` (${c.access ?? "read"})` : "";
4847
+ const why = c.scopes?.length ? `
4848
+ _scopes: ${c.scopes.join(", ")}_` : "";
4849
+ return `- **${c.name ?? c.provider}** → \`${host}\` — ${verbs}${paths}${repo}${why}`;
4850
+ });
4851
+ return `
4852
+
4853
+ ## Connectors & access — what this run can reach
4854
+
4855
+ These providers are connected for this run. Call their APIs with plain
4856
+ fetch/SDKs and **no Authorization header** — the platform injects the
4857
+ credential at the network layer. Requests outside the listed method/path are
4858
+ refused (403) and the token withheld. Anything NOT listed is unreachable; if
4859
+ you need it, \`agentc pause\` and ask for it to be connected.
4860
+
4861
+ ${rows.join(`
4862
+ `)}
4863
+ `;
4864
+ }
4865
+ function buildAgentContextDoc(env) {
4866
+ let connectors = [];
4867
+ const raw = env.AGENT_COMPOSE_CONNECTORS;
4868
+ if (raw) {
4869
+ try {
4870
+ const parsed = JSON.parse(raw);
4871
+ if (Array.isArray(parsed))
4872
+ connectors = parsed;
4873
+ } catch {}
4874
+ }
4875
+ return AGENT_COMPOSE_MANUAL + renderConnectorsSection(connectors);
4876
+ }
4877
+ async function writeAgentContext(args) {
4878
+ const doc = buildAgentContextDoc(args.env);
4879
+ const dir = args.cwd.replace(/\/+$/, "") || "/workspace";
4880
+ for (const name of ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]) {
4881
+ await args.sandbox.files.write(`${dir}/${name}`, doc);
4882
+ }
4883
+ }
4884
+
3970
4885
  // src/agent/protocol-suffix.md
3971
4886
  var protocol_suffix_default = '## Status Signal\n\nWhen you have finished your work or are blocked, emit a `<status>` block at the end of your response:\n\n```json\n<status>\n{\n "summary": "one sentence describing what was done or what is blocking",\n "completed": ["each acceptance criterion that is now fully met"],\n "blockers": [],\n "changed_files": ["relative/path/to/file"],\n "tests_run": true,\n "exit_signal": true\n}\n</status>\n```\n\n**Field semantics:**\n- `summary`: one sentence — what was accomplished or what is blocking\n- `completed`: acceptance criteria items that are fully done — be specific\n- `blockers`: non-empty when `exit_signal: false` — describe the exact obstacle\n- `changed_files`: relative paths of files you created or modified\n- `tests_run`: `true` if you ran any test suite (pass or fail); `false` if no tests exist or you skipped them\n- `exit_signal: true` — set when ALL acceptance criteria are met and no blockers remain\n- `exit_signal: false` — set when blocked or unfinished; `blockers` must be non-empty\n\n**If you are still actively working** and have not reached a natural stopping point, do NOT emit a `<status>` block — just keep working.\n\n**Example (done):**\n\n```json\n<status>\n{\n "summary": "Added input validation middleware to /api/tasks with tests",\n "completed": ["POST /api/tasks validates required fields", "Returns 400 with details on invalid input", "Unit tests passing"],\n "blockers": [],\n "changed_files": ["src/middleware/validate.ts", "src/routes/tasks.ts", "tests/validate.test.ts"],\n "tests_run": true,\n "exit_signal": true\n}\n</status>\n```\n\n**Example (blocked):**\n\n```json\n<status>\n{\n "summary": "Implemented middleware but tests are failing due to module resolution",\n "completed": ["Middleware created and wired into route"],\n "blockers": ["Tests fail: cannot resolve import \'./validate\' — module resolution config unclear"],\n "changed_files": ["src/middleware/validate.ts"],\n "tests_run": true,\n "exit_signal": false\n}\n</status>\n```\n';
3972
4887
 
@@ -4026,29 +4941,29 @@ function stripFrontmatter(content) {
4026
4941
  return end === -1 ? content : content.slice(end + 4).trimStart();
4027
4942
  }
4028
4943
  function zodTypeName(schema) {
4029
- if (schema instanceof z11.ZodString)
4944
+ if (schema instanceof z12.ZodString)
4030
4945
  return "string";
4031
- if (schema instanceof z11.ZodNumber)
4946
+ if (schema instanceof z12.ZodNumber)
4032
4947
  return "number";
4033
- if (schema instanceof z11.ZodBoolean)
4948
+ if (schema instanceof z12.ZodBoolean)
4034
4949
  return "boolean";
4035
- if (schema instanceof z11.ZodArray)
4950
+ if (schema instanceof z12.ZodArray)
4036
4951
  return `${zodTypeName(schema.element)}[]`;
4037
- if (schema instanceof z11.ZodEnum)
4952
+ if (schema instanceof z12.ZodEnum)
4038
4953
  return schema.options.map((o) => `"${o}"`).join(" | ");
4039
- if (schema instanceof z11.ZodOptional)
4954
+ if (schema instanceof z12.ZodOptional)
4040
4955
  return `${zodTypeName(schema.unwrap())} (optional)`;
4041
- if (schema instanceof z11.ZodNullable)
4956
+ if (schema instanceof z12.ZodNullable)
4042
4957
  return `${zodTypeName(schema.unwrap())} | null`;
4043
- if (schema instanceof z11.ZodLiteral)
4958
+ if (schema instanceof z12.ZodLiteral)
4044
4959
  return `"${String(schema.value)}"`;
4045
- if (schema instanceof z11.ZodObject)
4960
+ if (schema instanceof z12.ZodObject)
4046
4961
  return "object";
4047
4962
  return "any";
4048
4963
  }
4049
4964
  function buildResponseFormatAppendix(schema) {
4050
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>`:", ""];
4051
- const shape = schema instanceof z11.ZodObject ? schema.shape : null;
4966
+ const shape = schema instanceof z12.ZodObject ? schema.shape : null;
4052
4967
  const body = shape ? (() => {
4053
4968
  const entries = Object.entries(shape);
4054
4969
  const example = Object.fromEntries(entries.map(([k, v]) => [k, `<${zodTypeName(v)}>`]));
@@ -4082,7 +4997,28 @@ function resolveAgentId(explicitId) {
4082
4997
  return `step${activeCall.stepIndex}-agent-${activeCall.callIndex}`;
4083
4998
  }
4084
4999
  async function agent(opts) {
4085
- const workingDir = opts.workingDir ?? "";
5000
+ let workingDir = opts.workingDir || process.env.AGENT_COMPOSE_RUN_DIR || "/workspace";
5001
+ const CONTEXT_WRITE_DEADLINE_MS = 15000;
5002
+ const tryWriteContext = (cwd) => {
5003
+ let timer;
5004
+ return Promise.race([
5005
+ writeAgentContext({ sandbox: opts.sandbox, cwd, env: process.env }).then(() => true),
5006
+ new Promise((resolve) => {
5007
+ timer = setTimeout(() => resolve(false), CONTEXT_WRITE_DEADLINE_MS);
5008
+ })
5009
+ ]).catch((err) => {
5010
+ console.error(`[agent] writeAgentContext(${cwd}) failed: ${err instanceof Error ? err.message : String(err)}`);
5011
+ return false;
5012
+ }).finally(() => {
5013
+ if (timer)
5014
+ clearTimeout(timer);
5015
+ });
5016
+ };
5017
+ if (!await tryWriteContext(workingDir) && workingDir !== "/workspace") {
5018
+ console.error(`[agent] working dir ${workingDir} not writable within ${CONTEXT_WRITE_DEADLINE_MS}ms ` + `(factory drive degraded?) — falling back to /workspace so the agent can run`);
5019
+ workingDir = "/workspace";
5020
+ await tryWriteContext(workingDir);
5021
+ }
4086
5022
  const agentId = resolveAgentId(opts.agentId);
4087
5023
  const callbackUrl = process.env.AGENT_COMPOSE_URL;
4088
5024
  const callbackToken = process.env.AGENT_COMPOSE_RUN_TOKEN;
@@ -4157,6 +5093,7 @@ async function agent(opts) {
4157
5093
  ...inbox ? { inbox } : {},
4158
5094
  mode,
4159
5095
  consumeSteerPending: () => consumeSteerPending(agentId),
5096
+ consumeLocalPauseRequest: () => consumeLocalPauseRequest(agentId),
4160
5097
  ...steerPause ? { pause: steerPause } : {}
4161
5098
  });
4162
5099
  } finally {
@@ -4166,7 +5103,10 @@ async function agent(opts) {
4166
5103
  }
4167
5104
  export {
4168
5105
  writeTool,
5106
+ writeLocalPauseRequest,
5107
+ writeAgentContext,
4169
5108
  stepInputPath,
5109
+ snapshotResolves,
4170
5110
  serveStep,
4171
5111
  runWorkflowSteps,
4172
5112
  runWorkflowSingleStep,
@@ -4190,11 +5130,17 @@ export {
4190
5130
  killSandboxById,
4191
5131
  killAllSandboxes,
4192
5132
  isWorkflow,
5133
+ isPlatformE2bTemplateAlias,
5134
+ isE2bSupportedSize,
4193
5135
  invokeStep,
4194
5136
  importSourceModule,
5137
+ humanApproval,
4195
5138
  getSandboxQuotas,
4196
5139
  formatError,
4197
5140
  editTool,
5141
+ e2bMachineSpec,
5142
+ e2bBaseTemplate,
5143
+ e2bAgentEnvTemplate,
4198
5144
  denyTools,
4199
5145
  deleteSandboxSnapshot,
4200
5146
  defineWorkflow,
@@ -4206,6 +5152,7 @@ export {
4206
5152
  createCodexRuntime,
4207
5153
  createClaudeRuntime,
4208
5154
  createAmpRuntime,
5155
+ consumeLocalPauseRequest,
4209
5156
  codingTools,
4210
5157
  codex_default as codexRuntime,
4211
5158
  claude_default as claudeRuntime,
@@ -4213,6 +5160,7 @@ export {
4213
5160
  bundleWorkflow,
4214
5161
  buildStepEnvs,
4215
5162
  buildInvokeChild,
5163
+ buildAgentContextDoc,
4216
5164
  bashTool,
4217
5165
  assertDefaultExportIsDefineWorkflow,
4218
5166
  amp_default as ampRuntime,
@@ -4231,6 +5179,7 @@ export {
4231
5179
  STEP_RESULT_PREFIX,
4232
5180
  STEP_PAUSE_PREFIX,
4233
5181
  STEP_ENV,
5182
+ SANDBOX_VCPUS,
4234
5183
  SANDBOX_UNAVAILABLE_PREFIX,
4235
5184
  ReservedKeyError,
4236
5185
  RequestContext,
@@ -4243,6 +5192,8 @@ export {
4243
5192
  NonSerialisableValueError,
4244
5193
  LATEST_VERSION,
4245
5194
  EngineError,
5195
+ E2B_TEMPLATE_SIZES,
5196
+ DEFAULT_SANDBOX_SIZE,
4246
5197
  DEFAULT_CLAUDE_MODEL,
4247
5198
  ClaudeRunner,
4248
5199
  BUNDLER_VERSION,
@@ -4251,6 +5202,7 @@ export {
4251
5202
  AgentComposeError,
4252
5203
  AgentComposeClient,
4253
5204
  AGENT_COMPOSE_TAG,
5205
+ AGENT_COMPOSE_MANUAL,
4254
5206
  AC_WORKFLOW_ID,
4255
5207
  AC_TEAM_ID,
4256
5208
  AC_RUN_ID,