@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.
- package/dist/agent/agent-context.d.ts +1 -1
- package/dist/agent/agent-loop.d.ts +14 -12
- package/dist/agent/pause-client.d.ts +50 -0
- package/dist/agent/pause-client.test.d.ts +1 -0
- package/dist/agent/steer-control.d.ts +22 -6
- package/dist/client.d.ts +12 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +2379 -1463
- package/dist/pause/checkpoint.d.ts +27 -10
- package/dist/pause/manager.d.ts +1 -0
- package/dist/pause/pause-core.d.ts +23 -0
- package/dist/pause/state-dir.d.ts +1 -1
- package/dist/processors/builtins.d.ts +20 -1
- package/dist/processors/gate-pause.d.ts +46 -0
- package/dist/processors/gate-pause.test.d.ts +1 -0
- package/dist/processors/index.d.ts +3 -1
- package/dist/processors/processor.d.ts +13 -0
- package/dist/runtimes/_acp-client.d.ts +140 -0
- package/dist/runtimes/_cli-agent.d.ts +155 -3
- package/dist/runtimes/amp.d.ts +2 -2
- package/dist/runtimes/cli-agent-acp-live.test.d.ts +30 -0
- package/dist/runtimes/cli-agent.test.d.ts +22 -6
- package/dist/runtimes/codex.d.ts +7 -2
- package/dist/runtimes/openai-desktop.js +2365 -1463
- package/dist/runtimes/vercel.js +389 -2
- package/dist/sandbox.d.ts +113 -19
- package/dist/step-invocation/__tests__/background-invoker.test.d.ts +1 -0
- package/dist/step-invocation/index.d.ts +2 -1
- package/dist/step-invocation/invoker.d.ts +36 -0
- package/dist/types/__tests__/environment-build-flag.test.d.ts +1 -0
- package/dist/types/__tests__/workflow-metadata-provider.test.d.ts +1 -0
- package/dist/types/execution-context.d.ts +0 -8
- package/dist/types/protocol.d.ts +32 -1
- package/dist/types/runtime.d.ts +14 -0
- package/dist/types/sandbox-environment.d.ts +6 -1
- package/dist/types/sandbox.d.ts +86 -6
- package/dist/types/workflow-metadata.d.ts +40 -10
- package/dist/types/workflow.d.ts +22 -6
- package/dist/utils/bundler.d.ts +5 -1
- package/dist/workflow-steps/observability.d.ts +28 -2
- package/dist/workflow-steps/types.d.ts +11 -7
- package/dist/workflow-steps/workflow.d.ts +3 -2
- package/package.json +3 -2
- package/src/agent/agent-context.ts +14 -6
- package/src/agent/agent-loop.ts +32 -10
- package/src/agent/pause-client.ts +108 -0
- package/src/agent/run-agent.ts +9 -4
- package/src/agent/steer-control.ts +21 -7
- package/src/client.ts +35 -1
- package/src/index.ts +20 -2
- package/src/pause/checkpoint.ts +33 -14
- package/src/pause/manager.ts +2 -2
- package/src/pause/pause-core.ts +35 -0
- package/src/pause/state-dir.ts +2 -2
- package/src/processors/builtins.ts +44 -1
- package/src/processors/gate-pause.ts +94 -0
- package/src/processors/index.ts +7 -0
- package/src/processors/processor.ts +13 -0
- package/src/runtimes/_acp-client.ts +516 -0
- package/src/runtimes/_cli-agent.ts +416 -3
- package/src/runtimes/claude.ts +31 -3
- package/src/runtimes/codex.ts +21 -1
- package/src/runtimes/vercel.ts +4 -1
- package/src/sandbox.ts +426 -56
- package/src/step-invocation/index.ts +2 -1
- package/src/step-invocation/invoker.ts +195 -84
- package/src/types/execution-context.ts +0 -8
- package/src/types/protocol.ts +27 -1
- package/src/types/runtime.ts +14 -0
- package/src/types/sandbox-environment.ts +12 -1
- package/src/types/sandbox.ts +84 -6
- package/src/types/workflow-metadata.ts +42 -10
- package/src/types/workflow.ts +22 -7
- package/src/utils/bundler.ts +6 -1
- package/src/workflow-steps/observability.ts +51 -5
- package/src/workflow-steps/runner.ts +9 -5
- package/src/workflow-steps/types.ts +11 -7
- package/src/workflow-steps/workflow.ts +3 -2
package/dist/index.js
CHANGED
|
@@ -330,1485 +330,1629 @@ class RequestContext {
|
|
|
330
330
|
}
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
// src/
|
|
334
|
-
|
|
335
|
-
|
|
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/
|
|
339
|
-
|
|
340
|
-
|
|
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
|
|
343
|
-
return
|
|
367
|
+
function makeState(step) {
|
|
368
|
+
return {
|
|
369
|
+
stepIndex: step.stepIndex,
|
|
370
|
+
nextAgentCallIndex: 0,
|
|
371
|
+
pauseOrdinalByScope: new Map
|
|
372
|
+
};
|
|
344
373
|
}
|
|
345
|
-
function
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
409
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
416
|
-
|
|
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
|
-
|
|
420
|
-
|
|
514
|
+
return {
|
|
515
|
+
pauseId: expectedPauseId,
|
|
516
|
+
request: hasOwn(record, "request") ? record.request : null,
|
|
517
|
+
...record
|
|
518
|
+
};
|
|
421
519
|
}
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
|
514
|
-
|
|
515
|
-
|
|
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
|
|
519
|
-
|
|
520
|
-
|
|
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/
|
|
526
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
564
|
-
|
|
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/
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
|
599
|
-
return typeof
|
|
590
|
+
function isPauseSignal(err) {
|
|
591
|
+
return typeof err === "object" && err !== null && err[PAUSE_SIGNAL_BRAND] === true;
|
|
600
592
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
-
|
|
754
|
-
reader.releaseLock();
|
|
713
|
+
return parsed.data;
|
|
755
714
|
}
|
|
715
|
+
return payload;
|
|
756
716
|
}
|
|
757
717
|
|
|
758
|
-
// src/
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
|
|
766
|
-
|
|
767
|
-
|
|
722
|
+
|
|
723
|
+
// src/runtimes/vercel.ts
|
|
724
|
+
function now() {
|
|
725
|
+
return new Date().toISOString();
|
|
768
726
|
}
|
|
769
|
-
|
|
770
|
-
|
|
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
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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
|
-
|
|
852
|
-
|
|
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
|
|
855
|
-
const
|
|
856
|
-
|
|
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
|
-
|
|
859
|
-
return
|
|
793
|
+
captureCheckpoint() {
|
|
794
|
+
return { messages: [...this.messages] };
|
|
860
795
|
}
|
|
861
|
-
|
|
862
|
-
|
|
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
|
-
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
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
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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
|
-
|
|
893
|
-
|
|
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
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
-
|
|
915
|
-
|
|
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
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
if (opts
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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
|
-
|
|
977
|
-
const body =
|
|
978
|
-
return body
|
|
1301
|
+
register(payload) {
|
|
1302
|
+
const { factorySlug = DEFAULT_FACTORY, ...body } = payload;
|
|
1303
|
+
return this.fetch(templatePath(factorySlug), { method: "POST", body });
|
|
979
1304
|
}
|
|
980
|
-
|
|
981
|
-
const
|
|
982
|
-
const
|
|
983
|
-
const
|
|
984
|
-
|
|
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
|
-
|
|
987
|
-
|
|
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
|
|
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)}/
|
|
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
|
-
|
|
1028
|
-
return this.fetch(`/api/v1/
|
|
1354
|
+
deleteSnapshot(runId) {
|
|
1355
|
+
return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshot`, { method: "DELETE" });
|
|
1029
1356
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1357
|
+
async listRunSnapshots(runId) {
|
|
1358
|
+
const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshots`);
|
|
1359
|
+
return body.data;
|
|
1032
1360
|
}
|
|
1033
|
-
|
|
1034
|
-
return this.fetch(`/api/v1/
|
|
1361
|
+
deleteRunSnapshot(runId, snapshotId) {
|
|
1362
|
+
return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/snapshots/${encodeURIComponent(snapshotId)}`, { method: "DELETE" });
|
|
1035
1363
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
return body.schedules;
|
|
1364
|
+
getStatus(runId) {
|
|
1365
|
+
return this.fetch(`/api/v1/workflows/${runId}/status`);
|
|
1039
1366
|
}
|
|
1040
|
-
|
|
1041
|
-
const
|
|
1042
|
-
|
|
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: {
|
|
1373
|
+
body: { resumePayload: payload },
|
|
1374
|
+
...Object.keys(headers).length ? { headers } : {},
|
|
1375
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
1045
1376
|
});
|
|
1046
1377
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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: {
|
|
1384
|
+
body: { resumePayload: payload },
|
|
1385
|
+
...Object.keys(headers).length ? { headers } : {},
|
|
1386
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
1055
1387
|
});
|
|
1056
1388
|
}
|
|
1057
|
-
async
|
|
1058
|
-
|
|
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
|
-
|
|
1078
|
-
return this.
|
|
1392
|
+
async answerSteer(runId, pauseId, message, opts) {
|
|
1393
|
+
return this.resumePause(runId, pauseId, steerAnswerPayload(message, opts?.actor), opts);
|
|
1079
1394
|
}
|
|
1080
|
-
async
|
|
1081
|
-
const
|
|
1082
|
-
if (opts?.
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
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
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
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
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
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
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
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
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
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
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
-
|
|
1345
|
-
|
|
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
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
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
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
if (
|
|
1373
|
-
|
|
1374
|
-
|
|
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
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
if (
|
|
1383
|
-
|
|
1384
|
-
|
|
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
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
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
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1523
|
+
async listFactories() {
|
|
1524
|
+
const body = await this.fetch("/api/v1/factories");
|
|
1525
|
+
return body.factories;
|
|
1421
1526
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
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
|
-
|
|
1436
|
-
|
|
1530
|
+
getFactory(slug) {
|
|
1531
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`);
|
|
1437
1532
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1533
|
+
updateFactory(slug, updates) {
|
|
1534
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "PATCH", body: updates });
|
|
1440
1535
|
}
|
|
1441
|
-
|
|
1442
|
-
|
|
1536
|
+
deleteFactory(slug) {
|
|
1537
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "DELETE" });
|
|
1443
1538
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1539
|
+
async listSchedules(factorySlug = "default") {
|
|
1540
|
+
const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/schedules`);
|
|
1541
|
+
return body.schedules;
|
|
1446
1542
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
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
|
-
|
|
1510
|
-
|
|
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
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
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
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
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
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
this.
|
|
1585
|
-
|
|
1586
|
-
|
|
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
|
-
|
|
1591
|
-
import {
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
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
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
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/
|
|
1643
|
-
|
|
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 =
|
|
1650
|
+
this.name = "WorkflowSourceValidationError";
|
|
1647
1651
|
}
|
|
1648
1652
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
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
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
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
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
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
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
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
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
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
|
|
1706
|
-
return
|
|
1731
|
+
function sha256(text) {
|
|
1732
|
+
return createHash2("sha256").update(text, "utf8").digest("hex");
|
|
1707
1733
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
const
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
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
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
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
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
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
|
-
|
|
1729
|
-
return
|
|
1730
|
-
|
|
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
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
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
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
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
|
-
|
|
1791
|
-
const
|
|
1792
|
-
if (
|
|
1793
|
-
return
|
|
1794
|
-
|
|
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
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
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
|
-
|
|
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
|
|
1809
|
-
var SteerDecisionSchema =
|
|
1810
|
-
message:
|
|
1811
|
-
|
|
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.
|
|
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:
|
|
2413
|
-
iteration:
|
|
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
|
-
...
|
|
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
|
-
|
|
2487
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
3212
|
+
yield { type: "done", sessionId: sessionId ?? "", timestamp: now4() };
|
|
2600
3213
|
} catch (err) {
|
|
2601
|
-
yield { type: "error", text: formatError(err), timestamp:
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
2790
|
-
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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 = {
|
|
3111
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
4141
|
+
function scopedMemoize(scope) {
|
|
3321
4142
|
assertSafeCheckpointName(scope);
|
|
3322
4143
|
return async (name, fn) => {
|
|
3323
4144
|
assertSafeCheckpointName(name);
|
|
3324
|
-
return
|
|
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({
|
|
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({
|
|
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
|
|
3686
|
-
var StepObservabilitySchema =
|
|
3687
|
-
metadata:
|
|
3688
|
-
events:
|
|
3689
|
-
subSteps:
|
|
3690
|
-
name:
|
|
3691
|
-
startedAt:
|
|
3692
|
-
durationMs:
|
|
3693
|
-
status:
|
|
3694
|
-
error:
|
|
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 =
|
|
3698
|
-
|
|
3699
|
-
ok:
|
|
3700
|
-
output:
|
|
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
|
-
|
|
3704
|
-
ok:
|
|
3705
|
-
kind:
|
|
3706
|
-
error:
|
|
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 =
|
|
3710
|
-
pauseId:
|
|
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
|
-
|
|
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
|
|
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
|
|
3836
|
-
const
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
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 (
|
|
3867
|
-
const stderrTail =
|
|
3868
|
-
const stdoutTail =
|
|
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 ${
|
|
4681
|
+
message: `runner subprocess exited ${output.exitCode} before emitting a step result${details ? `
|
|
3881
4682
|
${details}` : ""}`,
|
|
3882
|
-
exitCode:
|
|
4683
|
+
exitCode: output.exitCode
|
|
3883
4684
|
}
|
|
3884
4685
|
};
|
|
3885
4686
|
}
|
|
3886
|
-
const tail =
|
|
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
|
|
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
|
|
4069
|
-
|
|
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
|
-
|
|
4072
|
-
|
|
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.
|
|
4087
|
-
|
|
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
|
|
5111
|
+
if (schema instanceof z12.ZodString)
|
|
4210
5112
|
return "string";
|
|
4211
|
-
if (schema instanceof
|
|
5113
|
+
if (schema instanceof z12.ZodNumber)
|
|
4212
5114
|
return "number";
|
|
4213
|
-
if (schema instanceof
|
|
5115
|
+
if (schema instanceof z12.ZodBoolean)
|
|
4214
5116
|
return "boolean";
|
|
4215
|
-
if (schema instanceof
|
|
5117
|
+
if (schema instanceof z12.ZodArray)
|
|
4216
5118
|
return `${zodTypeName(schema.element)}[]`;
|
|
4217
|
-
if (schema instanceof
|
|
5119
|
+
if (schema instanceof z12.ZodEnum)
|
|
4218
5120
|
return schema.options.map((o) => `"${o}"`).join(" | ");
|
|
4219
|
-
if (schema instanceof
|
|
5121
|
+
if (schema instanceof z12.ZodOptional)
|
|
4220
5122
|
return `${zodTypeName(schema.unwrap())} (optional)`;
|
|
4221
|
-
if (schema instanceof
|
|
5123
|
+
if (schema instanceof z12.ZodNullable)
|
|
4222
5124
|
return `${zodTypeName(schema.unwrap())} | null`;
|
|
4223
|
-
if (schema instanceof
|
|
5125
|
+
if (schema instanceof z12.ZodLiteral)
|
|
4224
5126
|
return `"${String(schema.value)}"`;
|
|
4225
|
-
if (schema instanceof
|
|
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
|
|
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 } : {},
|
|
@@ -4369,14 +5271,17 @@ export {
|
|
|
4369
5271
|
writeTool,
|
|
4370
5272
|
writeAgentContext,
|
|
4371
5273
|
stepInputPath,
|
|
5274
|
+
snapshotResolves,
|
|
4372
5275
|
serveStep,
|
|
4373
5276
|
runWorkflowSteps,
|
|
4374
5277
|
runWorkflowSingleStep,
|
|
4375
5278
|
runWorkflow,
|
|
4376
5279
|
runProcessorChain,
|
|
4377
5280
|
requireScope,
|
|
5281
|
+
requestPauseAndAwait,
|
|
4378
5282
|
requestContextPath,
|
|
4379
5283
|
redactPattern,
|
|
5284
|
+
reconnectStep,
|
|
4380
5285
|
reconnectSandbox,
|
|
4381
5286
|
readTool,
|
|
4382
5287
|
parseStepResult,
|
|
@@ -4389,14 +5294,21 @@ export {
|
|
|
4389
5294
|
makeDesktopSandboxProvider,
|
|
4390
5295
|
listVercelRuntimeModels,
|
|
4391
5296
|
listOwnedSandboxes,
|
|
5297
|
+
launchStep,
|
|
4392
5298
|
killSandboxById,
|
|
4393
5299
|
killAllSandboxes,
|
|
4394
5300
|
isWorkflow,
|
|
5301
|
+
isPlatformE2bTemplateAlias,
|
|
5302
|
+
isE2bSupportedSize,
|
|
4395
5303
|
invokeStep,
|
|
4396
5304
|
importSourceModule,
|
|
5305
|
+
humanApproval,
|
|
4397
5306
|
getSandboxQuotas,
|
|
4398
5307
|
formatError,
|
|
4399
5308
|
editTool,
|
|
5309
|
+
e2bMachineSpec,
|
|
5310
|
+
e2bBaseTemplate,
|
|
5311
|
+
e2bAgentEnvTemplate,
|
|
4400
5312
|
denyTools,
|
|
4401
5313
|
deleteSandboxSnapshot,
|
|
4402
5314
|
defineWorkflow,
|
|
@@ -4405,6 +5317,7 @@ export {
|
|
|
4405
5317
|
defineRuntime,
|
|
4406
5318
|
createVercelRuntime,
|
|
4407
5319
|
createSandbox,
|
|
5320
|
+
createGatePauseProcessor,
|
|
4408
5321
|
createCodexRuntime,
|
|
4409
5322
|
createClaudeRuntime,
|
|
4410
5323
|
createAmpRuntime,
|
|
@@ -4434,6 +5347,7 @@ export {
|
|
|
4434
5347
|
STEP_RESULT_PREFIX,
|
|
4435
5348
|
STEP_PAUSE_PREFIX,
|
|
4436
5349
|
STEP_ENV,
|
|
5350
|
+
SANDBOX_VCPUS,
|
|
4437
5351
|
SANDBOX_UNAVAILABLE_PREFIX,
|
|
4438
5352
|
ReservedKeyError,
|
|
4439
5353
|
RequestContext,
|
|
@@ -4446,6 +5360,8 @@ export {
|
|
|
4446
5360
|
NonSerialisableValueError,
|
|
4447
5361
|
LATEST_VERSION,
|
|
4448
5362
|
EngineError,
|
|
5363
|
+
E2B_TEMPLATE_SIZES,
|
|
5364
|
+
DEFAULT_SANDBOX_SIZE,
|
|
4449
5365
|
DEFAULT_CLAUDE_MODEL,
|
|
4450
5366
|
ClaudeRunner,
|
|
4451
5367
|
BUNDLER_VERSION,
|