@agent-compose/sdk 0.5.8 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/agent/agent-context.d.ts +1 -1
  2. package/dist/agent/agent-loop.d.ts +14 -12
  3. package/dist/agent/pause-client.d.ts +50 -0
  4. package/dist/agent/pause-client.test.d.ts +1 -0
  5. package/dist/agent/steer-control.d.ts +22 -6
  6. package/dist/client.d.ts +12 -1
  7. package/dist/index.d.ts +7 -5
  8. package/dist/index.js +2379 -1463
  9. package/dist/pause/checkpoint.d.ts +27 -10
  10. package/dist/pause/manager.d.ts +1 -0
  11. package/dist/pause/pause-core.d.ts +23 -0
  12. package/dist/pause/state-dir.d.ts +1 -1
  13. package/dist/processors/builtins.d.ts +20 -1
  14. package/dist/processors/gate-pause.d.ts +46 -0
  15. package/dist/processors/gate-pause.test.d.ts +1 -0
  16. package/dist/processors/index.d.ts +3 -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 +2365 -1463
  25. package/dist/runtimes/vercel.js +389 -2
  26. package/dist/sandbox.d.ts +113 -19
  27. package/dist/step-invocation/__tests__/background-invoker.test.d.ts +1 -0
  28. package/dist/step-invocation/index.d.ts +2 -1
  29. package/dist/step-invocation/invoker.d.ts +36 -0
  30. package/dist/types/__tests__/environment-build-flag.test.d.ts +1 -0
  31. package/dist/types/__tests__/workflow-metadata-provider.test.d.ts +1 -0
  32. package/dist/types/execution-context.d.ts +0 -8
  33. package/dist/types/protocol.d.ts +32 -1
  34. package/dist/types/runtime.d.ts +14 -0
  35. package/dist/types/sandbox-environment.d.ts +6 -1
  36. package/dist/types/sandbox.d.ts +86 -6
  37. package/dist/types/workflow-metadata.d.ts +40 -10
  38. package/dist/types/workflow.d.ts +22 -6
  39. package/dist/utils/bundler.d.ts +5 -1
  40. package/dist/workflow-steps/observability.d.ts +28 -2
  41. package/dist/workflow-steps/types.d.ts +11 -7
  42. package/dist/workflow-steps/workflow.d.ts +3 -2
  43. package/package.json +3 -2
  44. package/src/agent/agent-context.ts +14 -6
  45. package/src/agent/agent-loop.ts +32 -10
  46. package/src/agent/pause-client.ts +108 -0
  47. package/src/agent/run-agent.ts +9 -4
  48. package/src/agent/steer-control.ts +21 -7
  49. package/src/client.ts +35 -1
  50. package/src/index.ts +20 -2
  51. package/src/pause/checkpoint.ts +33 -14
  52. package/src/pause/manager.ts +2 -2
  53. package/src/pause/pause-core.ts +35 -0
  54. package/src/pause/state-dir.ts +2 -2
  55. package/src/processors/builtins.ts +44 -1
  56. package/src/processors/gate-pause.ts +94 -0
  57. package/src/processors/index.ts +7 -0
  58. package/src/processors/processor.ts +13 -0
  59. package/src/runtimes/_acp-client.ts +516 -0
  60. package/src/runtimes/_cli-agent.ts +416 -3
  61. package/src/runtimes/claude.ts +31 -3
  62. package/src/runtimes/codex.ts +21 -1
  63. package/src/runtimes/vercel.ts +4 -1
  64. package/src/sandbox.ts +426 -56
  65. package/src/step-invocation/index.ts +2 -1
  66. package/src/step-invocation/invoker.ts +195 -84
  67. package/src/types/execution-context.ts +0 -8
  68. package/src/types/protocol.ts +27 -1
  69. package/src/types/runtime.ts +14 -0
  70. package/src/types/sandbox-environment.ts +12 -1
  71. package/src/types/sandbox.ts +84 -6
  72. package/src/types/workflow-metadata.ts +42 -10
  73. package/src/types/workflow.ts +22 -7
  74. package/src/utils/bundler.ts +6 -1
  75. package/src/workflow-steps/observability.ts +51 -5
  76. package/src/workflow-steps/runner.ts +9 -5
  77. package/src/workflow-steps/types.ts +11 -7
  78. package/src/workflow-steps/workflow.ts +3 -2
@@ -330,1485 +330,1629 @@ 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.resources !== undefined)
544
- out.resources = Object.freeze({ ...source.resources });
545
- if (source.processors !== undefined)
546
- out.processors = Object.freeze([...source.processors]);
547
- if (source.connectors !== undefined)
548
- out.connectors = freezeMetadataValue(source.connectors);
549
- if (source.connectorOperation !== undefined)
550
- out.connectorOperation = Object.freeze({ ...source.connectorOperation });
551
- if (source.invokePolicy !== undefined)
552
- out.invokePolicy = freezeMetadataValue(source.invokePolicy);
553
- 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
+ }
554
558
  }
555
- function freezeMetadataValue(value) {
556
- if (value === null || typeof value !== "object")
557
- return value;
558
- if (Array.isArray(value)) {
559
- for (const v of value)
560
- freezeMetadataValue(v);
561
- 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;
562
566
  }
563
- for (const v of Object.values(value)) {
564
- freezeMetadataValue(v);
567
+ }
568
+
569
+ class PauseRequestError extends PauseError {
570
+ code = "pause_request";
571
+ constructor(message = "pause request is invalid") {
572
+ super(message);
565
573
  }
566
- return Object.freeze(value);
567
574
  }
568
575
 
569
- // src/workflow-steps/workflow.ts
570
- function createStepWorkflow(opts) {
571
- if (!opts.id)
572
- throw new Error("defineWorkflow: 'id' is required for step-based workflows");
573
- const build = (steps) => ({
574
- step(step) {
575
- return build([...steps, step]);
576
- },
577
- build() {
578
- if (steps.length === 0) {
579
- throw new Error(`defineWorkflow("${opts.id}").build: no steps registered — add at least one with .step(step)`);
580
- }
581
- const finalStep = steps[steps.length - 1];
582
- if (finalStep.output !== opts.output) {
583
- 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.`);
584
- }
585
- const workflow = {
586
- id: opts.id,
587
- input: opts.input,
588
- output: opts.output,
589
- steps: Object.freeze([...steps]),
590
- metadata: extractMetadata(opts)
591
- };
592
- Object.defineProperty(workflow, WORKFLOW_BRAND, { value: true, enumerable: false });
593
- return Object.freeze(workflow);
594
- }
595
- });
596
- 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
+ }
597
589
  }
598
- function isWorkflow(value) {
599
- 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;
600
592
  }
601
-
602
- // src/types/workflow.ts
603
- function compileRunForm(def, metadata) {
604
- const unknownSchema = z3.unknown();
605
- const inputSchema = def.input ?? unknownSchema;
606
- const outputSchema = def.output ?? unknownSchema;
607
- const step = {
608
- name: "run",
609
- input: inputSchema,
610
- output: outputSchema,
611
- run: async (stepCtx) => {
612
- const workflowCtx = {
613
- input: stepCtx.input,
614
- run: stepCtx.run,
615
- requestContext: stepCtx.requestContext,
616
- invokeChild: stepCtx.invokeChild,
617
- setMetadata: stepCtx.setMetadata,
618
- step: stepCtx.step,
619
- agentEvents: stepCtx.agentEvents,
620
- checkpoint: stepCtx.checkpoint,
621
- pause: stepCtx.pause,
622
- sleep: stepCtx.sleep,
623
- waitForEvent: stepCtx.waitForEvent,
624
- processors: metadata.processors ?? []
625
- };
626
- const sandbox = stepCtx.sandbox;
627
- if (!sandbox)
628
- throw new Error("legacy run-form workflow requires a sandbox in StepContext");
629
- 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)"));
630
597
  }
598
+ return boundary(req, scope);
631
599
  };
632
- const workflow = {
633
- id: "@run-form",
634
- input: inputSchema,
635
- output: outputSchema,
636
- steps: Object.freeze([step]),
637
- metadata
638
- };
639
- Object.defineProperty(workflow, WORKFLOW_BRAND, { value: true, enumerable: false });
640
- return Object.freeze(workflow);
641
600
  }
642
- function defineWorkflow(def) {
643
- if ("run" in def)
644
- return compileRunForm(def, extractMetadata(def));
645
- return createStepWorkflow(def);
601
+ var PAUSE_NAMESPACE = "5f3e2b8a-0c41-4e9d-9a7b-6d2c1f8e3a40";
602
+ function uuidToBytes(uuid) {
603
+ return Buffer.from(uuid.replace(/-/g, ""), "hex");
646
604
  }
647
- // src/types/sandbox-environment.ts
648
- function defineSandboxEnvironment(env) {
649
- if (typeof env.setup !== "function") {
650
- throw new Error(`defineSandboxEnvironment(${env.name}): 'setup' must be a function`);
651
- }
652
- return defineWorkflow({
653
- ...env.description !== undefined ? { description: env.description } : {},
654
- snapshots: env.snapshots ?? { saveLatest: true },
655
- run: async (_ctx, sandbox) => env.setup(sandbox)
656
- });
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)}`;
657
608
  }
658
- // src/processors/processor.ts
659
- var Verdict = {
660
- continue: (value) => ({ kind: "continue", value }),
661
- deny: (reason) => ({ kind: "deny", reason }),
662
- abort: (reason) => ({ kind: "abort", reason })
663
- };
664
- // src/processors/builtins.ts
665
- function denyTools(names) {
666
- const banned = new Set(names);
667
- return {
668
- name: "denyTools",
669
- processToolCall: (call) => banned.has(call.toolName) ? Verdict.deny(`tool "${call.toolName}" is denied by policy`) : Verdict.continue(call)
670
- };
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);
671
615
  }
672
- function requireScope(required) {
673
- return {
674
- name: "requireScope",
675
- processInput: (prompt, ctx) => {
676
- const missing = required.filter((s) => !ctx.requestContext.hasScope(s));
677
- return missing.length === 0 ? Verdict.continue(prompt) : Verdict.abort(`API key is missing required scopes: ${missing.join(", ")}`);
678
- }
679
- };
616
+ function scopeOf(coord) {
617
+ return coord.agentScope ? `${coord.runId}:${coord.stepIndex}:${coord.agentScope.agentId}:${coord.agentScope.iteration}` : `${coord.runId}:${coord.stepIndex}:bare`;
680
618
  }
681
- function redactPattern(pattern, replacement = "[REDACTED]") {
682
- return {
683
- name: "redactPattern",
684
- processOutput: (message) => {
685
- if (message.type === "text" || message.type === "thinking") {
686
- const redacted = message.text.replace(pattern, replacement);
687
- if (redacted !== message.text) {
688
- return Verdict.continue({ ...message, text: redacted });
689
- }
690
- }
691
- 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;
692
654
  }
693
- };
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;
694
673
  }
695
- // src/client.ts
696
- import { ofetch } from "ofetch";
697
-
698
- // src/errors.ts
699
- class AgentComposeError extends Error {
700
- status;
701
- constructor(status, message) {
702
- super(message);
703
- this.status = status;
704
- 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})`);
705
690
  }
691
+ return parsed.data;
706
692
  }
707
-
708
- // src/sse.ts
709
- async function* parseSseStream(body) {
710
- const reader = body.getReader();
711
- const decoder = new TextDecoder;
712
- let buffer = "";
713
- let event = "";
714
- let seq = 0;
715
- let dataLines = [];
716
- try {
717
- while (true) {
718
- const { done, value } = await reader.read();
719
- if (done)
720
- break;
721
- buffer += decoder.decode(value, { stream: true });
722
- const lines = buffer.split(`
723
- `);
724
- buffer = lines.pop() ?? "";
725
- for (const line of lines) {
726
- if (line.startsWith("id:")) {
727
- seq = Number(line.slice(3).trim());
728
- continue;
729
- }
730
- if (line.startsWith("event:")) {
731
- event = line.slice(6).trim();
732
- continue;
733
- }
734
- if (line.startsWith("data:")) {
735
- dataLines.push(line.slice(5).trim());
736
- continue;
737
- }
738
- if (line === "" && dataLines.length > 0) {
739
- let data;
740
- try {
741
- data = JSON.parse(dataLines.join(`
742
- `));
743
- } catch (err) {
744
- throw new Error(`Malformed SSE JSON payload for event "${event || "message"}": ${err instanceof Error ? err.message : String(err)}`);
745
- }
746
- yield { id: seq, event, data };
747
- event = "";
748
- seq = 0;
749
- dataLines = [];
750
- }
751
- }
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);
752
712
  }
753
- } finally {
754
- reader.releaseLock();
713
+ return parsed.data;
755
714
  }
715
+ return payload;
756
716
  }
757
717
 
758
- // src/client.ts
759
- var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
760
- var DEFAULT_FACTORY = "default";
761
- function detectAmbientParentRunId() {
762
- const envRunId = typeof process !== "undefined" ? process.env?.RUN_ID : undefined;
763
- 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);
764
721
  }
765
- function templatePath(factorySlug, ...rest) {
766
- const tail = rest.length > 0 ? "/" + rest.map(encodeURIComponent).join("/") : "";
767
- return `/api/v1/factories/${encodeURIComponent(factorySlug)}/templates${tail}`;
722
+
723
+ // src/runtimes/vercel.ts
724
+ function now() {
725
+ return new Date().toISOString();
768
726
  }
769
- var PUBLIC_API_HOST = "https://api.agentcompose.ai";
770
- function steerAnswerPayload(message, actor) {
771
- if (typeof message !== "string" || message.trim().length === 0) {
772
- throw new Error("answerSteer: `message` must be a non-empty string");
773
- }
774
- return { message, ...actor !== undefined ? { actor } : {} };
727
+ function serialiseOutput(value) {
728
+ return typeof value === "string" ? value : JSON.stringify(value ?? "");
775
729
  }
776
-
777
- class AgentComposeClient {
778
- fetch;
779
- baseUrl;
780
- apiKey;
781
- constructor(options = {}) {
782
- const apiKey = options.apiKey ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_API_KEY : undefined) ?? "";
783
- if (!apiKey) {
784
- throw new Error("AgentComposeClient: `apiKey` is required. Pass it as `{ apiKey }` " + "or set the AGENT_COMPOSE_API_KEY env var.");
785
- }
786
- const baseUrl = options.baseUrl ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_URL : undefined) ?? PUBLIC_API_HOST;
787
- this.baseUrl = baseUrl.replace(/\/$/, "");
788
- this.apiKey = apiKey;
789
- this.fetch = ofetch.create({
790
- baseURL: this.baseUrl,
791
- headers: { Authorization: `Bearer ${apiKey}` },
792
- async onResponseError({ response }) {
793
- const body = response._data;
794
- throw new AgentComposeError(response.status, body?.error ?? response.statusText);
795
- }
796
- });
797
- }
798
- register(payload) {
799
- const { factorySlug = DEFAULT_FACTORY, ...body } = payload;
800
- return this.fetch(templatePath(factorySlug), { method: "POST", body });
801
- }
802
- invoke(name, input, opts) {
803
- const parentRunId = opts?.parentRunId === undefined ? detectAmbientParentRunId() : opts.parentRunId;
804
- const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
805
- const headers = {};
806
- if (opts?.idempotencyKey)
807
- headers["Idempotency-Key"] = opts.idempotencyKey;
808
- return this.fetch(templatePath(factorySlug, name, "invoke"), {
809
- method: "POST",
810
- ...opts?.idempotencyKey ? { headers } : {},
811
- body: {
812
- input,
813
- ...opts?.snapshots !== undefined ? { snapshots: opts.snapshots } : {},
814
- ...opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {},
815
- ...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {},
816
- ...opts?.size !== undefined ? { size: opts.size } : {},
817
- ...parentRunId ? { parentRunId } : {},
818
- ...opts?.agentId ? { agentId: opts.agentId } : {}
819
- }
820
- });
821
- }
822
- async invokeAndWait(name, input, opts) {
823
- const timeoutMs = opts?.timeoutMs ?? 30 * 60 * 1000;
824
- const pollMs = opts?.pollIntervalMs ?? 1000;
825
- const { id: runId } = await this.invoke(name, input, opts);
826
- const deadline = Date.now() + timeoutMs;
827
- while (Date.now() < deadline) {
828
- const status = await this.getStatus(runId);
829
- if (status.status === "success" || status.status === "failed" || status.status === "abandoned") {
830
- return status;
831
- }
832
- await new Promise((r) => setTimeout(r, pollMs));
833
- }
834
- throw new AgentComposeError(504, `invokeAndWait: run ${runId} did not settle within ${timeoutMs}ms`);
835
- }
836
- async listSnapshots(opts) {
837
- const body = await this.listSnapshotsPage(opts);
838
- return body.data;
839
- }
840
- async listSnapshotsPage(opts) {
841
- const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
842
- const q2 = new URLSearchParams;
843
- if (opts?.workflow)
844
- q2.set("workflow", opts.workflow);
845
- if (opts?.limit != null)
846
- q2.set("limit", String(opts.limit));
847
- if (opts?.before)
848
- q2.set("before", opts.before);
849
- return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/snapshots${q2.toString() ? `?${q2}` : ""}`);
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 [];
850
767
  }
851
- deleteSnapshot(runId) {
852
- return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshot`, { method: "DELETE" });
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;
853
786
  }
854
- async listRunSnapshots(runId) {
855
- const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshots`);
856
- return body.data;
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;
857
792
  }
858
- deleteRunSnapshot(runId, snapshotId) {
859
- return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshots/${encodeURIComponent(snapshotId)}`, { method: "DELETE" });
793
+ captureCheckpoint() {
794
+ return { messages: [...this.messages] };
860
795
  }
861
- getStatus(runId) {
862
- return this.fetch(`/api/v1/workflows/${runId}/status`);
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);
863
806
  }
864
- async resumePause(runId, pauseId, payload, opts) {
865
- const headers = {};
866
- if (opts?.idempotencyKey)
867
- headers["Idempotency-Key"] = opts.idempotencyKey;
868
- return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/${encodeURIComponent(pauseId)}/resume`, {
869
- method: "POST",
870
- body: { resumePayload: payload },
871
- ...Object.keys(headers).length ? { headers } : {},
872
- ...opts?.signal ? { signal: opts.signal } : {}
873
- });
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;
874
851
  }
875
- async resumePauseByKey(runId, correlationKey, payload, opts) {
876
- const headers = {};
877
- if (opts?.idempotencyKey)
878
- headers["Idempotency-Key"] = opts.idempotencyKey;
879
- return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/by-key/${encodeURIComponent(correlationKey)}/resume`, {
880
- method: "POST",
881
- body: { resumePayload: payload },
882
- ...Object.keys(headers).length ? { headers } : {},
883
- ...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
884
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() };
885
898
  }
886
- async answerSteerByKey(runId, correlationKey, message, opts) {
887
- return this.resumePauseByKey(runId, correlationKey, steerAnswerPayload(message, opts?.actor), opts);
888
- }
889
- async answerSteer(runId, pauseId, message, opts) {
890
- return this.resumePause(runId, pauseId, steerAnswerPayload(message, opts?.actor), opts);
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);
891
951
  }
892
- async requestAgentPause(runId, agentId, opts) {
893
- const body = {};
894
- if (opts?.reason !== undefined)
895
- body.reason = opts.reason;
896
- if (opts?.correlationKey !== undefined)
897
- body.correlationKey = opts.correlationKey;
898
- return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/pause`, {
899
- method: "POST",
900
- body,
901
- ...opts?.signal ? { signal: opts.signal } : {}
902
- });
952
+ for (const v of Object.values(value)) {
953
+ freezeMetadataValue(v);
903
954
  }
904
- async sendAgentMessage(runId, agentId, message, opts) {
905
- const body = { message };
906
- if (opts?.senderName !== undefined)
907
- body.senderName = opts.senderName;
908
- return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/messages`, {
909
- method: "POST",
910
- body,
911
- ...opts?.signal ? { signal: opts.signal } : {}
912
- });
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)`);
969
+ }
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`);
913
1039
  }
914
- getRun(runId) {
915
- return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}`);
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/agent/pause-client.ts
1107
+ var isAbort = (signal) => Boolean(signal?.aborted);
1108
+ async function requestPauseAndAwait(opts, fetchImpl = fetch) {
1109
+ const base = opts.baseUrl.replace(/\/+$/, "");
1110
+ const headers = {
1111
+ authorization: `Bearer ${opts.token}`,
1112
+ "content-type": "application/json",
1113
+ accept: "application/json"
1114
+ };
1115
+ const createRes = await fetchImpl(`${base}/api/v1/runs/${opts.runId}/pauses`, {
1116
+ method: "POST",
1117
+ headers,
1118
+ body: JSON.stringify({
1119
+ reason: opts.reason,
1120
+ ...opts.ttlMs !== undefined ? { ttlMs: opts.ttlMs } : {},
1121
+ ...opts.options ? { options: opts.options } : {},
1122
+ ...opts.action ? { action: opts.action } : {},
1123
+ ...opts.correlationKey ? { correlationKey: opts.correlationKey } : {}
1124
+ }),
1125
+ ...opts.signal ? { signal: opts.signal } : {}
1126
+ });
1127
+ if (!createRes.ok) {
1128
+ const text = await createRes.text().catch(() => "");
1129
+ throw new Error(`pause create failed (${createRes.status}): ${text.slice(0, 300)}`);
916
1130
  }
917
- async getRunTimeline(runId, opts) {
918
- const q2 = new URLSearchParams;
919
- if (opts?.limit !== undefined)
920
- q2.set("limit", String(opts.limit));
921
- if (opts?.offset !== undefined)
922
- q2.set("offset", String(opts.offset));
923
- const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/timeline${q2.toString() ? `?${q2}` : ""}`);
924
- return body.events;
1131
+ const { pauseId } = await createRes.json();
1132
+ const pollUrl = `${base}/api/v1/runs/${opts.runId}/pauses/${pauseId}`;
1133
+ let backoffMs = 0;
1134
+ for (;; ) {
1135
+ if (isAbort(opts.signal))
1136
+ throw new Error("pause wait aborted");
1137
+ if (backoffMs > 0)
1138
+ await new Promise((r) => setTimeout(r, backoffMs));
1139
+ let res;
1140
+ try {
1141
+ res = await fetchImpl(pollUrl, { method: "GET", headers, ...opts.signal ? { signal: opts.signal } : {} });
1142
+ } catch (err) {
1143
+ if (isAbort(opts.signal))
1144
+ throw err;
1145
+ backoffMs = Math.min(8000, (backoffMs || 500) * 2);
1146
+ continue;
1147
+ }
1148
+ if (!res.ok) {
1149
+ backoffMs = Math.min(8000, (backoffMs || 500) * 2);
1150
+ continue;
1151
+ }
1152
+ backoffMs = 0;
1153
+ const body = await res.json();
1154
+ if (body.status === "pending")
1155
+ continue;
1156
+ return { status: body.status, decision: body.resumePayload ?? null };
925
1157
  }
926
- async reportEvent(runId, input) {
927
- const body = {
928
- ...input,
929
- ...input.timestamp instanceof Date ? { timestamp: input.timestamp.toISOString() } : {}
930
- };
931
- const response = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/events`, {
932
- method: "POST",
933
- body
934
- });
935
- return response.event;
1158
+ }
1159
+
1160
+ // src/processors/gate-pause.ts
1161
+ var DENY_ANSWERS = new Set(["deny", "no", "reject", "decline", "block"]);
1162
+ function answerText(decision) {
1163
+ const raw = decision !== null && typeof decision === "object" && "decision" in decision ? decision.decision : decision;
1164
+ return typeof raw === "string" ? raw.trim() : raw == null ? "" : JSON.stringify(raw);
1165
+ }
1166
+ function createGatePauseProcessor(opts) {
1167
+ return {
1168
+ name: "gate-pause",
1169
+ async processToolCall(call, ctx) {
1170
+ const approval = await opts.policy(call, ctx);
1171
+ if (!approval)
1172
+ return Verdict.continue(call);
1173
+ const conn = opts.connection ?? {
1174
+ baseUrl: process.env.AGENT_COMPOSE_URL ?? "",
1175
+ token: process.env.AGENT_COMPOSE_RUN_TOKEN ?? "",
1176
+ runId: process.env.RUN_ID ?? ""
1177
+ };
1178
+ if (!conn.baseUrl || !conn.token || !conn.runId)
1179
+ return Verdict.continue(call);
1180
+ const decision = await requestPauseAndAwait({
1181
+ baseUrl: conn.baseUrl,
1182
+ token: conn.token,
1183
+ runId: conn.runId,
1184
+ reason: approval.reason,
1185
+ action: { tool: call.toolName, input: call.toolInput },
1186
+ options: approval.options ?? [{ id: "approve", label: "Approve" }, { id: "deny", label: "Deny" }],
1187
+ signal: ctx.abortSignal
1188
+ });
1189
+ const answer = answerText(decision.decision);
1190
+ if (decision.status === "resolved" && !DENY_ANSWERS.has(answer.toLowerCase())) {
1191
+ return Verdict.continue(call);
1192
+ }
1193
+ const why = decision.status === "resolved" ? `denied: ${answer}` : `${decision.status} with no approval`;
1194
+ return Verdict.deny(`Human ${why}. Tool "${call.toolName}" was not run — adjust course or ask again.`);
1195
+ }
1196
+ };
1197
+ }
1198
+ // src/client.ts
1199
+ import { ofetch } from "ofetch";
1200
+
1201
+ // src/errors.ts
1202
+ class AgentComposeError extends Error {
1203
+ status;
1204
+ constructor(status, message) {
1205
+ super(message);
1206
+ this.status = status;
1207
+ this.name = "AgentComposeError";
936
1208
  }
937
- async listRunEvents(runId) {
938
- const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/events`);
939
- return body.events;
1209
+ }
1210
+
1211
+ // src/sse.ts
1212
+ async function* parseSseStream(body) {
1213
+ const reader = body.getReader();
1214
+ const decoder = new TextDecoder;
1215
+ let buffer = "";
1216
+ let event = "";
1217
+ let seq = 0;
1218
+ let dataLines = [];
1219
+ try {
1220
+ while (true) {
1221
+ const { done, value } = await reader.read();
1222
+ if (done)
1223
+ break;
1224
+ buffer += decoder.decode(value, { stream: true });
1225
+ const lines = buffer.split(`
1226
+ `);
1227
+ buffer = lines.pop() ?? "";
1228
+ for (const line of lines) {
1229
+ if (line.startsWith("id:")) {
1230
+ seq = Number(line.slice(3).trim());
1231
+ continue;
1232
+ }
1233
+ if (line.startsWith("event:")) {
1234
+ event = line.slice(6).trim();
1235
+ continue;
1236
+ }
1237
+ if (line.startsWith("data:")) {
1238
+ dataLines.push(line.slice(5).trim());
1239
+ continue;
1240
+ }
1241
+ if (line === "" && dataLines.length > 0) {
1242
+ let data;
1243
+ try {
1244
+ data = JSON.parse(dataLines.join(`
1245
+ `));
1246
+ } catch (err) {
1247
+ throw new Error(`Malformed SSE JSON payload for event "${event || "message"}": ${err instanceof Error ? err.message : String(err)}`);
1248
+ }
1249
+ yield { id: seq, event, data };
1250
+ event = "";
1251
+ seq = 0;
1252
+ dataLines = [];
1253
+ }
1254
+ }
1255
+ }
1256
+ } finally {
1257
+ reader.releaseLock();
940
1258
  }
941
- async listRunArtifacts(runId) {
942
- const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/artifacts`);
943
- return body.artifacts.map((a) => ({
944
- path: a.path,
945
- factorySlug: a.factory_slug,
946
- sizeBytes: a.size_bytes,
947
- lastWriteAt: a.last_write_at,
948
- preview: a.preview
949
- }));
1259
+ }
1260
+
1261
+ // src/client.ts
1262
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1263
+ var DEFAULT_FACTORY = "default";
1264
+ function detectAmbientParentRunId() {
1265
+ const envRunId = typeof process !== "undefined" ? process.env?.RUN_ID : undefined;
1266
+ return envRunId && UUID_REGEX.test(envRunId) ? envRunId : null;
1267
+ }
1268
+ function templatePath(factorySlug, ...rest) {
1269
+ const tail = rest.length > 0 ? "/" + rest.map(encodeURIComponent).join("/") : "";
1270
+ return `/api/v1/factories/${encodeURIComponent(factorySlug)}/templates${tail}`;
1271
+ }
1272
+ var PUBLIC_API_HOST = "https://api.agentcompose.ai";
1273
+ function steerAnswerPayload(message, actor) {
1274
+ if (typeof message !== "string" || message.trim().length === 0) {
1275
+ throw new Error("answerSteer: `message` must be a non-empty string");
950
1276
  }
951
- async putFactoryFile(path, content, opts) {
952
- const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
953
- const runToken = typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_RUN_TOKEN : undefined;
954
- const wire = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?path=${encodeURIComponent(path)}`, {
955
- method: "PUT",
956
- body: content,
957
- headers: {
958
- "content-type": opts?.contentType ?? "text/plain; charset=utf-8",
959
- ...runToken ? { "x-run-token": runToken } : {}
1277
+ return { message, ...actor !== undefined ? { actor } : {} };
1278
+ }
1279
+
1280
+ class AgentComposeClient {
1281
+ fetch;
1282
+ baseUrl;
1283
+ apiKey;
1284
+ constructor(options = {}) {
1285
+ const apiKey = options.apiKey ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_API_KEY : undefined) ?? "";
1286
+ if (!apiKey) {
1287
+ throw new Error("AgentComposeClient: `apiKey` is required. Pass it as `{ apiKey }` " + "or set the AGENT_COMPOSE_API_KEY env var.");
1288
+ }
1289
+ const baseUrl = options.baseUrl ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_URL : undefined) ?? PUBLIC_API_HOST;
1290
+ this.baseUrl = baseUrl.replace(/\/$/, "");
1291
+ this.apiKey = apiKey;
1292
+ this.fetch = ofetch.create({
1293
+ baseURL: this.baseUrl,
1294
+ headers: { Authorization: `Bearer ${apiKey}` },
1295
+ async onResponseError({ response }) {
1296
+ const body = response._data;
1297
+ throw new AgentComposeError(response.status, body?.error ?? response.statusText);
960
1298
  }
961
1299
  });
962
- return {
963
- path: wire.path,
964
- contentHash: wire.content_hash,
965
- sizeBytes: wire.size_bytes,
966
- created: wire.created
967
- };
968
- }
969
- async getFactoryFile(path, opts) {
970
- const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
971
- const q2 = new URLSearchParams({ path });
972
- if (opts?.revision !== undefined)
973
- q2.set("revision", String(opts.revision));
974
- return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?${q2}`, { responseType: "text" });
975
1300
  }
976
- async listMembers() {
977
- const body = await this.fetch("/api/v1/team/members");
978
- return body.members;
1301
+ register(payload) {
1302
+ const { factorySlug = DEFAULT_FACTORY, ...body } = payload;
1303
+ return this.fetch(templatePath(factorySlug), { method: "POST", body });
979
1304
  }
980
- async createMentions(input) {
981
- const factorySlug = input.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
982
- const runToken = typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_RUN_TOKEN : undefined;
983
- const { factorySlug: _omit, ...payload } = input;
984
- const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/mentions`, {
1305
+ invoke(name, input, opts) {
1306
+ const parentRunId = opts?.parentRunId === undefined ? detectAmbientParentRunId() : opts.parentRunId;
1307
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1308
+ const headers = {};
1309
+ if (opts?.idempotencyKey)
1310
+ headers["Idempotency-Key"] = opts.idempotencyKey;
1311
+ return this.fetch(templatePath(factorySlug, name, "invoke"), {
985
1312
  method: "POST",
986
- body: payload,
987
- ...runToken ? { headers: { "x-run-token": runToken } } : {}
1313
+ ...opts?.idempotencyKey ? { headers } : {},
1314
+ body: {
1315
+ input,
1316
+ ...opts?.snapshots !== undefined ? { snapshots: opts.snapshots } : {},
1317
+ ...opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {},
1318
+ ...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {},
1319
+ ...opts?.size !== undefined ? { size: opts.size } : {},
1320
+ ...parentRunId ? { parentRunId } : {},
1321
+ ...opts?.agentId ? { agentId: opts.agentId } : {}
1322
+ }
988
1323
  });
989
- return body.mentions;
990
1324
  }
991
- async listFactoryEvents(opts) {
1325
+ async invokeAndWait(name, input, opts) {
1326
+ const timeoutMs = opts?.timeoutMs ?? 30 * 60 * 1000;
1327
+ const pollMs = opts?.pollIntervalMs ?? 1000;
1328
+ const { id: runId } = await this.invoke(name, input, opts);
1329
+ const deadline = Date.now() + timeoutMs;
1330
+ while (Date.now() < deadline) {
1331
+ const status = await this.getStatus(runId);
1332
+ if (status.status === "success" || status.status === "failed" || status.status === "abandoned") {
1333
+ return status;
1334
+ }
1335
+ await new Promise((r) => setTimeout(r, pollMs));
1336
+ }
1337
+ throw new AgentComposeError(504, `invokeAndWait: run ${runId} did not settle within ${timeoutMs}ms`);
1338
+ }
1339
+ async listSnapshots(opts) {
1340
+ const body = await this.listSnapshotsPage(opts);
1341
+ return body.data;
1342
+ }
1343
+ async listSnapshotsPage(opts) {
992
1344
  const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
993
1345
  const q2 = new URLSearchParams;
1346
+ if (opts?.workflow)
1347
+ q2.set("workflow", opts.workflow);
994
1348
  if (opts?.limit != null)
995
1349
  q2.set("limit", String(opts.limit));
996
- if (opts?.name)
997
- q2.set("name", opts.name);
998
- if (opts?.from)
999
- q2.set("from", opts.from);
1000
1350
  if (opts?.before)
1001
1351
  q2.set("before", opts.before);
1002
- return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/events${q2.toString() ? `?${q2}` : ""}`);
1003
- }
1004
- async listRunLogs(runId, opts) {
1005
- const q2 = new URLSearchParams;
1006
- if (opts?.afterId !== undefined)
1007
- q2.set("afterId", String(opts.afterId));
1008
- if (opts?.limit !== undefined)
1009
- q2.set("limit", String(opts.limit));
1010
- if (opts?.direction !== undefined)
1011
- q2.set("direction", opts.direction);
1012
- const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/logs${q2.toString() ? `?${q2}` : ""}`);
1013
- return body.logs;
1014
- }
1015
- async listTemplates(opts) {
1016
- const path = opts?.factorySlug ? templatePath(opts.factorySlug) : "/api/v1/templates";
1017
- const body = await this.fetch(path);
1018
- return body.templates;
1019
- }
1020
- async listFactories() {
1021
- const body = await this.fetch("/api/v1/factories");
1022
- return body.factories;
1023
- }
1024
- createFactory(payload) {
1025
- return this.fetch("/api/v1/factories", { method: "POST", body: payload });
1352
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/snapshots${q2.toString() ? `?${q2}` : ""}`);
1026
1353
  }
1027
- getFactory(slug) {
1028
- return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`);
1354
+ deleteSnapshot(runId) {
1355
+ return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshot`, { method: "DELETE" });
1029
1356
  }
1030
- updateFactory(slug, updates) {
1031
- return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "PATCH", body: updates });
1357
+ async listRunSnapshots(runId) {
1358
+ const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshots`);
1359
+ return body.data;
1032
1360
  }
1033
- deleteFactory(slug) {
1034
- return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "DELETE" });
1361
+ deleteRunSnapshot(runId, snapshotId) {
1362
+ return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshots/${encodeURIComponent(snapshotId)}`, { method: "DELETE" });
1035
1363
  }
1036
- async listSchedules(factorySlug = "default") {
1037
- const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules`);
1038
- return body.schedules;
1364
+ getStatus(runId) {
1365
+ return this.fetch(`/api/v1/workflows/${runId}/status`);
1039
1366
  }
1040
- createSchedule(payload) {
1041
- const factorySlug = payload.factorySlug ?? "default";
1042
- return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules`, {
1367
+ async resumePause(runId, pauseId, payload, opts) {
1368
+ const headers = {};
1369
+ if (opts?.idempotencyKey)
1370
+ headers["Idempotency-Key"] = opts.idempotencyKey;
1371
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/${encodeURIComponent(pauseId)}/resume`, {
1043
1372
  method: "POST",
1044
- body: { name: payload.name, workflow: payload.workflow, cron: payload.cron }
1373
+ body: { resumePayload: payload },
1374
+ ...Object.keys(headers).length ? { headers } : {},
1375
+ ...opts?.signal ? { signal: opts.signal } : {}
1045
1376
  });
1046
1377
  }
1047
- deleteSchedule(id, factorySlug = "default") {
1048
- return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules/${encodeURIComponent(id)}`, { method: "DELETE" });
1049
- }
1050
- setSecret(workflowName, key, value, opts) {
1051
- const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1052
- return this.fetch(templatePath(factorySlug, workflowName, "secrets"), {
1378
+ async resumePauseByKey(runId, correlationKey, payload, opts) {
1379
+ const headers = {};
1380
+ if (opts?.idempotencyKey)
1381
+ headers["Idempotency-Key"] = opts.idempotencyKey;
1382
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/pauses/by-key/${encodeURIComponent(correlationKey)}/resume`, {
1053
1383
  method: "POST",
1054
- body: { key, value }
1384
+ body: { resumePayload: payload },
1385
+ ...Object.keys(headers).length ? { headers } : {},
1386
+ ...opts?.signal ? { signal: opts.signal } : {}
1055
1387
  });
1056
1388
  }
1057
- async listSecrets(workflowName, opts) {
1058
- const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1059
- const body = await this.fetch(templatePath(factorySlug, workflowName, "secrets"));
1060
- return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
1061
- }
1062
- deleteSecret(workflowName, key, opts) {
1063
- const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1064
- return this.fetch(templatePath(factorySlug, workflowName, "secrets", key), { method: "DELETE" });
1065
- }
1066
- createApiKey(input) {
1067
- return this.fetch("/api-keys", { method: "POST", body: input });
1068
- }
1069
- async listApiKeys() {
1070
- const body = await this.fetch("/api-keys");
1071
- return body.data;
1072
- }
1073
- getUsage(from, to) {
1074
- const qs = `?from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`;
1075
- return this.fetch(`/api/v1/usage${qs}`);
1389
+ async answerSteerByKey(runId, correlationKey, message, opts) {
1390
+ return this.resumePauseByKey(runId, correlationKey, steerAnswerPayload(message, opts?.actor), opts);
1076
1391
  }
1077
- cancelRun(runId) {
1078
- return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
1392
+ async answerSteer(runId, pauseId, message, opts) {
1393
+ return this.resumePause(runId, pauseId, steerAnswerPayload(message, opts?.actor), opts);
1079
1394
  }
1080
- async* streamRunLogs(runId, opts) {
1081
- const headers = { Authorization: `Bearer ${this.apiKey}` };
1082
- if (opts?.lastEventId && opts.lastEventId > 0) {
1083
- headers["Last-Event-ID"] = String(opts.lastEventId);
1084
- }
1085
- const res = await fetch(`${this.baseUrl}/api/v1/workflows/${encodeURIComponent(runId)}/stream`, {
1086
- headers,
1395
+ async requestAgentPause(runId, agentId, opts) {
1396
+ const body = {};
1397
+ if (opts?.reason !== undefined)
1398
+ body.reason = opts.reason;
1399
+ if (opts?.correlationKey !== undefined)
1400
+ body.correlationKey = opts.correlationKey;
1401
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/pause`, {
1402
+ method: "POST",
1403
+ body,
1087
1404
  ...opts?.signal ? { signal: opts.signal } : {}
1088
1405
  });
1089
- if (!res.ok || !res.body) {
1090
- let message = res.statusText;
1091
- try {
1092
- const body = await res.json();
1093
- if (body.error)
1094
- message = body.error;
1095
- } catch {}
1096
- throw new AgentComposeError(res.status, message);
1097
- }
1098
- for await (const ev of parseSseStream(res.body)) {
1099
- yield ev.data;
1100
- }
1101
- }
1102
- }
1103
- // src/utils/bundler.ts
1104
- import { createHash } from "node:crypto";
1105
- import { parse as babelParse } from "@babel/parser";
1106
-
1107
- // src/utils/source-loader.ts
1108
- import { dirname } from "path";
1109
- import { writeFile, rm, mkdir } from "node:fs/promises";
1110
- var TMP_DIR = process.env.TMP_DIR ?? "/tmp/agent-compose";
1111
- var LATEST_VERSION = "$LATEST";
1112
- async function importSourceModule(source, tmpPath) {
1113
- await mkdir(dirname(tmpPath), { recursive: true });
1114
- await writeFile(tmpPath, source, "utf8");
1115
- const mod = await import(tmpPath);
1116
- const cleanup = () => rm(tmpPath, { force: true });
1117
- return { mod, cleanup };
1118
- }
1119
-
1120
- // src/types/workflow-plan.ts
1121
- function workflowPlan(steps) {
1122
- return { steps };
1123
- }
1124
-
1125
- // src/utils/bundler.ts
1126
- var BUNDLER_VERSION = 2;
1127
-
1128
- class WorkflowSourceValidationError extends Error {
1129
- constructor(message) {
1130
- super(message);
1131
- this.name = "WorkflowSourceValidationError";
1132
- }
1133
- }
1134
- async function bundle(path, label) {
1135
- const bun = globalThis;
1136
- if (!bun.Bun?.build)
1137
- throw new Error("bundleWorkflow requires the Bun runtime (Bun.build)");
1138
- const result = await bun.Bun.build({ entrypoints: [path], format: "esm", target: "node" });
1139
- if (!result.success) {
1140
- throw new Error(`Failed to bundle ${label}:
1141
- ${result.logs.map((l) => l.message).join(`
1142
- `)}`);
1143
- }
1144
- return result.outputs[0].text();
1145
- }
1146
- async function extractFromBundle(source, label, pick) {
1147
- const tmpPath = `/tmp/agentc-${label}-${Date.now()}-${Math.random().toString(36).slice(2)}.js`;
1148
- try {
1149
- const loaded = await importSourceModule(source, tmpPath);
1150
- try {
1151
- return pick(loaded.mod.default);
1152
- } finally {
1153
- await loaded.cleanup();
1154
- }
1155
- } catch (err) {
1156
- throw new WorkflowSourceValidationError(`Bundled ${label} could not be evaluated: ${err instanceof Error ? err.message : String(err)}`);
1157
- }
1158
- }
1159
- var DEFINE_WORKFLOW_CALLEE = /^(?:defineWorkflow|defineSandboxEnvironment)\d*$/;
1160
- function isDefineWorkflowCall(node) {
1161
- if (node.callee.type === "Identifier") {
1162
- return DEFINE_WORKFLOW_CALLEE.test(node.callee.name);
1163
1406
  }
1164
- if (node.callee.type === "MemberExpression" && node.callee.object.type === "CallExpression") {
1165
- return isDefineWorkflowCall(node.callee.object);
1166
- }
1167
- return false;
1168
- }
1169
- function assertDefaultExportIsDefineWorkflow(source, label) {
1170
- let ast;
1171
- try {
1172
- ast = babelParse(source, {
1173
- sourceType: "module",
1174
- plugins: [],
1175
- errorRecovery: false
1407
+ async sendAgentMessage(runId, agentId, message, opts) {
1408
+ const body = { message };
1409
+ if (opts?.senderName !== undefined)
1410
+ body.senderName = opts.senderName;
1411
+ return this.fetch(`/api/v1/runs/${encodeURIComponent(runId)}/agents/${encodeURIComponent(agentId)}/messages`, {
1412
+ method: "POST",
1413
+ body,
1414
+ ...opts?.signal ? { signal: opts.signal } : {}
1176
1415
  });
1177
- } catch (err) {
1178
- const msg = err instanceof Error ? err.message : String(err);
1179
- 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.`);
1180
- }
1181
- const defaultExpr = findDefaultExportExpression(ast.program.body);
1182
- if (!defaultExpr) {
1183
- throw new WorkflowSourceValidationError(`Bundled workflow ${label} has no default export. ` + `Use \`export default defineWorkflow({ run: ... })\`.`);
1184
- }
1185
- if (defaultExpr.type !== "CallExpression" || !isDefineWorkflowCall(defaultExpr)) {
1186
- 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".`);
1187
1416
  }
1188
- }
1189
- function findDefaultExportExpression(body) {
1190
- const defaultDecl = body.find((node) => node.type === "ExportDefaultDeclaration");
1191
- if (defaultDecl) {
1192
- return defaultDecl.declaration;
1417
+ getRun(runId) {
1418
+ return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}`);
1193
1419
  }
1194
- const exportNamed = body.find((node) => node.type === "ExportNamedDeclaration" && node.specifiers.some((spec2) => spec2.type === "ExportSpecifier" && spec2.exported.type === "Identifier" && spec2.exported.name === "default"));
1195
- if (!exportNamed || exportNamed.type !== "ExportNamedDeclaration")
1196
- return null;
1197
- const spec = exportNamed.specifiers.find((s) => s.type === "ExportSpecifier" && s.exported.type === "Identifier" && s.exported.name === "default");
1198
- if (!spec || spec.type !== "ExportSpecifier" || spec.local.type !== "Identifier")
1199
- return null;
1200
- const localName = spec.local.name;
1201
- for (const stmt of body) {
1202
- if (stmt.type !== "VariableDeclaration")
1203
- continue;
1204
- for (const decl of stmt.declarations) {
1205
- if (decl.id.type === "Identifier" && decl.id.name === localName && decl.init && decl.init.type !== "TSSatisfiesExpression") {
1206
- return decl.init;
1207
- }
1208
- }
1420
+ async getRunTimeline(runId, opts) {
1421
+ const q2 = new URLSearchParams;
1422
+ if (opts?.limit !== undefined)
1423
+ q2.set("limit", String(opts.limit));
1424
+ if (opts?.offset !== undefined)
1425
+ q2.set("offset", String(opts.offset));
1426
+ const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/timeline${q2.toString() ? `?${q2}` : ""}`);
1427
+ return body.events;
1209
1428
  }
1210
- return null;
1211
- }
1212
- function sha256(text) {
1213
- return createHash("sha256").update(text, "utf8").digest("hex");
1214
- }
1215
- async function bundleWorkflow(workflowPath, overrides) {
1216
- const label = `"${workflowPath}"`;
1217
- const source = await bundle(workflowPath, `workflow ${label}`);
1218
- assertDefaultExportIsDefineWorkflow(source, label);
1219
- const workflow = await extractFromBundle(source, "workflow", (wf) => isWorkflow(wf) ? wf : undefined);
1220
- if (!workflow) {
1221
- 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.`);
1429
+ async reportEvent(runId, input) {
1430
+ const body = {
1431
+ ...input,
1432
+ ...input.timestamp instanceof Date ? { timestamp: input.timestamp.toISOString() } : {}
1433
+ };
1434
+ const response = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/events`, {
1435
+ method: "POST",
1436
+ body
1437
+ });
1438
+ return response.event;
1222
1439
  }
1223
- const { metadata } = workflow;
1224
- const plan = workflowPlan(workflow.steps.map((step, index) => ({ index, name: step.name })));
1225
- const manifest = {
1226
- definitionBrand: true,
1227
- sourceHash: sha256(source),
1228
- bundlerVersion: BUNDLER_VERSION
1229
- };
1230
- const inputSchema = extractIOSchema(workflow.input);
1231
- const outputSchema = extractIOSchema(workflow.output);
1232
- return {
1233
- source,
1234
- manifest,
1235
- workflowPlan: plan,
1236
- networkPolicy: overrides?.networkPolicy ?? metadata.networkPolicy,
1237
- placeholders: overrides?.placeholders ?? metadata.placeholders,
1238
- ...metadata.description !== undefined ? { description: metadata.description } : {},
1239
- ...inputSchema !== undefined ? { inputSchema } : {},
1240
- ...outputSchema !== undefined ? { outputSchema } : {},
1241
- ...metadata.snapshots !== undefined ? { snapshots: metadata.snapshots } : {},
1242
- ...metadata.resources !== undefined ? { resources: metadata.resources } : {},
1243
- ...metadata.connectors !== undefined ? { connectors: metadata.connectors } : {},
1244
- ...metadata.connectorOperation !== undefined ? { connectorOperation: metadata.connectorOperation } : {},
1245
- ...metadata.invokePolicy !== undefined ? { invokePolicy: metadata.invokePolicy } : {}
1246
- };
1247
- }
1248
- function extractIOSchema(zodSchema) {
1249
- let json;
1250
- try {
1251
- const z4 = __require("zod");
1252
- if (!z4.toJSONSchema)
1253
- return;
1254
- json = z4.toJSONSchema(zodSchema);
1255
- } catch {
1256
- return;
1440
+ async listRunEvents(runId) {
1441
+ const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/events`);
1442
+ return body.events;
1257
1443
  }
1258
- if (!json || typeof json !== "object")
1259
- return;
1260
- if (Object.keys(json).filter((k) => k !== "$schema").length === 0)
1261
- return;
1262
- const out = {};
1263
- if (json.type !== undefined)
1264
- out.type = json.type;
1265
- if (json.description !== undefined)
1266
- out.description = String(json.description);
1267
- if (json.properties && typeof json.properties === "object") {
1268
- const props = {};
1269
- for (const [key, raw] of Object.entries(json.properties)) {
1270
- if (raw && typeof raw === "object") {
1271
- const r = raw;
1272
- props[key] = {};
1273
- if (r.type !== undefined)
1274
- props[key].type = r.type;
1275
- if (r.description !== undefined)
1276
- props[key].description = String(r.description);
1277
- }
1278
- }
1279
- if (Object.keys(props).length > 0)
1280
- out.properties = props;
1444
+ async listRunArtifacts(runId) {
1445
+ const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/artifacts`);
1446
+ return body.artifacts.map((a) => ({
1447
+ path: a.path,
1448
+ factorySlug: a.factory_slug,
1449
+ sizeBytes: a.size_bytes,
1450
+ lastWriteAt: a.last_write_at,
1451
+ preview: a.preview
1452
+ }));
1281
1453
  }
1282
- if (Array.isArray(json.required))
1283
- out.required = json.required.map(String);
1284
- return out;
1285
- }
1286
- // src/utils/schemas.ts
1287
- import { z as z4 } from "zod";
1288
- var AgentStatusSchema = z4.object({
1289
- summary: z4.string(),
1290
- completed: z4.array(z4.string()),
1291
- blockers: z4.array(z4.string()),
1292
- exit_signal: z4.boolean(),
1293
- needs_input: z4.boolean().optional(),
1294
- question: z4.string().optional()
1295
- });
1296
- // src/runtimes/claude.ts
1297
- import { query } from "@anthropic-ai/claude-agent-sdk";
1298
-
1299
- // src/agent/agent-loop.ts
1300
- import { z as z9 } from "zod";
1301
-
1302
- // src/agent/protocol.ts
1303
- import { z as z5 } from "zod";
1304
- var AgentMessageSchema = z5.object({
1305
- type: z5.enum(["init", "text", "thinking", "tool_use", "tool_result", "done", "error", "usage"]),
1306
- timestamp: z5.string()
1307
- }).passthrough();
1308
- function parseAgentResponse(text) {
1309
- const match = text.match(/<response>([\s\S]*?)<\/response>/);
1310
- if (!match)
1311
- return null;
1312
- try {
1313
- return JSON.parse(match[1].trim());
1314
- } catch {
1315
- return null;
1454
+ async putFactoryFile(path, content, opts) {
1455
+ const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
1456
+ const runToken = typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_RUN_TOKEN : undefined;
1457
+ const wire = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?path=${encodeURIComponent(path)}`, {
1458
+ method: "PUT",
1459
+ body: content,
1460
+ headers: {
1461
+ "content-type": opts?.contentType ?? "text/plain; charset=utf-8",
1462
+ ...runToken ? { "x-run-token": runToken } : {}
1463
+ }
1464
+ });
1465
+ return {
1466
+ path: wire.path,
1467
+ contentHash: wire.content_hash,
1468
+ sizeBytes: wire.size_bytes,
1469
+ created: wire.created
1470
+ };
1316
1471
  }
1317
- }
1318
-
1319
- // src/agent/agent-loop.ts
1320
- import { randomUUID } from "node:crypto";
1321
-
1322
- // src/pause/manager.ts
1323
- import { z as z6 } from "zod";
1324
-
1325
- // src/pause/state-dir.ts
1326
- import { promises as fs } from "node:fs";
1327
- import { join as join2 } from "node:path";
1328
- import { randomBytes } from "node:crypto";
1329
- function getStateDir() {
1330
- return process.env.AGENT_COMPOSE_STATE_DIR ?? "/tmp/wf/state";
1331
- }
1332
- var checkpointsDir = () => join2(getStateDir(), "checkpoints");
1333
- var pausesDir = () => join2(getStateDir(), "pauses");
1334
- var agentStatePath = (agentInstanceId) => join2(getStateDir(), `agent-${agentInstanceId}.json`);
1335
- var runtimeStatePath = (agentInstanceId) => join2(getStateDir(), `runtime-${agentInstanceId}.json`);
1336
- var checkpointPath = (name) => join2(checkpointsDir(), `${name}.json`);
1337
- var pausePath = (pauseId) => join2(pausesDir(), `${pauseId}.json`);
1338
- var NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/;
1339
- function assertSafeName(label, name) {
1340
- if (!NAME_RE.test(name)) {
1341
- throw new Error(`${label} name must match ${NAME_RE} (got ${JSON.stringify(name)})`);
1472
+ async getFactoryFile(path, opts) {
1473
+ const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
1474
+ const q2 = new URLSearchParams({ path });
1475
+ if (opts?.revision !== undefined)
1476
+ q2.set("revision", String(opts.revision));
1477
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?${q2}`, { responseType: "text" });
1342
1478
  }
1343
- }
1344
- function assertSafeCheckpointName(name) {
1345
- assertSafeName("checkpoint", name);
1346
- }
1347
- async function ensureDirs() {
1348
- await fs.mkdir(checkpointsDir(), { recursive: true });
1349
- await fs.mkdir(pausesDir(), { recursive: true });
1350
- }
1351
- async function atomicWriteJSON(path, value) {
1352
- await ensureDirs();
1353
- const tmp = `${path}.tmp.${randomBytes(6).toString("hex")}`;
1354
- const payload = JSON.stringify(value);
1355
- if (payload === undefined) {
1356
- throw new Error("pause state values must be JSON-serialisable");
1479
+ async listMembers() {
1480
+ const body = await this.fetch("/api/v1/team/members");
1481
+ return body.members;
1357
1482
  }
1358
- const handle = await fs.open(tmp, "w");
1359
- try {
1360
- await handle.writeFile(payload, { encoding: "utf8" });
1361
- await handle.sync();
1362
- } finally {
1363
- await handle.close();
1483
+ async createMentions(input) {
1484
+ const factorySlug = input.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
1485
+ const runToken = typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_RUN_TOKEN : undefined;
1486
+ const { factorySlug: _omit, ...payload } = input;
1487
+ const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/mentions`, {
1488
+ method: "POST",
1489
+ body: payload,
1490
+ ...runToken ? { headers: { "x-run-token": runToken } } : {}
1491
+ });
1492
+ return body.mentions;
1364
1493
  }
1365
- await fs.rename(tmp, path);
1366
- }
1367
- async function readJSON(path) {
1368
- try {
1369
- const raw = await fs.readFile(path, "utf8");
1370
- return JSON.parse(raw);
1371
- } catch (err) {
1372
- if (err.code === "ENOENT")
1373
- return null;
1374
- throw err;
1494
+ async listFactoryEvents(opts) {
1495
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1496
+ const q2 = new URLSearchParams;
1497
+ if (opts?.limit != null)
1498
+ q2.set("limit", String(opts.limit));
1499
+ if (opts?.name)
1500
+ q2.set("name", opts.name);
1501
+ if (opts?.from)
1502
+ q2.set("from", opts.from);
1503
+ if (opts?.before)
1504
+ q2.set("before", opts.before);
1505
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/events${q2.toString() ? `?${q2}` : ""}`);
1375
1506
  }
1376
- }
1377
- async function readJSONWithPresence(path) {
1378
- try {
1379
- const raw = await fs.readFile(path, "utf8");
1380
- return { found: true, value: JSON.parse(raw) };
1381
- } catch (err) {
1382
- if (err.code === "ENOENT")
1383
- return { found: false };
1384
- throw err;
1507
+ async listRunLogs(runId, opts) {
1508
+ const q2 = new URLSearchParams;
1509
+ if (opts?.afterId !== undefined)
1510
+ q2.set("afterId", String(opts.afterId));
1511
+ if (opts?.limit !== undefined)
1512
+ q2.set("limit", String(opts.limit));
1513
+ if (opts?.direction !== undefined)
1514
+ q2.set("direction", opts.direction);
1515
+ const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/logs${q2.toString() ? `?${q2}` : ""}`);
1516
+ return body.logs;
1385
1517
  }
1386
- }
1387
- async function readAgentState(agentInstanceId) {
1388
- assertSafeName("agent", agentInstanceId);
1389
- return readJSON(agentStatePath(agentInstanceId));
1390
- }
1391
- async function writeAgentState(agentInstanceId, state) {
1392
- assertSafeName("agent", agentInstanceId);
1393
- await atomicWriteJSON(agentStatePath(agentInstanceId), state);
1394
- }
1395
- async function readRuntimeCheckpoint(agentInstanceId) {
1396
- assertSafeName("runtime", agentInstanceId);
1397
- return readJSON(runtimeStatePath(agentInstanceId));
1398
- }
1399
- async function writeRuntimeCheckpoint(agentInstanceId, blob) {
1400
- assertSafeName("runtime", agentInstanceId);
1401
- await atomicWriteJSON(runtimeStatePath(agentInstanceId), blob);
1402
- }
1403
- async function readCheckpoint(name) {
1404
- assertSafeCheckpointName(name);
1405
- return readJSONWithPresence(checkpointPath(name));
1406
- }
1407
- async function writeCheckpoint(name, value) {
1408
- assertSafeCheckpointName(name);
1409
- await atomicWriteJSON(checkpointPath(name), value);
1410
- }
1411
- function hasOwn(obj, key) {
1412
- return Object.prototype.hasOwnProperty.call(obj, key);
1413
- }
1414
- function validatePauseRecord(value, expectedPauseId) {
1415
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
1416
- throw new Error(`pause record ${expectedPauseId} is malformed: expected object`);
1518
+ async listTemplates(opts) {
1519
+ const path = opts?.factorySlug ? templatePath(opts.factorySlug) : "/api/v1/templates";
1520
+ const body = await this.fetch(path);
1521
+ return body.templates;
1417
1522
  }
1418
- const record = value;
1419
- if (record.pauseId !== undefined && record.pauseId !== expectedPauseId) {
1420
- throw new Error(`pause record ${expectedPauseId} is malformed: pauseId mismatch`);
1523
+ async listFactories() {
1524
+ const body = await this.fetch("/api/v1/factories");
1525
+ return body.factories;
1421
1526
  }
1422
- if (record.resolved === true) {
1423
- if (typeof record.expired !== "boolean") {
1424
- throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require boolean expired`);
1425
- }
1426
- if (!hasOwn(record, "resumePayload") || record.resumePayload === undefined) {
1427
- throw new Error(`pause record ${expectedPauseId} is malformed: resolved records require resumePayload`);
1428
- }
1429
- return {
1430
- pauseId: expectedPauseId,
1431
- request: hasOwn(record, "request") ? record.request : null,
1432
- ...record
1433
- };
1527
+ createFactory(payload) {
1528
+ return this.fetch("/api/v1/factories", { method: "POST", body: payload });
1434
1529
  }
1435
- if (record.pauseId !== expectedPauseId) {
1436
- throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require matching pauseId`);
1530
+ getFactory(slug) {
1531
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`);
1437
1532
  }
1438
- if (!hasOwn(record, "request") || record.request === undefined) {
1439
- throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records require request`);
1533
+ updateFactory(slug, updates) {
1534
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "PATCH", body: updates });
1440
1535
  }
1441
- if (record.resolved !== undefined && record.resolved !== false) {
1442
- throw new Error(`pause record ${expectedPauseId} is malformed: resolved must be true, false, or absent`);
1536
+ deleteFactory(slug) {
1537
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "DELETE" });
1443
1538
  }
1444
- if (hasOwn(record, "expired") || hasOwn(record, "resumePayload")) {
1445
- throw new Error(`pause record ${expectedPauseId} is malformed: unresolved records must not include resolution fields`);
1539
+ async listSchedules(factorySlug = "default") {
1540
+ const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules`);
1541
+ return body.schedules;
1446
1542
  }
1447
- return record;
1448
- }
1449
- async function readPauseRecord(pauseId) {
1450
- assertSafeName("pause", pauseId);
1451
- const record = await readJSON(pausePath(pauseId));
1452
- return record === null ? null : validatePauseRecord(record, pauseId);
1453
- }
1454
- async function writePauseRecord(record) {
1455
- assertSafeName("pause", record.pauseId);
1456
- validatePauseRecord(record, record.pauseId);
1457
- await atomicWriteJSON(pausePath(record.pauseId), record);
1458
- }
1459
-
1460
- // src/pause/manager.ts
1461
- var AGENT_LOOP_STATE_SCHEMA_VERSION = 2;
1462
- var SUPPORTED_AGENT_LOOP_STATE_VERSIONS = z6.union([z6.literal(1), z6.literal(2)]);
1463
- var RunningAgentLoopStateSchema = z6.object({
1464
- schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1465
- phase: z6.literal("running"),
1466
- iteration: z6.number().int().nonnegative(),
1467
- completedIterations: z6.number().int().nonnegative(),
1468
- lastSessionId: z6.string(),
1469
- lastStatus: AgentStatusSchema.nullable(),
1470
- lastResponseText: z6.string(),
1471
- lastResponseValidationError: z6.string(),
1472
- iterationsWithoutStatus: z6.number().int().nonnegative(),
1473
- blockerStreak: z6.object({ key: z6.string(), count: z6.number().int().nonnegative() }).nullable(),
1474
- pendingSteerPause: z6.object({ reason: z6.string(), correlationKey: z6.string().nullable(), at: z6.number().int() }).nullable().default(null)
1475
- });
1476
- var SettledAgentLoopStateSchema = z6.object({
1477
- schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1478
- phase: z6.literal("settled"),
1479
- result: z6.object({
1480
- sessionId: z6.string(),
1481
- lastStatus: AgentStatusSchema.nullable(),
1482
- iterations: z6.number().int().nonnegative(),
1483
- response: z6.unknown().optional()
1484
- })
1485
- });
1486
- var AgentLoopStateSchema = z6.discriminatedUnion("phase", [
1487
- RunningAgentLoopStateSchema,
1488
- SettledAgentLoopStateSchema
1489
- ]);
1490
- function toProgressState(state) {
1491
- return {
1492
- iteration: state.iteration,
1493
- completedIterations: state.completedIterations,
1494
- lastSessionId: state.lastSessionId,
1495
- lastStatus: state.lastStatus,
1496
- lastResponseText: state.lastResponseText,
1497
- lastResponseValidationError: state.lastResponseValidationError,
1498
- iterationsWithoutStatus: state.iterationsWithoutStatus,
1499
- blockerStreak: state.blockerStreak,
1500
- pendingSteerPause: state.pendingSteerPause
1501
- };
1502
- }
1503
-
1504
- class PauseManager {
1505
- agentId;
1506
- constructor(agentId) {
1507
- this.agentId = agentId;
1543
+ createSchedule(payload) {
1544
+ const factorySlug = payload.factorySlug ?? "default";
1545
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules`, {
1546
+ method: "POST",
1547
+ body: { name: payload.name, workflow: payload.workflow, cron: payload.cron }
1548
+ });
1508
1549
  }
1509
- async restoreAgentLoop(runtime) {
1510
- const priorState = await readAgentState(this.agentId);
1511
- if (priorState === null)
1512
- return { kind: "fresh" };
1513
- const parsed = AgentLoopStateSchema.safeParse(priorState);
1514
- if (!parsed.success)
1515
- return { kind: "invalid", error: parsed.error.message };
1516
- const state = parsed.data;
1517
- if (state.phase === "settled") {
1518
- return {
1519
- kind: "settled",
1520
- result: {
1521
- sessionId: state.result.sessionId,
1522
- lastStatus: state.result.lastStatus,
1523
- iterations: state.result.iterations,
1524
- ...Object.prototype.hasOwnProperty.call(state.result, "response") ? { response: state.result.response } : {}
1525
- }
1526
- };
1527
- }
1528
- const runtimeBlob = await readRuntimeCheckpoint(this.agentId);
1529
- if (runtimeBlob !== null && !runtime.restoreCheckpoint) {
1530
- throw new Error(`runtime checkpoint exists for resumed agentId=${this.agentId}, but runtime cannot restore it`);
1531
- }
1532
- if (runtime.restoreCheckpoint && runtimeBlob === null) {
1533
- throw new Error(`missing runtime checkpoint for resumed agentId=${this.agentId}`);
1534
- }
1535
- if (runtimeBlob !== null)
1536
- runtime.restoreCheckpoint?.(runtimeBlob);
1537
- return { kind: "running", state: toProgressState(state) };
1550
+ deleteSchedule(id, factorySlug = "default") {
1551
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules/${encodeURIComponent(id)}`, { method: "DELETE" });
1538
1552
  }
1539
- async saveRunningAgentLoop(state, runtime) {
1540
- await writeAgentState(this.agentId, {
1541
- schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1542
- phase: "running",
1543
- ...state
1553
+ setSecret(workflowName, key, value, opts) {
1554
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1555
+ return this.fetch(templatePath(factorySlug, workflowName, "secrets"), {
1556
+ method: "POST",
1557
+ body: { key, value }
1544
1558
  });
1545
- if (runtime.captureCheckpoint) {
1546
- const blob = runtime.captureCheckpoint();
1547
- if (blob !== undefined)
1548
- await writeRuntimeCheckpoint(this.agentId, blob);
1549
- }
1550
1559
  }
1551
- async saveSettledAgentLoop(result) {
1552
- await writeAgentState(this.agentId, {
1553
- schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1554
- phase: "settled",
1555
- result: {
1556
- sessionId: result.sessionId,
1557
- lastStatus: result.lastStatus,
1558
- iterations: result.iterations,
1559
- ...result.response !== undefined ? { response: result.response } : {}
1560
- }
1560
+ async listSecrets(workflowName, opts) {
1561
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1562
+ const body = await this.fetch(templatePath(factorySlug, workflowName, "secrets"));
1563
+ return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
1564
+ }
1565
+ deleteSecret(workflowName, key, opts) {
1566
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1567
+ return this.fetch(templatePath(factorySlug, workflowName, "secrets", key), { method: "DELETE" });
1568
+ }
1569
+ setFactorySecret(key, value, opts) {
1570
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1571
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/secrets`, {
1572
+ method: "POST",
1573
+ body: { key, value }
1561
1574
  });
1562
1575
  }
1563
- }
1564
-
1565
- // src/pause/pause-core.ts
1566
- import { createHash as createHash2 } from "node:crypto";
1567
-
1568
- // src/step-invocation/types.ts
1569
- import { z as z7 } from "zod";
1570
- var StepPauseRequestSchema = z7.object({
1571
- reason: z7.string().min(1),
1572
- kind: z7.enum(["decision", "sleep", "event", "custom"]),
1573
- payload: z7.record(z7.string(), z7.unknown()).optional(),
1574
- ttlMs: z7.number().int().positive().optional(),
1575
- correlationKey: z7.string().min(1).optional(),
1576
- snapshot: z7.boolean().optional()
1577
- });
1578
-
1579
- class StepExecutionError extends Error {
1580
- kind;
1581
- exitCode;
1582
- constructor(error, stepLabel) {
1583
- super(`[${error.kind}] ${stepLabel}: ${error.message}`);
1584
- this.name = "StepExecutionError";
1585
- this.kind = error.kind;
1586
- this.exitCode = error.kind === "runner-exit" ? error.exitCode : undefined;
1576
+ async listFactorySecrets(opts) {
1577
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1578
+ const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/secrets`);
1579
+ return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
1580
+ }
1581
+ deleteFactorySecret(key, opts) {
1582
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1583
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/secrets/${encodeURIComponent(key)}`, { method: "DELETE" });
1584
+ }
1585
+ createApiKey(input) {
1586
+ return this.fetch("/api-keys", { method: "POST", body: input });
1587
+ }
1588
+ async listApiKeys() {
1589
+ const body = await this.fetch("/api-keys");
1590
+ return body.data;
1591
+ }
1592
+ getUsage(from, to) {
1593
+ const qs = `?from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`;
1594
+ return this.fetch(`/api/v1/usage${qs}`);
1595
+ }
1596
+ cancelRun(runId) {
1597
+ return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
1598
+ }
1599
+ async* streamRunLogs(runId, opts) {
1600
+ const headers = { Authorization: `Bearer ${this.apiKey}` };
1601
+ if (opts?.lastEventId && opts.lastEventId > 0) {
1602
+ headers["Last-Event-ID"] = String(opts.lastEventId);
1603
+ }
1604
+ const res = await fetch(`${this.baseUrl}/api/v1/workflows/${encodeURIComponent(runId)}/stream`, {
1605
+ headers,
1606
+ ...opts?.signal ? { signal: opts.signal } : {}
1607
+ });
1608
+ if (!res.ok || !res.body) {
1609
+ let message = res.statusText;
1610
+ try {
1611
+ const body = await res.json();
1612
+ if (body.error)
1613
+ message = body.error;
1614
+ } catch {}
1615
+ throw new AgentComposeError(res.status, message);
1616
+ }
1617
+ for await (const ev of parseSseStream(res.body)) {
1618
+ yield ev.data;
1619
+ }
1587
1620
  }
1588
1621
  }
1589
-
1590
- // src/active-step.ts
1591
- import { AsyncLocalStorage } from "node:async_hooks";
1592
- var activeStepStorage = new AsyncLocalStorage;
1593
- var fallbackActive = null;
1594
- var BRIDGE_KEY = Symbol.for("agent-compose.activeStep.bridge");
1595
- function bridgeSlot() {
1596
- const g = globalThis;
1597
- return g[BRIDGE_KEY] ??= { state: null };
1598
- }
1599
- function makeState(step) {
1600
- return {
1601
- stepIndex: step.stepIndex,
1602
- nextAgentCallIndex: 0,
1603
- pauseOrdinalByScope: new Map
1604
- };
1605
- }
1606
- function currentState() {
1607
- return activeStepStorage.getStore() ?? bridgeSlot().state ?? fallbackActive;
1608
- }
1609
- async function runWithActiveStep(step, fn) {
1610
- return activeStepStorage.run(makeState(step), fn);
1611
- }
1612
- function setActiveStepBridge(step) {
1613
- const slot = bridgeSlot();
1614
- const prev = { state: slot.state };
1615
- slot.state = step ? makeState(step) : null;
1616
- return prev;
1617
- }
1618
- function restoreActiveStepBridge(prev) {
1619
- bridgeSlot().state = prev.state;
1620
- }
1621
- function getActiveStep() {
1622
- const state = currentState();
1623
- return state ? { stepIndex: state.stepIndex } : null;
1624
- }
1625
- function nextAgentCallInActiveStep() {
1626
- const state = currentState();
1627
- if (!state)
1628
- return null;
1629
- const callIndex = state.nextAgentCallIndex;
1630
- state.nextAgentCallIndex += 1;
1631
- return { stepIndex: state.stepIndex, callIndex };
1622
+ // src/utils/bundler.ts
1623
+ import { createHash as createHash2 } from "node:crypto";
1624
+ import { parse as babelParse } from "@babel/parser";
1625
+
1626
+ // src/utils/source-loader.ts
1627
+ import { dirname } from "path";
1628
+ import { writeFile, rm, mkdir } from "node:fs/promises";
1629
+ var TMP_DIR = process.env.TMP_DIR ?? "/tmp/agent-compose";
1630
+ var LATEST_VERSION = "$LATEST";
1631
+ async function importSourceModule(source, tmpPath) {
1632
+ await mkdir(dirname(tmpPath), { recursive: true });
1633
+ await writeFile(tmpPath, source, "utf8");
1634
+ const mod = await import(tmpPath);
1635
+ const cleanup = () => rm(tmpPath, { force: true });
1636
+ return { mod, cleanup };
1632
1637
  }
1633
- function nextPauseOrdinalInActiveStep(scope) {
1634
- const state = currentState();
1635
- if (!state)
1636
- return null;
1637
- const next = state.pauseOrdinalByScope.get(scope) ?? 0;
1638
- state.pauseOrdinalByScope.set(scope, next + 1);
1639
- return next;
1638
+
1639
+ // src/types/workflow-plan.ts
1640
+ function workflowPlan(steps) {
1641
+ return { steps };
1640
1642
  }
1641
1643
 
1642
- // src/pause/errors.ts
1643
- class PauseError extends Error {
1644
+ // src/utils/bundler.ts
1645
+ var BUNDLER_VERSION = 2;
1646
+
1647
+ class WorkflowSourceValidationError extends Error {
1644
1648
  constructor(message) {
1645
1649
  super(message);
1646
- this.name = new.target.name;
1650
+ this.name = "WorkflowSourceValidationError";
1647
1651
  }
1648
1652
  }
1649
-
1650
- class PauseExpiredError extends PauseError {
1651
- code = "pause_expired";
1652
- constructor(message = "pause expired before it was resolved") {
1653
- super(message);
1653
+ async function bundle(path, label) {
1654
+ const bun = globalThis;
1655
+ if (!bun.Bun?.build)
1656
+ throw new Error("bundleWorkflow requires the Bun runtime (Bun.build)");
1657
+ const result = await bun.Bun.build({ entrypoints: [path], format: "esm", target: "node" });
1658
+ if (!result.success) {
1659
+ throw new Error(`Failed to bundle ${label}:
1660
+ ${result.logs.map((l) => l.message).join(`
1661
+ `)}`);
1654
1662
  }
1663
+ return result.outputs[0].text();
1655
1664
  }
1656
-
1657
- class PauseSchemaError extends PauseError {
1658
- code = "pause_schema";
1659
- issues;
1660
- constructor(message, issues) {
1661
- super(message);
1662
- this.issues = issues;
1665
+ async function extractFromBundle(source, label, pick) {
1666
+ const tmpPath = `/tmp/agentc-${label}-${Date.now()}-${Math.random().toString(36).slice(2)}.js`;
1667
+ try {
1668
+ const loaded = await importSourceModule(source, tmpPath);
1669
+ try {
1670
+ return pick(loaded.mod.default);
1671
+ } finally {
1672
+ await loaded.cleanup();
1673
+ }
1674
+ } catch (err) {
1675
+ throw new WorkflowSourceValidationError(`Bundled ${label} could not be evaluated: ${err instanceof Error ? err.message : String(err)}`);
1663
1676
  }
1664
1677
  }
1665
-
1666
- class PauseRequestError extends PauseError {
1667
- code = "pause_request";
1668
- constructor(message = "pause request is invalid") {
1669
- super(message);
1678
+ var DEFINE_WORKFLOW_CALLEE = /^(?:defineWorkflow|defineSandboxEnvironment)\d*$/;
1679
+ function isDefineWorkflowCall(node) {
1680
+ if (node.callee.type === "Identifier") {
1681
+ return DEFINE_WORKFLOW_CALLEE.test(node.callee.name);
1670
1682
  }
1671
- }
1672
-
1673
- // src/pause/pause-core.ts
1674
- var PAUSE_SIGNAL_BRAND = Symbol.for("agent-compose.PauseSignal");
1675
-
1676
- class PauseSignal {
1677
- pauseId;
1678
- pauseRequest;
1679
- observability;
1680
- [PAUSE_SIGNAL_BRAND] = true;
1681
- constructor(pauseId, pauseRequest, observability) {
1682
- this.pauseId = pauseId;
1683
- this.pauseRequest = pauseRequest;
1684
- this.observability = observability;
1683
+ if (node.callee.type === "MemberExpression" && node.callee.object.type === "CallExpression") {
1684
+ return isDefineWorkflowCall(node.callee.object);
1685
1685
  }
1686
+ return false;
1686
1687
  }
1687
- function isPauseSignal(err) {
1688
- return typeof err === "object" && err !== null && err[PAUSE_SIGNAL_BRAND] === true;
1689
- }
1690
- var PAUSE_NAMESPACE = "5f3e2b8a-0c41-4e9d-9a7b-6d2c1f8e3a40";
1691
- function uuidToBytes(uuid) {
1692
- return Buffer.from(uuid.replace(/-/g, ""), "hex");
1693
- }
1694
- function bytesToUuid(b) {
1695
- const h = b.toString("hex");
1696
- return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
1688
+ function assertDefaultExportIsDefineWorkflow(source, label) {
1689
+ let ast;
1690
+ try {
1691
+ ast = babelParse(source, {
1692
+ sourceType: "module",
1693
+ plugins: [],
1694
+ errorRecovery: false
1695
+ });
1696
+ } catch (err) {
1697
+ const msg = err instanceof Error ? err.message : String(err);
1698
+ 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.`);
1699
+ }
1700
+ const defaultExpr = findDefaultExportExpression(ast.program.body);
1701
+ if (!defaultExpr) {
1702
+ throw new WorkflowSourceValidationError(`Bundled workflow ${label} has no default export. ` + `Use \`export default defineWorkflow({ run: ... })\`.`);
1703
+ }
1704
+ if (defaultExpr.type !== "CallExpression" || !isDefineWorkflowCall(defaultExpr)) {
1705
+ 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".`);
1706
+ }
1697
1707
  }
1698
- function uuidv5(name, namespace) {
1699
- const hash = createHash2("sha1").update(uuidToBytes(namespace)).update(Buffer.from(name, "utf8")).digest();
1700
- const bytes = Buffer.from(hash.subarray(0, 16));
1701
- bytes[6] = bytes[6] & 15 | 80;
1702
- bytes[8] = bytes[8] & 63 | 128;
1703
- return bytesToUuid(bytes);
1708
+ function findDefaultExportExpression(body) {
1709
+ const defaultDecl = body.find((node) => node.type === "ExportDefaultDeclaration");
1710
+ if (defaultDecl) {
1711
+ return defaultDecl.declaration;
1712
+ }
1713
+ const exportNamed = body.find((node) => node.type === "ExportNamedDeclaration" && node.specifiers.some((spec2) => spec2.type === "ExportSpecifier" && spec2.exported.type === "Identifier" && spec2.exported.name === "default"));
1714
+ if (!exportNamed || exportNamed.type !== "ExportNamedDeclaration")
1715
+ return null;
1716
+ const spec = exportNamed.specifiers.find((s) => s.type === "ExportSpecifier" && s.exported.type === "Identifier" && s.exported.name === "default");
1717
+ if (!spec || spec.type !== "ExportSpecifier" || spec.local.type !== "Identifier")
1718
+ return null;
1719
+ const localName = spec.local.name;
1720
+ for (const stmt of body) {
1721
+ if (stmt.type !== "VariableDeclaration")
1722
+ continue;
1723
+ for (const decl of stmt.declarations) {
1724
+ if (decl.id.type === "Identifier" && decl.id.name === localName && decl.init && decl.init.type !== "TSSatisfiesExpression") {
1725
+ return decl.init;
1726
+ }
1727
+ }
1728
+ }
1729
+ return null;
1704
1730
  }
1705
- function scopeOf(coord) {
1706
- return coord.agentScope ? `${coord.runId}:${coord.stepIndex}:${coord.agentScope.agentId}:${coord.agentScope.iteration}` : `${coord.runId}:${coord.stepIndex}:bare`;
1731
+ function sha256(text) {
1732
+ return createHash2("sha256").update(text, "utf8").digest("hex");
1707
1733
  }
1708
- var pauseOrdinalByScope = new Map;
1709
- function nextPauseOrdinal(scope) {
1710
- const activeOrdinal = nextPauseOrdinalInActiveStep(scope);
1711
- if (activeOrdinal !== null)
1712
- return activeOrdinal;
1713
- const next = pauseOrdinalByScope.get(scope) ?? 0;
1714
- pauseOrdinalByScope.set(scope, next + 1);
1715
- return next;
1734
+ async function bundleWorkflow(workflowPath, overrides) {
1735
+ const label = `"${workflowPath}"`;
1736
+ const source = await bundle(workflowPath, `workflow ${label}`);
1737
+ assertDefaultExportIsDefineWorkflow(source, label);
1738
+ const workflow = await extractFromBundle(source, "workflow", (wf) => isWorkflow(wf) ? wf : undefined);
1739
+ if (!workflow) {
1740
+ 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.`);
1741
+ }
1742
+ const { metadata } = workflow;
1743
+ const plan = workflowPlan(workflow.steps.map((step, index) => ({ index, name: step.name })));
1744
+ const manifest = {
1745
+ definitionBrand: true,
1746
+ sourceHash: sha256(source),
1747
+ bundlerVersion: BUNDLER_VERSION
1748
+ };
1749
+ const inputSchema = extractIOSchema(workflow.input);
1750
+ const outputSchema = extractIOSchema(workflow.output);
1751
+ return {
1752
+ source,
1753
+ manifest,
1754
+ workflowPlan: plan,
1755
+ networkPolicy: overrides?.networkPolicy ?? metadata.networkPolicy,
1756
+ placeholders: overrides?.placeholders ?? metadata.placeholders,
1757
+ ...metadata.description !== undefined ? { description: metadata.description } : {},
1758
+ ...inputSchema !== undefined ? { inputSchema } : {},
1759
+ ...outputSchema !== undefined ? { outputSchema } : {},
1760
+ ...metadata.snapshots !== undefined ? { snapshots: metadata.snapshots } : {},
1761
+ ...metadata.resources !== undefined ? { resources: metadata.resources } : {},
1762
+ ...metadata.connectors !== undefined ? { connectors: metadata.connectors } : {},
1763
+ ...metadata.connectorOperation !== undefined ? { connectorOperation: metadata.connectorOperation } : {},
1764
+ ...metadata.invokePolicy !== undefined ? { invokePolicy: metadata.invokePolicy } : {},
1765
+ ...metadata.environmentBuild !== undefined ? { environmentBuild: metadata.environmentBuild } : {}
1766
+ };
1716
1767
  }
1717
- function nextPauseId(coord) {
1718
- const scope = scopeOf(coord);
1719
- const ordinal = nextPauseOrdinal(scope);
1720
- return uuidv5(`${scope}:${ordinal}`, PAUSE_NAMESPACE);
1768
+ function extractIOSchema(zodSchema) {
1769
+ let json;
1770
+ try {
1771
+ const z6 = __require("zod");
1772
+ if (!z6.toJSONSchema)
1773
+ return;
1774
+ json = z6.toJSONSchema(zodSchema);
1775
+ } catch {
1776
+ return;
1777
+ }
1778
+ if (!json || typeof json !== "object")
1779
+ return;
1780
+ if (Object.keys(json).filter((k) => k !== "$schema").length === 0)
1781
+ return;
1782
+ const out = {};
1783
+ if (json.type !== undefined)
1784
+ out.type = json.type;
1785
+ if (json.description !== undefined)
1786
+ out.description = String(json.description);
1787
+ if (json.properties && typeof json.properties === "object") {
1788
+ const props = {};
1789
+ for (const [key, raw] of Object.entries(json.properties)) {
1790
+ if (raw && typeof raw === "object") {
1791
+ const r = raw;
1792
+ props[key] = {};
1793
+ if (r.type !== undefined)
1794
+ props[key].type = r.type;
1795
+ if (r.description !== undefined)
1796
+ props[key].description = String(r.description);
1797
+ }
1798
+ }
1799
+ if (Object.keys(props).length > 0)
1800
+ out.properties = props;
1801
+ }
1802
+ if (Array.isArray(json.required))
1803
+ out.required = json.required.map(String);
1804
+ return out;
1721
1805
  }
1722
- function nonJsonPath(value, path = "$", seen = new Set) {
1723
- if (value === null)
1724
- return null;
1725
- const kind = typeof value;
1726
- if (kind === "string" || kind === "boolean")
1806
+ // src/utils/schemas.ts
1807
+ import { z as z6 } from "zod";
1808
+ var AgentStatusSchema = z6.object({
1809
+ summary: z6.string(),
1810
+ completed: z6.array(z6.string()),
1811
+ blockers: z6.array(z6.string()),
1812
+ exit_signal: z6.boolean(),
1813
+ needs_input: z6.boolean().optional(),
1814
+ question: z6.string().optional()
1815
+ });
1816
+ // src/runtimes/claude.ts
1817
+ import { query } from "@anthropic-ai/claude-agent-sdk";
1818
+
1819
+ // src/agent/agent-loop.ts
1820
+ import { z as z10 } from "zod";
1821
+
1822
+ // src/agent/protocol.ts
1823
+ import { z as z7 } from "zod";
1824
+ var AgentMessageSchema = z7.object({
1825
+ type: z7.enum(["init", "text", "thinking", "tool_use", "tool_result", "done", "error", "usage"]),
1826
+ timestamp: z7.string()
1827
+ }).passthrough();
1828
+ function parseAgentResponse(text) {
1829
+ const match = text.match(/<response>([\s\S]*?)<\/response>/);
1830
+ if (!match)
1727
1831
  return null;
1728
- if (kind === "number")
1729
- return Number.isFinite(value) ? null : path;
1730
- if (kind === "undefined" || kind === "bigint" || kind === "function" || kind === "symbol")
1731
- return path;
1732
- if (kind !== "object")
1733
- return path;
1734
- const obj = value;
1735
- if (seen.has(obj))
1736
- return path;
1737
- seen.add(obj);
1738
- if (Array.isArray(value)) {
1739
- for (let i = 0;i < value.length; i++) {
1740
- const child = nonJsonPath(value[i], `${path}[${i}]`, seen);
1741
- if (child)
1742
- return child;
1743
- }
1744
- seen.delete(obj);
1832
+ try {
1833
+ return JSON.parse(match[1].trim());
1834
+ } catch {
1745
1835
  return null;
1746
1836
  }
1747
- const proto = Object.getPrototypeOf(obj);
1748
- if (proto !== Object.prototype && proto !== null)
1749
- return path;
1750
- for (const key of Reflect.ownKeys(obj)) {
1751
- if (typeof key !== "string")
1752
- return `${path}.${String(key)}`;
1753
- const desc = Object.getOwnPropertyDescriptor(obj, key);
1754
- if (!desc?.enumerable)
1755
- return `${path}.${key}`;
1756
- const child = nonJsonPath(value[key], `${path}.${key}`, seen);
1757
- if (child)
1758
- return child;
1759
- }
1760
- seen.delete(obj);
1761
- return null;
1762
1837
  }
1763
- function toWireRequest(req, kind) {
1764
- const wire = {
1765
- reason: req.reason,
1766
- kind,
1767
- ...req.payload !== undefined ? { payload: req.payload } : {},
1768
- ...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {},
1769
- ...req.correlationKey !== undefined ? { correlationKey: req.correlationKey } : {},
1770
- ...req.snapshot !== undefined ? { snapshot: req.snapshot } : {}
1838
+
1839
+ // src/agent/agent-loop.ts
1840
+ import { randomUUID } from "node:crypto";
1841
+
1842
+ // src/pause/manager.ts
1843
+ import { z as z8 } from "zod";
1844
+ var AGENT_LOOP_STATE_SCHEMA_VERSION = 2;
1845
+ var SUPPORTED_AGENT_LOOP_STATE_VERSIONS = z8.union([z8.literal(1), z8.literal(2)]);
1846
+ var RunningAgentLoopStateSchema = z8.object({
1847
+ schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1848
+ phase: z8.literal("running"),
1849
+ iteration: z8.number().int().nonnegative(),
1850
+ completedIterations: z8.number().int().nonnegative(),
1851
+ lastSessionId: z8.string(),
1852
+ lastStatus: AgentStatusSchema.nullable(),
1853
+ lastResponseText: z8.string(),
1854
+ lastResponseValidationError: z8.string(),
1855
+ iterationsWithoutStatus: z8.number().int().nonnegative(),
1856
+ blockerStreak: z8.object({ key: z8.string(), count: z8.number().int().nonnegative() }).nullable(),
1857
+ 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)
1858
+ });
1859
+ var SettledAgentLoopStateSchema = z8.object({
1860
+ schemaVersion: SUPPORTED_AGENT_LOOP_STATE_VERSIONS,
1861
+ phase: z8.literal("settled"),
1862
+ result: z8.object({
1863
+ sessionId: z8.string(),
1864
+ lastStatus: AgentStatusSchema.nullable(),
1865
+ iterations: z8.number().int().nonnegative(),
1866
+ response: z8.unknown().optional()
1867
+ })
1868
+ });
1869
+ var AgentLoopStateSchema = z8.discriminatedUnion("phase", [
1870
+ RunningAgentLoopStateSchema,
1871
+ SettledAgentLoopStateSchema
1872
+ ]);
1873
+ function toProgressState(state) {
1874
+ return {
1875
+ iteration: state.iteration,
1876
+ completedIterations: state.completedIterations,
1877
+ lastSessionId: state.lastSessionId,
1878
+ lastStatus: state.lastStatus,
1879
+ lastResponseText: state.lastResponseText,
1880
+ lastResponseValidationError: state.lastResponseValidationError,
1881
+ iterationsWithoutStatus: state.iterationsWithoutStatus,
1882
+ blockerStreak: state.blockerStreak,
1883
+ pendingSteerPause: state.pendingSteerPause
1771
1884
  };
1772
- const parsed = StepPauseRequestSchema.safeParse(wire);
1773
- if (!parsed.success) {
1774
- throw new PauseRequestError(`pause request failed validation: ${parsed.error.message}`);
1775
- }
1776
- const badPath = nonJsonPath(parsed.data);
1777
- if (badPath) {
1778
- throw new PauseRequestError(`pause request must be JSON-serializable (invalid value at ${badPath})`);
1779
- }
1780
- return parsed.data;
1781
1885
  }
1782
- async function corePause(req, coord, kind = "custom") {
1783
- const wire = toWireRequest(req, kind);
1784
- const pauseId = nextPauseId(coord);
1785
- const record = await readPauseRecord(pauseId);
1786
- if (record === null || record.resolved !== true) {
1787
- await writePauseRecord({ pauseId, request: wire });
1788
- throw new PauseSignal(pauseId, wire);
1886
+
1887
+ class PauseManager {
1888
+ agentId;
1889
+ constructor(agentId) {
1890
+ this.agentId = agentId;
1789
1891
  }
1790
- if (record.expired === true) {
1791
- const onExpiry = req.onExpiry ?? { mode: "throw" };
1792
- if (onExpiry.mode === "resolve")
1793
- return onExpiry.value;
1794
- throw new PauseExpiredError(`pause "${req.reason}" expired before it was resolved`);
1892
+ async restoreAgentLoop(runtime) {
1893
+ const priorState = await readAgentState(this.agentId);
1894
+ if (priorState === null)
1895
+ return { kind: "fresh" };
1896
+ const parsed = AgentLoopStateSchema.safeParse(priorState);
1897
+ if (!parsed.success)
1898
+ return { kind: "invalid", error: parsed.error.message };
1899
+ const state = parsed.data;
1900
+ if (state.phase === "settled") {
1901
+ return {
1902
+ kind: "settled",
1903
+ result: {
1904
+ sessionId: state.result.sessionId,
1905
+ lastStatus: state.result.lastStatus,
1906
+ iterations: state.result.iterations,
1907
+ ...Object.prototype.hasOwnProperty.call(state.result, "response") ? { response: state.result.response } : {}
1908
+ }
1909
+ };
1910
+ }
1911
+ const runtimeBlob = await readRuntimeCheckpoint(this.agentId);
1912
+ if (runtimeBlob !== null && !runtime.restoreCheckpoint) {
1913
+ throw new Error(`runtime checkpoint exists for resumed agentId=${this.agentId}, but runtime cannot restore it`);
1914
+ }
1915
+ if (runtime.restoreCheckpoint && runtimeBlob === null) {
1916
+ throw new Error(`missing runtime checkpoint for resumed agentId=${this.agentId}`);
1917
+ }
1918
+ if (runtimeBlob !== null)
1919
+ runtime.restoreCheckpoint?.(runtimeBlob);
1920
+ return { kind: "running", state: toProgressState(state) };
1795
1921
  }
1796
- const payload = record.resumePayload ?? null;
1797
- if (req.schema) {
1798
- const parsed = req.schema.safeParse(payload);
1799
- if (!parsed.success) {
1800
- throw new PauseSchemaError(`resume payload for pause "${req.reason}" failed schema validation: ${parsed.error.message}`, parsed.error.issues);
1922
+ async saveRunningAgentLoop(state, runtime) {
1923
+ await writeAgentState(this.agentId, {
1924
+ schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1925
+ phase: "running",
1926
+ ...state
1927
+ });
1928
+ if (runtime.captureCheckpoint) {
1929
+ const blob = runtime.captureCheckpoint();
1930
+ if (blob !== undefined)
1931
+ await writeRuntimeCheckpoint(this.agentId, blob);
1801
1932
  }
1802
- return parsed.data;
1803
1933
  }
1804
- return payload;
1934
+ async saveSettledAgentLoop(result) {
1935
+ await writeAgentState(this.agentId, {
1936
+ schemaVersion: AGENT_LOOP_STATE_SCHEMA_VERSION,
1937
+ phase: "settled",
1938
+ result: {
1939
+ sessionId: result.sessionId,
1940
+ lastStatus: result.lastStatus,
1941
+ iterations: result.iterations,
1942
+ ...result.response !== undefined ? { response: result.response } : {}
1943
+ }
1944
+ });
1945
+ }
1805
1946
  }
1806
1947
 
1807
1948
  // src/agent/steer-control.ts
1808
- import { z as z8 } from "zod";
1809
- var SteerDecisionSchema = z8.object({
1810
- message: z8.string().min(1),
1811
- actor: z8.string().nullish()
1949
+ import { z as z9 } from "zod";
1950
+ var SteerDecisionSchema = z9.object({
1951
+ message: z9.string().min(1).optional(),
1952
+ decision: z9.string().min(1).optional(),
1953
+ actor: z9.string().nullish()
1954
+ }).transform((d) => ({ message: (d.message ?? d.decision ?? "").trim(), actor: d.actor ?? null })).refine((d) => d.message.length > 0, {
1955
+ message: "a steer/resume answer needs a non-empty `message` or `decision`"
1812
1956
  });
1813
1957
  var steerPending = new Map;
1814
1958
  function signalSteerPending(agentId, payload) {
@@ -1921,6 +2065,8 @@ function summarizeAgentMessage(msg) {
1921
2065
  return { type: "done", sessionId: msg.sessionId };
1922
2066
  case "error":
1923
2067
  return { type: "error", text: truncate(msg.text) };
2068
+ case "plan":
2069
+ return { type: "plan", entries: msg.entries };
1924
2070
  }
1925
2071
  }
1926
2072
  async function agentLoop(opts) {
@@ -1946,7 +2092,8 @@ async function agentLoop(opts) {
1946
2092
  abortSignal: loopAbort.signal,
1947
2093
  retryCount: 0,
1948
2094
  agentId,
1949
- iteration
2095
+ iteration,
2096
+ pause: boundProcessorPause(opts.pause, { agentId, iteration })
1950
2097
  });
1951
2098
  if (!opts.runtime)
1952
2099
  throw new Error("agentLoop: opts.runtime is required");
@@ -1958,7 +2105,8 @@ async function agentLoop(opts) {
1958
2105
  processors,
1959
2106
  requestContext,
1960
2107
  agentId,
1961
- ...opts.responseSchema ? { outputFormat: { type: "json_schema", schema: z9.toJSONSchema(opts.responseSchema) } } : {}
2108
+ ...opts.pause ? { pause: opts.pause } : {},
2109
+ ...opts.responseSchema ? { outputFormat: { type: "json_schema", schema: z10.toJSONSchema(opts.responseSchema) } } : {}
1962
2110
  });
1963
2111
  if (processors.some((p) => p.processToolCall) && !client.supportsToolCallProcessor) {
1964
2112
  process.stderr.write(`${logLabel} processToolCall registered but runtime does not support pre-tool-use gating — hook is dormant
@@ -2052,6 +2200,7 @@ async function agentLoop(opts) {
2052
2200
  steerDecision = await opts.pause({
2053
2201
  reason: pendingSteerPause.reason,
2054
2202
  ...pendingSteerPause.correlationKey !== null ? { correlationKey: pendingSteerPause.correlationKey } : {},
2203
+ ...pendingSteerPause.payload !== undefined ? { payload: pendingSteerPause.payload } : {},
2055
2204
  schema: SteerDecisionSchema
2056
2205
  }, { agentId, iteration });
2057
2206
  pendingSteerPause = null;
@@ -2088,6 +2237,7 @@ Re-emit the COMPLETE corrected <response> JSON now: every required field present
2088
2237
  prompt,
2089
2238
  sessionId: lastSessionId ?? undefined,
2090
2239
  iteration: iteration + 1,
2240
+ signal: loopAbort.signal,
2091
2241
  ...opts.inbox ? { inboxStream: opts.inbox } : {}
2092
2242
  })) {
2093
2243
  const outputVerdict = await runProcessorChain(processors, (p) => p.processOutput, rawMsg, procCtx);
@@ -2405,12 +2555,15 @@ class ClaudeRunner {
2405
2555
  toolInput: pre.tool_input ?? {},
2406
2556
  toolUseId: toolUseId ?? `${pre.tool_name}-${Date.now()}`
2407
2557
  };
2558
+ const gateAgentId = this.options.agentId ?? "agent";
2559
+ const gateIteration = opts.iteration ?? 1;
2408
2560
  const gate = await this.gateToolCall(call, {
2409
2561
  requestContext,
2410
2562
  abortSignal: signal ?? opts.signal ?? new AbortController().signal,
2411
2563
  retryCount: 0,
2412
- agentId: this.options.agentId ?? "agent",
2413
- iteration: opts.iteration ?? 1
2564
+ agentId: gateAgentId,
2565
+ iteration: gateIteration,
2566
+ pause: boundProcessorPause(this.options.pause, { agentId: gateAgentId, iteration: gateIteration })
2414
2567
  });
2415
2568
  if (gate.kind === "allow")
2416
2569
  return { hookSpecificOutput: { hookEventName: pre.hook_event_name, permissionDecision: "allow", updatedInput: gate.call.toolInput } };
@@ -2441,6 +2594,17 @@ class ClaudeRunner {
2441
2594
  })();
2442
2595
  }
2443
2596
  const promptForQuery = inboxQueue ?? opts.prompt;
2597
+ const queryAbort = new AbortController;
2598
+ const onLoopAbort = () => queryAbort.abort();
2599
+ if (opts.signal) {
2600
+ if (opts.signal.aborted)
2601
+ queryAbort.abort();
2602
+ else
2603
+ opts.signal.addEventListener("abort", onLoopAbort, { once: true });
2604
+ }
2605
+ const systemPromptAppend = [this.config.claudeMdContent, this.options.agentManual].filter((s) => Boolean(s && s.trim())).join(`
2606
+
2607
+ `);
2444
2608
  try {
2445
2609
  let emittedAssistantText = false;
2446
2610
  for await (const message of query({
@@ -2454,9 +2618,10 @@ class ClaudeRunner {
2454
2618
  thinking: resolveThinking(this.model, this.config.thinking),
2455
2619
  effort: this.config.effort,
2456
2620
  cwd: this.options.cwd,
2621
+ abortController: queryAbort,
2457
2622
  env: { ...process.env, ...this.config.env ?? {} },
2458
2623
  pathToClaudeCodeExecutable: this.config.pathToClaudeCodeExecutable ?? process.env.CLAUDE_CODE_EXECUTABLE ?? DEFAULT_CLAUDE_PATH,
2459
- ...this.config.claudeMdContent ? { systemPrompt: { type: "preset", preset: "claude_code", append: this.config.claudeMdContent } } : {},
2624
+ ...systemPromptAppend ? { systemPrompt: { type: "preset", preset: "claude_code", append: systemPromptAppend } } : {},
2460
2625
  skills: this.config.skills ?? "all",
2461
2626
  resume: opts.sessionId,
2462
2627
  mcpServers: this.config.mcpServers,
@@ -2477,30 +2642,314 @@ class ClaudeRunner {
2477
2642
  inboxQueue.close();
2478
2643
  }
2479
2644
  } catch (err) {
2480
- const text = formatError(err);
2481
- if (/maximum number of turns/i.test(text)) {
2482
- yield { type: "done", sessionId: opts.sessionId ?? "", timestamp: now2() };
2483
- } else {
2484
- yield { type: "error", text, timestamp: now2() };
2645
+ const text = formatError(err);
2646
+ if (/maximum number of turns/i.test(text)) {
2647
+ yield { type: "done", sessionId: opts.sessionId ?? "", timestamp: now2() };
2648
+ } else {
2649
+ yield { type: "error", text, timestamp: now2() };
2650
+ }
2651
+ } finally {
2652
+ inboxQueue?.close();
2653
+ opts.signal?.removeEventListener("abort", onLoopAbort);
2654
+ }
2655
+ }
2656
+ }
2657
+ function createClaudeRuntime(config = {}) {
2658
+ return defineRuntime({
2659
+ create: (sandbox, opts) => new ClaudeRunner(sandbox, opts, config)
2660
+ });
2661
+ }
2662
+ var claude_default = createClaudeRuntime();
2663
+ // src/runtimes/_cli-agent.ts
2664
+ import { ndJsonStream } from "@agentclientprotocol/sdk";
2665
+
2666
+ // src/runtimes/_acp-client.ts
2667
+ import {
2668
+ ClientSideConnection,
2669
+ RequestError
2670
+ } from "@agentclientprotocol/sdk";
2671
+ function now3() {
2672
+ return new Date().toISOString();
2673
+ }
2674
+ var ACP_PROTOCOL_VERSION = 1;
2675
+ var OPTION_KIND_FOR_VERDICT = {
2676
+ allow: ["allow_once", "allow_always"],
2677
+ deny: ["reject_once", "reject_always"]
2678
+ };
2679
+ function contentBlockText(block) {
2680
+ if (!block)
2681
+ return "";
2682
+ return block.type === "text" ? block.text : "";
2683
+ }
2684
+ function renderToolContent(content) {
2685
+ const parts = [];
2686
+ const diffs = [];
2687
+ for (const block of content ?? []) {
2688
+ if (block.type === "content") {
2689
+ parts.push(contentBlockText(block.content));
2690
+ } else if (block.type === "diff") {
2691
+ diffs.push({ path: block.path, oldText: block.oldText ?? null, newText: block.newText });
2692
+ const header = block.oldText == null ? `--- (new file)
2693
+ +++ ${block.path}` : `--- ${block.path}
2694
+ +++ ${block.path}`;
2695
+ parts.push(`${header}
2696
+ ${block.newText}`);
2697
+ } else if (block.type === "terminal") {
2698
+ parts.push(`[terminal ${block.terminalId}]`);
2699
+ }
2700
+ }
2701
+ return { output: parts.filter((p) => p !== "").join(`
2702
+ `), diffs };
2703
+ }
2704
+ function mapLocations(locations) {
2705
+ if (!locations || locations.length === 0)
2706
+ return;
2707
+ return locations.map((l) => l.line != null ? { path: l.path, line: l.line } : { path: l.path });
2708
+ }
2709
+ function terminalToolResult(args) {
2710
+ const { output, diffs } = renderToolContent(args.content);
2711
+ const locations = mapLocations(args.locations);
2712
+ return {
2713
+ type: "tool_result",
2714
+ toolUseId: args.toolCallId,
2715
+ output,
2716
+ isError: args.status === "failed",
2717
+ timestamp: args.timestamp,
2718
+ ...diffs.length > 0 ? { diffs } : {},
2719
+ ...locations ? { locations } : {}
2720
+ };
2721
+ }
2722
+ function normalizeSessionUpdate(update) {
2723
+ const ts = now3();
2724
+ switch (update.sessionUpdate) {
2725
+ case "agent_message_chunk":
2726
+ return [{ type: "text", text: contentBlockText(update.content), timestamp: ts }];
2727
+ case "agent_thought_chunk":
2728
+ return [{ type: "thinking", text: contentBlockText(update.content), timestamp: ts }];
2729
+ case "tool_call": {
2730
+ const messages = [{
2731
+ type: "tool_use",
2732
+ toolName: update.title || update.kind || "tool",
2733
+ toolInput: update.rawInput ?? {},
2734
+ toolUseId: update.toolCallId,
2735
+ timestamp: ts
2736
+ }];
2737
+ if (update.status === "completed" || update.status === "failed") {
2738
+ messages.push(terminalToolResult({
2739
+ toolCallId: update.toolCallId,
2740
+ status: update.status,
2741
+ content: update.content,
2742
+ locations: update.locations,
2743
+ timestamp: ts
2744
+ }));
2745
+ }
2746
+ return messages;
2747
+ }
2748
+ case "tool_call_update": {
2749
+ if (update.status !== "completed" && update.status !== "failed")
2750
+ return [];
2751
+ return [terminalToolResult({
2752
+ toolCallId: update.toolCallId,
2753
+ status: update.status,
2754
+ content: update.content,
2755
+ locations: update.locations,
2756
+ timestamp: ts
2757
+ })];
2758
+ }
2759
+ case "plan":
2760
+ return [{
2761
+ type: "plan",
2762
+ entries: update.entries.map((e) => ({ content: e.content, priority: e.priority, status: e.status })),
2763
+ timestamp: ts
2764
+ }];
2765
+ case "user_message_chunk":
2766
+ case "available_commands_update":
2767
+ case "current_mode_update":
2768
+ case "usage_update":
2769
+ case "plan_update":
2770
+ case "plan_removed":
2771
+ case "config_option_update":
2772
+ case "session_info_update":
2773
+ return [];
2774
+ default:
2775
+ return [];
2776
+ }
2777
+ }
2778
+
2779
+ class AcpClientPeer {
2780
+ deps;
2781
+ conn;
2782
+ updates = new AsyncQueue;
2783
+ sessionId;
2784
+ pendingPause = null;
2785
+ cancelled = false;
2786
+ abortPrompt = null;
2787
+ constructor(deps) {
2788
+ this.deps = deps;
2789
+ const handler = {
2790
+ sessionUpdate: (params) => {
2791
+ for (const msg of normalizeSessionUpdate(params.update))
2792
+ this.updates.push(msg);
2793
+ },
2794
+ requestPermission: (params) => this.onRequestPermission(params)
2795
+ };
2796
+ this.conn = new ClientSideConnection((_agent) => handler, this.deps.stream);
2797
+ if (this.deps.signal) {
2798
+ if (this.deps.signal.aborted)
2799
+ this.cancel();
2800
+ else
2801
+ this.deps.signal.addEventListener("abort", () => this.cancel(), { once: true });
2802
+ }
2803
+ }
2804
+ async initialize() {
2805
+ const res = await this.conn.initialize({
2806
+ protocolVersion: ACP_PROTOCOL_VERSION,
2807
+ clientCapabilities: {
2808
+ fs: { readTextFile: false, writeTextFile: false },
2809
+ terminal: false
2810
+ }
2811
+ });
2812
+ return { protocolVersion: res.protocolVersion };
2813
+ }
2814
+ async startSession(cwd) {
2815
+ const res = await this.conn.newSession({ cwd, mcpServers: [] });
2816
+ this.sessionId = res.sessionId;
2817
+ return res.sessionId;
2818
+ }
2819
+ async* prompt(text) {
2820
+ if (!this.sessionId)
2821
+ throw new Error("AcpClientPeer.prompt called before startSession");
2822
+ const updates = new AsyncQueue;
2823
+ this.updates = updates;
2824
+ const aborted = new Promise((resolve) => {
2825
+ this.abortPrompt = () => {
2826
+ resolve();
2827
+ updates.close();
2828
+ };
2829
+ });
2830
+ if (this.cancelled)
2831
+ this.abortPrompt();
2832
+ const promptPromise = this.conn.prompt({ sessionId: this.sessionId, prompt: [{ type: "text", text }] }).then((res) => {
2833
+ updates.close();
2834
+ return { ok: true, res };
2835
+ }, (err) => {
2836
+ updates.close();
2837
+ return { ok: false, err };
2838
+ });
2839
+ for await (const msg of updates) {
2840
+ yield msg;
2841
+ }
2842
+ if (this.pendingPause !== null) {
2843
+ const pause = this.pendingPause;
2844
+ this.pendingPause = null;
2845
+ this.abortPrompt = null;
2846
+ throw pause;
2847
+ }
2848
+ const settled = await Promise.race([
2849
+ promptPromise.then((o) => ({ kind: "prompt", o })),
2850
+ aborted.then(() => ({ kind: "aborted" }))
2851
+ ]);
2852
+ this.abortPrompt = null;
2853
+ if (settled.kind === "aborted")
2854
+ return;
2855
+ const outcome = settled.o;
2856
+ if (!outcome.ok) {
2857
+ yield { type: "error", text: formatError(outcome.err), timestamp: now3() };
2858
+ return;
2859
+ }
2860
+ const stopReason = outcome.res.stopReason;
2861
+ if (stopReason === "refusal") {
2862
+ yield { type: "error", text: "agent refused the request", timestamp: now3() };
2863
+ return;
2864
+ }
2865
+ const usage = outcome.res.usage;
2866
+ if (usage) {
2867
+ yield {
2868
+ type: "usage",
2869
+ inputTokens: usage.inputTokens,
2870
+ outputTokens: usage.outputTokens,
2871
+ cacheReadTokens: usage.cachedReadTokens ?? 0,
2872
+ cacheCreationTokens: usage.cachedWriteTokens ?? 0,
2873
+ durationMs: 0,
2874
+ numTurns: 1,
2875
+ timestamp: now3()
2876
+ };
2877
+ }
2878
+ }
2879
+ get currentSessionId() {
2880
+ return this.sessionId;
2881
+ }
2882
+ cancel() {
2883
+ if (this.cancelled) {
2884
+ this.abortPrompt?.();
2885
+ return;
2886
+ }
2887
+ this.cancelled = true;
2888
+ if (this.sessionId) {
2889
+ this.conn.cancel({ sessionId: this.sessionId }).catch(() => {});
2890
+ }
2891
+ this.abortPrompt?.();
2892
+ }
2893
+ close() {
2894
+ this.abortPrompt?.();
2895
+ this.updates.close();
2896
+ }
2897
+ async onRequestPermission(params) {
2898
+ if (this.cancelled || this.deps.signal?.aborted) {
2899
+ return { outcome: { outcome: "cancelled" } };
2900
+ }
2901
+ const tc = params.toolCall;
2902
+ const call = {
2903
+ toolName: tc.title || tc.kind || "tool",
2904
+ toolInput: tc.rawInput ?? {},
2905
+ toolUseId: tc.toolCallId
2906
+ };
2907
+ let verdict;
2908
+ try {
2909
+ verdict = await this.deps.gateToolCall(call, this.deps.procCtx());
2910
+ } catch (err) {
2911
+ if (isPauseSignal(err)) {
2912
+ this.pendingPause = err;
2913
+ this.cancel();
2914
+ return { outcome: { outcome: "cancelled" } };
2485
2915
  }
2486
- } finally {
2487
- inboxQueue?.close();
2916
+ throw err;
2917
+ }
2918
+ if (verdict.kind === "abort") {
2919
+ this.cancel();
2920
+ return { outcome: { outcome: "cancelled" } };
2488
2921
  }
2922
+ const wantedKinds = verdict.kind === "allow" ? OPTION_KIND_FOR_VERDICT.allow : OPTION_KIND_FOR_VERDICT.deny;
2923
+ const option = params.options.find((o) => wantedKinds.includes(o.kind)) ?? params.options[0];
2924
+ if (!option) {
2925
+ return { outcome: { outcome: "cancelled" } };
2926
+ }
2927
+ return { outcome: { outcome: "selected", optionId: option.optionId } };
2489
2928
  }
2490
2929
  }
2491
- function createClaudeRuntime(config = {}) {
2492
- return defineRuntime({
2493
- create: (sandbox, opts) => new ClaudeRunner(sandbox, opts, config)
2494
- });
2495
- }
2496
- var claude_default = createClaudeRuntime();
2930
+
2497
2931
  // src/runtimes/_cli-agent.ts
2498
- function now3() {
2932
+ function now4() {
2499
2933
  return new Date().toISOString();
2500
2934
  }
2501
2935
  function shellQuote(value) {
2502
2936
  return `'${value.replace(/'/g, `'\\''`)}'`;
2503
2937
  }
2938
+ var ACP_FALLBACK = Symbol("acp-fallback");
2939
+ var ACP_HANDSHAKE_TIMEOUT_MS = Number(process.env.AC_ACP_HANDSHAKE_TIMEOUT_MS) || 1e4;
2940
+ var ACP_TRANSPORT_READY = true;
2941
+ async function withHandshakeTimeout(p, ms) {
2942
+ let timer;
2943
+ const timeout = new Promise((_resolve, reject) => {
2944
+ timer = setTimeout(() => reject(new Error(`ACP handshake timed out after ${ms}ms`)), ms);
2945
+ });
2946
+ try {
2947
+ return await Promise.race([p, timeout]);
2948
+ } finally {
2949
+ if (timer)
2950
+ clearTimeout(timer);
2951
+ }
2952
+ }
2504
2953
 
2505
2954
  class CliAgentRunner {
2506
2955
  sandbox;
@@ -2508,6 +2957,16 @@ class CliAgentRunner {
2508
2957
  spec;
2509
2958
  configModel;
2510
2959
  kind;
2960
+ get acpCapable() {
2961
+ return this.spec.acp !== undefined && this.acpTransportReady && typeof this.sandbox.commands.spawnDuplex === "function";
2962
+ }
2963
+ get supportsToolCallProcessor() {
2964
+ return this.acpCapable;
2965
+ }
2966
+ acpDecision;
2967
+ acpSession;
2968
+ currentAcpProcCtx;
2969
+ acpTransportReady = ACP_TRANSPORT_READY;
2511
2970
  constructor(sandbox, options, spec, configModel) {
2512
2971
  this.sandbox = sandbox;
2513
2972
  this.options = options;
@@ -2515,9 +2974,37 @@ class CliAgentRunner {
2515
2974
  this.configModel = configModel;
2516
2975
  this.kind = spec.kind;
2517
2976
  }
2977
+ enableAcpForTesting() {
2978
+ this.acpTransportReady = true;
2979
+ }
2518
2980
  get model() {
2519
2981
  return this.configModel ?? this.options.model ?? this.spec.defaultModel;
2520
2982
  }
2983
+ async gateToolCall(call, ctx) {
2984
+ const verdict = await runProcessorChain(this.options.processors ?? [], (p) => p.processToolCall, call, ctx);
2985
+ if (verdict.kind === "continue")
2986
+ return { kind: "allow", call: verdict.value };
2987
+ return verdict;
2988
+ }
2989
+ buildProcCtx(signal, iteration) {
2990
+ const agentId = this.options.agentId ?? "agent";
2991
+ const iter = iteration ?? 1;
2992
+ return {
2993
+ requestContext: this.options.requestContext ?? RequestContext.fromReserved({
2994
+ teamId: "",
2995
+ runId: "",
2996
+ workflowId: "",
2997
+ factoryId: null,
2998
+ apiKeyScopes: [],
2999
+ parentRunId: null
3000
+ }),
3001
+ abortSignal: signal ?? new AbortController().signal,
3002
+ retryCount: 0,
3003
+ agentId,
3004
+ iteration: iter,
3005
+ pause: boundProcessorPause(this.options.pause, { agentId, iteration: iter })
3006
+ };
3007
+ }
2521
3008
  installed = false;
2522
3009
  async ensureInstalled() {
2523
3010
  if (this.installed)
@@ -2534,7 +3021,133 @@ class CliAgentRunner {
2534
3021
  this.installed = true;
2535
3022
  }
2536
3023
  async* sendMessage(opts) {
2537
- yield { type: "init", sessionId: opts.sessionId ?? "", timestamp: now3() };
3024
+ yield { type: "init", sessionId: opts.sessionId ?? "", timestamp: now4() };
3025
+ if (this.acpCapable && this.acpDecision !== "jsonl") {
3026
+ let fellBack = false;
3027
+ try {
3028
+ for await (const ev of this.sendMessageAcp(opts)) {
3029
+ if (ev === ACP_FALLBACK) {
3030
+ fellBack = true;
3031
+ break;
3032
+ }
3033
+ yield ev;
3034
+ }
3035
+ } catch (err) {
3036
+ if (isPauseSignal(err))
3037
+ throw err;
3038
+ yield { type: "error", text: formatError(err), timestamp: now4() };
3039
+ return;
3040
+ }
3041
+ if (!fellBack) {
3042
+ this.acpDecision = "acp";
3043
+ return;
3044
+ }
3045
+ this.acpDecision = "jsonl";
3046
+ }
3047
+ yield* this.sendMessageJsonl(opts);
3048
+ }
3049
+ async teardownAcp(proc, peer) {
3050
+ peer.close();
3051
+ try {
3052
+ await proc.stream.writable.close();
3053
+ } catch {}
3054
+ proc.kill();
3055
+ proc.exited.catch(() => {});
3056
+ }
3057
+ async ensureAcpSession(opts) {
3058
+ if (this.acpSession)
3059
+ return this.acpSession;
3060
+ await this.ensureInstalled();
3061
+ const acp = this.spec.acp;
3062
+ const proc = this.spawnAcpProcess(acp, opts.signal);
3063
+ const peer = new AcpClientPeer({
3064
+ stream: proc.stream,
3065
+ gateToolCall: (call, ctx) => this.gateToolCall(call, ctx),
3066
+ procCtx: () => this.currentAcpProcCtx ?? this.buildProcCtx(opts.signal, opts.iteration),
3067
+ ...opts.signal ? { signal: opts.signal } : {}
3068
+ });
3069
+ let negotiated;
3070
+ try {
3071
+ ({ protocolVersion: negotiated } = await withHandshakeTimeout(peer.initialize(), ACP_HANDSHAKE_TIMEOUT_MS));
3072
+ } catch {
3073
+ await this.teardownAcp(proc, peer);
3074
+ return null;
3075
+ }
3076
+ if (negotiated !== ACP_PROTOCOL_VERSION) {
3077
+ await this.teardownAcp(proc, peer);
3078
+ return null;
3079
+ }
3080
+ let sessionId;
3081
+ try {
3082
+ sessionId = await withHandshakeTimeout(peer.startSession(this.options.cwd ?? "/"), ACP_HANDSHAKE_TIMEOUT_MS);
3083
+ } catch {
3084
+ await this.teardownAcp(proc, peer);
3085
+ return null;
3086
+ }
3087
+ this.acpSession = { proc, peer, sessionId };
3088
+ return this.acpSession;
3089
+ }
3090
+ async* sendMessageAcp(opts) {
3091
+ this.currentAcpProcCtx = this.buildProcCtx(opts.signal, opts.iteration);
3092
+ const session = await this.ensureAcpSession(opts);
3093
+ if (!session) {
3094
+ yield ACP_FALLBACK;
3095
+ return;
3096
+ }
3097
+ const { proc, peer, sessionId } = session;
3098
+ let sawError = false;
3099
+ let stalled = false;
3100
+ let watchdog;
3101
+ const armWatchdog = () => {
3102
+ if (watchdog)
3103
+ clearTimeout(watchdog);
3104
+ watchdog = setTimeout(() => {
3105
+ stalled = true;
3106
+ peer.cancel();
3107
+ }, ACP_HANDSHAKE_TIMEOUT_MS);
3108
+ };
3109
+ try {
3110
+ armWatchdog();
3111
+ for await (const msg of peer.prompt(opts.prompt)) {
3112
+ armWatchdog();
3113
+ if (msg.type === "error")
3114
+ sawError = true;
3115
+ yield msg;
3116
+ }
3117
+ } finally {
3118
+ if (watchdog)
3119
+ clearTimeout(watchdog);
3120
+ }
3121
+ if (stalled && !sawError) {
3122
+ await this.teardownAcp(proc, peer);
3123
+ this.acpSession = undefined;
3124
+ yield { type: "error", text: `${this.spec.kind} prompt turn stalled (no activity for ${ACP_HANDSHAKE_TIMEOUT_MS}ms)`, timestamp: now4() };
3125
+ return;
3126
+ }
3127
+ if (!sawError)
3128
+ yield { type: "done", sessionId: peer.currentSessionId ?? sessionId, timestamp: now4() };
3129
+ }
3130
+ spawnAcpProcess(acp, signal) {
3131
+ const spawnDuplex = this.sandbox.commands.spawnDuplex;
3132
+ if (!spawnDuplex) {
3133
+ throw new Error("spawnAcpProcess called on a provider without commands.spawnDuplex");
3134
+ }
3135
+ const cmd = `${acp.command} ${acp.args.map(shellQuote).join(" ")}`.trim();
3136
+ const agentEnv = { ...acp.env };
3137
+ const proc = spawnDuplex.call(this.sandbox.commands, cmd, {
3138
+ ...this.options.cwd ? { cwd: this.options.cwd } : {},
3139
+ ...Object.keys(agentEnv).length > 0 ? { envs: agentEnv } : {}
3140
+ });
3141
+ const kill = () => proc.kill();
3142
+ if (signal) {
3143
+ if (signal.aborted)
3144
+ kill();
3145
+ else
3146
+ signal.addEventListener("abort", kill, { once: true });
3147
+ }
3148
+ return { stream: ndJsonStream(proc.stdin, proc.stdout), exited: proc.exited, kill };
3149
+ }
3150
+ async* sendMessageJsonl(opts) {
2538
3151
  let sessionId = opts.sessionId;
2539
3152
  let sawError = false;
2540
3153
  try {
@@ -2592,13 +3205,13 @@ class CliAgentRunner {
2592
3205
  const res = await runPromise;
2593
3206
  if (res.exitCode !== 0 && !sawError) {
2594
3207
  const tail = (res.stderr ?? "").slice(-2000);
2595
- yield { type: "error", text: `${this.spec.kind} exited with code ${res.exitCode}${tail ? `: ${tail}` : ""}`, timestamp: now3() };
3208
+ yield { type: "error", text: `${this.spec.kind} exited with code ${res.exitCode}${tail ? `: ${tail}` : ""}`, timestamp: now4() };
2596
3209
  return;
2597
3210
  }
2598
3211
  if (!sawError)
2599
- yield { type: "done", sessionId: sessionId ?? "", timestamp: now3() };
3212
+ yield { type: "done", sessionId: sessionId ?? "", timestamp: now4() };
2600
3213
  } catch (err) {
2601
- yield { type: "error", text: formatError(err), timestamp: now3() };
3214
+ yield { type: "error", text: formatError(err), timestamp: now4() };
2602
3215
  }
2603
3216
  }
2604
3217
  }
@@ -2609,13 +3222,19 @@ function createCliAgentRuntime(spec, configModel) {
2609
3222
  }
2610
3223
 
2611
3224
  // src/runtimes/codex.ts
2612
- function now4() {
3225
+ function now5() {
2613
3226
  return new Date().toISOString();
2614
3227
  }
3228
+ var CODEX_ACP_ADAPTER = "@agentclientprotocol/codex-acp@0.1.0";
2615
3229
  var codexSpec = {
2616
3230
  kind: "codex",
2617
3231
  authEnv: "CODEX_API_KEY",
2618
3232
  bin: "codex",
3233
+ acp: {
3234
+ command: "npx",
3235
+ args: ["-y", CODEX_ACP_ADAPTER],
3236
+ ...process.env.CODEX_PATH ? { env: { CODEX_PATH: process.env.CODEX_PATH } } : {}
3237
+ },
2619
3238
  install: 'sudo npm install -g @openai/codex && (command -v codex >/dev/null 2>&1 || sudo ln -sf "$(npm prefix -g)/bin/codex" /usr/local/bin/codex)',
2620
3239
  promptPayload: (prompt) => prompt,
2621
3240
  buildCommand: ({ promptPath, sessionId, model, cwd }) => {
@@ -2631,7 +3250,7 @@ var codexSpec = {
2631
3250
  },
2632
3251
  extractSessionId: (p) => p.type === "thread.started" && typeof p.thread_id === "string" ? p.thread_id : undefined,
2633
3252
  mapEvent: (p) => {
2634
- const ts = now4();
3253
+ const ts = now5();
2635
3254
  switch (p.type) {
2636
3255
  case "item.started":
2637
3256
  case "item.completed": {
@@ -2686,7 +3305,7 @@ function createCodexRuntime(config = {}) {
2686
3305
  }
2687
3306
  var codex_default = createCodexRuntime();
2688
3307
  // src/runtimes/amp.ts
2689
- function now5() {
3308
+ function now6() {
2690
3309
  return new Date().toISOString();
2691
3310
  }
2692
3311
  function blocks(msg) {
@@ -2706,7 +3325,7 @@ var ampSpec = {
2706
3325
  },
2707
3326
  extractSessionId: (p) => typeof p.session_id === "string" ? p.session_id : undefined,
2708
3327
  mapEvent: (p) => {
2709
- const ts = now5();
3328
+ const ts = now6();
2710
3329
  if (p.type === "assistant") {
2711
3330
  return blocks(p).flatMap((b) => {
2712
3331
  if (b.type === "text")
@@ -2751,6 +3370,7 @@ var amp_default = createAmpRuntime();
2751
3370
  import { promises as fs2 } from "node:fs";
2752
3371
  import { dirname as dirname2 } from "node:path";
2753
3372
  import { spawn } from "node:child_process";
3373
+ import { Readable, Writable } from "node:stream";
2754
3374
  import { Sandbox, SandboxNotFoundError, RateLimitError } from "e2b";
2755
3375
  import { Sandbox as Desktop } from "@e2b/desktop";
2756
3376
  import pRetry from "p-retry";
@@ -2779,6 +3399,26 @@ var SANDBOX_VCPUS = {
2779
3399
  "32vcpu-64gb": 32
2780
3400
  };
2781
3401
  var DEFAULT_SANDBOX_SIZE = "2vcpu-4gb";
3402
+ var E2B_TEMPLATE_SIZES = [
3403
+ "2vcpu-4gb",
3404
+ "4vcpu-8gb"
3405
+ ];
3406
+ function isE2bSupportedSize(size) {
3407
+ return E2B_TEMPLATE_SIZES.includes(size);
3408
+ }
3409
+ function e2bMachineSpec(size) {
3410
+ const cpuCount = SANDBOX_VCPUS[size];
3411
+ return { cpuCount, memoryMB: cpuCount * 2048 };
3412
+ }
3413
+ function e2bBaseTemplate(size) {
3414
+ return `agent-compose-base-${size}`;
3415
+ }
3416
+ function e2bAgentEnvTemplate(size) {
3417
+ return `agent-env-${size}`;
3418
+ }
3419
+ function isPlatformE2bTemplateAlias(id) {
3420
+ return E2B_TEMPLATE_SIZES.some((size) => id === e2bBaseTemplate(size) || id === e2bAgentEnvTemplate(size));
3421
+ }
2782
3422
  function makeSandboxProvider(sb) {
2783
3423
  const commands = sb.commands;
2784
3424
  return {
@@ -2786,18 +3426,33 @@ function makeSandboxProvider(sb) {
2786
3426
  commands: {
2787
3427
  async run(cmd, opts) {
2788
3428
  let stderr = "";
2789
- const result = await commands.run(cmd, {
2790
- ...opts,
3429
+ const { sudo, ...rest } = opts ?? {};
3430
+ const runOpts = {
3431
+ ...rest,
3432
+ ...sudo ? { user: "root" } : {},
2791
3433
  onStderr: (chunk) => {
2792
3434
  stderr += chunk;
2793
3435
  opts?.onStderr?.(chunk);
2794
3436
  }
2795
- });
2796
- return {
2797
- exitCode: result.exitCode,
2798
- stdout: result.stdout,
2799
- stderr: typeof result.stderr === "string" ? result.stderr : stderr
2800
3437
  };
3438
+ try {
3439
+ const result = await commands.run(cmd, runOpts);
3440
+ return {
3441
+ exitCode: result.exitCode,
3442
+ stdout: result.stdout,
3443
+ stderr: typeof result.stderr === "string" ? result.stderr : stderr
3444
+ };
3445
+ } catch (e) {
3446
+ const ce = e;
3447
+ if (typeof ce.exitCode === "number") {
3448
+ return {
3449
+ exitCode: ce.exitCode,
3450
+ stdout: typeof ce.stdout === "string" ? ce.stdout : "",
3451
+ stderr: typeof ce.stderr === "string" ? ce.stderr : stderr
3452
+ };
3453
+ }
3454
+ throw e;
3455
+ }
2801
3456
  }
2802
3457
  },
2803
3458
  files: {
@@ -2805,7 +3460,9 @@ function makeSandboxProvider(sb) {
2805
3460
  await sb.files.write(path, content);
2806
3461
  }
2807
3462
  },
2808
- kill: () => sb.kill()
3463
+ async kill() {
3464
+ await sb.kill();
3465
+ }
2809
3466
  };
2810
3467
  }
2811
3468
  function e2bMaxSandboxMs() {
@@ -2826,12 +3483,66 @@ function withSnapshotRetry(p) {
2826
3483
  const snapshot = p.snapshot.bind(p);
2827
3484
  return { ...p, snapshot: () => withSandboxRetry(snapshot) };
2828
3485
  }
3486
+ function wrapE2bBackgroundProcess(handle) {
3487
+ return {
3488
+ pid: handle.pid,
3489
+ async wait() {
3490
+ try {
3491
+ const r = await handle.wait();
3492
+ return { exitCode: r.exitCode ?? 0, stdout: r.stdout, stderr: r.stderr };
3493
+ } catch (e) {
3494
+ const ce = e;
3495
+ if (typeof ce.exitCode === "number") {
3496
+ return {
3497
+ exitCode: ce.exitCode,
3498
+ stdout: typeof ce.stdout === "string" ? ce.stdout : "",
3499
+ stderr: typeof ce.stderr === "string" ? ce.stderr : ""
3500
+ };
3501
+ }
3502
+ throw e;
3503
+ }
3504
+ },
3505
+ async kill() {
3506
+ await handle.kill();
3507
+ }
3508
+ };
3509
+ }
2829
3510
  function makeE2bSandboxProvider(sb) {
3511
+ const base = makeSandboxProvider(sb);
2830
3512
  return {
2831
- ...makeSandboxProvider(sb),
3513
+ ...base,
3514
+ commands: {
3515
+ ...base.commands,
3516
+ async runBackground(cmd, opts) {
3517
+ const { sudo, onStdout, onStderr, ...rest } = opts ?? {};
3518
+ const handle = await sb.commands.run(cmd, {
3519
+ ...rest,
3520
+ background: true,
3521
+ ...sudo ? { user: "root" } : {},
3522
+ ...onStdout ? { onStdout } : {},
3523
+ ...onStderr ? { onStderr } : {}
3524
+ });
3525
+ return wrapE2bBackgroundProcess(handle);
3526
+ },
3527
+ async connectProcess(pid, opts) {
3528
+ const handle = await sb.commands.connect(pid, {
3529
+ ...opts?.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {},
3530
+ ...opts?.onStdout ? { onStdout: opts.onStdout } : {},
3531
+ ...opts?.onStderr ? { onStderr: opts.onStderr } : {}
3532
+ });
3533
+ return wrapE2bBackgroundProcess(handle);
3534
+ }
3535
+ },
2832
3536
  async snapshot() {
2833
3537
  const { snapshotId } = await sb.createSnapshot();
2834
3538
  return { snapshotId };
3539
+ },
3540
+ async pauseProcess() {
3541
+ await sb.pause();
3542
+ return { resumeHandle: sb.sandboxId };
3543
+ },
3544
+ async updateNetworkPolicy(policy) {
3545
+ await sb.updateNetwork(toE2bNetwork(policy));
2835
3546
  }
2836
3547
  };
2837
3548
  }
@@ -2923,6 +3634,31 @@ function toVercelNetworkPolicy(policy) {
2923
3634
  }
2924
3635
  return { ...policy, allow };
2925
3636
  }
3637
+ function toE2bNetwork(policy) {
3638
+ if (policy === "allow-all")
3639
+ return {};
3640
+ if (policy === "deny-all")
3641
+ return { denyOut: ({ allTraffic }) => [allTraffic] };
3642
+ const allowHosts = Array.isArray(policy.allow) ? [...policy.allow] : Object.keys(policy.allow ?? {});
3643
+ const subnetAllow = policy.subnets?.allow ?? [];
3644
+ const allowOut = [...new Set([...allowHosts, ...subnetAllow])];
3645
+ const network = {
3646
+ denyOut: ({ allTraffic }) => [allTraffic]
3647
+ };
3648
+ if (allowOut.length > 0)
3649
+ network.allowOut = allowOut;
3650
+ if (!Array.isArray(policy.allow) && policy.allow) {
3651
+ const rules = {};
3652
+ for (const [host, hostRules] of Object.entries(policy.allow)) {
3653
+ const hostTransforms = hostRules.flatMap((rule) => (rule.transform ?? []).filter((t) => t.headers && Object.keys(t.headers).length > 0).map((t) => ({ transform: { headers: t.headers } })));
3654
+ if (hostTransforms.length > 0)
3655
+ rules[host] = hostTransforms;
3656
+ }
3657
+ if (Object.keys(rules).length > 0)
3658
+ network.rules = rules;
3659
+ }
3660
+ return network;
3661
+ }
2926
3662
  var VERCEL_MAX_TAGS = 5;
2927
3663
  function buildVercelTags(metadata) {
2928
3664
  const tags = { ...metadata, executor: AGENT_COMPOSE_TAG };
@@ -3031,6 +3767,38 @@ function makeLocalSandboxProvider() {
3031
3767
  proc.on("error", reject);
3032
3768
  proc.on("close", (code) => resolve({ exitCode: code ?? 0, stdout, stderr }));
3033
3769
  });
3770
+ },
3771
+ spawnDuplex(cmd, opts) {
3772
+ const proc = spawn("sh", ["-c", cmd], {
3773
+ ...opts?.cwd ? { cwd: opts.cwd } : {},
3774
+ env: { ...process.env, ...opts?.envs ?? {} },
3775
+ stdio: ["pipe", "pipe", "pipe"],
3776
+ detached: true
3777
+ });
3778
+ let stderr = "";
3779
+ proc.stderr.setEncoding("utf8");
3780
+ proc.stderr.on("data", (chunk) => {
3781
+ stderr += chunk;
3782
+ });
3783
+ const exited = new Promise((resolve, reject) => {
3784
+ proc.on("error", reject);
3785
+ proc.on("close", (code) => resolve({ exitCode: code ?? 0, stderr }));
3786
+ });
3787
+ return {
3788
+ stdin: Writable.toWeb(proc.stdin),
3789
+ stdout: Readable.toWeb(proc.stdout),
3790
+ exited,
3791
+ kill() {
3792
+ if (proc.pid === undefined)
3793
+ return;
3794
+ try {
3795
+ process.kill(-proc.pid, "SIGTERM");
3796
+ } catch (err) {
3797
+ if (err.code !== "ESRCH")
3798
+ throw err;
3799
+ }
3800
+ }
3801
+ };
3034
3802
  }
3035
3803
  },
3036
3804
  files: {
@@ -3098,17 +3866,36 @@ var SANDBOX_PROVIDERS = {
3098
3866
  const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
3099
3867
  const snap = await Snapshot.get({ snapshotId, ...creds });
3100
3868
  await snap.delete();
3869
+ },
3870
+ snapshotExists: async (snapshotId, env) => {
3871
+ const { Snapshot } = await import("@vercel/sandbox");
3872
+ const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
3873
+ try {
3874
+ await Snapshot.get({ snapshotId, ...creds });
3875
+ return true;
3876
+ } catch (err) {
3877
+ const message = err instanceof Error ? err.message : String(err ?? "");
3878
+ const status = err?.status ?? err?.statusCode;
3879
+ if (status === 404 || /\b404\b|not[\s_-]?found|no such snapshot/i.test(message))
3880
+ return false;
3881
+ throw err;
3882
+ }
3101
3883
  }
3102
3884
  },
3103
3885
  e2b: {
3104
3886
  requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
3105
- create: async ({ template, timeoutMs, networkPolicy: _np, size: _size, ...rest }) => {
3887
+ create: async ({ template, timeoutMs, networkPolicy, size, ...rest }) => {
3106
3888
  const maxMs = e2bMaxSandboxMs();
3107
3889
  if (timeoutMs > maxMs) {
3108
3890
  console.warn(`[sandbox] e2b create timeout clamped: requested ${timeoutMs}ms exceeds the plan cap ${maxMs}ms — ` + `the sandbox dies at the cap, not the requested deadline. Raise E2B_MAX_SANDBOX_MS on plans that allow more.`);
3109
3891
  }
3110
- const sandboxOpts = { ...rest, timeoutMs: Math.min(timeoutMs, maxMs) };
3111
- const tmpl = template ?? process.env.E2B_DEFAULT_TEMPLATE;
3892
+ const sandboxOpts = {
3893
+ ...rest,
3894
+ timeoutMs: Math.min(timeoutMs, maxMs),
3895
+ ...networkPolicy ? { network: toE2bNetwork(networkPolicy) } : {}
3896
+ };
3897
+ const resolvedSize = size ?? DEFAULT_SANDBOX_SIZE;
3898
+ const tmpl = template ?? (isE2bSupportedSize(resolvedSize) ? e2bBaseTemplate(resolvedSize) : undefined) ?? process.env.E2B_DEFAULT_TEMPLATE;
3112
3899
  return makeE2bSandboxProvider(await (tmpl ? Sandbox.create(tmpl, sandboxOpts) : Sandbox.create(sandboxOpts)));
3113
3900
  },
3114
3901
  reconnect: async (sandboxId) => makeE2bSandboxProvider(await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY ?? "", timeoutMs: 3600000 })),
@@ -3144,10 +3931,16 @@ var SANDBOX_PROVIDERS = {
3144
3931
  },
3145
3932
  "e2b-desktop": {
3146
3933
  requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
3147
- create: async ({ template, timeoutMs, networkPolicy: _np, size: _size, ...rest }) => {
3934
+ create: async ({ template, timeoutMs, networkPolicy, size: _size, ...rest }) => {
3148
3935
  if (!template)
3149
3936
  throw new Error("E2B Desktop provider requires an explicit `template` (Dockerfile-based — no default base image)");
3150
- return makeDesktopSandboxProvider(await Desktop.create(template, { ...rest, timeoutMs: Math.min(timeoutMs, e2bMaxSandboxMs()) }));
3937
+ if (networkPolicy && networkPolicy !== "allow-all") {
3938
+ 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.");
3939
+ }
3940
+ return makeDesktopSandboxProvider(await Desktop.create(template, {
3941
+ ...rest,
3942
+ timeoutMs: Math.min(timeoutMs, e2bMaxSandboxMs())
3943
+ }));
3151
3944
  }
3152
3945
  }
3153
3946
  };
@@ -3180,6 +3973,17 @@ ${missing.join(`
3180
3973
  `)}`);
3181
3974
  return def.deleteSnapshot(snapshotId, Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k]])));
3182
3975
  }
3976
+ async function snapshotResolves(provider, snapshotId) {
3977
+ const def = SANDBOX_PROVIDERS[provider];
3978
+ if (!def?.snapshotExists)
3979
+ throw new Error(`Provider "${provider}" does not support snapshotExists`);
3980
+ const missing = Object.entries(def.requiredEnv).filter(([k]) => !process.env[k]).map(([k, desc]) => ` ${k} — ${desc}`);
3981
+ if (missing.length > 0)
3982
+ throw new Error(`Sandbox provider "${provider}" requires env vars:
3983
+ ${missing.join(`
3984
+ `)}`);
3985
+ return def.snapshotExists(snapshotId, Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k]])));
3986
+ }
3183
3987
  async function getSandboxQuotas() {
3184
3988
  const out = {};
3185
3989
  await Promise.all(Object.entries(SANDBOX_PROVIDERS).map(async ([name, def]) => {
@@ -3235,17 +4039,34 @@ class StepObservabilityCollector {
3235
4039
  events = [];
3236
4040
  subSteps = [];
3237
4041
  liveEmitter;
4042
+ memoize;
4043
+ usedStepNames = new Set;
3238
4044
  ackedSeqs = new Set;
3239
4045
  inFlight = new Set;
3240
4046
  constructor(opts = {}) {
3241
4047
  this.liveEmitter = opts.liveEmitter;
4048
+ this.memoize = opts.memoize;
3242
4049
  }
3243
4050
  setMetadata = async (data) => {
3244
4051
  Object.assign(this.metadata, data);
3245
4052
  };
3246
4053
  step = async (name, fn) => {
4054
+ if (this.usedStepNames.has(name)) {
4055
+ 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.`);
4056
+ }
4057
+ this.usedStepNames.add(name);
3247
4058
  const startedAt = Date.now();
3248
4059
  try {
4060
+ if (this.memoize) {
4061
+ const { value, restored } = await this.memoize(name, fn);
4062
+ this.subSteps.push({
4063
+ name,
4064
+ startedAt,
4065
+ durationMs: restored ? 0 : Date.now() - startedAt,
4066
+ status: restored ? "restored" : "completed"
4067
+ });
4068
+ return value;
4069
+ }
3249
4070
  const result = await fn();
3250
4071
  this.subSteps.push({
3251
4072
  name,
@@ -3309,19 +4130,19 @@ class StepObservabilityCollector {
3309
4130
  }
3310
4131
 
3311
4132
  // src/pause/checkpoint.ts
3312
- async function checkpoint(name, fn) {
4133
+ async function memoize(name, fn) {
3313
4134
  const existing = await readCheckpoint(name);
3314
4135
  if (existing.found)
3315
- return existing.value;
4136
+ return { value: existing.value, restored: true };
3316
4137
  const value = await fn();
3317
4138
  await writeCheckpoint(name, value);
3318
- return value;
4139
+ return { value, restored: false };
3319
4140
  }
3320
- function scopedCheckpoint(scope) {
4141
+ function scopedMemoize(scope) {
3321
4142
  assertSafeCheckpointName(scope);
3322
4143
  return async (name, fn) => {
3323
4144
  assertSafeCheckpointName(name);
3324
- return checkpoint(`${scope}.${name}`, fn);
4145
+ return memoize(`${scope}.${name}`, fn);
3325
4146
  };
3326
4147
  }
3327
4148
 
@@ -3389,7 +4210,10 @@ async function runWorkflowSingleStep(opts) {
3389
4210
  const parsedInput = step.input.safeParse(opts.input);
3390
4211
  if (!parsedInput.success)
3391
4212
  throw new StepValidationError(step.name, "input", parsedInput.error);
3392
- const collector = new StepObservabilityCollector({ liveEmitter: opts.liveAgentEventEmitter });
4213
+ const collector = new StepObservabilityCollector({
4214
+ liveEmitter: opts.liveAgentEventEmitter,
4215
+ memoize: scopedMemoize(`step${opts.stepIndex}`)
4216
+ });
3393
4217
  const coord = { runId: opts.run.id, stepIndex: opts.stepIndex };
3394
4218
  const pauseWithKind = function(req, kind) {
3395
4219
  return corePause(req, coord, kind);
@@ -3410,7 +4234,6 @@ async function runWorkflowSingleStep(opts) {
3410
4234
  setMetadata: collector.setMetadata,
3411
4235
  step: collector.step,
3412
4236
  agentEvents: collector.agentEvents,
3413
- checkpoint: scopedCheckpoint(`step${opts.stepIndex}`),
3414
4237
  pause,
3415
4238
  ...buildPauseWrappers(pauseWithKind)
3416
4239
  };
@@ -3466,7 +4289,10 @@ async function runWorkflowSteps(opts) {
3466
4289
  await opts.onStepFailed?.(i, step.name, err, 0);
3467
4290
  throw err;
3468
4291
  }
3469
- const collector = new StepObservabilityCollector({ liveEmitter: opts.liveAgentEventEmitter });
4292
+ const collector = new StepObservabilityCollector({
4293
+ liveEmitter: opts.liveAgentEventEmitter,
4294
+ memoize: scopedMemoize(`step${i}`)
4295
+ });
3470
4296
  const coord = { runId: run.id, stepIndex: i };
3471
4297
  const pauseWithKind = function(req, kind) {
3472
4298
  return corePause(req, coord, kind);
@@ -3485,7 +4311,6 @@ async function runWorkflowSteps(opts) {
3485
4311
  setMetadata: collector.setMetadata,
3486
4312
  step: collector.step,
3487
4313
  agentEvents: collector.agentEvents,
3488
- checkpoint: scopedCheckpoint(`step${i}`),
3489
4314
  pause,
3490
4315
  ...buildPauseWrappers(pauseWithKind)
3491
4316
  };
@@ -3682,32 +4507,32 @@ function stepResultFilePath(token) {
3682
4507
  }
3683
4508
  // src/step-invocation/invoker.ts
3684
4509
  import { randomBytes as randomBytes2 } from "node:crypto";
3685
- import { z as z10 } from "zod";
3686
- var StepObservabilitySchema = z10.object({
3687
- metadata: z10.record(z10.string(), z10.unknown()).optional(),
3688
- events: z10.array(z10.unknown()).optional(),
3689
- subSteps: z10.array(z10.object({
3690
- name: z10.string(),
3691
- startedAt: z10.number(),
3692
- durationMs: z10.number(),
3693
- status: z10.enum(["completed", "failed"]),
3694
- error: z10.string().optional()
4510
+ import { z as z11 } from "zod";
4511
+ var StepObservabilitySchema = z11.object({
4512
+ metadata: z11.record(z11.string(), z11.unknown()).optional(),
4513
+ events: z11.array(z11.unknown()).optional(),
4514
+ subSteps: z11.array(z11.object({
4515
+ name: z11.string(),
4516
+ startedAt: z11.number(),
4517
+ durationMs: z11.number(),
4518
+ status: z11.enum(["completed", "failed"]),
4519
+ error: z11.string().optional()
3695
4520
  })).optional()
3696
4521
  }).passthrough();
3697
- var StepResultEnvelopeSchema = z10.discriminatedUnion("ok", [
3698
- z10.object({
3699
- ok: z10.literal(true),
3700
- output: z10.unknown().optional(),
4522
+ var StepResultEnvelopeSchema = z11.discriminatedUnion("ok", [
4523
+ z11.object({
4524
+ ok: z11.literal(true),
4525
+ output: z11.unknown().optional(),
3701
4526
  observability: StepObservabilitySchema.optional()
3702
4527
  }),
3703
- z10.object({
3704
- ok: z10.literal(false),
3705
- kind: z10.enum(["protocol", "user-step"]),
3706
- error: z10.string().optional()
4528
+ z11.object({
4529
+ ok: z11.literal(false),
4530
+ kind: z11.enum(["protocol", "user-step"]),
4531
+ error: z11.string().optional()
3707
4532
  })
3708
4533
  ]);
3709
- var StepPauseEnvelopeSchema = z10.object({
3710
- pauseId: z10.string().min(1),
4534
+ var StepPauseEnvelopeSchema = z11.object({
4535
+ pauseId: z11.string().min(1),
3711
4536
  pauseRequest: StepPauseRequestSchema,
3712
4537
  observability: StepObservabilitySchema.optional()
3713
4538
  });
@@ -3786,25 +4611,11 @@ function parseStepResult(stdout, resultToken) {
3786
4611
  const message = baseMessage;
3787
4612
  return { ok: false, error: { kind, message } };
3788
4613
  }
3789
- async function invokeStep(sandbox, request, opts) {
3790
- const resultToken = randomBytes2(16).toString("hex");
3791
- await Promise.all([
3792
- sandbox.files.write(stepInputPath(request.stepIndex), JSON.stringify(request.input)),
3793
- sandbox.files.write(requestContextPath(request.stepIndex), JSON.stringify(request.requestContext))
3794
- ]);
3795
- const envs = {
3796
- ...opts?.envs ?? {},
3797
- ...buildStepEnvs({
3798
- runId: request.runId,
3799
- stepIndex: request.stepIndex,
3800
- resultToken,
3801
- isResume: opts?.isResume
3802
- })
3803
- };
4614
+ function makeStreamSplitters(opts, resultToken) {
3804
4615
  const resultSentinel = stepResultLinePrefix(resultToken);
3805
4616
  const pauseSentinel = stepPauseLinePrefix(resultToken);
3806
4617
  const isSentinel = (line) => line.startsWith(resultSentinel) || line.startsWith(pauseSentinel);
3807
- const makeLineSplitter = (sink, filterSentinel) => {
4618
+ const make = (sink, filterSentinel) => {
3808
4619
  if (!sink)
3809
4620
  return { onChunk: undefined, flush: () => {} };
3810
4621
  let buf = "";
@@ -3832,29 +4643,19 @@ async function invokeStep(sandbox, request, opts) {
3832
4643
  }
3833
4644
  };
3834
4645
  };
3835
- const stdoutSplitter = makeLineSplitter(opts?.onStdout, true);
3836
- const stderrSplitter = makeLineSplitter(opts?.onStderr, false);
3837
- let result;
3838
- try {
3839
- result = await sandbox.commands.run(RUNNER_COMMAND, {
3840
- envs,
3841
- timeoutMs: 0,
3842
- ...stdoutSplitter.onChunk ? { onStdout: stdoutSplitter.onChunk } : {},
3843
- ...stderrSplitter.onChunk ? { onStderr: stderrSplitter.onChunk } : {}
3844
- });
3845
- } catch (e) {
3846
- if (e instanceof SandboxUnavailableError)
3847
- throw e;
3848
- const ce = e;
3849
- result = {
3850
- stdout: ce.stdout ?? ce.result?.stdout ?? "",
3851
- stderr: ce.stderr ?? ce.result?.stderr ?? (e instanceof Error ? e.message : String(e)),
3852
- exitCode: ce.exitCode ?? ce.result?.exitCode ?? 1
3853
- };
3854
- }
3855
- stdoutSplitter.flush();
3856
- stderrSplitter.flush();
3857
- const parsed = parseStepResult(result.stdout, resultToken);
4646
+ const stdout = make(opts?.onStdout, true);
4647
+ const stderr = make(opts?.onStderr, false);
4648
+ return {
4649
+ ...stdout.onChunk ? { onStdout: stdout.onChunk } : {},
4650
+ ...stderr.onChunk ? { onStderr: stderr.onChunk } : {},
4651
+ flush: () => {
4652
+ stdout.flush();
4653
+ stderr.flush();
4654
+ }
4655
+ };
4656
+ }
4657
+ async function classifyRunnerOutcome(sandbox, output, resultToken) {
4658
+ const parsed = parseStepResult(output.stdout, resultToken);
3858
4659
  if (parsed)
3859
4660
  return parsed;
3860
4661
  const fileRead = await sandbox.commands.run(`cat ${stepResultFilePath(resultToken)} 2>/dev/null || true`).catch(() => null);
@@ -3863,9 +4664,9 @@ async function invokeStep(sandbox, request, opts) {
3863
4664
  if (fromFile)
3864
4665
  return fromFile;
3865
4666
  }
3866
- if (result.exitCode !== 0) {
3867
- const stderrTail = result.stderr.trim().slice(-2000);
3868
- const stdoutTail = result.stdout.trim().slice(-2000);
4667
+ if (output.exitCode !== 0) {
4668
+ const stderrTail = output.stderr.trim().slice(-2000);
4669
+ const stdoutTail = output.stdout.trim().slice(-2000);
3869
4670
  const details = [
3870
4671
  stderrTail ? `stderr:
3871
4672
  ${stderrTail}` : "",
@@ -3877,13 +4678,13 @@ ${stdoutTail}` : ""
3877
4678
  ok: false,
3878
4679
  error: {
3879
4680
  kind: "runner-exit",
3880
- message: `runner subprocess exited ${result.exitCode} before emitting a step result${details ? `
4681
+ message: `runner subprocess exited ${output.exitCode} before emitting a step result${details ? `
3881
4682
  ${details}` : ""}`,
3882
- exitCode: result.exitCode
4683
+ exitCode: output.exitCode
3883
4684
  }
3884
4685
  };
3885
4686
  }
3886
- const tail = result.stdout.trim().slice(-500);
4687
+ const tail = output.stdout.trim().slice(-500);
3887
4688
  return {
3888
4689
  ok: false,
3889
4690
  error: {
@@ -3894,6 +4695,99 @@ ${tail}` : ""}`
3894
4695
  }
3895
4696
  };
3896
4697
  }
4698
+ async function prepareStepLaunch(sandbox, request, opts) {
4699
+ const resultToken = randomBytes2(16).toString("hex");
4700
+ await Promise.all([
4701
+ sandbox.files.write(stepInputPath(request.stepIndex), JSON.stringify(request.input)),
4702
+ sandbox.files.write(requestContextPath(request.stepIndex), JSON.stringify(request.requestContext))
4703
+ ]);
4704
+ const envs = {
4705
+ ...opts?.envs ?? {},
4706
+ ...buildStepEnvs({ runId: request.runId, stepIndex: request.stepIndex, resultToken, isResume: opts?.isResume })
4707
+ };
4708
+ return { resultToken, envs };
4709
+ }
4710
+ async function invokeStep(sandbox, request, opts) {
4711
+ const { resultToken, envs } = await prepareStepLaunch(sandbox, request, opts);
4712
+ const splitters = makeStreamSplitters(opts, resultToken);
4713
+ let result;
4714
+ try {
4715
+ result = await sandbox.commands.run(RUNNER_COMMAND, {
4716
+ envs,
4717
+ timeoutMs: 0,
4718
+ ...splitters.onStdout ? { onStdout: splitters.onStdout } : {},
4719
+ ...splitters.onStderr ? { onStderr: splitters.onStderr } : {}
4720
+ });
4721
+ } catch (e) {
4722
+ if (e instanceof SandboxUnavailableError)
4723
+ throw e;
4724
+ const ce = e;
4725
+ result = {
4726
+ stdout: ce.stdout ?? ce.result?.stdout ?? "",
4727
+ stderr: ce.stderr ?? ce.result?.stderr ?? (e instanceof Error ? e.message : String(e)),
4728
+ exitCode: ce.exitCode ?? ce.result?.exitCode ?? 1
4729
+ };
4730
+ }
4731
+ splitters.flush();
4732
+ return classifyRunnerOutcome(sandbox, result, resultToken);
4733
+ }
4734
+ async function launchStep(sandbox, request, opts) {
4735
+ if (!sandbox.commands.runBackground) {
4736
+ throw new Error("launchStep requires a provider with background-command support (commands.runBackground)");
4737
+ }
4738
+ const { resultToken, envs } = await prepareStepLaunch(sandbox, request, opts);
4739
+ const splitters = makeStreamSplitters(opts, resultToken);
4740
+ const proc = await sandbox.commands.runBackground(RUNNER_COMMAND, {
4741
+ envs,
4742
+ timeoutMs: 0,
4743
+ ...splitters.onStdout ? { onStdout: splitters.onStdout } : {},
4744
+ ...splitters.onStderr ? { onStderr: splitters.onStderr } : {}
4745
+ });
4746
+ return {
4747
+ runnerPid: proc.pid,
4748
+ resultToken,
4749
+ async wait() {
4750
+ const result = await proc.wait();
4751
+ splitters.flush();
4752
+ return classifyRunnerOutcome(sandbox, result, resultToken);
4753
+ }
4754
+ };
4755
+ }
4756
+ async function reconnectStep(sandbox, resume, opts) {
4757
+ if (!sandbox.commands.connectProcess) {
4758
+ throw new Error("reconnectStep requires a provider with background-command support (commands.connectProcess)");
4759
+ }
4760
+ const { runnerPid, resultToken } = resume;
4761
+ const splitters = makeStreamSplitters(opts, resultToken);
4762
+ let proc = null;
4763
+ try {
4764
+ proc = await sandbox.commands.connectProcess(runnerPid, {
4765
+ ...splitters.onStdout ? { onStdout: splitters.onStdout } : {},
4766
+ ...splitters.onStderr ? { onStderr: splitters.onStderr } : {}
4767
+ });
4768
+ } catch (e) {
4769
+ if (e instanceof SandboxUnavailableError)
4770
+ throw e;
4771
+ }
4772
+ const attached = proc;
4773
+ return {
4774
+ runnerPid,
4775
+ resultToken,
4776
+ async wait() {
4777
+ let output = { stdout: "", stderr: "", exitCode: 0 };
4778
+ if (attached) {
4779
+ try {
4780
+ output = await attached.wait();
4781
+ } catch (e) {
4782
+ if (e instanceof SandboxUnavailableError)
4783
+ throw e;
4784
+ }
4785
+ }
4786
+ splitters.flush();
4787
+ return classifyRunnerOutcome(sandbox, output, resultToken);
4788
+ }
4789
+ };
4790
+ }
3897
4791
  // src/step-invocation/server.ts
3898
4792
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3899
4793
  function writeSentinelLine(line) {
@@ -4001,7 +4895,7 @@ async function serveStep(handler) {
4001
4895
  process.exit(exitCode);
4002
4896
  }
4003
4897
  // src/agent/run-agent.ts
4004
- import { z as z11 } from "zod";
4898
+ import { z as z12 } from "zod";
4005
4899
  import { randomUUID as randomUUID2 } from "node:crypto";
4006
4900
 
4007
4901
  // src/agent/agent-context.ts
@@ -4065,11 +4959,19 @@ Use \`/ac:generate-workflow\` / \`/ac:generate-agent\` to scaffold, then
4065
4959
 
4066
4960
  ## Pausing to ask the human — \`agentc pause\`
4067
4961
 
4068
- When you can't or shouldn't proceed without a human, run \`agentc pause\`. It
4069
- blocks until they answer on the dashboard, then prints their answer to stdout:
4962
+ When you can't or shouldn't proceed without a human, run \`agentc pause\`, then
4963
+ **END YOUR TURN**:
4964
+
4965
+ agentc pause --reason "Notion returned 401 — connect Notion to continue" \\
4966
+ --option retry --option skip
4070
4967
 
4071
- ANSWER=$(agentc pause --reason "Notion returned 401 — connect Notion to continue" \\
4072
- --option retry --option skip)
4968
+ \`agentc pause\` does NOT block and does NOT print the answer. It records your
4969
+ question and returns immediately. The moment you end your turn, the run pauses
4970
+ (your sandbox is snapshotted and compute stops while the human decides) and the
4971
+ human's answer is delivered to you as your **next message** — you pick up
4972
+ exactly where you left off, with the answer in hand. So: ask, end your turn,
4973
+ and wait. Do NOT keep working, do NOT call more tools, and do NOT mark the task
4974
+ complete after pausing.
4073
4975
 
4074
4976
  Reach for it the moment you hit — or foresee — any of these:
4075
4977
  - **A wall only a human can clear:** a 401/403, a missing credential, an
@@ -4083,8 +4985,8 @@ Reach for it the moment you hit — or foresee — any of these:
4083
4985
  several valid paths, a conflict with existing state, missing input only they have.
4084
4986
 
4085
4987
  You compose the \`--reason\` (the ask) yourself; pass \`--option\` choices when
4086
- there are clear ones, omit them for a free-form answer. Read the printed answer
4087
- and act on it. Each agent pauses independently — pausing doesn't stop the others.
4988
+ there are clear ones, omit them for a free-form answer. Each agent pauses
4989
+ independently — pausing doesn't stop the others.
4088
4990
 
4089
4991
  ## Credentials
4090
4992
 
@@ -4206,29 +5108,29 @@ function stripFrontmatter(content) {
4206
5108
  return end === -1 ? content : content.slice(end + 4).trimStart();
4207
5109
  }
4208
5110
  function zodTypeName(schema) {
4209
- if (schema instanceof z11.ZodString)
5111
+ if (schema instanceof z12.ZodString)
4210
5112
  return "string";
4211
- if (schema instanceof z11.ZodNumber)
5113
+ if (schema instanceof z12.ZodNumber)
4212
5114
  return "number";
4213
- if (schema instanceof z11.ZodBoolean)
5115
+ if (schema instanceof z12.ZodBoolean)
4214
5116
  return "boolean";
4215
- if (schema instanceof z11.ZodArray)
5117
+ if (schema instanceof z12.ZodArray)
4216
5118
  return `${zodTypeName(schema.element)}[]`;
4217
- if (schema instanceof z11.ZodEnum)
5119
+ if (schema instanceof z12.ZodEnum)
4218
5120
  return schema.options.map((o) => `"${o}"`).join(" | ");
4219
- if (schema instanceof z11.ZodOptional)
5121
+ if (schema instanceof z12.ZodOptional)
4220
5122
  return `${zodTypeName(schema.unwrap())} (optional)`;
4221
- if (schema instanceof z11.ZodNullable)
5123
+ if (schema instanceof z12.ZodNullable)
4222
5124
  return `${zodTypeName(schema.unwrap())} | null`;
4223
- if (schema instanceof z11.ZodLiteral)
5125
+ if (schema instanceof z12.ZodLiteral)
4224
5126
  return `"${String(schema.value)}"`;
4225
- if (schema instanceof z11.ZodObject)
5127
+ if (schema instanceof z12.ZodObject)
4226
5128
  return "object";
4227
5129
  return "any";
4228
5130
  }
4229
5131
  function buildResponseFormatAppendix(schema) {
4230
5132
  const header = ["## Response Format", "", "When you emit `exit_signal: true` in your `<status>` block, also include a `<response>` block containing JSON immediately after `<status>`:", ""];
4231
- const shape = schema instanceof z11.ZodObject ? schema.shape : null;
5133
+ const shape = schema instanceof z12.ZodObject ? schema.shape : null;
4232
5134
  const body = shape ? (() => {
4233
5135
  const entries = Object.entries(shape);
4234
5136
  const example = Object.fromEntries(entries.map(([k, v]) => [k, `<${zodTypeName(v)}>`]));
@@ -4320,7 +5222,7 @@ async function agent(opts) {
4320
5222
  const mode = opts.mode ?? "auto";
4321
5223
  try {
4322
5224
  return await agentLoop({
4323
- runtime: (runtimeOpts) => opts.runtime.create(opts.sandbox, runtimeOpts),
5225
+ runtime: (runtimeOpts) => opts.runtime.create(opts.sandbox, { ...runtimeOpts, agentManual: buildAgentContextDoc(process.env) }),
4324
5226
  agentId,
4325
5227
  ...opts.label !== undefined ? { label: opts.label } : {},
4326
5228
  ...opts.budget?.turnsPerIteration !== undefined ? { turnsPerIteration: opts.budget.turnsPerIteration } : {},