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