@agent-compose/sdk 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -39
- package/dist/agent/__tests__/runtime-json-schema.test.d.ts +10 -0
- package/dist/agent/agent-context.d.ts +21 -1
- package/dist/agent/agent-loop.d.ts +24 -1
- package/dist/client.d.ts +338 -534
- package/dist/directives.d.ts +112 -0
- package/dist/display.d.ts +242 -0
- package/dist/errors.d.ts +24 -1
- package/dist/index.d.ts +24 -12
- package/dist/index.js +3545 -1667
- package/dist/pause/wrappers.d.ts +31 -9
- package/dist/runtimes/_acp-client.d.ts +46 -1
- package/dist/runtimes/_cli-agent.d.ts +49 -4
- package/dist/runtimes/_jsonl-guard.d.ts +103 -0
- package/dist/runtimes/amp.d.ts +2 -2
- package/dist/runtimes/claude-code.d.ts +59 -0
- package/dist/runtimes/claude-code.test.d.ts +14 -0
- package/dist/runtimes/claude.d.ts +16 -0
- package/dist/runtimes/claude.test.d.ts +8 -0
- package/dist/runtimes/codex.d.ts +9 -3
- package/dist/runtimes/cursor.d.ts +2 -2
- package/dist/runtimes/droid.d.ts +2 -2
- package/dist/runtimes/jsonl-guard.test.d.ts +19 -0
- package/dist/runtimes/openai-desktop.js +2691 -864
- package/dist/runtimes/opencode.d.ts +2 -2
- package/dist/runtimes/vercel.js +12 -1
- package/dist/sandbox/devbox.d.ts +42 -0
- package/dist/sandbox/exec-stream.d.ts +14 -0
- package/dist/sandbox/network-policy.d.ts +100 -0
- package/dist/sandbox/provider-def.d.ts +79 -0
- package/dist/sandbox/providers/desktop.d.ts +10 -0
- package/dist/sandbox/providers/e2b.d.ts +17 -0
- package/dist/sandbox/providers/local.d.ts +11 -0
- package/dist/sandbox/providers/vercel.d.ts +18 -0
- package/dist/sandbox/registry.d.ts +45 -0
- package/dist/sandbox/sizes.d.ts +68 -0
- package/dist/sandbox.d.ts +24 -299
- package/dist/step-invocation/__tests__/foreground-recovery.test.d.ts +1 -0
- package/dist/step-invocation/invoker.d.ts +10 -0
- package/dist/step-invocation/protocol.d.ts +5 -0
- package/dist/types/api-compliance.d.ts +71 -0
- package/dist/types/api-conversations.d.ts +492 -0
- package/dist/types/api-factory.d.ts +309 -0
- package/dist/types/api-projects.d.ts +131 -0
- package/dist/types/api-runs.d.ts +377 -0
- package/dist/types/api-scopes.d.ts +102 -0
- package/dist/types/conversation-stream.d.ts +191 -0
- package/dist/types/execution-context.d.ts +12 -2
- package/dist/types/protocol.d.ts +30 -1
- package/dist/types/sandbox-environment.d.ts +8 -5
- package/dist/types/sandbox.d.ts +74 -4
- package/dist/types/workflow-metadata.d.ts +33 -8
- package/dist/types/workflow-plan.d.ts +10 -0
- package/dist/types/workflow.d.ts +18 -205
- package/dist/utils/bundler.d.ts +12 -1
- package/dist/workflow-steps/index.d.ts +1 -1
- package/dist/workflow-steps/observability.d.ts +8 -1
- package/dist/workflow-steps/runner.d.ts +3 -3
- package/dist/workflow-steps/step.d.ts +15 -1
- package/dist/workflow-steps/types.d.ts +19 -5
- package/dist/workflow-steps/workflow.d.ts +22 -1
- package/dist/workflows/engine.d.ts +3 -2
- package/dist/workflows/invoke-child.d.ts +2 -2
- package/package.json +1 -1
- package/src/agent/agent-context.ts +186 -3
- package/src/agent/agent-loop.ts +31 -2
- package/src/client.ts +909 -621
- package/src/directives.ts +184 -0
- package/src/display.ts +788 -0
- package/src/errors.ts +39 -0
- package/src/index.ts +104 -10
- package/src/pause/wrappers.ts +44 -9
- package/src/runtimes/_acp-client.ts +72 -3
- package/src/runtimes/_cli-agent.ts +159 -36
- package/src/runtimes/_jsonl-guard.ts +219 -0
- package/src/runtimes/claude-code.ts +246 -0
- package/src/runtimes/claude.ts +32 -2
- package/src/runtimes/codex.ts +55 -3
- package/src/runtimes/openai-desktop.ts +59 -14
- package/src/sandbox/devbox.ts +48 -0
- package/src/sandbox/exec-stream.ts +48 -0
- package/src/sandbox/network-policy.ts +181 -0
- package/src/sandbox/provider-def.ts +94 -0
- package/src/sandbox/providers/desktop.ts +57 -0
- package/src/sandbox/providers/e2b.ts +354 -0
- package/src/sandbox/providers/local.ts +106 -0
- package/src/sandbox/providers/vercel.ts +331 -0
- package/src/sandbox/registry.ts +198 -0
- package/src/sandbox/sizes.ts +95 -0
- package/src/sandbox.ts +59 -1275
- package/src/step-invocation/invoker.ts +151 -28
- package/src/step-invocation/protocol.ts +8 -0
- package/src/types/api-compliance.ts +79 -0
- package/src/types/api-conversations.ts +522 -0
- package/src/types/api-factory.ts +336 -0
- package/src/types/api-projects.ts +140 -0
- package/src/types/api-runs.ts +412 -0
- package/src/types/api-scopes.ts +102 -0
- package/src/types/conversation-stream.ts +231 -0
- package/src/types/execution-context.ts +10 -2
- package/src/types/protocol.ts +33 -0
- package/src/types/sandbox-environment.ts +28 -9
- package/src/types/sandbox.ts +73 -4
- package/src/types/workflow-metadata.ts +35 -8
- package/src/types/workflow-plan.ts +11 -0
- package/src/types/workflow.ts +25 -292
- package/src/utils/bundler.ts +32 -5
- package/src/utils/errors.ts +16 -1
- package/src/workflow-steps/index.ts +1 -0
- package/src/workflow-steps/observability.ts +19 -8
- package/src/workflow-steps/runner.ts +4 -4
- package/src/workflow-steps/step.ts +49 -1
- package/src/workflow-steps/types.ts +20 -5
- package/src/workflow-steps/workflow.ts +22 -1
- package/src/workflows/engine.ts +3 -2
- package/src/workflows/invoke-child.ts +2 -2
|
@@ -717,8 +717,19 @@ async function corePause(req, coord, kind = "custom") {
|
|
|
717
717
|
|
|
718
718
|
// src/utils/errors.ts
|
|
719
719
|
function formatError(err) {
|
|
720
|
-
if (!(err instanceof Error))
|
|
720
|
+
if (!(err instanceof Error)) {
|
|
721
|
+
if (typeof err === "object" && err !== null) {
|
|
722
|
+
const msg = err.message;
|
|
723
|
+
if (typeof msg === "string" && msg.length > 0)
|
|
724
|
+
return msg;
|
|
725
|
+
try {
|
|
726
|
+
return JSON.stringify(err) ?? String(err);
|
|
727
|
+
} catch {
|
|
728
|
+
return String(err);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
721
731
|
return String(err);
|
|
732
|
+
}
|
|
722
733
|
const parts = [err.message];
|
|
723
734
|
const seen = new Set([err]);
|
|
724
735
|
let cause = err.cause;
|
|
@@ -916,9 +927,6 @@ async function listVercelRuntimeModels() {
|
|
|
916
927
|
const { models } = await gateway.getAvailableModels();
|
|
917
928
|
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));
|
|
918
929
|
}
|
|
919
|
-
// src/types/workflow.ts
|
|
920
|
-
import { z as z4 } from "zod";
|
|
921
|
-
|
|
922
930
|
// src/workflow-steps/types.ts
|
|
923
931
|
var WORKFLOW_BRAND = Symbol.for("@agent-compose/sdk.Workflow");
|
|
924
932
|
|
|
@@ -939,6 +947,8 @@ function extractMetadata(source) {
|
|
|
939
947
|
out.snapshots = Object.freeze({ ...source.snapshots });
|
|
940
948
|
if (source.resources !== undefined)
|
|
941
949
|
out.resources = Object.freeze({ ...source.resources });
|
|
950
|
+
if (source.mergePolicy !== undefined)
|
|
951
|
+
out.mergePolicy = source.mergePolicy;
|
|
942
952
|
if (source.processors !== undefined)
|
|
943
953
|
out.processors = Object.freeze([...source.processors]);
|
|
944
954
|
if (source.connectors !== undefined)
|
|
@@ -999,61 +1009,39 @@ function isWorkflow(value) {
|
|
|
999
1009
|
}
|
|
1000
1010
|
|
|
1001
1011
|
// src/types/workflow.ts
|
|
1002
|
-
function compileRunForm(def, metadata) {
|
|
1003
|
-
const unknownSchema = z4.unknown();
|
|
1004
|
-
const inputSchema = def.input ?? unknownSchema;
|
|
1005
|
-
const outputSchema = def.output ?? unknownSchema;
|
|
1006
|
-
const step = {
|
|
1007
|
-
name: "run",
|
|
1008
|
-
input: inputSchema,
|
|
1009
|
-
output: outputSchema,
|
|
1010
|
-
run: async (stepCtx) => {
|
|
1011
|
-
const workflowCtx = {
|
|
1012
|
-
input: stepCtx.input,
|
|
1013
|
-
run: stepCtx.run,
|
|
1014
|
-
requestContext: stepCtx.requestContext,
|
|
1015
|
-
invokeChild: stepCtx.invokeChild,
|
|
1016
|
-
setMetadata: stepCtx.setMetadata,
|
|
1017
|
-
step: stepCtx.step,
|
|
1018
|
-
agentEvents: stepCtx.agentEvents,
|
|
1019
|
-
pause: stepCtx.pause,
|
|
1020
|
-
sleep: stepCtx.sleep,
|
|
1021
|
-
waitForEvent: stepCtx.waitForEvent,
|
|
1022
|
-
processors: metadata.processors ?? []
|
|
1023
|
-
};
|
|
1024
|
-
const sandbox = stepCtx.sandbox;
|
|
1025
|
-
if (!sandbox)
|
|
1026
|
-
throw new Error("legacy run-form workflow requires a sandbox in StepContext");
|
|
1027
|
-
return def.run(workflowCtx, sandbox);
|
|
1028
|
-
}
|
|
1029
|
-
};
|
|
1030
|
-
const workflow = {
|
|
1031
|
-
id: "@run-form",
|
|
1032
|
-
input: inputSchema,
|
|
1033
|
-
output: outputSchema,
|
|
1034
|
-
steps: Object.freeze([step]),
|
|
1035
|
-
metadata
|
|
1036
|
-
};
|
|
1037
|
-
Object.defineProperty(workflow, WORKFLOW_BRAND, { value: true, enumerable: false });
|
|
1038
|
-
return Object.freeze(workflow);
|
|
1039
|
-
}
|
|
1040
1012
|
function defineWorkflow(def) {
|
|
1041
|
-
if ("run" in def)
|
|
1042
|
-
|
|
1013
|
+
if ("run" in def) {
|
|
1014
|
+
throw new Error("defineWorkflow: the legacy run-form (`defineWorkflow({ run(ctx, sandbox) { … } })`) has been removed. " + "Author workflows in step-form — `defineWorkflow({ id, input, output }).step(defineStep(...)).build()` — " + "or run /ac:generate-workflow to scaffold the current shape.");
|
|
1015
|
+
}
|
|
1043
1016
|
return createStepWorkflow(def);
|
|
1044
1017
|
}
|
|
1045
1018
|
// src/types/sandbox-environment.ts
|
|
1019
|
+
import { z as z4 } from "zod";
|
|
1046
1020
|
function defineSandboxEnvironment(env) {
|
|
1047
1021
|
if (typeof env.setup !== "function") {
|
|
1048
1022
|
throw new Error(`defineSandboxEnvironment(${env.name}): 'setup' must be a function`);
|
|
1049
1023
|
}
|
|
1050
|
-
|
|
1024
|
+
const input = z4.unknown();
|
|
1025
|
+
const output = z4.unknown();
|
|
1026
|
+
return createStepWorkflow({
|
|
1027
|
+
id: env.name,
|
|
1051
1028
|
...env.description !== undefined ? { description: env.description } : {},
|
|
1052
1029
|
...env.resources !== undefined ? { resources: env.resources } : {},
|
|
1030
|
+
input,
|
|
1031
|
+
output,
|
|
1053
1032
|
snapshots: env.snapshots ?? { saveLatest: true },
|
|
1054
|
-
environmentBuild: true
|
|
1055
|
-
|
|
1056
|
-
|
|
1033
|
+
environmentBuild: true
|
|
1034
|
+
}).step({
|
|
1035
|
+
name: "setup",
|
|
1036
|
+
input,
|
|
1037
|
+
output,
|
|
1038
|
+
run: async (ctx) => {
|
|
1039
|
+
const sandbox = ctx.sandbox;
|
|
1040
|
+
if (!sandbox)
|
|
1041
|
+
throw new Error(`defineSandboxEnvironment(${env.name}): setup requires a sandbox in StepContext`);
|
|
1042
|
+
await env.setup(sandbox);
|
|
1043
|
+
}
|
|
1044
|
+
}).build();
|
|
1057
1045
|
}
|
|
1058
1046
|
// src/processors/processor.ts
|
|
1059
1047
|
var Verdict = {
|
|
@@ -1279,12 +1267,32 @@ import { ofetch } from "ofetch";
|
|
|
1279
1267
|
// src/errors.ts
|
|
1280
1268
|
class AgentComposeError extends Error {
|
|
1281
1269
|
status;
|
|
1282
|
-
|
|
1270
|
+
retryAfterMs;
|
|
1271
|
+
code;
|
|
1272
|
+
capability;
|
|
1273
|
+
constructor(status, message, opts) {
|
|
1283
1274
|
super(message);
|
|
1284
1275
|
this.status = status;
|
|
1285
1276
|
this.name = "AgentComposeError";
|
|
1277
|
+
if (opts?.retryAfterMs !== undefined)
|
|
1278
|
+
this.retryAfterMs = opts.retryAfterMs;
|
|
1279
|
+
if (opts?.code !== undefined)
|
|
1280
|
+
this.code = opts.code;
|
|
1281
|
+
if (opts?.capability !== undefined)
|
|
1282
|
+
this.capability = opts.capability;
|
|
1286
1283
|
}
|
|
1287
1284
|
}
|
|
1285
|
+
function parseRetryAfterMs(header) {
|
|
1286
|
+
if (!header)
|
|
1287
|
+
return;
|
|
1288
|
+
const seconds = Number(header);
|
|
1289
|
+
if (Number.isFinite(seconds))
|
|
1290
|
+
return Math.max(0, seconds * 1000);
|
|
1291
|
+
const date = Date.parse(header);
|
|
1292
|
+
if (!Number.isNaN(date))
|
|
1293
|
+
return Math.max(0, date - Date.now());
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1288
1296
|
|
|
1289
1297
|
// src/sse.ts
|
|
1290
1298
|
async function* parseSseStream(body) {
|
|
@@ -1336,6 +1344,19 @@ async function* parseSseStream(body) {
|
|
|
1336
1344
|
}
|
|
1337
1345
|
}
|
|
1338
1346
|
|
|
1347
|
+
// src/types/conversation-stream.ts
|
|
1348
|
+
function normalizeConversationStreamEvent(eventName, data, sseId) {
|
|
1349
|
+
const name = eventName || (typeof data.event === "string" ? data.event : "message");
|
|
1350
|
+
const dataId = typeof data.id === "number" && Number.isFinite(data.id) ? data.id : undefined;
|
|
1351
|
+
const durableId = sseId !== undefined && Number.isFinite(sseId) && sseId > 0 ? sseId : dataId !== undefined && dataId > 0 ? dataId : undefined;
|
|
1352
|
+
const ev = { ...data, event: name };
|
|
1353
|
+
if (durableId !== undefined)
|
|
1354
|
+
ev.id = durableId;
|
|
1355
|
+
else
|
|
1356
|
+
delete ev.id;
|
|
1357
|
+
return ev;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1339
1360
|
// src/client.ts
|
|
1340
1361
|
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1341
1362
|
var DEFAULT_FACTORY = "default";
|
|
@@ -1372,10 +1393,39 @@ class AgentComposeClient {
|
|
|
1372
1393
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
1373
1394
|
async onResponseError({ response }) {
|
|
1374
1395
|
const body = response._data;
|
|
1375
|
-
throw new AgentComposeError(response.status, body?.error ?? response.statusText
|
|
1396
|
+
throw new AgentComposeError(response.status, body?.error ?? response.statusText, {
|
|
1397
|
+
retryAfterMs: parseRetryAfterMs(response.headers.get("retry-after")),
|
|
1398
|
+
...body?.code ? { code: body.code } : {},
|
|
1399
|
+
...body?.capability ? { capability: body.capability } : {}
|
|
1400
|
+
});
|
|
1376
1401
|
}
|
|
1377
1402
|
});
|
|
1378
1403
|
}
|
|
1404
|
+
async* openSseStream(path, opts) {
|
|
1405
|
+
const headers = {
|
|
1406
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
1407
|
+
Accept: "text/event-stream"
|
|
1408
|
+
};
|
|
1409
|
+
if (opts?.lastEventId && opts.lastEventId > 0) {
|
|
1410
|
+
headers["Last-Event-ID"] = String(opts.lastEventId);
|
|
1411
|
+
}
|
|
1412
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
1413
|
+
headers,
|
|
1414
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
1415
|
+
});
|
|
1416
|
+
if (!res.ok || !res.body) {
|
|
1417
|
+
let message = res.statusText;
|
|
1418
|
+
try {
|
|
1419
|
+
const body = await res.json();
|
|
1420
|
+
if (body.error)
|
|
1421
|
+
message = body.error;
|
|
1422
|
+
} catch {}
|
|
1423
|
+
throw new AgentComposeError(res.status, message, {
|
|
1424
|
+
retryAfterMs: parseRetryAfterMs(res.headers.get("retry-after"))
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
yield* parseSseStream(res.body);
|
|
1428
|
+
}
|
|
1379
1429
|
register(payload) {
|
|
1380
1430
|
const { factorySlug = DEFAULT_FACTORY, ...body } = payload;
|
|
1381
1431
|
return this.fetch(templatePath(factorySlug), { method: "POST", body });
|
|
@@ -1495,6 +1545,22 @@ class AgentComposeClient {
|
|
|
1495
1545
|
getRun(runId) {
|
|
1496
1546
|
return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}`);
|
|
1497
1547
|
}
|
|
1548
|
+
async listRuns(opts = {}) {
|
|
1549
|
+
const q2 = new URLSearchParams;
|
|
1550
|
+
if (opts.workflow)
|
|
1551
|
+
q2.set("workflow", opts.workflow);
|
|
1552
|
+
if (opts.search)
|
|
1553
|
+
q2.set("search", opts.search);
|
|
1554
|
+
if (opts.outcome)
|
|
1555
|
+
q2.set("outcome", opts.outcome);
|
|
1556
|
+
if (opts.sort)
|
|
1557
|
+
q2.set("sort", opts.sort);
|
|
1558
|
+
if (opts.limit !== undefined)
|
|
1559
|
+
q2.set("limit", String(opts.limit));
|
|
1560
|
+
const slug = encodeURIComponent(opts.factorySlug ?? "default");
|
|
1561
|
+
const body = await this.fetch(`/api/v1/factories/${slug}/runs${q2.toString() ? `?${q2}` : ""}`);
|
|
1562
|
+
return body.workflows;
|
|
1563
|
+
}
|
|
1498
1564
|
async getRunTimeline(runId, opts) {
|
|
1499
1565
|
const q2 = new URLSearchParams;
|
|
1500
1566
|
if (opts?.limit !== undefined)
|
|
@@ -1504,6 +1570,9 @@ class AgentComposeClient {
|
|
|
1504
1570
|
const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/timeline${q2.toString() ? `?${q2}` : ""}`);
|
|
1505
1571
|
return body.events;
|
|
1506
1572
|
}
|
|
1573
|
+
async getRunFunding(runId) {
|
|
1574
|
+
return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/funding`);
|
|
1575
|
+
}
|
|
1507
1576
|
async reportEvent(runId, input) {
|
|
1508
1577
|
const body = {
|
|
1509
1578
|
...input,
|
|
@@ -1547,12 +1616,151 @@ class AgentComposeClient {
|
|
|
1547
1616
|
created: wire.created
|
|
1548
1617
|
};
|
|
1549
1618
|
}
|
|
1550
|
-
async
|
|
1619
|
+
async getFactoryFileBytes(path, opts) {
|
|
1551
1620
|
const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
|
|
1552
1621
|
const q2 = new URLSearchParams({ path });
|
|
1553
1622
|
if (opts?.revision !== undefined)
|
|
1554
1623
|
q2.set("revision", String(opts.revision));
|
|
1555
|
-
|
|
1624
|
+
const buf = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?${q2}`, { responseType: "arrayBuffer" });
|
|
1625
|
+
return new Uint8Array(buf);
|
|
1626
|
+
}
|
|
1627
|
+
async getFactoryFile(path, opts) {
|
|
1628
|
+
return new TextDecoder().decode(await this.getFactoryFileBytes(path, opts));
|
|
1629
|
+
}
|
|
1630
|
+
listConversations(opts) {
|
|
1631
|
+
const q2 = new URLSearchParams;
|
|
1632
|
+
if (opts?.cursor)
|
|
1633
|
+
q2.set("cursor", opts.cursor);
|
|
1634
|
+
return this.fetch(`/api/v1/conversations${q2.toString() ? `?${q2}` : ""}`);
|
|
1635
|
+
}
|
|
1636
|
+
listSessions(factoryId, opts) {
|
|
1637
|
+
const q2 = new URLSearchParams({ factoryId });
|
|
1638
|
+
if (opts?.cursor)
|
|
1639
|
+
q2.set("cursor", opts.cursor);
|
|
1640
|
+
return this.fetch(`/api/v1/sessions?${q2}`);
|
|
1641
|
+
}
|
|
1642
|
+
getConversation(id) {
|
|
1643
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(id)}`);
|
|
1644
|
+
}
|
|
1645
|
+
createCloudSession(input) {
|
|
1646
|
+
return this.fetch(`/api/v1/sessions/cloud`, {
|
|
1647
|
+
method: "POST",
|
|
1648
|
+
body: {
|
|
1649
|
+
...input.type ? { type: input.type } : {},
|
|
1650
|
+
...input.runtime ? { runtime: input.runtime } : {},
|
|
1651
|
+
factoryId: input.factoryId,
|
|
1652
|
+
...input.presetId ? { presetId: input.presetId } : {},
|
|
1653
|
+
...input.preStart ? { preStart: input.preStart } : {},
|
|
1654
|
+
...input.title ? { title: input.title } : {},
|
|
1655
|
+
...input.repoUrl ? { repoUrl: input.repoUrl } : {},
|
|
1656
|
+
...input.repoBranch ? { repoBranch: input.repoBranch } : {},
|
|
1657
|
+
...input.channelId ? { channelId: input.channelId } : {},
|
|
1658
|
+
...input.templateId ? { templateId: input.templateId } : {},
|
|
1659
|
+
...input.sandboxSize ? { sandboxSize: input.sandboxSize } : {},
|
|
1660
|
+
...input.networkPolicy !== undefined ? { networkPolicy: input.networkPolicy } : {},
|
|
1661
|
+
...input.connectorProfileId !== undefined ? { connectorProfileId: input.connectorProfileId } : {}
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
getConversationThread(conversationId, rootId) {
|
|
1666
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/threads/${encodeURIComponent(rootId)}`);
|
|
1667
|
+
}
|
|
1668
|
+
async listChannelSessions(channelId) {
|
|
1669
|
+
const body = await this.fetch(`/api/v1/conversations/${encodeURIComponent(channelId)}/sessions`);
|
|
1670
|
+
return body.sessions;
|
|
1671
|
+
}
|
|
1672
|
+
async attachChannelSession(channelId, sessionConversationId) {
|
|
1673
|
+
await this.fetch(`/api/v1/conversations/${encodeURIComponent(channelId)}/sessions`, { method: "POST", body: { sessionConversationId } });
|
|
1674
|
+
}
|
|
1675
|
+
async detachChannelSession(channelId, sessionConversationId) {
|
|
1676
|
+
await this.fetch(`/api/v1/conversations/${encodeURIComponent(channelId)}/sessions/${encodeURIComponent(sessionConversationId)}`, { method: "DELETE" });
|
|
1677
|
+
}
|
|
1678
|
+
postSessionChannelMessage(channelId, input) {
|
|
1679
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(channelId)}/session-messages`, { method: "POST", body: { text: input.text, ...input.threadRootId ? { threadRootId: input.threadRootId } : {} } });
|
|
1680
|
+
}
|
|
1681
|
+
openPreview(conversationId, input) {
|
|
1682
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/preview`, {
|
|
1683
|
+
method: "POST",
|
|
1684
|
+
body: {
|
|
1685
|
+
port: input.port,
|
|
1686
|
+
...input.name ? { name: input.name } : {},
|
|
1687
|
+
...input.path ? { path: input.path } : {}
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
async listPreviews(conversationId) {
|
|
1692
|
+
const body = await this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/previews`);
|
|
1693
|
+
return body.previews;
|
|
1694
|
+
}
|
|
1695
|
+
async closePreview(conversationId, port) {
|
|
1696
|
+
const body = await this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/preview/${port}`, { method: "DELETE" });
|
|
1697
|
+
return body.closed;
|
|
1698
|
+
}
|
|
1699
|
+
forkSession(conversationId, input) {
|
|
1700
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/fork`, { method: "POST", body: input?.title ? { title: input.title } : {} });
|
|
1701
|
+
}
|
|
1702
|
+
getSessionChanges(conversationId) {
|
|
1703
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/changes`);
|
|
1704
|
+
}
|
|
1705
|
+
mergeSessionChanges(conversationId, opts = {}) {
|
|
1706
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/changes/merge`, { method: "POST", body: opts.branch ? { branch: opts.branch } : {} });
|
|
1707
|
+
}
|
|
1708
|
+
discardSessionChanges(conversationId, opts = {}) {
|
|
1709
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/changes/discard`, { method: "POST", body: opts.branch ? { branch: opts.branch } : {} });
|
|
1710
|
+
}
|
|
1711
|
+
async getConversationMembers(conversationId) {
|
|
1712
|
+
const body = await this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/members`);
|
|
1713
|
+
return body.members;
|
|
1714
|
+
}
|
|
1715
|
+
async addConversationMembers(conversationId, members) {
|
|
1716
|
+
const body = await this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/members`, { method: "POST", body: { members } });
|
|
1717
|
+
return body.members;
|
|
1718
|
+
}
|
|
1719
|
+
async removeConversationMember(conversationId, userId) {
|
|
1720
|
+
const body = await this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/members/${encodeURIComponent(userId)}`, { method: "DELETE" });
|
|
1721
|
+
return body.members;
|
|
1722
|
+
}
|
|
1723
|
+
async setConversationMemberRole(conversationId, userId, role) {
|
|
1724
|
+
const body = await this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/members/${encodeURIComponent(userId)}`, { method: "PATCH", body: { role } });
|
|
1725
|
+
return body.members;
|
|
1726
|
+
}
|
|
1727
|
+
sendConversationMessage(conversationId, input) {
|
|
1728
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/messages`, {
|
|
1729
|
+
method: "POST",
|
|
1730
|
+
body: {
|
|
1731
|
+
text: input.text,
|
|
1732
|
+
...input.threadRootId ? { threadRootId: input.threadRootId } : {},
|
|
1733
|
+
...input.pageContext ? { pageContext: input.pageContext } : {}
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
prewarmConversation(conversationId) {
|
|
1738
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/prewarm`, { method: "POST", body: {} });
|
|
1739
|
+
}
|
|
1740
|
+
cancelConversationTurn(conversationId) {
|
|
1741
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/cancel-turn`, { method: "POST", body: {} });
|
|
1742
|
+
}
|
|
1743
|
+
heartbeatConversationPresence(conversationId, input) {
|
|
1744
|
+
return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/presence`, { method: "POST", body: input });
|
|
1745
|
+
}
|
|
1746
|
+
async* streamConversation(conversationId, opts) {
|
|
1747
|
+
const path = `/api/v1/conversations/${encodeURIComponent(conversationId)}/stream`;
|
|
1748
|
+
for await (const ev of this.openSseStream(path, opts)) {
|
|
1749
|
+
yield normalizeConversationStreamEvent(ev.event, ev.data, ev.id > 0 ? ev.id : undefined);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
async listAgents() {
|
|
1753
|
+
const body = await this.fetch("/api/v1/agents");
|
|
1754
|
+
return body.agents;
|
|
1755
|
+
}
|
|
1756
|
+
searchFactoryFiles(query, opts) {
|
|
1757
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
1758
|
+
const q2 = new URLSearchParams({ q: query });
|
|
1759
|
+
if (opts?.limit != null)
|
|
1760
|
+
q2.set("limit", String(opts.limit));
|
|
1761
|
+
if (opts?.cursor)
|
|
1762
|
+
q2.set("cursor", opts.cursor);
|
|
1763
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/search?${q2}`);
|
|
1556
1764
|
}
|
|
1557
1765
|
async listMembers() {
|
|
1558
1766
|
const body = await this.fetch("/api/v1/team/members");
|
|
@@ -1598,6 +1806,88 @@ class AgentComposeClient {
|
|
|
1598
1806
|
const body = await this.fetch(path);
|
|
1599
1807
|
return body.templates;
|
|
1600
1808
|
}
|
|
1809
|
+
getTemplate(name, opts) {
|
|
1810
|
+
return this.fetch(templatePath(opts?.factorySlug ?? DEFAULT_FACTORY, name));
|
|
1811
|
+
}
|
|
1812
|
+
setTemplateScope(name, input, opts) {
|
|
1813
|
+
return this.fetch(templatePath(opts?.factorySlug ?? DEFAULT_FACTORY, name, "scope"), {
|
|
1814
|
+
method: "PUT",
|
|
1815
|
+
body: {
|
|
1816
|
+
grants: input.grants,
|
|
1817
|
+
...input.conversationId !== undefined ? { conversationId: input.conversationId } : {}
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
setFileScope(path, input, opts) {
|
|
1822
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
1823
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/scope`, { method: "PUT", body: { path, grants: input.grants } });
|
|
1824
|
+
}
|
|
1825
|
+
async shareFileWithSession(factorySlug, input) {
|
|
1826
|
+
await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/session-grants`, { method: "POST", body: { path: input.path, conversationId: input.conversationId } });
|
|
1827
|
+
}
|
|
1828
|
+
async unshareFileWithSession(factorySlug, input) {
|
|
1829
|
+
await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/session-grants`, { method: "DELETE", body: { path: input.path, conversationId: input.conversationId } });
|
|
1830
|
+
}
|
|
1831
|
+
listProjects(opts) {
|
|
1832
|
+
const q2 = new URLSearchParams;
|
|
1833
|
+
if (opts?.cursor)
|
|
1834
|
+
q2.set("cursor", opts.cursor);
|
|
1835
|
+
return this.fetch(`/api/v1/projects${q2.toString() ? `?${q2}` : ""}`);
|
|
1836
|
+
}
|
|
1837
|
+
async createProject(name) {
|
|
1838
|
+
const body = await this.fetch("/api/v1/projects", {
|
|
1839
|
+
method: "POST",
|
|
1840
|
+
body: { name }
|
|
1841
|
+
});
|
|
1842
|
+
return body.project;
|
|
1843
|
+
}
|
|
1844
|
+
async getProject(id) {
|
|
1845
|
+
const body = await this.fetch(`/api/v1/projects/${encodeURIComponent(id)}`);
|
|
1846
|
+
return body.project;
|
|
1847
|
+
}
|
|
1848
|
+
async renameProject(id, name) {
|
|
1849
|
+
const body = await this.fetch(`/api/v1/projects/${encodeURIComponent(id)}`, { method: "PATCH", body: { name } });
|
|
1850
|
+
return body.project;
|
|
1851
|
+
}
|
|
1852
|
+
async deleteProject(id) {
|
|
1853
|
+
await this.fetch(`/api/v1/projects/${encodeURIComponent(id)}`, {
|
|
1854
|
+
method: "DELETE"
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
async getProjectMembers(id) {
|
|
1858
|
+
const body = await this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/members`);
|
|
1859
|
+
return body.members;
|
|
1860
|
+
}
|
|
1861
|
+
async addProjectMembers(id, members) {
|
|
1862
|
+
const body = await this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/members`, { method: "POST", body: { members } });
|
|
1863
|
+
return body.members;
|
|
1864
|
+
}
|
|
1865
|
+
async removeProjectMember(id, userId) {
|
|
1866
|
+
const body = await this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/members/${encodeURIComponent(userId)}`, { method: "DELETE" });
|
|
1867
|
+
return body.members;
|
|
1868
|
+
}
|
|
1869
|
+
async setProjectMemberRole(id, userId, role) {
|
|
1870
|
+
const body = await this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/members/${encodeURIComponent(userId)}`, { method: "PATCH", body: { role } });
|
|
1871
|
+
return body.members;
|
|
1872
|
+
}
|
|
1873
|
+
listProjectObjects(id, opts) {
|
|
1874
|
+
const q2 = new URLSearchParams;
|
|
1875
|
+
if (opts?.cursor)
|
|
1876
|
+
q2.set("cursor", opts.cursor);
|
|
1877
|
+
return this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/objects${q2.toString() ? `?${q2}` : ""}`);
|
|
1878
|
+
}
|
|
1879
|
+
previewProjectObject(id, input) {
|
|
1880
|
+
return this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/objects/preview`, { method: "POST", body: { type: "session", conversationId: input.conversationId } });
|
|
1881
|
+
}
|
|
1882
|
+
addProjectObject(id, input) {
|
|
1883
|
+
return this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/objects`, { method: "POST", body: input });
|
|
1884
|
+
}
|
|
1885
|
+
refreshProjectObject(id, objectId) {
|
|
1886
|
+
return this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/objects/${encodeURIComponent(objectId)}/refresh`, { method: "POST", body: {} });
|
|
1887
|
+
}
|
|
1888
|
+
async removeProjectObject(id, objectId) {
|
|
1889
|
+
await this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/objects/${encodeURIComponent(objectId)}`, { method: "DELETE" });
|
|
1890
|
+
}
|
|
1601
1891
|
async listFactories() {
|
|
1602
1892
|
const body = await this.fetch("/api/v1/factories");
|
|
1603
1893
|
return body.factories;
|
|
@@ -1635,15 +1925,32 @@ class AgentComposeClient {
|
|
|
1635
1925
|
body: { key, value }
|
|
1636
1926
|
});
|
|
1637
1927
|
}
|
|
1638
|
-
async
|
|
1639
|
-
const
|
|
1640
|
-
const body = await this.fetch(templatePath(factorySlug, workflowName, "secrets"));
|
|
1928
|
+
async listSecretEntries(path) {
|
|
1929
|
+
const body = await this.fetch(path);
|
|
1641
1930
|
return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
|
|
1642
1931
|
}
|
|
1932
|
+
listSecrets(workflowName, opts) {
|
|
1933
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
1934
|
+
return this.listSecretEntries(templatePath(factorySlug, workflowName, "secrets"));
|
|
1935
|
+
}
|
|
1643
1936
|
deleteSecret(workflowName, key, opts) {
|
|
1644
1937
|
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
1645
1938
|
return this.fetch(templatePath(factorySlug, workflowName, "secrets", key), { method: "DELETE" });
|
|
1646
1939
|
}
|
|
1940
|
+
async listRepoLinks(opts) {
|
|
1941
|
+
const slug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
1942
|
+
const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}/repo-links`);
|
|
1943
|
+
return body.links;
|
|
1944
|
+
}
|
|
1945
|
+
async createRepoLink(input, opts) {
|
|
1946
|
+
const slug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
1947
|
+
const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}/repo-links`, { method: "POST", body: input });
|
|
1948
|
+
return body.link;
|
|
1949
|
+
}
|
|
1950
|
+
deleteRepoLink(linkId, opts) {
|
|
1951
|
+
const slug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
1952
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}/repo-links/${encodeURIComponent(linkId)}`, { method: "DELETE" });
|
|
1953
|
+
}
|
|
1647
1954
|
setFactorySecret(key, value, opts) {
|
|
1648
1955
|
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
1649
1956
|
return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/secrets`, {
|
|
@@ -1651,10 +1958,9 @@ class AgentComposeClient {
|
|
|
1651
1958
|
body: { key, value }
|
|
1652
1959
|
});
|
|
1653
1960
|
}
|
|
1654
|
-
|
|
1961
|
+
listFactorySecrets(opts) {
|
|
1655
1962
|
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
1656
|
-
|
|
1657
|
-
return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
|
|
1963
|
+
return this.listSecretEntries(`/api/v1/factories/${encodeURIComponent(factorySlug)}/secrets`);
|
|
1658
1964
|
}
|
|
1659
1965
|
deleteFactorySecret(key, opts) {
|
|
1660
1966
|
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
@@ -1674,96 +1980,557 @@ class AgentComposeClient {
|
|
|
1674
1980
|
cancelRun(runId) {
|
|
1675
1981
|
return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
|
|
1676
1982
|
}
|
|
1677
|
-
async
|
|
1678
|
-
const
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1983
|
+
async requestComplianceSession(input) {
|
|
1984
|
+
const body = await this.fetch("/api/v1/compliance/sessions", {
|
|
1985
|
+
method: "POST",
|
|
1986
|
+
body: {
|
|
1987
|
+
scopeKind: input.scopeKind,
|
|
1988
|
+
reason: input.reason,
|
|
1989
|
+
ttlSeconds: input.ttlSeconds,
|
|
1990
|
+
...input.scopeRef != null ? { scopeRef: input.scopeRef } : {}
|
|
1991
|
+
}
|
|
1685
1992
|
});
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1993
|
+
return body.session;
|
|
1994
|
+
}
|
|
1995
|
+
async approveComplianceSession(sessionId) {
|
|
1996
|
+
const body = await this.fetch(`/api/v1/compliance/sessions/${encodeURIComponent(sessionId)}/approve`, { method: "POST" });
|
|
1997
|
+
return body.session;
|
|
1998
|
+
}
|
|
1999
|
+
async revokeComplianceSession(sessionId) {
|
|
2000
|
+
const body = await this.fetch(`/api/v1/compliance/sessions/${encodeURIComponent(sessionId)}/revoke`, { method: "POST" });
|
|
2001
|
+
return body.session;
|
|
2002
|
+
}
|
|
2003
|
+
listComplianceSessions(opts) {
|
|
2004
|
+
const q2 = new URLSearchParams;
|
|
2005
|
+
if (opts?.status)
|
|
2006
|
+
q2.set("status", opts.status);
|
|
2007
|
+
if (opts?.limit != null)
|
|
2008
|
+
q2.set("limit", String(opts.limit));
|
|
2009
|
+
if (opts?.cursor)
|
|
2010
|
+
q2.set("cursor", opts.cursor);
|
|
2011
|
+
return this.fetch(`/api/v1/compliance/sessions${q2.toString() ? `?${q2}` : ""}`);
|
|
2012
|
+
}
|
|
2013
|
+
listComplianceAccesses(sessionId, opts) {
|
|
2014
|
+
const q2 = new URLSearchParams;
|
|
2015
|
+
if (opts?.limit != null)
|
|
2016
|
+
q2.set("limit", String(opts.limit));
|
|
2017
|
+
if (opts?.cursor)
|
|
2018
|
+
q2.set("cursor", opts.cursor);
|
|
2019
|
+
return this.fetch(`/api/v1/compliance/sessions/${encodeURIComponent(sessionId)}/accesses${q2.toString() ? `?${q2}` : ""}`);
|
|
2020
|
+
}
|
|
2021
|
+
async* streamRunLogs(runId, opts) {
|
|
2022
|
+
const path = `/api/v1/workflows/${encodeURIComponent(runId)}/stream`;
|
|
2023
|
+
for await (const ev of this.openSseStream(path, opts)) {
|
|
1696
2024
|
yield ev.data;
|
|
1697
2025
|
}
|
|
1698
2026
|
}
|
|
1699
2027
|
}
|
|
1700
|
-
// src/
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
var
|
|
1708
|
-
var
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
return
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
2028
|
+
// src/directives.ts
|
|
2029
|
+
var DIRECTIVE_MARKER_PREFIX = "[[ac:directive:v1 ";
|
|
2030
|
+
var DIRECTIVE_MARKER_SUFFIX = "]]";
|
|
2031
|
+
var MAX_DIRECTIVES_PER_OUTPUT = 4;
|
|
2032
|
+
var RUN_ID_RE = /^(latest|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
2033
|
+
var REVISION_SELECTOR_RE = /^(\d{1,12}|head|prev|head~[1-9]\d{0,2})$/;
|
|
2034
|
+
var DIRECTIVE_PATH_MAX = 512;
|
|
2035
|
+
var ASK_DIRECTIVE_PROMPT_MAX = 500;
|
|
2036
|
+
var ASK_DIRECTIVE_MAX_OPTIONS = 6;
|
|
2037
|
+
var ASK_DIRECTIVE_OPTION_ID_MAX = 50;
|
|
2038
|
+
var ASK_DIRECTIVE_OPTION_LABEL_MAX = 60;
|
|
2039
|
+
var ASK_APPROVER_MIN = 3;
|
|
2040
|
+
var ASK_APPROVER_MAX = 320;
|
|
2041
|
+
function validDirectivePath(v) {
|
|
2042
|
+
return typeof v === "string" && v.length >= 1 && v.length <= DIRECTIVE_PATH_MAX ? v : null;
|
|
2043
|
+
}
|
|
2044
|
+
function renderDirectiveMarker(directive) {
|
|
2045
|
+
return `${DIRECTIVE_MARKER_PREFIX}${JSON.stringify(directive)}${DIRECTIVE_MARKER_SUFFIX}`;
|
|
2046
|
+
}
|
|
2047
|
+
function validDirective(value) {
|
|
2048
|
+
if (typeof value !== "object" || value === null)
|
|
2049
|
+
return null;
|
|
2050
|
+
const d = value;
|
|
2051
|
+
if (d.kind === "navigate") {
|
|
2052
|
+
const url = d.url;
|
|
2053
|
+
if (typeof url !== "string" || url.length === 0 || url.length > 400)
|
|
2054
|
+
return null;
|
|
2055
|
+
if (!url.startsWith("/") || url.startsWith("//"))
|
|
2056
|
+
return null;
|
|
2057
|
+
return { kind: "navigate", url };
|
|
1729
2058
|
}
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
`)}`);
|
|
2059
|
+
if (d.kind === "run" || d.kind === "run_changes") {
|
|
2060
|
+
const runId = d.runId;
|
|
2061
|
+
if (typeof runId !== "string" || !RUN_ID_RE.test(runId))
|
|
2062
|
+
return null;
|
|
2063
|
+
const workflow = d.workflow;
|
|
2064
|
+
if (workflow !== undefined && (typeof workflow !== "string" || workflow.length === 0 || workflow.length > 255)) {
|
|
2065
|
+
return null;
|
|
2066
|
+
}
|
|
2067
|
+
return { kind: d.kind, runId, ...workflow !== undefined ? { workflow } : {} };
|
|
1740
2068
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
2069
|
+
if (d.kind === "preview" || d.kind === "table_file") {
|
|
2070
|
+
const path = validDirectivePath(d.path);
|
|
2071
|
+
if (!path)
|
|
2072
|
+
return null;
|
|
2073
|
+
if (d.kind === "table_file" && !/\.(csv|json)$/i.test(path))
|
|
2074
|
+
return null;
|
|
2075
|
+
return { kind: d.kind, path };
|
|
2076
|
+
}
|
|
2077
|
+
if (d.kind === "file_diff") {
|
|
2078
|
+
const path = validDirectivePath(d.path);
|
|
2079
|
+
if (!path)
|
|
2080
|
+
return null;
|
|
2081
|
+
const { from, to } = d;
|
|
2082
|
+
if (typeof from !== "string" || !REVISION_SELECTOR_RE.test(from))
|
|
2083
|
+
return null;
|
|
2084
|
+
if (typeof to !== "string" || !REVISION_SELECTOR_RE.test(to))
|
|
2085
|
+
return null;
|
|
2086
|
+
return { kind: "file_diff", path, from, to };
|
|
2087
|
+
}
|
|
2088
|
+
if (d.kind === "ask") {
|
|
2089
|
+
const { prompt, approver } = d;
|
|
2090
|
+
if (typeof prompt !== "string" || prompt.length < 1 || prompt.length > ASK_DIRECTIVE_PROMPT_MAX)
|
|
2091
|
+
return null;
|
|
2092
|
+
if (typeof approver !== "string" || approver.length < ASK_APPROVER_MIN || approver.length > ASK_APPROVER_MAX) {
|
|
2093
|
+
return null;
|
|
1751
2094
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
2095
|
+
let options;
|
|
2096
|
+
if (d.options !== undefined) {
|
|
2097
|
+
if (!Array.isArray(d.options) || d.options.length === 0 || d.options.length > ASK_DIRECTIVE_MAX_OPTIONS) {
|
|
2098
|
+
return null;
|
|
2099
|
+
}
|
|
2100
|
+
options = [];
|
|
2101
|
+
for (const raw of d.options) {
|
|
2102
|
+
if (typeof raw !== "object" || raw === null)
|
|
2103
|
+
return null;
|
|
2104
|
+
const { id, label } = raw;
|
|
2105
|
+
if (typeof id !== "string" || id.length < 1 || id.length > ASK_DIRECTIVE_OPTION_ID_MAX)
|
|
2106
|
+
return null;
|
|
2107
|
+
if (typeof label !== "string" || label.length < 1 || label.length > ASK_DIRECTIVE_OPTION_LABEL_MAX) {
|
|
2108
|
+
return null;
|
|
2109
|
+
}
|
|
2110
|
+
options.push({ id, label });
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
return { kind: "ask", prompt, approver, ...options !== undefined ? { options } : {} };
|
|
1754
2114
|
}
|
|
2115
|
+
return null;
|
|
1755
2116
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
2117
|
+
function parseDirectiveMarkers(text) {
|
|
2118
|
+
const out = [];
|
|
2119
|
+
let from = 0;
|
|
2120
|
+
while (out.length < MAX_DIRECTIVES_PER_OUTPUT) {
|
|
2121
|
+
const start = text.indexOf(DIRECTIVE_MARKER_PREFIX, from);
|
|
2122
|
+
if (start < 0)
|
|
2123
|
+
break;
|
|
2124
|
+
const bodyStart = start + DIRECTIVE_MARKER_PREFIX.length;
|
|
2125
|
+
const end = text.indexOf(DIRECTIVE_MARKER_SUFFIX, bodyStart);
|
|
2126
|
+
if (end < 0)
|
|
2127
|
+
break;
|
|
2128
|
+
from = end + DIRECTIVE_MARKER_SUFFIX.length;
|
|
2129
|
+
const body = text.slice(bodyStart, end);
|
|
2130
|
+
if (body.length > 1024)
|
|
2131
|
+
continue;
|
|
2132
|
+
try {
|
|
2133
|
+
const directive = validDirective(JSON.parse(body));
|
|
2134
|
+
if (directive)
|
|
2135
|
+
out.push(directive);
|
|
2136
|
+
} catch {}
|
|
1763
2137
|
}
|
|
1764
|
-
return
|
|
2138
|
+
return out;
|
|
1765
2139
|
}
|
|
1766
|
-
|
|
2140
|
+
// src/runtimes/_acp-client.ts
|
|
2141
|
+
import {
|
|
2142
|
+
ClientSideConnection,
|
|
2143
|
+
RequestError
|
|
2144
|
+
} from "@agentclientprotocol/sdk";
|
|
2145
|
+
|
|
2146
|
+
// src/agent/async-queue.ts
|
|
2147
|
+
class AsyncQueue {
|
|
2148
|
+
buffer = [];
|
|
2149
|
+
resolvers = [];
|
|
2150
|
+
done = false;
|
|
2151
|
+
push(value) {
|
|
2152
|
+
if (this.done)
|
|
2153
|
+
return;
|
|
2154
|
+
const resolve = this.resolvers.shift();
|
|
2155
|
+
if (resolve) {
|
|
2156
|
+
resolve({ value, done: false });
|
|
2157
|
+
} else {
|
|
2158
|
+
this.buffer.push(value);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
close() {
|
|
2162
|
+
if (this.done)
|
|
2163
|
+
return;
|
|
2164
|
+
this.done = true;
|
|
2165
|
+
while (this.resolvers.length > 0) {
|
|
2166
|
+
const resolve = this.resolvers.shift();
|
|
2167
|
+
resolve({ value: undefined, done: true });
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
[Symbol.asyncIterator]() {
|
|
2171
|
+
return {
|
|
2172
|
+
next: () => {
|
|
2173
|
+
const buffered = this.buffer.shift();
|
|
2174
|
+
if (buffered !== undefined)
|
|
2175
|
+
return Promise.resolve({ value: buffered, done: false });
|
|
2176
|
+
if (this.done)
|
|
2177
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
2178
|
+
return new Promise((resolve) => {
|
|
2179
|
+
this.resolvers.push(resolve);
|
|
2180
|
+
});
|
|
2181
|
+
},
|
|
2182
|
+
return: () => {
|
|
2183
|
+
this.close();
|
|
2184
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
2185
|
+
}
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
// src/runtimes/_acp-client.ts
|
|
2191
|
+
function now2() {
|
|
2192
|
+
return new Date().toISOString();
|
|
2193
|
+
}
|
|
2194
|
+
var ACP_PROTOCOL_VERSION = 1;
|
|
2195
|
+
var OPTION_KIND_FOR_VERDICT = {
|
|
2196
|
+
allow: ["allow_once", "allow_always"],
|
|
2197
|
+
deny: ["reject_once", "reject_always"]
|
|
2198
|
+
};
|
|
2199
|
+
function contentBlockText(block) {
|
|
2200
|
+
if (!block)
|
|
2201
|
+
return "";
|
|
2202
|
+
return block.type === "text" ? block.text : "";
|
|
2203
|
+
}
|
|
2204
|
+
function renderToolContent(content) {
|
|
2205
|
+
const parts = [];
|
|
2206
|
+
const diffs = [];
|
|
2207
|
+
for (const block of content ?? []) {
|
|
2208
|
+
if (block.type === "content") {
|
|
2209
|
+
parts.push(contentBlockText(block.content));
|
|
2210
|
+
} else if (block.type === "diff") {
|
|
2211
|
+
diffs.push({ path: block.path, oldText: block.oldText ?? null, newText: block.newText });
|
|
2212
|
+
const header = block.oldText == null ? `--- (new file)
|
|
2213
|
+
+++ ${block.path}` : `--- ${block.path}
|
|
2214
|
+
+++ ${block.path}`;
|
|
2215
|
+
parts.push(`${header}
|
|
2216
|
+
${block.newText}`);
|
|
2217
|
+
} else if (block.type === "terminal") {
|
|
2218
|
+
parts.push(`[terminal ${block.terminalId}]`);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
return { output: parts.filter((p) => p !== "").join(`
|
|
2222
|
+
`), diffs };
|
|
2223
|
+
}
|
|
2224
|
+
function mapLocations(locations) {
|
|
2225
|
+
if (!locations || locations.length === 0)
|
|
2226
|
+
return;
|
|
2227
|
+
return locations.map((l) => l.line != null ? { path: l.path, line: l.line } : { path: l.path });
|
|
2228
|
+
}
|
|
2229
|
+
function terminalToolResult(args) {
|
|
2230
|
+
const { output, diffs } = renderToolContent(args.content);
|
|
2231
|
+
const locations = mapLocations(args.locations);
|
|
2232
|
+
return {
|
|
2233
|
+
type: "tool_result",
|
|
2234
|
+
toolUseId: args.toolCallId,
|
|
2235
|
+
output,
|
|
2236
|
+
isError: args.status === "failed",
|
|
2237
|
+
timestamp: args.timestamp,
|
|
2238
|
+
...diffs.length > 0 ? { diffs } : {},
|
|
2239
|
+
...locations ? { locations } : {}
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
function normalizeSessionUpdate(update) {
|
|
2243
|
+
const ts = now2();
|
|
2244
|
+
switch (update.sessionUpdate) {
|
|
2245
|
+
case "agent_message_chunk":
|
|
2246
|
+
return [{ type: "text", text: contentBlockText(update.content), timestamp: ts }];
|
|
2247
|
+
case "agent_thought_chunk":
|
|
2248
|
+
return [{ type: "thinking", text: contentBlockText(update.content), timestamp: ts }];
|
|
2249
|
+
case "tool_call": {
|
|
2250
|
+
const messages = [{
|
|
2251
|
+
type: "tool_use",
|
|
2252
|
+
toolName: update.title || update.kind || "tool",
|
|
2253
|
+
toolInput: update.rawInput ?? {},
|
|
2254
|
+
toolUseId: update.toolCallId,
|
|
2255
|
+
timestamp: ts
|
|
2256
|
+
}];
|
|
2257
|
+
if (update.status === "completed" || update.status === "failed") {
|
|
2258
|
+
messages.push(terminalToolResult({
|
|
2259
|
+
toolCallId: update.toolCallId,
|
|
2260
|
+
status: update.status,
|
|
2261
|
+
content: update.content,
|
|
2262
|
+
locations: update.locations,
|
|
2263
|
+
timestamp: ts
|
|
2264
|
+
}));
|
|
2265
|
+
}
|
|
2266
|
+
return messages;
|
|
2267
|
+
}
|
|
2268
|
+
case "tool_call_update": {
|
|
2269
|
+
if (update.status !== "completed" && update.status !== "failed")
|
|
2270
|
+
return [];
|
|
2271
|
+
return [terminalToolResult({
|
|
2272
|
+
toolCallId: update.toolCallId,
|
|
2273
|
+
status: update.status,
|
|
2274
|
+
content: update.content,
|
|
2275
|
+
locations: update.locations,
|
|
2276
|
+
timestamp: ts
|
|
2277
|
+
})];
|
|
2278
|
+
}
|
|
2279
|
+
case "plan":
|
|
2280
|
+
return [{
|
|
2281
|
+
type: "plan",
|
|
2282
|
+
entries: update.entries.map((e) => ({ content: e.content, priority: e.priority, status: e.status })),
|
|
2283
|
+
timestamp: ts
|
|
2284
|
+
}];
|
|
2285
|
+
case "user_message_chunk":
|
|
2286
|
+
case "available_commands_update":
|
|
2287
|
+
case "current_mode_update":
|
|
2288
|
+
case "usage_update":
|
|
2289
|
+
case "plan_update":
|
|
2290
|
+
case "plan_removed":
|
|
2291
|
+
case "config_option_update":
|
|
2292
|
+
case "session_info_update":
|
|
2293
|
+
return [];
|
|
2294
|
+
default:
|
|
2295
|
+
return [];
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
class AcpClientPeer {
|
|
2300
|
+
deps;
|
|
2301
|
+
conn;
|
|
2302
|
+
updates = new AsyncQueue;
|
|
2303
|
+
sessionId;
|
|
2304
|
+
pendingPause = null;
|
|
2305
|
+
cancelled = false;
|
|
2306
|
+
abortPrompt = null;
|
|
2307
|
+
constructor(deps) {
|
|
2308
|
+
this.deps = deps;
|
|
2309
|
+
const handler = {
|
|
2310
|
+
sessionUpdate: (params) => {
|
|
2311
|
+
if (params.update.sessionUpdate === "available_commands_update") {
|
|
2312
|
+
this.deps.onAvailableCommands?.(params.update.availableCommands.map((c) => ({
|
|
2313
|
+
name: c.name,
|
|
2314
|
+
description: c.description
|
|
2315
|
+
})));
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
for (const msg of normalizeSessionUpdate(params.update))
|
|
2319
|
+
this.updates.push(msg);
|
|
2320
|
+
},
|
|
2321
|
+
requestPermission: (params) => this.onRequestPermission(params)
|
|
2322
|
+
};
|
|
2323
|
+
this.conn = new ClientSideConnection((_agent) => handler, this.deps.stream);
|
|
2324
|
+
if (this.deps.signal) {
|
|
2325
|
+
if (this.deps.signal.aborted)
|
|
2326
|
+
this.cancel();
|
|
2327
|
+
else
|
|
2328
|
+
this.deps.signal.addEventListener("abort", () => this.cancel(), { once: true });
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
async initialize() {
|
|
2332
|
+
const res = await this.conn.initialize({
|
|
2333
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
2334
|
+
clientCapabilities: {
|
|
2335
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
2336
|
+
terminal: false
|
|
2337
|
+
}
|
|
2338
|
+
});
|
|
2339
|
+
return {
|
|
2340
|
+
protocolVersion: res.protocolVersion,
|
|
2341
|
+
caps: {
|
|
2342
|
+
loadSession: res.agentCapabilities?.loadSession ?? false,
|
|
2343
|
+
resume: res.agentCapabilities?.sessionCapabilities?.resume != null
|
|
2344
|
+
}
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
async startSession(cwd) {
|
|
2348
|
+
const res = await this.conn.newSession({ cwd, mcpServers: [] });
|
|
2349
|
+
this.sessionId = res.sessionId;
|
|
2350
|
+
return res.sessionId;
|
|
2351
|
+
}
|
|
2352
|
+
async resumeSession(cwd, acpSessionId) {
|
|
2353
|
+
await this.conn.resumeSession({ sessionId: acpSessionId, cwd, mcpServers: [] });
|
|
2354
|
+
this.sessionId = acpSessionId;
|
|
2355
|
+
}
|
|
2356
|
+
async* prompt(text) {
|
|
2357
|
+
if (!this.sessionId)
|
|
2358
|
+
throw new Error("AcpClientPeer.prompt called before startSession");
|
|
2359
|
+
const updates = new AsyncQueue;
|
|
2360
|
+
this.updates = updates;
|
|
2361
|
+
const aborted = new Promise((resolve) => {
|
|
2362
|
+
this.abortPrompt = () => {
|
|
2363
|
+
resolve();
|
|
2364
|
+
updates.close();
|
|
2365
|
+
};
|
|
2366
|
+
});
|
|
2367
|
+
if (this.cancelled)
|
|
2368
|
+
this.abortPrompt();
|
|
2369
|
+
const promptPromise = this.conn.prompt({ sessionId: this.sessionId, prompt: [{ type: "text", text }] }).then((res) => {
|
|
2370
|
+
updates.close();
|
|
2371
|
+
return { ok: true, res };
|
|
2372
|
+
}, (err) => {
|
|
2373
|
+
updates.close();
|
|
2374
|
+
return { ok: false, err };
|
|
2375
|
+
});
|
|
2376
|
+
for await (const msg of updates) {
|
|
2377
|
+
yield msg;
|
|
2378
|
+
}
|
|
2379
|
+
if (this.pendingPause !== null) {
|
|
2380
|
+
const pause = this.pendingPause;
|
|
2381
|
+
this.pendingPause = null;
|
|
2382
|
+
this.abortPrompt = null;
|
|
2383
|
+
throw pause;
|
|
2384
|
+
}
|
|
2385
|
+
const settled = await Promise.race([
|
|
2386
|
+
promptPromise.then((o) => ({ kind: "prompt", o })),
|
|
2387
|
+
aborted.then(() => ({ kind: "aborted" }))
|
|
2388
|
+
]);
|
|
2389
|
+
this.abortPrompt = null;
|
|
2390
|
+
if (settled.kind === "aborted")
|
|
2391
|
+
return;
|
|
2392
|
+
const outcome = settled.o;
|
|
2393
|
+
if (!outcome.ok) {
|
|
2394
|
+
yield { type: "error", text: formatError(outcome.err), timestamp: now2() };
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
const stopReason = outcome.res.stopReason;
|
|
2398
|
+
if (stopReason === "refusal") {
|
|
2399
|
+
yield { type: "error", text: "agent refused the request", timestamp: now2() };
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
const usage = outcome.res.usage;
|
|
2403
|
+
if (usage) {
|
|
2404
|
+
yield {
|
|
2405
|
+
type: "usage",
|
|
2406
|
+
inputTokens: usage.inputTokens,
|
|
2407
|
+
outputTokens: usage.outputTokens,
|
|
2408
|
+
cacheReadTokens: usage.cachedReadTokens ?? 0,
|
|
2409
|
+
cacheCreationTokens: usage.cachedWriteTokens ?? 0,
|
|
2410
|
+
durationMs: 0,
|
|
2411
|
+
numTurns: 1,
|
|
2412
|
+
timestamp: now2()
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
get currentSessionId() {
|
|
2417
|
+
return this.sessionId;
|
|
2418
|
+
}
|
|
2419
|
+
cancel() {
|
|
2420
|
+
if (this.cancelled) {
|
|
2421
|
+
this.abortPrompt?.();
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
this.cancelled = true;
|
|
2425
|
+
if (this.sessionId) {
|
|
2426
|
+
this.conn.cancel({ sessionId: this.sessionId }).catch(() => {});
|
|
2427
|
+
}
|
|
2428
|
+
this.abortPrompt?.();
|
|
2429
|
+
}
|
|
2430
|
+
close() {
|
|
2431
|
+
this.abortPrompt?.();
|
|
2432
|
+
this.updates.close();
|
|
2433
|
+
}
|
|
2434
|
+
async onRequestPermission(params) {
|
|
2435
|
+
if (this.cancelled || this.deps.signal?.aborted) {
|
|
2436
|
+
return { outcome: { outcome: "cancelled" } };
|
|
2437
|
+
}
|
|
2438
|
+
const tc = params.toolCall;
|
|
2439
|
+
const call = {
|
|
2440
|
+
toolName: tc.title || tc.kind || "tool",
|
|
2441
|
+
toolInput: tc.rawInput ?? {},
|
|
2442
|
+
toolUseId: tc.toolCallId
|
|
2443
|
+
};
|
|
2444
|
+
let verdict;
|
|
2445
|
+
try {
|
|
2446
|
+
verdict = await this.deps.gateToolCall(call, this.deps.procCtx());
|
|
2447
|
+
} catch (err) {
|
|
2448
|
+
if (isPauseSignal(err)) {
|
|
2449
|
+
this.pendingPause = err;
|
|
2450
|
+
this.cancel();
|
|
2451
|
+
return { outcome: { outcome: "cancelled" } };
|
|
2452
|
+
}
|
|
2453
|
+
throw err;
|
|
2454
|
+
}
|
|
2455
|
+
if (verdict.kind === "abort") {
|
|
2456
|
+
this.cancel();
|
|
2457
|
+
return { outcome: { outcome: "cancelled" } };
|
|
2458
|
+
}
|
|
2459
|
+
const wantedKinds = verdict.kind === "allow" ? OPTION_KIND_FOR_VERDICT.allow : OPTION_KIND_FOR_VERDICT.deny;
|
|
2460
|
+
const option = params.options.find((o) => wantedKinds.includes(o.kind)) ?? params.options[0];
|
|
2461
|
+
if (!option) {
|
|
2462
|
+
return { outcome: { outcome: "cancelled" } };
|
|
2463
|
+
}
|
|
2464
|
+
return { outcome: { outcome: "selected", optionId: option.optionId } };
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
// src/utils/bundler.ts
|
|
2468
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2469
|
+
import { parse as babelParse } from "@babel/parser";
|
|
2470
|
+
|
|
2471
|
+
// src/utils/source-loader.ts
|
|
2472
|
+
import { dirname } from "path";
|
|
2473
|
+
import { writeFile, rm, mkdir } from "node:fs/promises";
|
|
2474
|
+
var TMP_DIR = process.env.TMP_DIR ?? "/tmp/agent-compose";
|
|
2475
|
+
var LATEST_VERSION = "$LATEST";
|
|
2476
|
+
async function importSourceModule(source, tmpPath) {
|
|
2477
|
+
await mkdir(dirname(tmpPath), { recursive: true });
|
|
2478
|
+
await writeFile(tmpPath, source, "utf8");
|
|
2479
|
+
const mod = await import(tmpPath);
|
|
2480
|
+
const cleanup = () => rm(tmpPath, { force: true });
|
|
2481
|
+
return { mod, cleanup };
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
// src/types/workflow-plan.ts
|
|
2485
|
+
function workflowPlan(steps) {
|
|
2486
|
+
return { steps };
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
// src/utils/bundler.ts
|
|
2490
|
+
var BUNDLER_VERSION = 2;
|
|
2491
|
+
|
|
2492
|
+
class WorkflowSourceValidationError extends Error {
|
|
2493
|
+
constructor(message) {
|
|
2494
|
+
super(message);
|
|
2495
|
+
this.name = "WorkflowSourceValidationError";
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
async function bundle(path, label) {
|
|
2499
|
+
const bun = globalThis;
|
|
2500
|
+
if (!bun.Bun?.build)
|
|
2501
|
+
throw new Error("bundleWorkflow requires the Bun runtime (Bun.build)");
|
|
2502
|
+
const result = await bun.Bun.build({ entrypoints: [path], format: "esm", target: "node" });
|
|
2503
|
+
if (!result.success) {
|
|
2504
|
+
throw new Error(`Failed to bundle ${label}:
|
|
2505
|
+
${result.logs.map((l) => l.message).join(`
|
|
2506
|
+
`)}`);
|
|
2507
|
+
}
|
|
2508
|
+
return result.outputs[0].text();
|
|
2509
|
+
}
|
|
2510
|
+
async function extractFromBundle(source, label, pick) {
|
|
2511
|
+
const tmpPath = `/tmp/agentc-${label}-${Date.now()}-${Math.random().toString(36).slice(2)}.js`;
|
|
2512
|
+
try {
|
|
2513
|
+
const loaded = await importSourceModule(source, tmpPath);
|
|
2514
|
+
try {
|
|
2515
|
+
return pick(loaded.mod.default);
|
|
2516
|
+
} finally {
|
|
2517
|
+
await loaded.cleanup();
|
|
2518
|
+
}
|
|
2519
|
+
} catch (err) {
|
|
2520
|
+
throw new WorkflowSourceValidationError(`Bundled ${label} could not be evaluated: ${err instanceof Error ? err.message : String(err)}`);
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
var DEFINE_WORKFLOW_CALLEE = /^(?:defineWorkflow|defineSandboxEnvironment)\d*$/;
|
|
2524
|
+
function isDefineWorkflowCall(node) {
|
|
2525
|
+
if (node.callee.type === "Identifier") {
|
|
2526
|
+
return DEFINE_WORKFLOW_CALLEE.test(node.callee.name);
|
|
2527
|
+
}
|
|
2528
|
+
if (node.callee.type === "MemberExpression" && node.callee.object.type === "CallExpression") {
|
|
2529
|
+
return isDefineWorkflowCall(node.callee.object);
|
|
2530
|
+
}
|
|
2531
|
+
return false;
|
|
2532
|
+
}
|
|
2533
|
+
function assertDefaultExportIsDefineWorkflow(source, label) {
|
|
1767
2534
|
let ast;
|
|
1768
2535
|
try {
|
|
1769
2536
|
ast = babelParse(source, {
|
|
@@ -1809,6 +2576,19 @@ function findDefaultExportExpression(body) {
|
|
|
1809
2576
|
function sha256(text) {
|
|
1810
2577
|
return createHash2("sha256").update(text, "utf8").digest("hex");
|
|
1811
2578
|
}
|
|
2579
|
+
function extractWorkflowPlan(workflow) {
|
|
2580
|
+
return workflowPlan(workflow.steps.map((step, index) => ({
|
|
2581
|
+
index,
|
|
2582
|
+
name: step.name,
|
|
2583
|
+
...step.summary !== undefined ? { summary: step.summary } : {},
|
|
2584
|
+
...step.deliverables?.length ? {
|
|
2585
|
+
deliverables: step.deliverables.map((d) => ({
|
|
2586
|
+
path: d.path,
|
|
2587
|
+
...d.description !== undefined ? { description: d.description } : {}
|
|
2588
|
+
}))
|
|
2589
|
+
} : {}
|
|
2590
|
+
})));
|
|
2591
|
+
}
|
|
1812
2592
|
async function bundleWorkflow(workflowPath, overrides) {
|
|
1813
2593
|
const label = `"${workflowPath}"`;
|
|
1814
2594
|
const source = await bundle(workflowPath, `workflow ${label}`);
|
|
@@ -1818,7 +2598,7 @@ async function bundleWorkflow(workflowPath, overrides) {
|
|
|
1818
2598
|
throw new WorkflowSourceValidationError(`Bundled workflow ${label} default export is not a Workflow. ` + `Wrap your workflow with \`defineWorkflow\` from "@agent-compose/sdk" — ` + `raw \`export default async function\` is no longer supported.`);
|
|
1819
2599
|
}
|
|
1820
2600
|
const { metadata } = workflow;
|
|
1821
|
-
const plan =
|
|
2601
|
+
const plan = extractWorkflowPlan(workflow);
|
|
1822
2602
|
const manifest = {
|
|
1823
2603
|
definitionBrand: true,
|
|
1824
2604
|
sourceHash: sha256(source),
|
|
@@ -1837,6 +2617,7 @@ async function bundleWorkflow(workflowPath, overrides) {
|
|
|
1837
2617
|
...outputSchema !== undefined ? { outputSchema } : {},
|
|
1838
2618
|
...metadata.snapshots !== undefined ? { snapshots: metadata.snapshots } : {},
|
|
1839
2619
|
...metadata.resources !== undefined ? { resources: metadata.resources } : {},
|
|
2620
|
+
...metadata.mergePolicy !== undefined ? { mergePolicy: metadata.mergePolicy } : {},
|
|
1840
2621
|
...metadata.connectors !== undefined ? { connectors: metadata.connectors } : {},
|
|
1841
2622
|
...metadata.connectorOperation !== undefined ? { connectorOperation: metadata.connectorOperation } : {},
|
|
1842
2623
|
...metadata.invokePolicy !== undefined ? { invokePolicy: metadata.invokePolicy } : {},
|
|
@@ -2107,6 +2888,10 @@ function parseAgentStatus(text) {
|
|
|
2107
2888
|
}
|
|
2108
2889
|
}
|
|
2109
2890
|
var DEFAULT_ALLOWED_TOOLS = ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebFetch"];
|
|
2891
|
+
function runtimeJsonSchema(schema) {
|
|
2892
|
+
const { $schema: _$schema, ...rest } = z10.toJSONSchema(schema);
|
|
2893
|
+
return rest;
|
|
2894
|
+
}
|
|
2110
2895
|
function truncate(value) {
|
|
2111
2896
|
return value.length > MESSAGE_PREVIEW_CHARS ? `${value.slice(0, MESSAGE_PREVIEW_CHARS)}…` : value;
|
|
2112
2897
|
}
|
|
@@ -2184,7 +2969,7 @@ async function agentLoop(opts) {
|
|
|
2184
2969
|
requestContext,
|
|
2185
2970
|
agentId,
|
|
2186
2971
|
...opts.pause ? { pause: opts.pause } : {},
|
|
2187
|
-
...opts.responseSchema ? { outputFormat: { type: "json_schema", schema:
|
|
2972
|
+
...opts.responseSchema ? { outputFormat: { type: "json_schema", schema: runtimeJsonSchema(opts.responseSchema) } } : {}
|
|
2188
2973
|
});
|
|
2189
2974
|
if (processors.some((p) => p.processToolCall) && !client.supportsToolCallProcessor) {
|
|
2190
2975
|
process.stderr.write(`${logLabel} processToolCall registered but runtime does not support pre-tool-use gating — hook is dormant
|
|
@@ -2318,6 +3103,8 @@ Re-emit the COMPLETE corrected <response> JSON now: every required field present
|
|
|
2318
3103
|
signal: loopAbort.signal,
|
|
2319
3104
|
...opts.inbox ? { inboxStream: opts.inbox } : {}
|
|
2320
3105
|
})) {
|
|
3106
|
+
if (rawMsg.type === "text_delta" || rawMsg.type === "usage_delta")
|
|
3107
|
+
continue;
|
|
2321
3108
|
const outputVerdict = await runProcessorChain(processors, (p) => p.processOutput, rawMsg, procCtx);
|
|
2322
3109
|
if (outputVerdict.kind === "abort") {
|
|
2323
3110
|
loopAbort.abort();
|
|
@@ -2329,6 +3116,8 @@ Re-emit the COMPLETE corrected <response> JSON now: every required field present
|
|
|
2329
3116
|
continue;
|
|
2330
3117
|
}
|
|
2331
3118
|
const msg = outputVerdict.value;
|
|
3119
|
+
if (msg.type === "text_delta" || msg.type === "usage_delta")
|
|
3120
|
+
continue;
|
|
2332
3121
|
opts.onAgentEvent?.(iteration, msg);
|
|
2333
3122
|
const summary = summarizeAgentMessage(msg);
|
|
2334
3123
|
opts.onAgentLifecycleEvent?.({
|
|
@@ -2456,74 +3245,30 @@ raw: ${JSON.stringify(rawResponse).slice(0, 400)}
|
|
|
2456
3245
|
await writeSettledState(result);
|
|
2457
3246
|
opts.onAgentLifecycleEvent?.({ event: "agent.settled", at: Date.now(), agentId, label, outcome: "success", iterations: maxIterations, durationMs: Date.now() - startedAt });
|
|
2458
3247
|
return result;
|
|
2459
|
-
} catch (err) {
|
|
2460
|
-
if (isPauseSignal(err))
|
|
2461
|
-
throw err;
|
|
2462
|
-
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) });
|
|
2463
|
-
throw err;
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
function parseRawJsonResponse(text) {
|
|
2467
|
-
const trimmed = text.trim();
|
|
2468
|
-
if (!trimmed.startsWith("{") || !trimmed.endsWith("}"))
|
|
2469
|
-
return null;
|
|
2470
|
-
try {
|
|
2471
|
-
return JSON.parse(trimmed);
|
|
2472
|
-
} catch {
|
|
2473
|
-
return null;
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
|
-
// src/agent/async-queue.ts
|
|
2478
|
-
class AsyncQueue {
|
|
2479
|
-
buffer = [];
|
|
2480
|
-
resolvers = [];
|
|
2481
|
-
done = false;
|
|
2482
|
-
push(value) {
|
|
2483
|
-
if (this.done)
|
|
2484
|
-
return;
|
|
2485
|
-
const resolve = this.resolvers.shift();
|
|
2486
|
-
if (resolve) {
|
|
2487
|
-
resolve({ value, done: false });
|
|
2488
|
-
} else {
|
|
2489
|
-
this.buffer.push(value);
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
close() {
|
|
2493
|
-
if (this.done)
|
|
2494
|
-
return;
|
|
2495
|
-
this.done = true;
|
|
2496
|
-
while (this.resolvers.length > 0) {
|
|
2497
|
-
const resolve = this.resolvers.shift();
|
|
2498
|
-
resolve({ value: undefined, done: true });
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
[Symbol.asyncIterator]() {
|
|
2502
|
-
return {
|
|
2503
|
-
next: () => {
|
|
2504
|
-
const buffered = this.buffer.shift();
|
|
2505
|
-
if (buffered !== undefined)
|
|
2506
|
-
return Promise.resolve({ value: buffered, done: false });
|
|
2507
|
-
if (this.done)
|
|
2508
|
-
return Promise.resolve({ value: undefined, done: true });
|
|
2509
|
-
return new Promise((resolve) => {
|
|
2510
|
-
this.resolvers.push(resolve);
|
|
2511
|
-
});
|
|
2512
|
-
},
|
|
2513
|
-
return: () => {
|
|
2514
|
-
this.close();
|
|
2515
|
-
return Promise.resolve({ value: undefined, done: true });
|
|
2516
|
-
}
|
|
2517
|
-
};
|
|
3248
|
+
} catch (err) {
|
|
3249
|
+
if (isPauseSignal(err))
|
|
3250
|
+
throw err;
|
|
3251
|
+
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) });
|
|
3252
|
+
throw err;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
function parseRawJsonResponse(text) {
|
|
3256
|
+
const trimmed = text.trim();
|
|
3257
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}"))
|
|
3258
|
+
return null;
|
|
3259
|
+
try {
|
|
3260
|
+
return JSON.parse(trimmed);
|
|
3261
|
+
} catch {
|
|
3262
|
+
return null;
|
|
2518
3263
|
}
|
|
2519
3264
|
}
|
|
2520
3265
|
|
|
2521
3266
|
// src/runtimes/claude.ts
|
|
2522
|
-
function
|
|
3267
|
+
function now3() {
|
|
2523
3268
|
return new Date().toISOString();
|
|
2524
3269
|
}
|
|
2525
3270
|
function translateMessage(message) {
|
|
2526
|
-
const ts =
|
|
3271
|
+
const ts = now3();
|
|
2527
3272
|
const msgs = [];
|
|
2528
3273
|
if (message.type === "system" && message.subtype === "init") {
|
|
2529
3274
|
msgs.push({ type: "init", sessionId: String(message.session_id ?? ""), timestamp: ts });
|
|
@@ -2597,6 +3342,22 @@ function resolveThinking(model, thinking) {
|
|
|
2597
3342
|
return;
|
|
2598
3343
|
return thinking;
|
|
2599
3344
|
}
|
|
3345
|
+
var STDERR_TAIL_CAP = 2000;
|
|
3346
|
+
|
|
3347
|
+
class StderrTail {
|
|
3348
|
+
buf = "";
|
|
3349
|
+
append(data) {
|
|
3350
|
+
this.buf = (this.buf + data).slice(-STDERR_TAIL_CAP);
|
|
3351
|
+
}
|
|
3352
|
+
get text() {
|
|
3353
|
+
return this.buf.trim();
|
|
3354
|
+
}
|
|
3355
|
+
decorate(message) {
|
|
3356
|
+
return this.text ? `${message}
|
|
3357
|
+
stderr tail:
|
|
3358
|
+
${this.text}` : message;
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
2600
3361
|
var DEFAULT_CLAUDE_PATH = "/usr/local/bin/claude";
|
|
2601
3362
|
|
|
2602
3363
|
class ClaudeRunner {
|
|
@@ -2683,6 +3444,7 @@ class ClaudeRunner {
|
|
|
2683
3444
|
const systemPromptAppend = [this.config.claudeMdContent, this.options.agentManual].filter((s) => Boolean(s && s.trim())).join(`
|
|
2684
3445
|
|
|
2685
3446
|
`);
|
|
3447
|
+
const stderrTail = new StderrTail;
|
|
2686
3448
|
try {
|
|
2687
3449
|
let emittedAssistantText = false;
|
|
2688
3450
|
for await (const message of query({
|
|
@@ -2697,6 +3459,7 @@ class ClaudeRunner {
|
|
|
2697
3459
|
effort: this.config.effort,
|
|
2698
3460
|
cwd: this.options.cwd,
|
|
2699
3461
|
abortController: queryAbort,
|
|
3462
|
+
stderr: (data) => stderrTail.append(data),
|
|
2700
3463
|
env: { ...process.env, ...this.config.env ?? {} },
|
|
2701
3464
|
pathToClaudeCodeExecutable: this.config.pathToClaudeCodeExecutable ?? process.env.CLAUDE_CODE_EXECUTABLE ?? DEFAULT_CLAUDE_PATH,
|
|
2702
3465
|
...systemPromptAppend ? { systemPrompt: { type: "preset", preset: "claude_code", append: systemPromptAppend } } : {},
|
|
@@ -2714,7 +3477,7 @@ class ClaudeRunner {
|
|
|
2714
3477
|
continue;
|
|
2715
3478
|
if (msg.type === "text" && raw.type === "assistant")
|
|
2716
3479
|
emittedAssistantText = true;
|
|
2717
|
-
yield msg;
|
|
3480
|
+
yield msg.type === "error" ? { ...msg, text: stderrTail.decorate(msg.text) } : msg;
|
|
2718
3481
|
}
|
|
2719
3482
|
if (raw.type === "result" && inboxQueue)
|
|
2720
3483
|
inboxQueue.close();
|
|
@@ -2722,9 +3485,9 @@ class ClaudeRunner {
|
|
|
2722
3485
|
} catch (err) {
|
|
2723
3486
|
const text = formatError(err);
|
|
2724
3487
|
if (/maximum number of turns/i.test(text)) {
|
|
2725
|
-
yield { type: "done", sessionId: opts.sessionId ?? "", timestamp:
|
|
3488
|
+
yield { type: "done", sessionId: opts.sessionId ?? "", timestamp: now3() };
|
|
2726
3489
|
} else {
|
|
2727
|
-
yield { type: "error", text, timestamp:
|
|
3490
|
+
yield { type: "error", text: stderrTail.decorate(text), timestamp: now3() };
|
|
2728
3491
|
}
|
|
2729
3492
|
} finally {
|
|
2730
3493
|
inboxQueue?.close();
|
|
@@ -2736,274 +3499,117 @@ function createClaudeRuntime(config = {}) {
|
|
|
2736
3499
|
return defineRuntime({
|
|
2737
3500
|
create: (sandbox, opts) => new ClaudeRunner(sandbox, opts, config)
|
|
2738
3501
|
});
|
|
2739
|
-
}
|
|
2740
|
-
var claude_default = createClaudeRuntime();
|
|
2741
|
-
// src/runtimes/_cli-agent.ts
|
|
2742
|
-
import { ndJsonStream } from "@agentclientprotocol/sdk";
|
|
2743
|
-
|
|
2744
|
-
// src/runtimes/
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
};
|
|
2757
|
-
function contentBlockText(block) {
|
|
2758
|
-
if (!block)
|
|
2759
|
-
return "";
|
|
2760
|
-
return block.type === "text" ? block.text : "";
|
|
2761
|
-
}
|
|
2762
|
-
function renderToolContent(content) {
|
|
2763
|
-
const parts = [];
|
|
2764
|
-
const diffs = [];
|
|
2765
|
-
for (const block of content ?? []) {
|
|
2766
|
-
if (block.type === "content") {
|
|
2767
|
-
parts.push(contentBlockText(block.content));
|
|
2768
|
-
} else if (block.type === "diff") {
|
|
2769
|
-
diffs.push({ path: block.path, oldText: block.oldText ?? null, newText: block.newText });
|
|
2770
|
-
const header = block.oldText == null ? `--- (new file)
|
|
2771
|
-
+++ ${block.path}` : `--- ${block.path}
|
|
2772
|
-
+++ ${block.path}`;
|
|
2773
|
-
parts.push(`${header}
|
|
2774
|
-
${block.newText}`);
|
|
2775
|
-
} else if (block.type === "terminal") {
|
|
2776
|
-
parts.push(`[terminal ${block.terminalId}]`);
|
|
2777
|
-
}
|
|
2778
|
-
}
|
|
2779
|
-
return { output: parts.filter((p) => p !== "").join(`
|
|
2780
|
-
`), diffs };
|
|
2781
|
-
}
|
|
2782
|
-
function mapLocations(locations) {
|
|
2783
|
-
if (!locations || locations.length === 0)
|
|
2784
|
-
return;
|
|
2785
|
-
return locations.map((l) => l.line != null ? { path: l.path, line: l.line } : { path: l.path });
|
|
2786
|
-
}
|
|
2787
|
-
function terminalToolResult(args) {
|
|
2788
|
-
const { output, diffs } = renderToolContent(args.content);
|
|
2789
|
-
const locations = mapLocations(args.locations);
|
|
2790
|
-
return {
|
|
2791
|
-
type: "tool_result",
|
|
2792
|
-
toolUseId: args.toolCallId,
|
|
2793
|
-
output,
|
|
2794
|
-
isError: args.status === "failed",
|
|
2795
|
-
timestamp: args.timestamp,
|
|
2796
|
-
...diffs.length > 0 ? { diffs } : {},
|
|
2797
|
-
...locations ? { locations } : {}
|
|
2798
|
-
};
|
|
2799
|
-
}
|
|
2800
|
-
function normalizeSessionUpdate(update) {
|
|
2801
|
-
const ts = now3();
|
|
2802
|
-
switch (update.sessionUpdate) {
|
|
2803
|
-
case "agent_message_chunk":
|
|
2804
|
-
return [{ type: "text", text: contentBlockText(update.content), timestamp: ts }];
|
|
2805
|
-
case "agent_thought_chunk":
|
|
2806
|
-
return [{ type: "thinking", text: contentBlockText(update.content), timestamp: ts }];
|
|
2807
|
-
case "tool_call": {
|
|
2808
|
-
const messages = [{
|
|
2809
|
-
type: "tool_use",
|
|
2810
|
-
toolName: update.title || update.kind || "tool",
|
|
2811
|
-
toolInput: update.rawInput ?? {},
|
|
2812
|
-
toolUseId: update.toolCallId,
|
|
2813
|
-
timestamp: ts
|
|
2814
|
-
}];
|
|
2815
|
-
if (update.status === "completed" || update.status === "failed") {
|
|
2816
|
-
messages.push(terminalToolResult({
|
|
2817
|
-
toolCallId: update.toolCallId,
|
|
2818
|
-
status: update.status,
|
|
2819
|
-
content: update.content,
|
|
2820
|
-
locations: update.locations,
|
|
2821
|
-
timestamp: ts
|
|
2822
|
-
}));
|
|
2823
|
-
}
|
|
2824
|
-
return messages;
|
|
2825
|
-
}
|
|
2826
|
-
case "tool_call_update": {
|
|
2827
|
-
if (update.status !== "completed" && update.status !== "failed")
|
|
2828
|
-
return [];
|
|
2829
|
-
return [terminalToolResult({
|
|
2830
|
-
toolCallId: update.toolCallId,
|
|
2831
|
-
status: update.status,
|
|
2832
|
-
content: update.content,
|
|
2833
|
-
locations: update.locations,
|
|
2834
|
-
timestamp: ts
|
|
2835
|
-
})];
|
|
2836
|
-
}
|
|
2837
|
-
case "plan":
|
|
2838
|
-
return [{
|
|
2839
|
-
type: "plan",
|
|
2840
|
-
entries: update.entries.map((e) => ({ content: e.content, priority: e.priority, status: e.status })),
|
|
2841
|
-
timestamp: ts
|
|
2842
|
-
}];
|
|
2843
|
-
case "user_message_chunk":
|
|
2844
|
-
case "available_commands_update":
|
|
2845
|
-
case "current_mode_update":
|
|
2846
|
-
case "usage_update":
|
|
2847
|
-
case "plan_update":
|
|
2848
|
-
case "plan_removed":
|
|
2849
|
-
case "config_option_update":
|
|
2850
|
-
case "session_info_update":
|
|
2851
|
-
return [];
|
|
2852
|
-
default:
|
|
2853
|
-
return [];
|
|
2854
|
-
}
|
|
2855
|
-
}
|
|
2856
|
-
|
|
2857
|
-
class AcpClientPeer {
|
|
2858
|
-
deps;
|
|
2859
|
-
conn;
|
|
2860
|
-
updates = new AsyncQueue;
|
|
2861
|
-
sessionId;
|
|
2862
|
-
pendingPause = null;
|
|
2863
|
-
cancelled = false;
|
|
2864
|
-
abortPrompt = null;
|
|
2865
|
-
constructor(deps) {
|
|
2866
|
-
this.deps = deps;
|
|
2867
|
-
const handler = {
|
|
2868
|
-
sessionUpdate: (params) => {
|
|
2869
|
-
for (const msg of normalizeSessionUpdate(params.update))
|
|
2870
|
-
this.updates.push(msg);
|
|
2871
|
-
},
|
|
2872
|
-
requestPermission: (params) => this.onRequestPermission(params)
|
|
2873
|
-
};
|
|
2874
|
-
this.conn = new ClientSideConnection((_agent) => handler, this.deps.stream);
|
|
2875
|
-
if (this.deps.signal) {
|
|
2876
|
-
if (this.deps.signal.aborted)
|
|
2877
|
-
this.cancel();
|
|
2878
|
-
else
|
|
2879
|
-
this.deps.signal.addEventListener("abort", () => this.cancel(), { once: true });
|
|
2880
|
-
}
|
|
2881
|
-
}
|
|
2882
|
-
async initialize() {
|
|
2883
|
-
const res = await this.conn.initialize({
|
|
2884
|
-
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
2885
|
-
clientCapabilities: {
|
|
2886
|
-
fs: { readTextFile: false, writeTextFile: false },
|
|
2887
|
-
terminal: false
|
|
2888
|
-
}
|
|
2889
|
-
});
|
|
2890
|
-
return { protocolVersion: res.protocolVersion };
|
|
2891
|
-
}
|
|
2892
|
-
async startSession(cwd) {
|
|
2893
|
-
const res = await this.conn.newSession({ cwd, mcpServers: [] });
|
|
2894
|
-
this.sessionId = res.sessionId;
|
|
2895
|
-
return res.sessionId;
|
|
2896
|
-
}
|
|
2897
|
-
async* prompt(text) {
|
|
2898
|
-
if (!this.sessionId)
|
|
2899
|
-
throw new Error("AcpClientPeer.prompt called before startSession");
|
|
2900
|
-
const updates = new AsyncQueue;
|
|
2901
|
-
this.updates = updates;
|
|
2902
|
-
const aborted = new Promise((resolve) => {
|
|
2903
|
-
this.abortPrompt = () => {
|
|
2904
|
-
resolve();
|
|
2905
|
-
updates.close();
|
|
2906
|
-
};
|
|
2907
|
-
});
|
|
2908
|
-
if (this.cancelled)
|
|
2909
|
-
this.abortPrompt();
|
|
2910
|
-
const promptPromise = this.conn.prompt({ sessionId: this.sessionId, prompt: [{ type: "text", text }] }).then((res) => {
|
|
2911
|
-
updates.close();
|
|
2912
|
-
return { ok: true, res };
|
|
2913
|
-
}, (err) => {
|
|
2914
|
-
updates.close();
|
|
2915
|
-
return { ok: false, err };
|
|
2916
|
-
});
|
|
2917
|
-
for await (const msg of updates) {
|
|
2918
|
-
yield msg;
|
|
2919
|
-
}
|
|
2920
|
-
if (this.pendingPause !== null) {
|
|
2921
|
-
const pause = this.pendingPause;
|
|
2922
|
-
this.pendingPause = null;
|
|
2923
|
-
this.abortPrompt = null;
|
|
2924
|
-
throw pause;
|
|
2925
|
-
}
|
|
2926
|
-
const settled = await Promise.race([
|
|
2927
|
-
promptPromise.then((o) => ({ kind: "prompt", o })),
|
|
2928
|
-
aborted.then(() => ({ kind: "aborted" }))
|
|
2929
|
-
]);
|
|
2930
|
-
this.abortPrompt = null;
|
|
2931
|
-
if (settled.kind === "aborted")
|
|
2932
|
-
return;
|
|
2933
|
-
const outcome = settled.o;
|
|
2934
|
-
if (!outcome.ok) {
|
|
2935
|
-
yield { type: "error", text: formatError(outcome.err), timestamp: now3() };
|
|
2936
|
-
return;
|
|
2937
|
-
}
|
|
2938
|
-
const stopReason = outcome.res.stopReason;
|
|
2939
|
-
if (stopReason === "refusal") {
|
|
2940
|
-
yield { type: "error", text: "agent refused the request", timestamp: now3() };
|
|
2941
|
-
return;
|
|
2942
|
-
}
|
|
2943
|
-
const usage = outcome.res.usage;
|
|
2944
|
-
if (usage) {
|
|
2945
|
-
yield {
|
|
2946
|
-
type: "usage",
|
|
2947
|
-
inputTokens: usage.inputTokens,
|
|
2948
|
-
outputTokens: usage.outputTokens,
|
|
2949
|
-
cacheReadTokens: usage.cachedReadTokens ?? 0,
|
|
2950
|
-
cacheCreationTokens: usage.cachedWriteTokens ?? 0,
|
|
2951
|
-
durationMs: 0,
|
|
2952
|
-
numTurns: 1,
|
|
2953
|
-
timestamp: now3()
|
|
2954
|
-
};
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
get currentSessionId() {
|
|
2958
|
-
return this.sessionId;
|
|
2959
|
-
}
|
|
2960
|
-
cancel() {
|
|
2961
|
-
if (this.cancelled) {
|
|
2962
|
-
this.abortPrompt?.();
|
|
2963
|
-
return;
|
|
2964
|
-
}
|
|
2965
|
-
this.cancelled = true;
|
|
2966
|
-
if (this.sessionId) {
|
|
2967
|
-
this.conn.cancel({ sessionId: this.sessionId }).catch(() => {});
|
|
2968
|
-
}
|
|
2969
|
-
this.abortPrompt?.();
|
|
2970
|
-
}
|
|
2971
|
-
close() {
|
|
2972
|
-
this.abortPrompt?.();
|
|
2973
|
-
this.updates.close();
|
|
3502
|
+
}
|
|
3503
|
+
var claude_default = createClaudeRuntime();
|
|
3504
|
+
// src/runtimes/_cli-agent.ts
|
|
3505
|
+
import { ndJsonStream } from "@agentclientprotocol/sdk";
|
|
3506
|
+
|
|
3507
|
+
// src/runtimes/_jsonl-guard.ts
|
|
3508
|
+
var JSONL_GUARD_LINE_CAP_BYTES = Number(process.env.AC_JSONL_LINE_CAP_BYTES) || 131072;
|
|
3509
|
+
var JSONL_GUARD_KEEP_BYTES = Number(process.env.AC_JSONL_KEEP_BYTES) || 32768;
|
|
3510
|
+
var JSONL_GUARD_NO_SENTINEL_EXIT = 121;
|
|
3511
|
+
function capJsonlLine(line, maxBytes, keepBytes) {
|
|
3512
|
+
if (Buffer.byteLength(line, "utf8") <= maxBytes)
|
|
3513
|
+
return line;
|
|
3514
|
+
let parsed;
|
|
3515
|
+
try {
|
|
3516
|
+
parsed = JSON.parse(line);
|
|
3517
|
+
} catch {
|
|
3518
|
+
parsed = undefined;
|
|
2974
3519
|
}
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
3520
|
+
if (parsed !== null && typeof parsed === "object") {
|
|
3521
|
+
const truncate2 = (value, budget) => {
|
|
3522
|
+
const bytes = Buffer.byteLength(value, "utf8");
|
|
3523
|
+
if (bytes <= budget)
|
|
3524
|
+
return value;
|
|
3525
|
+
let head = Buffer.from(value, "utf8").subarray(0, budget).toString("utf8");
|
|
3526
|
+
while (head.endsWith("�"))
|
|
3527
|
+
head = head.slice(0, -1);
|
|
3528
|
+
if (budget < 2048) {
|
|
3529
|
+
return head + `
|
|
3530
|
+
[agent-compose: PAYLOAD ELIDED IN TRANSIT - kept first ` + budget + " of " + bytes + " bytes; full content remains in the sandbox]";
|
|
3531
|
+
}
|
|
3532
|
+
return head + `
|
|
3533
|
+
|
|
3534
|
+
[agent-compose: PAYLOAD ELIDED IN TRANSIT - this content was ` + bytes + " bytes; only the first " + budget + " bytes are shown. It was cut to protect the" + " session's transport, NOT because the source is short: the full content still exists" + " inside this sandbox (the file on disk / the command's output). Do not guess the" + " elided part - re-read the source file, or re-run the command with narrower output" + " (head, tail, grep, offset+limit), to see it.]";
|
|
2984
3535
|
};
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
3536
|
+
const walk = (node, budget) => {
|
|
3537
|
+
if (typeof node === "string")
|
|
3538
|
+
return truncate2(node, budget);
|
|
3539
|
+
if (Array.isArray(node))
|
|
3540
|
+
return node.map((item) => walk(item, budget));
|
|
3541
|
+
if (node !== null && typeof node === "object") {
|
|
3542
|
+
const out = {};
|
|
3543
|
+
for (const key of Object.keys(node)) {
|
|
3544
|
+
out[key] = walk(node[key], budget);
|
|
3545
|
+
}
|
|
3546
|
+
return out;
|
|
3547
|
+
}
|
|
3548
|
+
return node;
|
|
3549
|
+
};
|
|
3550
|
+
for (const shrink of [1, 8, 64]) {
|
|
3551
|
+
const budget = Math.max(64, Math.floor(keepBytes / shrink));
|
|
3552
|
+
try {
|
|
3553
|
+
const capped = JSON.stringify(walk(parsed, budget));
|
|
3554
|
+
if (typeof capped === "string" && Buffer.byteLength(capped, "utf8") <= maxBytes) {
|
|
3555
|
+
return capped;
|
|
3556
|
+
}
|
|
3557
|
+
} catch {
|
|
3558
|
+
break;
|
|
2993
3559
|
}
|
|
2994
|
-
throw err;
|
|
2995
|
-
}
|
|
2996
|
-
if (verdict.kind === "abort") {
|
|
2997
|
-
this.cancel();
|
|
2998
|
-
return { outcome: { outcome: "cancelled" } };
|
|
2999
|
-
}
|
|
3000
|
-
const wantedKinds = verdict.kind === "allow" ? OPTION_KIND_FOR_VERDICT.allow : OPTION_KIND_FOR_VERDICT.deny;
|
|
3001
|
-
const option = params.options.find((o) => wantedKinds.includes(o.kind)) ?? params.options[0];
|
|
3002
|
-
if (!option) {
|
|
3003
|
-
return { outcome: { outcome: "cancelled" } };
|
|
3004
3560
|
}
|
|
3005
|
-
return { outcome: { outcome: "selected", optionId: option.optionId } };
|
|
3006
3561
|
}
|
|
3562
|
+
let raw = Buffer.from(line, "utf8").subarray(0, maxBytes).toString("utf8");
|
|
3563
|
+
while (raw.endsWith("�"))
|
|
3564
|
+
raw = raw.slice(0, -1);
|
|
3565
|
+
return raw;
|
|
3566
|
+
}
|
|
3567
|
+
function jsonlGuardScript() {
|
|
3568
|
+
return [
|
|
3569
|
+
"'use strict';",
|
|
3570
|
+
`const capJsonlLine = ${capJsonlLine.toString()};`,
|
|
3571
|
+
`const NO_SENTINEL_EXIT = ${JSONL_GUARD_NO_SENTINEL_EXIT};`,
|
|
3572
|
+
"const sentinel = process.argv[2];",
|
|
3573
|
+
"const maxBytes = Number(process.argv[3]);",
|
|
3574
|
+
"const keepBytes = Number(process.argv[4]);",
|
|
3575
|
+
"let sawSentinel = false;",
|
|
3576
|
+
"let buf = '';",
|
|
3577
|
+
"// Downstream (the command-stream consumer) going away must never crash",
|
|
3578
|
+
"// the pipe with EPIPE noise — the producer is being killed anyway.",
|
|
3579
|
+
"process.stdout.on('error', () => process.exit(0));",
|
|
3580
|
+
"function handle(line) {",
|
|
3581
|
+
" const t = line.trim();",
|
|
3582
|
+
" if (sentinel && t.startsWith(sentinel)) {",
|
|
3583
|
+
" const code = Number(t.slice(sentinel.length).trim());",
|
|
3584
|
+
" process.exitCode = Number.isFinite(code) ? code : 0;",
|
|
3585
|
+
" sawSentinel = true;",
|
|
3586
|
+
" return;",
|
|
3587
|
+
" }",
|
|
3588
|
+
" process.stdout.write(capJsonlLine(line, maxBytes, keepBytes) + '\\n');",
|
|
3589
|
+
"}",
|
|
3590
|
+
"process.stdin.setEncoding('utf8');",
|
|
3591
|
+
"process.stdin.on('data', (d) => {",
|
|
3592
|
+
" buf += d;",
|
|
3593
|
+
" let nl;",
|
|
3594
|
+
" while ((nl = buf.indexOf('\\n')) >= 0) {",
|
|
3595
|
+
" const line = buf.slice(0, nl);",
|
|
3596
|
+
" buf = buf.slice(nl + 1);",
|
|
3597
|
+
" if (line.length > 0) handle(line);",
|
|
3598
|
+
" }",
|
|
3599
|
+
"});",
|
|
3600
|
+
"process.stdin.on('end', () => {",
|
|
3601
|
+
" if (buf.trim().length > 0) handle(buf);",
|
|
3602
|
+
" if (!sawSentinel) process.exitCode = NO_SENTINEL_EXIT;",
|
|
3603
|
+
"});"
|
|
3604
|
+
].join(`
|
|
3605
|
+
`);
|
|
3606
|
+
}
|
|
3607
|
+
function quote(value) {
|
|
3608
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
3609
|
+
}
|
|
3610
|
+
function wrapJsonlCommand(args) {
|
|
3611
|
+
const { cmd, guardPath, sentinel, maxBytes, keepBytes } = args;
|
|
3612
|
+
return `if command -v node >/dev/null 2>&1; then ` + `{ ( ${cmd} ); printf '\\n%s %s\\n' ${quote(sentinel)} "$?"; } ` + `| node ${quote(guardPath)} ${quote(sentinel)} ${maxBytes} ${keepBytes}; ` + `else ${cmd}; fi`;
|
|
3007
3613
|
}
|
|
3008
3614
|
|
|
3009
3615
|
// src/runtimes/_cli-agent.ts
|
|
@@ -3013,6 +3619,11 @@ function now4() {
|
|
|
3013
3619
|
function shellQuote(value) {
|
|
3014
3620
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
3015
3621
|
}
|
|
3622
|
+
var promptFileCounter = 0;
|
|
3623
|
+
function uniquePromptPath(kind, agentId, iteration) {
|
|
3624
|
+
const unique = `${Date.now().toString(36)}-${(promptFileCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3625
|
+
return `/tmp/ac-${kind}-${agentId}-${iteration}-${unique}.in`;
|
|
3626
|
+
}
|
|
3016
3627
|
var ACP_FALLBACK = Symbol("acp-fallback");
|
|
3017
3628
|
var ACP_HANDSHAKE_TIMEOUT_MS = Number(process.env.AC_ACP_HANDSHAKE_TIMEOUT_MS) || 1e4;
|
|
3018
3629
|
var ACP_TURN_IDLE_TIMEOUT_MS = Number(process.env.AC_ACP_TURN_IDLE_TIMEOUT_MS) || 120000;
|
|
@@ -3035,6 +3646,7 @@ class CliAgentRunner {
|
|
|
3035
3646
|
options;
|
|
3036
3647
|
spec;
|
|
3037
3648
|
configModel;
|
|
3649
|
+
configEffort;
|
|
3038
3650
|
kind;
|
|
3039
3651
|
get acpCapable() {
|
|
3040
3652
|
return this.spec.acp !== undefined && this.acpTransportReady && typeof this.sandbox.commands.spawnDuplex === "function";
|
|
@@ -3046,11 +3658,12 @@ class CliAgentRunner {
|
|
|
3046
3658
|
acpSession;
|
|
3047
3659
|
currentAcpProcCtx;
|
|
3048
3660
|
acpTransportReady = ACP_TRANSPORT_READY;
|
|
3049
|
-
constructor(sandbox, options, spec, configModel) {
|
|
3661
|
+
constructor(sandbox, options, spec, configModel, configEffort) {
|
|
3050
3662
|
this.sandbox = sandbox;
|
|
3051
3663
|
this.options = options;
|
|
3052
3664
|
this.spec = spec;
|
|
3053
3665
|
this.configModel = configModel;
|
|
3666
|
+
this.configEffort = configEffort;
|
|
3054
3667
|
this.kind = spec.kind;
|
|
3055
3668
|
}
|
|
3056
3669
|
enableAcpForTesting() {
|
|
@@ -3230,14 +3843,27 @@ class CliAgentRunner {
|
|
|
3230
3843
|
let sessionId = opts.sessionId;
|
|
3231
3844
|
let sawError = false;
|
|
3232
3845
|
try {
|
|
3846
|
+
if (opts.signal?.aborted)
|
|
3847
|
+
return;
|
|
3233
3848
|
await this.ensureInstalled();
|
|
3234
|
-
const promptPath =
|
|
3849
|
+
const promptPath = uniquePromptPath(this.spec.kind, this.options.agentId ?? "agent", opts.iteration ?? 0);
|
|
3235
3850
|
await this.sandbox.files.write(promptPath, this.spec.promptPayload(opts.prompt));
|
|
3236
3851
|
const cmd = this.spec.buildCommand({
|
|
3237
3852
|
promptPath,
|
|
3238
3853
|
sessionId: opts.sessionId,
|
|
3239
3854
|
model: this.model,
|
|
3240
|
-
cwd: this.options.cwd
|
|
3855
|
+
cwd: this.options.cwd,
|
|
3856
|
+
effort: this.configEffort
|
|
3857
|
+
});
|
|
3858
|
+
const guardPath = `${promptPath}.guard.js`;
|
|
3859
|
+
const sentinel = `__AC_JSONL_EXIT_${Math.random().toString(36).slice(2, 10)}__`;
|
|
3860
|
+
await this.sandbox.files.write(guardPath, jsonlGuardScript());
|
|
3861
|
+
const guardedCmd = wrapJsonlCommand({
|
|
3862
|
+
cmd,
|
|
3863
|
+
guardPath,
|
|
3864
|
+
sentinel,
|
|
3865
|
+
maxBytes: JSONL_GUARD_LINE_CAP_BYTES,
|
|
3866
|
+
keepBytes: JSONL_GUARD_KEEP_BYTES
|
|
3241
3867
|
});
|
|
3242
3868
|
const lines = new AsyncQueue;
|
|
3243
3869
|
let buf = "";
|
|
@@ -3252,51 +3878,83 @@ class CliAgentRunner {
|
|
|
3252
3878
|
lines.push(line);
|
|
3253
3879
|
}
|
|
3254
3880
|
};
|
|
3255
|
-
const
|
|
3881
|
+
const runOpts = {
|
|
3256
3882
|
...this.options.cwd ? { cwd: this.options.cwd } : {},
|
|
3257
|
-
onStdout
|
|
3258
|
-
|
|
3883
|
+
onStdout,
|
|
3884
|
+
timeoutMs: 0
|
|
3885
|
+
};
|
|
3886
|
+
let exited = false;
|
|
3887
|
+
const settle = (res) => {
|
|
3888
|
+
exited = true;
|
|
3259
3889
|
const tail = buf.trim();
|
|
3260
3890
|
if (tail)
|
|
3261
3891
|
lines.push(tail);
|
|
3262
3892
|
lines.close();
|
|
3263
|
-
return
|
|
3264
|
-
}
|
|
3893
|
+
return res;
|
|
3894
|
+
};
|
|
3895
|
+
const fail = (err) => {
|
|
3896
|
+
exited = true;
|
|
3265
3897
|
lines.close();
|
|
3266
3898
|
throw err;
|
|
3267
|
-
}
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3899
|
+
};
|
|
3900
|
+
let kill;
|
|
3901
|
+
let runPromise;
|
|
3902
|
+
if (this.sandbox.commands.runBackground) {
|
|
3903
|
+
const handle = await this.sandbox.commands.runBackground(guardedCmd, runOpts);
|
|
3904
|
+
kill = () => handle.kill();
|
|
3905
|
+
runPromise = handle.wait().then(settle, fail);
|
|
3906
|
+
} else {
|
|
3907
|
+
runPromise = this.sandbox.commands.run(guardedCmd, runOpts).then(settle, fail);
|
|
3908
|
+
}
|
|
3909
|
+
runPromise.catch(() => {});
|
|
3910
|
+
const reap = () => {
|
|
3911
|
+
if (!exited)
|
|
3912
|
+
kill?.().catch(() => {});
|
|
3913
|
+
};
|
|
3914
|
+
if (opts.signal) {
|
|
3915
|
+
if (opts.signal.aborted)
|
|
3916
|
+
reap();
|
|
3917
|
+
else
|
|
3918
|
+
opts.signal.addEventListener("abort", reap, { once: true });
|
|
3919
|
+
}
|
|
3920
|
+
try {
|
|
3921
|
+
for await (const line of lines) {
|
|
3922
|
+
let parsed;
|
|
3923
|
+
try {
|
|
3924
|
+
parsed = JSON.parse(line);
|
|
3925
|
+
} catch {
|
|
3926
|
+
continue;
|
|
3927
|
+
}
|
|
3928
|
+
const sid = this.spec.extractSessionId(parsed);
|
|
3929
|
+
if (sid)
|
|
3930
|
+
sessionId = sid;
|
|
3931
|
+
for (const msg of this.spec.mapEvent(parsed)) {
|
|
3932
|
+
if (msg.type === "error")
|
|
3933
|
+
sawError = true;
|
|
3934
|
+
yield msg;
|
|
3935
|
+
}
|
|
3274
3936
|
}
|
|
3275
|
-
const
|
|
3276
|
-
if (
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
sawError = true;
|
|
3281
|
-
yield msg;
|
|
3937
|
+
const res = await runPromise;
|
|
3938
|
+
if (res.exitCode !== 0 && !sawError) {
|
|
3939
|
+
const tail = (res.stderr ?? "").slice(-2000);
|
|
3940
|
+
yield { type: "error", text: `${this.spec.kind} exited with code ${res.exitCode}${tail ? `: ${tail}` : ""}`, timestamp: now4() };
|
|
3941
|
+
return;
|
|
3282
3942
|
}
|
|
3943
|
+
if (!sawError)
|
|
3944
|
+
yield { type: "done", sessionId: sessionId ?? "", timestamp: now4() };
|
|
3945
|
+
} finally {
|
|
3946
|
+
opts.signal?.removeEventListener("abort", reap);
|
|
3947
|
+
reap();
|
|
3948
|
+
this.sandbox.commands.run(`rm -f ${shellQuote(promptPath)} ${shellQuote(guardPath)}`, { timeoutMs: 1e4 }).catch(() => {});
|
|
3283
3949
|
}
|
|
3284
|
-
const res = await runPromise;
|
|
3285
|
-
if (res.exitCode !== 0 && !sawError) {
|
|
3286
|
-
const tail = (res.stderr ?? "").slice(-2000);
|
|
3287
|
-
yield { type: "error", text: `${this.spec.kind} exited with code ${res.exitCode}${tail ? `: ${tail}` : ""}`, timestamp: now4() };
|
|
3288
|
-
return;
|
|
3289
|
-
}
|
|
3290
|
-
if (!sawError)
|
|
3291
|
-
yield { type: "done", sessionId: sessionId ?? "", timestamp: now4() };
|
|
3292
3950
|
} catch (err) {
|
|
3293
3951
|
yield { type: "error", text: formatError(err), timestamp: now4() };
|
|
3294
3952
|
}
|
|
3295
3953
|
}
|
|
3296
3954
|
}
|
|
3297
|
-
function createCliAgentRuntime(spec, configModel) {
|
|
3955
|
+
function createCliAgentRuntime(spec, configModel, configEffort) {
|
|
3298
3956
|
return defineRuntime({
|
|
3299
|
-
create: (sandbox, opts) => new CliAgentRunner(sandbox, opts, spec, configModel)
|
|
3957
|
+
create: (sandbox, opts) => new CliAgentRunner(sandbox, opts, spec, configModel, configEffort)
|
|
3300
3958
|
});
|
|
3301
3959
|
}
|
|
3302
3960
|
|
|
@@ -3305,6 +3963,12 @@ function now5() {
|
|
|
3305
3963
|
return new Date().toISOString();
|
|
3306
3964
|
}
|
|
3307
3965
|
var CODEX_ACP_ADAPTER = "@agentclientprotocol/codex-acp@0.1.0";
|
|
3966
|
+
var CODEX_ADVISORY_PATTERNS = [
|
|
3967
|
+
/Model metadata for .+ not found\. Defaulting to fallback metadata/
|
|
3968
|
+
];
|
|
3969
|
+
function isCodexAdvisoryNoise(text) {
|
|
3970
|
+
return CODEX_ADVISORY_PATTERNS.some((re) => re.test(text));
|
|
3971
|
+
}
|
|
3308
3972
|
var codexSpec = {
|
|
3309
3973
|
kind: "codex",
|
|
3310
3974
|
authEnv: "CODEX_API_KEY",
|
|
@@ -3316,12 +3980,13 @@ var codexSpec = {
|
|
|
3316
3980
|
},
|
|
3317
3981
|
install: 'sudo npm install -g @openai/codex && (command -v codex >/dev/null 2>&1 || sudo ln -sf "$(npm prefix -g)/bin/codex" /usr/local/bin/codex)',
|
|
3318
3982
|
promptPayload: (prompt) => prompt,
|
|
3319
|
-
buildCommand: ({ promptPath, sessionId, model, cwd }) => {
|
|
3983
|
+
buildCommand: ({ promptPath, sessionId, model, cwd, effort }) => {
|
|
3320
3984
|
const flags = [
|
|
3321
3985
|
"--json",
|
|
3322
3986
|
"--skip-git-repo-check",
|
|
3323
3987
|
"--dangerously-bypass-approvals-and-sandbox",
|
|
3324
3988
|
...model ? ["-m", shellQuote(model)] : [],
|
|
3989
|
+
...effort ? ["-c", `model_reasoning_effort=${effort}`] : [],
|
|
3325
3990
|
...cwd ? ["-C", shellQuote(cwd)] : []
|
|
3326
3991
|
].join(" ");
|
|
3327
3992
|
const exec = sessionId ? `codex exec resume ${shellQuote(sessionId)} ${flags}` : `codex exec ${flags}`;
|
|
@@ -3351,6 +4016,16 @@ var codexSpec = {
|
|
|
3351
4016
|
const failed = item.status === "failed" || typeof item.exit_code === "number" && item.exit_code !== 0;
|
|
3352
4017
|
return [{ type: "tool_result", toolUseId: id, output: String(item.aggregated_output ?? item.output ?? ""), isError: failed, timestamp: ts }];
|
|
3353
4018
|
}
|
|
4019
|
+
if (itype === "error") {
|
|
4020
|
+
if (p.type !== "item.completed")
|
|
4021
|
+
return [];
|
|
4022
|
+
const text = formatError(item.message ?? item);
|
|
4023
|
+
if (isCodexAdvisoryNoise(text)) {
|
|
4024
|
+
console.warn(`[codex] advisory (kept out of transcript): ${text}`);
|
|
4025
|
+
return [];
|
|
4026
|
+
}
|
|
4027
|
+
return [{ type: "error", text, timestamp: ts }];
|
|
4028
|
+
}
|
|
3354
4029
|
if (p.type === "item.completed") {
|
|
3355
4030
|
return [{ type: "tool_use", toolName: itype || "item", toolInput: item, toolUseId: String(item.id ?? ""), timestamp: ts }];
|
|
3356
4031
|
}
|
|
@@ -3380,7 +4055,7 @@ var codexSpec = {
|
|
|
3380
4055
|
}
|
|
3381
4056
|
};
|
|
3382
4057
|
function createCodexRuntime(config = {}) {
|
|
3383
|
-
return createCliAgentRuntime(codexSpec, config.model);
|
|
4058
|
+
return createCliAgentRuntime(codexSpec, config.model, config.effort);
|
|
3384
4059
|
}
|
|
3385
4060
|
var codex_default = createCodexRuntime();
|
|
3386
4061
|
// src/runtimes/amp.ts
|
|
@@ -3520,59 +4195,278 @@ function createDroidRuntime(config = {}) {
|
|
|
3520
4195
|
return createCliAgentRuntime(droidSpec, config.model ?? droidSpec.defaultModel);
|
|
3521
4196
|
}
|
|
3522
4197
|
var droid_default = createDroidRuntime();
|
|
3523
|
-
// src/
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
4198
|
+
// src/runtimes/claude-code.ts
|
|
4199
|
+
function now10() {
|
|
4200
|
+
return new Date().toISOString();
|
|
4201
|
+
}
|
|
4202
|
+
var CLAUDE_CODE_ACP_ADAPTER = "@zed-industries/claude-code-acp@0.16.2";
|
|
4203
|
+
function toolResultText(content) {
|
|
4204
|
+
if (typeof content === "string")
|
|
4205
|
+
return content;
|
|
4206
|
+
if (Array.isArray(content)) {
|
|
4207
|
+
return content.map((b) => typeof b?.text === "string" ? b.text : "").join("");
|
|
4208
|
+
}
|
|
4209
|
+
return content == null ? "" : JSON.stringify(content) ?? "";
|
|
4210
|
+
}
|
|
4211
|
+
var CLAUDE_CODE_THINKING_TOKENS = { low: 2048, medium: 8192, high: 16384 };
|
|
4212
|
+
var claudeCodeSpec = {
|
|
4213
|
+
kind: "claude-code",
|
|
4214
|
+
authEnv: "ANTHROPIC_API_KEY",
|
|
4215
|
+
bin: "claude",
|
|
4216
|
+
acp: { command: "npx", args: ["--yes", CLAUDE_CODE_ACP_ADAPTER] },
|
|
4217
|
+
install: "curl -fsSL https://claude.ai/install.sh | bash && " + 'REAL=$(readlink -f "$HOME/.local/bin/claude") && test -f "$REAL" && ' + 'sudo cp "$REAL" /usr/local/bin/claude && sudo chmod 0755 /usr/local/bin/claude',
|
|
4218
|
+
promptPayload: (prompt) => prompt,
|
|
4219
|
+
buildCommand: ({ promptPath, sessionId, model, cwd, effort }) => {
|
|
4220
|
+
const flags = [
|
|
4221
|
+
"-p",
|
|
4222
|
+
"--output-format stream-json",
|
|
4223
|
+
"--verbose",
|
|
4224
|
+
"--include-partial-messages",
|
|
4225
|
+
"--dangerously-skip-permissions",
|
|
4226
|
+
...model ? [`--model ${shellQuote(model)}`] : [],
|
|
4227
|
+
...sessionId ? [`--resume ${shellQuote(sessionId)}`] : []
|
|
4228
|
+
].join(" ");
|
|
4229
|
+
const thinking = effort ? `MAX_THINKING_TOKENS=${CLAUDE_CODE_THINKING_TOKENS[effort]} ` : "";
|
|
4230
|
+
return `${cwd ? `cd ${shellQuote(cwd)} && ` : ""}${thinking}IS_SANDBOX=1 claude ${flags} < ${shellQuote(promptPath)}`;
|
|
4231
|
+
},
|
|
4232
|
+
extractSessionId: (p) => typeof p.session_id === "string" ? p.session_id : undefined,
|
|
4233
|
+
mapEvent: (p) => {
|
|
4234
|
+
const ts = now10();
|
|
4235
|
+
switch (p.type) {
|
|
4236
|
+
case "assistant": {
|
|
4237
|
+
const message = p.message;
|
|
4238
|
+
const blocks2 = Array.isArray(message?.content) ? message.content : [];
|
|
4239
|
+
return blocks2.flatMap((b) => {
|
|
4240
|
+
if (b.type === "text" && typeof b.text === "string" && b.text.length > 0) {
|
|
4241
|
+
return [{ type: "text", text: b.text, timestamp: ts }];
|
|
4242
|
+
}
|
|
4243
|
+
if (b.type === "thinking" && typeof b.thinking === "string" && b.thinking.length > 0) {
|
|
4244
|
+
return [{ type: "thinking", text: b.thinking, timestamp: ts }];
|
|
4245
|
+
}
|
|
4246
|
+
if (b.type === "tool_use") {
|
|
4247
|
+
return [{
|
|
4248
|
+
type: "tool_use",
|
|
4249
|
+
toolName: String(b.name ?? "tool"),
|
|
4250
|
+
toolInput: b.input ?? {},
|
|
4251
|
+
toolUseId: String(b.id ?? ""),
|
|
4252
|
+
timestamp: ts
|
|
4253
|
+
}];
|
|
4254
|
+
}
|
|
4255
|
+
return [];
|
|
4256
|
+
});
|
|
4257
|
+
}
|
|
4258
|
+
case "user": {
|
|
4259
|
+
const message = p.message;
|
|
4260
|
+
const blocks2 = Array.isArray(message?.content) ? message.content : [];
|
|
4261
|
+
return blocks2.flatMap((b) => b.type === "tool_result" ? [{
|
|
4262
|
+
type: "tool_result",
|
|
4263
|
+
toolUseId: String(b.tool_use_id ?? ""),
|
|
4264
|
+
output: toolResultText(b.content),
|
|
4265
|
+
isError: b.is_error === true,
|
|
4266
|
+
timestamp: ts
|
|
4267
|
+
}] : []);
|
|
4268
|
+
}
|
|
4269
|
+
case "stream_event": {
|
|
4270
|
+
const ev = p.event;
|
|
4271
|
+
if (ev?.type === "content_block_delta" && ev.delta?.type === "text_delta" && typeof ev.delta.text === "string" && ev.delta.text.length > 0) {
|
|
4272
|
+
return [{ type: "text_delta", text: ev.delta.text, timestamp: ts }];
|
|
4273
|
+
}
|
|
4274
|
+
if (ev?.type === "message_start" && ev.message?.usage) {
|
|
4275
|
+
const u = ev.message.usage;
|
|
4276
|
+
return [{
|
|
4277
|
+
type: "usage_delta",
|
|
4278
|
+
boundary: "call_start",
|
|
4279
|
+
inputTokens: u.input_tokens ?? 0,
|
|
4280
|
+
outputTokens: u.output_tokens ?? 0,
|
|
4281
|
+
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
4282
|
+
cacheCreationTokens: u.cache_creation_input_tokens ?? 0,
|
|
4283
|
+
timestamp: ts
|
|
4284
|
+
}];
|
|
4285
|
+
}
|
|
4286
|
+
if (ev?.type === "message_delta" && ev.usage && typeof ev.usage.output_tokens === "number") {
|
|
4287
|
+
return [{
|
|
4288
|
+
type: "usage_delta",
|
|
4289
|
+
boundary: "call_delta",
|
|
4290
|
+
inputTokens: 0,
|
|
4291
|
+
outputTokens: ev.usage.output_tokens,
|
|
4292
|
+
cacheReadTokens: 0,
|
|
4293
|
+
cacheCreationTokens: 0,
|
|
4294
|
+
timestamp: ts
|
|
4295
|
+
}];
|
|
4296
|
+
}
|
|
4297
|
+
return [];
|
|
4298
|
+
}
|
|
4299
|
+
case "result": {
|
|
4300
|
+
if (p.is_error === true) {
|
|
4301
|
+
return [{ type: "error", text: formatError(p.result ?? p.subtype ?? p), timestamp: ts }];
|
|
4302
|
+
}
|
|
4303
|
+
const u = p.usage;
|
|
4304
|
+
if (!u)
|
|
4305
|
+
return [];
|
|
4306
|
+
return [{
|
|
4307
|
+
type: "usage",
|
|
4308
|
+
inputTokens: u.input_tokens ?? 0,
|
|
4309
|
+
outputTokens: u.output_tokens ?? 0,
|
|
4310
|
+
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
4311
|
+
cacheCreationTokens: u.cache_creation_input_tokens ?? 0,
|
|
4312
|
+
durationMs: typeof p.duration_ms === "number" ? p.duration_ms : 0,
|
|
4313
|
+
numTurns: typeof p.num_turns === "number" ? p.num_turns : 1,
|
|
4314
|
+
timestamp: ts
|
|
4315
|
+
}];
|
|
4316
|
+
}
|
|
4317
|
+
default:
|
|
4318
|
+
return [];
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
};
|
|
4322
|
+
function createClaudeCodeRuntime(config = {}) {
|
|
4323
|
+
return createCliAgentRuntime(claudeCodeSpec, config.model, config.effort);
|
|
4324
|
+
}
|
|
4325
|
+
var claude_code_default = createClaudeCodeRuntime();
|
|
4326
|
+
// src/sandbox/network-policy.ts
|
|
4327
|
+
var DOT_SEGMENT_PATH_RE2 = ".*(?:^|/|%2[fF])(?:\\.|%2[eE]){2}(?:/|%2[fF]|$).*";
|
|
4328
|
+
var DOT_SEGMENT_RE = /(?:^|\/|%2f)(?:\.|%2e){2}(?:\/|%2f|$)/i;
|
|
4329
|
+
function assertSafePathPrefix(domain, prefix) {
|
|
4330
|
+
if (!prefix.startsWith("/") || DOT_SEGMENT_RE.test(prefix)) {
|
|
4331
|
+
throw new Error(`Invalid Tier-2 pathPrefix ${JSON.stringify(prefix)} for "${domain}": prefixes must start with "/" and contain no dot-segments`);
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
function toVercelNetworkPolicy(policy) {
|
|
4335
|
+
if (typeof policy === "string" || !policy.allow || Array.isArray(policy.allow)) {
|
|
4336
|
+
return policy;
|
|
4337
|
+
}
|
|
4338
|
+
const allow = {};
|
|
4339
|
+
for (const [domain, rules] of Object.entries(policy.allow)) {
|
|
4340
|
+
const translated = rules.flatMap((rule) => {
|
|
4341
|
+
const { requestRules, ...rest } = rule;
|
|
4342
|
+
const methods = requestRules?.methods ?? [];
|
|
4343
|
+
const prefixes = requestRules?.pathPrefixes ?? [];
|
|
4344
|
+
if (methods.length === 0 && prefixes.length === 0)
|
|
4345
|
+
return [rest];
|
|
4346
|
+
const methodMatch = methods.length > 0 ? { method: methods } : {};
|
|
4347
|
+
if (prefixes.length === 0)
|
|
4348
|
+
return [{ ...rest, match: methodMatch }];
|
|
4349
|
+
for (const prefix of prefixes)
|
|
4350
|
+
assertSafePathPrefix(domain, prefix);
|
|
4351
|
+
return prefixes.map((prefix) => ({ ...rest, match: { ...methodMatch, path: { startsWith: prefix } } }));
|
|
4352
|
+
});
|
|
4353
|
+
const hasPathGate = rules.some((rule) => (rule.requestRules?.pathPrefixes?.length ?? 0) > 0);
|
|
4354
|
+
allow[domain] = hasPathGate ? [{ match: { path: { regex: DOT_SEGMENT_PATH_RE2 } } }, ...translated] : translated;
|
|
4355
|
+
}
|
|
4356
|
+
return { ...policy, allow };
|
|
4357
|
+
}
|
|
4358
|
+
function toE2bNetwork(policy) {
|
|
4359
|
+
if (policy === "allow-all")
|
|
4360
|
+
return {};
|
|
4361
|
+
if (policy === "deny-all")
|
|
4362
|
+
return { denyOut: ({ allTraffic }) => [allTraffic] };
|
|
4363
|
+
const allowHosts = Array.isArray(policy.allow) ? [...policy.allow] : Object.keys(policy.allow ?? {});
|
|
4364
|
+
const subnetAllow = policy.subnets?.allow ?? [];
|
|
4365
|
+
const allowOut = [...new Set([...allowHosts, ...subnetAllow])];
|
|
4366
|
+
const network = {
|
|
4367
|
+
denyOut: ({ allTraffic }) => [allTraffic]
|
|
4368
|
+
};
|
|
4369
|
+
if (allowOut.length > 0)
|
|
4370
|
+
network.allowOut = allowOut;
|
|
4371
|
+
if (!Array.isArray(policy.allow) && policy.allow) {
|
|
4372
|
+
const rules = {};
|
|
4373
|
+
for (const [host, hostRules] of Object.entries(policy.allow)) {
|
|
4374
|
+
const hostTransforms = hostRules.flatMap((rule) => (rule.transform ?? []).filter((t) => t.headers && Object.keys(t.headers).length > 0).map((t) => ({ transform: { headers: t.headers } })));
|
|
4375
|
+
if (hostTransforms.length > 0)
|
|
4376
|
+
rules[host] = hostTransforms;
|
|
4377
|
+
}
|
|
4378
|
+
if (Object.keys(rules).length > 0)
|
|
4379
|
+
network.rules = rules;
|
|
4380
|
+
}
|
|
4381
|
+
return network;
|
|
4382
|
+
}
|
|
3534
4383
|
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
4384
|
+
// src/sandbox/sizes.ts
|
|
4385
|
+
var SANDBOX_VCPUS = {
|
|
4386
|
+
"2vcpu-4gb": 2,
|
|
4387
|
+
"4vcpu-8gb": 4,
|
|
4388
|
+
"8vcpu-16gb": 8,
|
|
4389
|
+
"32vcpu-64gb": 32
|
|
4390
|
+
};
|
|
4391
|
+
var DEFAULT_SANDBOX_SIZE = "2vcpu-4gb";
|
|
4392
|
+
var E2B_TEMPLATE_SIZES = [
|
|
4393
|
+
"2vcpu-4gb",
|
|
4394
|
+
"4vcpu-8gb"
|
|
4395
|
+
];
|
|
4396
|
+
function isE2bSupportedSize(size) {
|
|
4397
|
+
return E2B_TEMPLATE_SIZES.includes(size);
|
|
4398
|
+
}
|
|
4399
|
+
function e2bMachineSpec(size) {
|
|
4400
|
+
const cpuCount = SANDBOX_VCPUS[size];
|
|
4401
|
+
return { cpuCount, memoryMB: cpuCount * 2048 };
|
|
4402
|
+
}
|
|
4403
|
+
function e2bBaseTemplate(size) {
|
|
4404
|
+
return `agent-compose-base-${size}`;
|
|
4405
|
+
}
|
|
4406
|
+
function e2bAgentEnvTemplate(size) {
|
|
4407
|
+
return `agent-env-${size}`;
|
|
4408
|
+
}
|
|
4409
|
+
function isPlatformE2bTemplateAlias(id) {
|
|
4410
|
+
return E2B_TEMPLATE_SIZES.some((size) => id === e2bBaseTemplate(size) || id === e2bAgentEnvTemplate(size));
|
|
4411
|
+
}
|
|
4412
|
+
// src/sandbox/devbox.ts
|
|
4413
|
+
var E2B_DEVBOX_TEMPLATE = "agent-compose-devbox";
|
|
4414
|
+
var E2B_DEVBOX_SPEC = {
|
|
4415
|
+
cpuCount: 8,
|
|
4416
|
+
memoryMB: 8192
|
|
4417
|
+
};
|
|
4418
|
+
var E2B_DEVBOX_RECIPE_VERSION = "1";
|
|
4419
|
+
function e2bDevboxTemplateRef() {
|
|
4420
|
+
return `${E2B_DEVBOX_TEMPLATE}@${E2B_DEVBOX_RECIPE_VERSION}`;
|
|
4421
|
+
}
|
|
4422
|
+
// src/sandbox/provider-def.ts
|
|
4423
|
+
var AGENT_COMPOSE_TAG = process.env.AGENT_COMPOSE_TAG ?? `agent-compose-${process.env.AGENT_COMPOSE_ENV ?? "dev"}`;
|
|
4424
|
+
// src/sandbox/exec-stream.ts
|
|
4425
|
+
async function parseSseExecStream(body, opts) {
|
|
4426
|
+
let stdout = "", stderr = "", exitCode = 0, exited = false;
|
|
4427
|
+
const reader = body.getReader(), decoder = new TextDecoder;
|
|
4428
|
+
let buf = "";
|
|
4429
|
+
while (true) {
|
|
4430
|
+
const { done, value } = await reader.read();
|
|
4431
|
+
if (done)
|
|
4432
|
+
break;
|
|
4433
|
+
buf += decoder.decode(value, { stream: true });
|
|
4434
|
+
const lines = buf.replace(/\r\n/g, `
|
|
4435
|
+
`).split(`
|
|
4436
|
+
`);
|
|
4437
|
+
buf = lines.pop() ?? "";
|
|
4438
|
+
for (const line of lines) {
|
|
4439
|
+
if (!line.startsWith("data: "))
|
|
4440
|
+
continue;
|
|
4441
|
+
let event;
|
|
4442
|
+
try {
|
|
4443
|
+
event = JSON.parse(line.slice(6));
|
|
4444
|
+
} catch (err) {
|
|
4445
|
+
throw new Error(`Malformed sandbox exec event: ${err instanceof Error ? err.message : String(err)}`);
|
|
4446
|
+
}
|
|
4447
|
+
if (event.type === "stdout") {
|
|
4448
|
+
stdout += event.data ?? "";
|
|
4449
|
+
opts?.onStdout?.(event.data ?? "");
|
|
4450
|
+
} else if (event.type === "stderr") {
|
|
4451
|
+
stderr += event.data ?? "";
|
|
4452
|
+
opts?.onStderr?.(event.data ?? "");
|
|
4453
|
+
} else if (event.type === "exit") {
|
|
4454
|
+
exitCode = event.exitCode ?? 0;
|
|
4455
|
+
exited = true;
|
|
4456
|
+
} else if (event.type === "error") {
|
|
4457
|
+
throw new Error(`Sandbox exec error: ${event.data}`);
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
if (exited)
|
|
4461
|
+
break;
|
|
3543
4462
|
}
|
|
4463
|
+
if (!exited)
|
|
4464
|
+
throw new Error("Sandbox exec stream ended without an exit event");
|
|
4465
|
+
return { exitCode, stdout, stderr };
|
|
3544
4466
|
}
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
var VERCEL_VM_LIFETIME_WINDOW_MS = 6 * 60 * 60 * 1000;
|
|
3549
|
-
var SANDBOX_VCPUS = {
|
|
3550
|
-
"2vcpu-4gb": 2,
|
|
3551
|
-
"4vcpu-8gb": 4,
|
|
3552
|
-
"8vcpu-16gb": 8,
|
|
3553
|
-
"32vcpu-64gb": 32
|
|
3554
|
-
};
|
|
3555
|
-
var DEFAULT_SANDBOX_SIZE = "2vcpu-4gb";
|
|
3556
|
-
var E2B_TEMPLATE_SIZES = [
|
|
3557
|
-
"2vcpu-4gb",
|
|
3558
|
-
"4vcpu-8gb"
|
|
3559
|
-
];
|
|
3560
|
-
function isE2bSupportedSize(size) {
|
|
3561
|
-
return E2B_TEMPLATE_SIZES.includes(size);
|
|
3562
|
-
}
|
|
3563
|
-
function e2bMachineSpec(size) {
|
|
3564
|
-
const cpuCount = SANDBOX_VCPUS[size];
|
|
3565
|
-
return { cpuCount, memoryMB: cpuCount * 2048 };
|
|
3566
|
-
}
|
|
3567
|
-
function e2bBaseTemplate(size) {
|
|
3568
|
-
return `agent-compose-base-${size}`;
|
|
3569
|
-
}
|
|
3570
|
-
function e2bAgentEnvTemplate(size) {
|
|
3571
|
-
return `agent-env-${size}`;
|
|
3572
|
-
}
|
|
3573
|
-
function isPlatformE2bTemplateAlias(id) {
|
|
3574
|
-
return E2B_TEMPLATE_SIZES.some((size) => id === e2bBaseTemplate(size) || id === e2bAgentEnvTemplate(size));
|
|
3575
|
-
}
|
|
4467
|
+
// src/sandbox/providers/e2b.ts
|
|
4468
|
+
import { Sandbox } from "e2b";
|
|
4469
|
+
import pRetry from "p-retry";
|
|
3576
4470
|
function makeSandboxProvider(sb) {
|
|
3577
4471
|
const commands = sb.commands;
|
|
3578
4472
|
return {
|
|
@@ -3619,27 +4513,15 @@ function makeSandboxProvider(sb) {
|
|
|
3619
4513
|
},
|
|
3620
4514
|
async kill() {
|
|
3621
4515
|
await sb.kill();
|
|
4516
|
+
},
|
|
4517
|
+
getHost(port) {
|
|
4518
|
+
return sb.getHost(port);
|
|
3622
4519
|
}
|
|
3623
4520
|
};
|
|
3624
4521
|
}
|
|
3625
4522
|
function e2bMaxSandboxMs() {
|
|
3626
4523
|
return Number(process.env.E2B_MAX_SANDBOX_MS) || 60 * 60 * 1000;
|
|
3627
4524
|
}
|
|
3628
|
-
function isTransientSandboxError(error) {
|
|
3629
|
-
if (error instanceof SandboxNotFoundError || error instanceof RateLimitError)
|
|
3630
|
-
return true;
|
|
3631
|
-
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
3632
|
-
return /paus|stopp|resum|transition|status(?:\s+code)?[:\s]+5\d\d\b|\b5\d\d\s+(?:internal|bad gateway|service|gateway)|\b429\b|ECONNRESET|ECONNREFUSED|ETIMEDOUT|socket hang up|fetch failed/i.test(message);
|
|
3633
|
-
}
|
|
3634
|
-
function withSandboxRetry(fn, shouldRetry = isTransientSandboxError) {
|
|
3635
|
-
return pRetry(fn, { retries: 4, minTimeout: 400, factor: 2, shouldRetry });
|
|
3636
|
-
}
|
|
3637
|
-
function withSnapshotRetry(p) {
|
|
3638
|
-
if (!p.snapshot)
|
|
3639
|
-
return p;
|
|
3640
|
-
const snapshot = p.snapshot.bind(p);
|
|
3641
|
-
return { ...p, snapshot: () => withSandboxRetry(snapshot) };
|
|
3642
|
-
}
|
|
3643
4525
|
function wrapE2bBackgroundProcess(handle) {
|
|
3644
4526
|
return {
|
|
3645
4527
|
pid: handle.pid,
|
|
@@ -3664,6 +4546,22 @@ function wrapE2bBackgroundProcess(handle) {
|
|
|
3664
4546
|
}
|
|
3665
4547
|
};
|
|
3666
4548
|
}
|
|
4549
|
+
function wrapE2bPtyHandle(sb, handle, onExit) {
|
|
4550
|
+
handle.wait().catch(() => {
|
|
4551
|
+
return;
|
|
4552
|
+
}).finally(() => onExit?.());
|
|
4553
|
+
return {
|
|
4554
|
+
pid: handle.pid,
|
|
4555
|
+
sendInput: (data) => sb.pty.sendInput(handle.pid, data),
|
|
4556
|
+
resize: (size) => sb.pty.resize(handle.pid, size),
|
|
4557
|
+
kill: async () => {
|
|
4558
|
+
await sb.pty.kill(handle.pid);
|
|
4559
|
+
},
|
|
4560
|
+
disconnect: async () => {
|
|
4561
|
+
await handle.disconnect();
|
|
4562
|
+
}
|
|
4563
|
+
};
|
|
4564
|
+
}
|
|
3667
4565
|
function makeE2bSandboxProvider(sb) {
|
|
3668
4566
|
const base = makeSandboxProvider(sb);
|
|
3669
4567
|
return {
|
|
@@ -3690,6 +4588,24 @@ function makeE2bSandboxProvider(sb) {
|
|
|
3690
4588
|
return wrapE2bBackgroundProcess(handle);
|
|
3691
4589
|
}
|
|
3692
4590
|
},
|
|
4591
|
+
async createPty(opts) {
|
|
4592
|
+
const handle = await sb.pty.create({
|
|
4593
|
+
cols: opts.cols,
|
|
4594
|
+
rows: opts.rows,
|
|
4595
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
4596
|
+
envs: { TERM: "xterm-256color", ...opts.envs ?? {} },
|
|
4597
|
+
onData: opts.onData,
|
|
4598
|
+
timeoutMs: 0
|
|
4599
|
+
});
|
|
4600
|
+
return wrapE2bPtyHandle(sb, handle, opts.onExit);
|
|
4601
|
+
},
|
|
4602
|
+
async connectPty(pid, opts) {
|
|
4603
|
+
const handle = await sb.pty.connect(pid, {
|
|
4604
|
+
onData: opts.onData,
|
|
4605
|
+
timeoutMs: 0
|
|
4606
|
+
});
|
|
4607
|
+
return wrapE2bPtyHandle(sb, handle, opts.onExit);
|
|
4608
|
+
},
|
|
3693
4609
|
async snapshot() {
|
|
3694
4610
|
const { snapshotId } = await sb.createSnapshot();
|
|
3695
4611
|
return { snapshotId };
|
|
@@ -3698,11 +4614,64 @@ function makeE2bSandboxProvider(sb) {
|
|
|
3698
4614
|
await sb.pause();
|
|
3699
4615
|
return { resumeHandle: sb.sandboxId };
|
|
3700
4616
|
},
|
|
4617
|
+
async extendLifetime(ms) {
|
|
4618
|
+
await sb.setTimeout(ms);
|
|
4619
|
+
},
|
|
3701
4620
|
async updateNetworkPolicy(policy) {
|
|
3702
4621
|
await sb.updateNetwork(toE2bNetwork(policy));
|
|
3703
4622
|
}
|
|
3704
4623
|
};
|
|
3705
4624
|
}
|
|
4625
|
+
async function listOwned() {
|
|
4626
|
+
const paginator = Sandbox.list({ query: { metadata: { executor: AGENT_COMPOSE_TAG }, state: ["running"] } });
|
|
4627
|
+
const out = [];
|
|
4628
|
+
while (paginator.hasNext) {
|
|
4629
|
+
const items = await pRetry(() => paginator.nextItems(), { retries: 3, minTimeout: 500, factor: 2 });
|
|
4630
|
+
for (const sb of items) {
|
|
4631
|
+
out.push({
|
|
4632
|
+
sandboxId: sb.sandboxId,
|
|
4633
|
+
createdAt: new Date(sb.startedAt),
|
|
4634
|
+
metadata: sb.metadata ?? {}
|
|
4635
|
+
});
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
return out;
|
|
4639
|
+
}
|
|
4640
|
+
var e2bProviderDef = {
|
|
4641
|
+
requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
|
|
4642
|
+
create: async ({ template, timeoutMs, networkPolicy, size, ...rest }) => {
|
|
4643
|
+
const maxMs = e2bMaxSandboxMs();
|
|
4644
|
+
if (timeoutMs > maxMs) {
|
|
4645
|
+
console.warn(`[sandbox] e2b create timeout clamped: requested ${timeoutMs}ms exceeds the plan cap ${maxMs}ms — ` + `the sandbox dies at the cap, not the requested deadline. Raise E2B_MAX_SANDBOX_MS on plans that allow more.`);
|
|
4646
|
+
}
|
|
4647
|
+
const sandboxOpts = {
|
|
4648
|
+
...rest,
|
|
4649
|
+
timeoutMs: Math.min(timeoutMs, maxMs),
|
|
4650
|
+
...networkPolicy ? { network: toE2bNetwork(networkPolicy) } : {}
|
|
4651
|
+
};
|
|
4652
|
+
const resolvedSize = size ?? DEFAULT_SANDBOX_SIZE;
|
|
4653
|
+
const tmpl = template ?? (isE2bSupportedSize(resolvedSize) ? e2bBaseTemplate(resolvedSize) : undefined) ?? process.env.E2B_DEFAULT_TEMPLATE;
|
|
4654
|
+
return makeE2bSandboxProvider(await (tmpl ? Sandbox.create(tmpl, sandboxOpts) : Sandbox.create(sandboxOpts)));
|
|
4655
|
+
},
|
|
4656
|
+
reconnect: async (sandboxId) => makeE2bSandboxProvider(await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY ?? "", timeoutMs: 60 * 60 * 1000 })),
|
|
4657
|
+
killAll: async () => {
|
|
4658
|
+
const paginator = Sandbox.list({ query: { metadata: { executor: AGENT_COMPOSE_TAG } } });
|
|
4659
|
+
const sandboxes = [];
|
|
4660
|
+
while (paginator.hasNext)
|
|
4661
|
+
sandboxes.push(...await paginator.nextItems());
|
|
4662
|
+
if (sandboxes.length === 0)
|
|
4663
|
+
return;
|
|
4664
|
+
await Promise.all(sandboxes.map((s) => Sandbox.kill(s.sandboxId)));
|
|
4665
|
+
console.info(`[sandbox] killed ${sandboxes.length} stale E2B sandboxes (tag: ${AGENT_COMPOSE_TAG})`);
|
|
4666
|
+
},
|
|
4667
|
+
getActiveCount: async () => (await listOwned()).length,
|
|
4668
|
+
listOwned,
|
|
4669
|
+
deleteSnapshot: async (snapshotId, env) => {
|
|
4670
|
+
await Sandbox.deleteSnapshot(snapshotId, { apiKey: env.E2B_API_KEY });
|
|
4671
|
+
}
|
|
4672
|
+
};
|
|
4673
|
+
// src/sandbox/providers/desktop.ts
|
|
4674
|
+
import { Sandbox as Desktop } from "@e2b/desktop";
|
|
3706
4675
|
function makeDesktopSandboxProvider(sb) {
|
|
3707
4676
|
return {
|
|
3708
4677
|
...makeSandboxProvider(sb),
|
|
@@ -3718,104 +4687,39 @@ function makeDesktopSandboxProvider(sb) {
|
|
|
3718
4687
|
drag: (from, to) => sb.drag(from, to)
|
|
3719
4688
|
};
|
|
3720
4689
|
}
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
break;
|
|
3729
|
-
buf += decoder.decode(value, { stream: true });
|
|
3730
|
-
const lines = buf.replace(/\r\n/g, `
|
|
3731
|
-
`).split(`
|
|
3732
|
-
`);
|
|
3733
|
-
buf = lines.pop() ?? "";
|
|
3734
|
-
for (const line of lines) {
|
|
3735
|
-
if (!line.startsWith("data: "))
|
|
3736
|
-
continue;
|
|
3737
|
-
let event;
|
|
3738
|
-
try {
|
|
3739
|
-
event = JSON.parse(line.slice(6));
|
|
3740
|
-
} catch (err) {
|
|
3741
|
-
throw new Error(`Malformed sandbox exec event: ${err instanceof Error ? err.message : String(err)}`);
|
|
3742
|
-
}
|
|
3743
|
-
if (event.type === "stdout") {
|
|
3744
|
-
stdout += event.data ?? "";
|
|
3745
|
-
opts?.onStdout?.(event.data ?? "");
|
|
3746
|
-
} else if (event.type === "stderr") {
|
|
3747
|
-
stderr += event.data ?? "";
|
|
3748
|
-
opts?.onStderr?.(event.data ?? "");
|
|
3749
|
-
} else if (event.type === "exit") {
|
|
3750
|
-
exitCode = event.exitCode ?? 0;
|
|
3751
|
-
exited = true;
|
|
3752
|
-
} else if (event.type === "error") {
|
|
3753
|
-
throw new Error(`Sandbox exec error: ${event.data}`);
|
|
3754
|
-
}
|
|
4690
|
+
var e2bDesktopProviderDef = {
|
|
4691
|
+
requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
|
|
4692
|
+
create: async ({ template, timeoutMs, networkPolicy, size: _size, ...rest }) => {
|
|
4693
|
+
if (!template)
|
|
4694
|
+
throw new Error("E2B Desktop provider requires an explicit `template` (Dockerfile-based — no default base image)");
|
|
4695
|
+
if (networkPolicy && networkPolicy !== "allow-all") {
|
|
4696
|
+
throw new Error("E2B Desktop provider cannot enforce an egress network policy — `@e2b/desktop` bundles an e2b version that predates the native firewall. " + "e2b-desktop is registry-only and must not run with a confined policy.");
|
|
3755
4697
|
}
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
throw new Error("Sandbox exec stream ended without an exit event");
|
|
3761
|
-
return { exitCode, stdout, stderr };
|
|
3762
|
-
}
|
|
3763
|
-
var DOT_SEGMENT_PATH_RE2 = ".*(?:^|/|%2[fF])(?:\\.|%2[eE]){2}(?:/|%2[fF]|$).*";
|
|
3764
|
-
var DOT_SEGMENT_RE = /(?:^|\/|%2f)(?:\.|%2e){2}(?:\/|%2f|$)/i;
|
|
3765
|
-
function assertSafePathPrefix(domain, prefix) {
|
|
3766
|
-
if (!prefix.startsWith("/") || DOT_SEGMENT_RE.test(prefix)) {
|
|
3767
|
-
throw new Error(`Invalid Tier-2 pathPrefix ${JSON.stringify(prefix)} for "${domain}": prefixes must start with "/" and contain no dot-segments`);
|
|
3768
|
-
}
|
|
3769
|
-
}
|
|
3770
|
-
function toVercelNetworkPolicy(policy) {
|
|
3771
|
-
if (typeof policy === "string" || !policy.allow || Array.isArray(policy.allow)) {
|
|
3772
|
-
return policy;
|
|
3773
|
-
}
|
|
3774
|
-
const allow = {};
|
|
3775
|
-
for (const [domain, rules] of Object.entries(policy.allow)) {
|
|
3776
|
-
const translated = rules.flatMap((rule) => {
|
|
3777
|
-
const { requestRules, ...rest } = rule;
|
|
3778
|
-
const methods = requestRules?.methods ?? [];
|
|
3779
|
-
const prefixes = requestRules?.pathPrefixes ?? [];
|
|
3780
|
-
if (methods.length === 0 && prefixes.length === 0)
|
|
3781
|
-
return [rest];
|
|
3782
|
-
const methodMatch = methods.length > 0 ? { method: methods } : {};
|
|
3783
|
-
if (prefixes.length === 0)
|
|
3784
|
-
return [{ ...rest, match: methodMatch }];
|
|
3785
|
-
for (const prefix of prefixes)
|
|
3786
|
-
assertSafePathPrefix(domain, prefix);
|
|
3787
|
-
return prefixes.map((prefix) => ({ ...rest, match: { ...methodMatch, path: { startsWith: prefix } } }));
|
|
3788
|
-
});
|
|
3789
|
-
const hasPathGate = rules.some((rule) => (rule.requestRules?.pathPrefixes?.length ?? 0) > 0);
|
|
3790
|
-
allow[domain] = hasPathGate ? [{ match: { path: { regex: DOT_SEGMENT_PATH_RE2 } } }, ...translated] : translated;
|
|
4698
|
+
return makeDesktopSandboxProvider(await Desktop.create(template, {
|
|
4699
|
+
...rest,
|
|
4700
|
+
timeoutMs: Math.min(timeoutMs, e2bMaxSandboxMs())
|
|
4701
|
+
}));
|
|
3791
4702
|
}
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
if (!Array.isArray(policy.allow) && policy.allow) {
|
|
3808
|
-
const rules = {};
|
|
3809
|
-
for (const [host, hostRules] of Object.entries(policy.allow)) {
|
|
3810
|
-
const hostTransforms = hostRules.flatMap((rule) => (rule.transform ?? []).filter((t) => t.headers && Object.keys(t.headers).length > 0).map((t) => ({ transform: { headers: t.headers } })));
|
|
3811
|
-
if (hostTransforms.length > 0)
|
|
3812
|
-
rules[host] = hostTransforms;
|
|
3813
|
-
}
|
|
3814
|
-
if (Object.keys(rules).length > 0)
|
|
3815
|
-
network.rules = rules;
|
|
4703
|
+
};
|
|
4704
|
+
// src/sandbox/providers/vercel.ts
|
|
4705
|
+
import pRetry2 from "p-retry";
|
|
4706
|
+
|
|
4707
|
+
// src/sandbox-errors.ts
|
|
4708
|
+
var SANDBOX_UNAVAILABLE_PREFIX = "[sandbox-unavailable";
|
|
4709
|
+
|
|
4710
|
+
class SandboxUnavailableError extends Error {
|
|
4711
|
+
retryable;
|
|
4712
|
+
sandboxId;
|
|
4713
|
+
constructor(detail, opts) {
|
|
4714
|
+
super(`${SANDBOX_UNAVAILABLE_PREFIX}:${opts.retryable ? "retryable" : "terminal"}] ${detail}`);
|
|
4715
|
+
this.name = "SandboxUnavailableError";
|
|
4716
|
+
this.retryable = opts.retryable;
|
|
4717
|
+
this.sandboxId = opts.sandboxId;
|
|
3816
4718
|
}
|
|
3817
|
-
return network;
|
|
3818
4719
|
}
|
|
4720
|
+
|
|
4721
|
+
// src/sandbox/providers/vercel.ts
|
|
4722
|
+
var VERCEL_VM_LIFETIME_WINDOW_MS = 6 * 60 * 60 * 1000;
|
|
3819
4723
|
var VERCEL_MAX_TAGS = 5;
|
|
3820
4724
|
function buildVercelTags(metadata) {
|
|
3821
4725
|
const tags = { ...metadata, executor: AGENT_COMPOSE_TAG };
|
|
@@ -3838,23 +4742,33 @@ function makeVercelSandboxProvider(sb, globalEnvs) {
|
|
|
3838
4742
|
let stderr = "";
|
|
3839
4743
|
const handle = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal, ...opts?.sudo ? { sudo: true } : {} }).catch((err) => asUnavailable(err, true));
|
|
3840
4744
|
const collect = async () => {
|
|
3841
|
-
await
|
|
4745
|
+
const finished = await pRetry2(async (attempt) => {
|
|
3842
4746
|
const h = attempt === 1 ? handle : await sb.getCommand(handle.cmdId);
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
4747
|
+
let seenOut = 0;
|
|
4748
|
+
let seenErr = 0;
|
|
4749
|
+
const fresh = (data, seen, delivered) => {
|
|
4750
|
+
const skip = Math.min(Math.max(delivered - seen, 0), data.length);
|
|
4751
|
+
return skip > 0 ? data.slice(skip) : data;
|
|
4752
|
+
};
|
|
3847
4753
|
for await (const log of h.logs()) {
|
|
3848
4754
|
if (log.stream === "stdout") {
|
|
3849
|
-
|
|
3850
|
-
|
|
4755
|
+
const add = fresh(log.data, seenOut, stdout.length);
|
|
4756
|
+
seenOut += log.data.length;
|
|
4757
|
+
if (add) {
|
|
4758
|
+
stdout += add;
|
|
4759
|
+
opts?.onStdout?.(add);
|
|
4760
|
+
}
|
|
3851
4761
|
} else {
|
|
3852
|
-
|
|
3853
|
-
|
|
4762
|
+
const add = fresh(log.data, seenErr, stderr.length);
|
|
4763
|
+
seenErr += log.data.length;
|
|
4764
|
+
if (add) {
|
|
4765
|
+
stderr += add;
|
|
4766
|
+
opts?.onStderr?.(add);
|
|
4767
|
+
}
|
|
3854
4768
|
}
|
|
3855
4769
|
}
|
|
4770
|
+
return await h.wait();
|
|
3856
4771
|
}, { retries: 3, minTimeout: 1000, factor: 2 });
|
|
3857
|
-
const finished = await handle.wait();
|
|
3858
4772
|
return { exitCode: finished.exitCode, stdout, stderr };
|
|
3859
4773
|
};
|
|
3860
4774
|
let timer;
|
|
@@ -3878,6 +4792,12 @@ function makeVercelSandboxProvider(sb, globalEnvs) {
|
|
|
3878
4792
|
files: {
|
|
3879
4793
|
async write(path, content) {
|
|
3880
4794
|
await sb.writeFiles([{ path, content }]).catch((err) => asUnavailable(err, true));
|
|
4795
|
+
},
|
|
4796
|
+
async read(path) {
|
|
4797
|
+
const buf = await sb.readFileToBuffer({ path });
|
|
4798
|
+
if (buf === null)
|
|
4799
|
+
throw new Error(`file not found: ${path}`);
|
|
4800
|
+
return buf.toString("utf8");
|
|
3881
4801
|
}
|
|
3882
4802
|
},
|
|
3883
4803
|
async kill() {
|
|
@@ -3897,6 +4817,84 @@ function makeVercelSandboxProvider(sb, globalEnvs) {
|
|
|
3897
4817
|
}
|
|
3898
4818
|
};
|
|
3899
4819
|
}
|
|
4820
|
+
async function listOwned2(env) {
|
|
4821
|
+
const { Sandbox: VercelSandbox } = await import("@vercel/sandbox");
|
|
4822
|
+
const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
|
|
4823
|
+
const since = Date.now() - VERCEL_VM_LIFETIME_WINDOW_MS;
|
|
4824
|
+
const out = [];
|
|
4825
|
+
let cursor;
|
|
4826
|
+
for (let page = 0;page < 200; page++) {
|
|
4827
|
+
const result = await pRetry2(() => VercelSandbox.list({ ...creds, limit: 50, sortBy: "createdAt", sortOrder: "desc", tags: { executor: AGENT_COMPOSE_TAG }, ...cursor !== undefined ? { cursor } : {} }), { retries: 3, minTimeout: 500, factor: 2 });
|
|
4828
|
+
for (const sb of result.sandboxes) {
|
|
4829
|
+
if (sb.createdAt < since)
|
|
4830
|
+
return out;
|
|
4831
|
+
if (sb.status === "running") {
|
|
4832
|
+
out.push({ sandboxId: sb.name, createdAt: new Date(sb.createdAt), metadata: sb.tags ?? {} });
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
if (result.pagination.next === null)
|
|
4836
|
+
return out;
|
|
4837
|
+
cursor = result.pagination.next;
|
|
4838
|
+
}
|
|
4839
|
+
throw new Error("Vercel listSandboxes exceeded 200 pages (10k sandboxes) within the lifetime window — refuse to silently truncate");
|
|
4840
|
+
}
|
|
4841
|
+
var vercelProviderDef = {
|
|
4842
|
+
requiredEnv: {
|
|
4843
|
+
VERCEL_ACCESS_TOKEN: "Vercel access token — vercel.com/account/tokens",
|
|
4844
|
+
VERCEL_TEAM_ID: "Vercel team ID — vercel.com/account/settings",
|
|
4845
|
+
VERCEL_PROJECT_ID: "Vercel project ID — vercel.com/[team]/[project]/settings"
|
|
4846
|
+
},
|
|
4847
|
+
create: async (opts, env) => {
|
|
4848
|
+
const { Sandbox: VercelSandbox } = await import("@vercel/sandbox");
|
|
4849
|
+
const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
|
|
4850
|
+
const np = opts.networkPolicy ? { networkPolicy: toVercelNetworkPolicy(opts.networkPolicy) } : {};
|
|
4851
|
+
const tags = buildVercelTags(opts.metadata);
|
|
4852
|
+
const tmpl = opts.template ?? process.env.VERCEL_DEFAULT_SNAPSHOT;
|
|
4853
|
+
const resources = { vcpus: SANDBOX_VCPUS[opts.size ?? DEFAULT_SANDBOX_SIZE] };
|
|
4854
|
+
const sb = await VercelSandbox.create(tmpl ? { source: { type: "snapshot", snapshotId: tmpl }, timeout: opts.timeoutMs, env: opts.envs, tags, persistent: false, resources, ...np, ...creds } : { runtime: "node24", timeout: opts.timeoutMs, env: opts.envs, tags, persistent: false, resources, ...np, ...creds });
|
|
4855
|
+
return makeVercelSandboxProvider(sb, opts.envs);
|
|
4856
|
+
},
|
|
4857
|
+
reconnect: async (sandboxId) => {
|
|
4858
|
+
const { Sandbox: VercelSandbox } = await import("@vercel/sandbox");
|
|
4859
|
+
const creds = {
|
|
4860
|
+
token: process.env.VERCEL_ACCESS_TOKEN ?? "",
|
|
4861
|
+
teamId: process.env.VERCEL_TEAM_ID ?? "",
|
|
4862
|
+
projectId: process.env.VERCEL_PROJECT_ID ?? ""
|
|
4863
|
+
};
|
|
4864
|
+
const sb = await VercelSandbox.get({ name: sandboxId, ...creds }).catch((err) => {
|
|
4865
|
+
throw new SandboxUnavailableError(err instanceof Error ? err.message : String(err), { retryable: true, sandboxId });
|
|
4866
|
+
});
|
|
4867
|
+
return makeVercelSandboxProvider(sb);
|
|
4868
|
+
},
|
|
4869
|
+
getActiveCount: async (env) => (await listOwned2(env)).length,
|
|
4870
|
+
listOwned: listOwned2,
|
|
4871
|
+
deleteSnapshot: async (snapshotId, env) => {
|
|
4872
|
+
const { Snapshot } = await import("@vercel/sandbox");
|
|
4873
|
+
const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
|
|
4874
|
+
const snap = await Snapshot.get({ snapshotId, ...creds });
|
|
4875
|
+
await snap.delete();
|
|
4876
|
+
},
|
|
4877
|
+
snapshotExists: async (snapshotId, env) => {
|
|
4878
|
+
const { Snapshot } = await import("@vercel/sandbox");
|
|
4879
|
+
const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
|
|
4880
|
+
try {
|
|
4881
|
+
await Snapshot.get({ snapshotId, ...creds });
|
|
4882
|
+
return true;
|
|
4883
|
+
} catch (err) {
|
|
4884
|
+
const message = err instanceof Error ? err.message : String(err ?? "");
|
|
4885
|
+
const status = err?.status ?? err?.statusCode;
|
|
4886
|
+
if (status === 404 || /\b404\b|not[\s_-]?found|no such snapshot/i.test(message))
|
|
4887
|
+
return false;
|
|
4888
|
+
throw err;
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
};
|
|
4892
|
+
|
|
4893
|
+
// src/sandbox/providers/local.ts
|
|
4894
|
+
import { promises as fs2 } from "node:fs";
|
|
4895
|
+
import { dirname as dirname2 } from "node:path";
|
|
4896
|
+
import { spawn } from "node:child_process";
|
|
4897
|
+
import { Readable, Writable } from "node:stream";
|
|
3900
4898
|
function makeLocalSandboxProvider() {
|
|
3901
4899
|
return {
|
|
3902
4900
|
sandboxId: "local",
|
|
@@ -3955,155 +4953,44 @@ function makeLocalSandboxProvider() {
|
|
|
3955
4953
|
throw err;
|
|
3956
4954
|
}
|
|
3957
4955
|
}
|
|
3958
|
-
};
|
|
3959
|
-
}
|
|
3960
|
-
},
|
|
3961
|
-
files: {
|
|
3962
|
-
async write(path, content) {
|
|
3963
|
-
await fs2.mkdir(dirname2(path), { recursive: true });
|
|
3964
|
-
await fs2.writeFile(path, content);
|
|
3965
|
-
},
|
|
3966
|
-
async read(path) {
|
|
3967
|
-
return await fs2.readFile(path, "utf8");
|
|
3968
|
-
}
|
|
3969
|
-
},
|
|
3970
|
-
async kill() {}
|
|
3971
|
-
};
|
|
3972
|
-
}
|
|
3973
|
-
var SANDBOX_PROVIDERS = {
|
|
3974
|
-
vercel: {
|
|
3975
|
-
requiredEnv: {
|
|
3976
|
-
VERCEL_ACCESS_TOKEN: "Vercel access token — vercel.com/account/tokens",
|
|
3977
|
-
VERCEL_TEAM_ID: "Vercel team ID — vercel.com/account/settings",
|
|
3978
|
-
VERCEL_PROJECT_ID: "Vercel project ID — vercel.com/[team]/[project]/settings"
|
|
3979
|
-
},
|
|
3980
|
-
create: async (opts, env) => {
|
|
3981
|
-
const { Sandbox: VercelSandbox } = await import("@vercel/sandbox");
|
|
3982
|
-
const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
|
|
3983
|
-
const np = opts.networkPolicy ? { networkPolicy: toVercelNetworkPolicy(opts.networkPolicy) } : {};
|
|
3984
|
-
const tags = buildVercelTags(opts.metadata);
|
|
3985
|
-
const tmpl = opts.template ?? process.env.VERCEL_DEFAULT_SNAPSHOT;
|
|
3986
|
-
const resources = { vcpus: SANDBOX_VCPUS[opts.size ?? DEFAULT_SANDBOX_SIZE] };
|
|
3987
|
-
const sb = await VercelSandbox.create(tmpl ? { source: { type: "snapshot", snapshotId: tmpl }, timeout: opts.timeoutMs, env: opts.envs, tags, persistent: false, resources, ...np, ...creds } : { runtime: "node24", timeout: opts.timeoutMs, env: opts.envs, tags, persistent: false, resources, ...np, ...creds });
|
|
3988
|
-
return makeVercelSandboxProvider(sb, opts.envs);
|
|
3989
|
-
},
|
|
3990
|
-
reconnect: async (sandboxId) => {
|
|
3991
|
-
const { Sandbox: VercelSandbox } = await import("@vercel/sandbox");
|
|
3992
|
-
const creds = {
|
|
3993
|
-
token: process.env.VERCEL_ACCESS_TOKEN ?? "",
|
|
3994
|
-
teamId: process.env.VERCEL_TEAM_ID ?? "",
|
|
3995
|
-
projectId: process.env.VERCEL_PROJECT_ID ?? ""
|
|
3996
|
-
};
|
|
3997
|
-
const sb = await VercelSandbox.get({ name: sandboxId, ...creds }).catch((err) => {
|
|
3998
|
-
throw new SandboxUnavailableError(err instanceof Error ? err.message : String(err), { retryable: true, sandboxId });
|
|
3999
|
-
});
|
|
4000
|
-
return makeVercelSandboxProvider(sb);
|
|
4001
|
-
},
|
|
4002
|
-
getActiveCount: async (env) => (await SANDBOX_PROVIDERS.vercel.listOwned(env)).length,
|
|
4003
|
-
listOwned: async (env) => {
|
|
4004
|
-
const { Sandbox: VercelSandbox } = await import("@vercel/sandbox");
|
|
4005
|
-
const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
|
|
4006
|
-
const since = Date.now() - VERCEL_VM_LIFETIME_WINDOW_MS;
|
|
4007
|
-
const out = [];
|
|
4008
|
-
let cursor;
|
|
4009
|
-
for (let page = 0;page < 200; page++) {
|
|
4010
|
-
const result = await pRetry(() => VercelSandbox.list({ ...creds, limit: 50, sortBy: "createdAt", sortOrder: "desc", tags: { executor: AGENT_COMPOSE_TAG }, ...cursor !== undefined ? { cursor } : {} }), { retries: 3, minTimeout: 500, factor: 2 });
|
|
4011
|
-
for (const sb of result.sandboxes) {
|
|
4012
|
-
if (sb.createdAt < since)
|
|
4013
|
-
return out;
|
|
4014
|
-
if (sb.status === "running") {
|
|
4015
|
-
out.push({ sandboxId: sb.name, createdAt: new Date(sb.createdAt), metadata: sb.tags ?? {} });
|
|
4016
|
-
}
|
|
4017
|
-
}
|
|
4018
|
-
if (result.pagination.next === null)
|
|
4019
|
-
return out;
|
|
4020
|
-
cursor = result.pagination.next;
|
|
4021
|
-
}
|
|
4022
|
-
throw new Error("Vercel listSandboxes exceeded 200 pages (10k sandboxes) within the lifetime window — refuse to silently truncate");
|
|
4023
|
-
},
|
|
4024
|
-
deleteSnapshot: async (snapshotId, env) => {
|
|
4025
|
-
const { Snapshot } = await import("@vercel/sandbox");
|
|
4026
|
-
const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
|
|
4027
|
-
const snap = await Snapshot.get({ snapshotId, ...creds });
|
|
4028
|
-
await snap.delete();
|
|
4029
|
-
},
|
|
4030
|
-
snapshotExists: async (snapshotId, env) => {
|
|
4031
|
-
const { Snapshot } = await import("@vercel/sandbox");
|
|
4032
|
-
const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
|
|
4033
|
-
try {
|
|
4034
|
-
await Snapshot.get({ snapshotId, ...creds });
|
|
4035
|
-
return true;
|
|
4036
|
-
} catch (err) {
|
|
4037
|
-
const message = err instanceof Error ? err.message : String(err ?? "");
|
|
4038
|
-
const status = err?.status ?? err?.statusCode;
|
|
4039
|
-
if (status === 404 || /\b404\b|not[\s_-]?found|no such snapshot/i.test(message))
|
|
4040
|
-
return false;
|
|
4041
|
-
throw err;
|
|
4042
|
-
}
|
|
4043
|
-
}
|
|
4044
|
-
},
|
|
4045
|
-
e2b: {
|
|
4046
|
-
requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
|
|
4047
|
-
create: async ({ template, timeoutMs, networkPolicy, size, ...rest }) => {
|
|
4048
|
-
const maxMs = e2bMaxSandboxMs();
|
|
4049
|
-
if (timeoutMs > maxMs) {
|
|
4050
|
-
console.warn(`[sandbox] e2b create timeout clamped: requested ${timeoutMs}ms exceeds the plan cap ${maxMs}ms — ` + `the sandbox dies at the cap, not the requested deadline. Raise E2B_MAX_SANDBOX_MS on plans that allow more.`);
|
|
4051
|
-
}
|
|
4052
|
-
const sandboxOpts = {
|
|
4053
|
-
...rest,
|
|
4054
|
-
timeoutMs: Math.min(timeoutMs, maxMs),
|
|
4055
|
-
...networkPolicy ? { network: toE2bNetwork(networkPolicy) } : {}
|
|
4056
|
-
};
|
|
4057
|
-
const resolvedSize = size ?? DEFAULT_SANDBOX_SIZE;
|
|
4058
|
-
const tmpl = template ?? (isE2bSupportedSize(resolvedSize) ? e2bBaseTemplate(resolvedSize) : undefined) ?? process.env.E2B_DEFAULT_TEMPLATE;
|
|
4059
|
-
return makeE2bSandboxProvider(await (tmpl ? Sandbox.create(tmpl, sandboxOpts) : Sandbox.create(sandboxOpts)));
|
|
4060
|
-
},
|
|
4061
|
-
reconnect: async (sandboxId) => makeE2bSandboxProvider(await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY ?? "", timeoutMs: 3600000 })),
|
|
4062
|
-
killAll: async () => {
|
|
4063
|
-
const paginator = Sandbox.list({ query: { metadata: { executor: AGENT_COMPOSE_TAG } } });
|
|
4064
|
-
const sandboxes = [];
|
|
4065
|
-
while (paginator.hasNext)
|
|
4066
|
-
sandboxes.push(...await paginator.nextItems());
|
|
4067
|
-
if (sandboxes.length === 0)
|
|
4068
|
-
return;
|
|
4069
|
-
await Promise.all(sandboxes.map((s) => Sandbox.kill(s.sandboxId)));
|
|
4070
|
-
console.info(`[sandbox] killed ${sandboxes.length} stale E2B sandboxes (tag: ${AGENT_COMPOSE_TAG})`);
|
|
4071
|
-
},
|
|
4072
|
-
getActiveCount: async () => (await SANDBOX_PROVIDERS.e2b.listOwned({})).length,
|
|
4073
|
-
listOwned: async () => {
|
|
4074
|
-
const paginator = Sandbox.list({ query: { metadata: { executor: AGENT_COMPOSE_TAG }, state: ["running"] } });
|
|
4075
|
-
const out = [];
|
|
4076
|
-
while (paginator.hasNext) {
|
|
4077
|
-
const items = await pRetry(() => paginator.nextItems(), { retries: 3, minTimeout: 500, factor: 2 });
|
|
4078
|
-
for (const sb of items) {
|
|
4079
|
-
out.push({
|
|
4080
|
-
sandboxId: sb.sandboxId,
|
|
4081
|
-
createdAt: new Date(sb.startedAt),
|
|
4082
|
-
metadata: sb.metadata ?? {}
|
|
4083
|
-
});
|
|
4084
|
-
}
|
|
4956
|
+
};
|
|
4085
4957
|
}
|
|
4086
|
-
return out;
|
|
4087
4958
|
},
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
if (!template)
|
|
4096
|
-
throw new Error("E2B Desktop provider requires an explicit `template` (Dockerfile-based — no default base image)");
|
|
4097
|
-
if (networkPolicy && networkPolicy !== "allow-all") {
|
|
4098
|
-
throw new Error("E2B Desktop provider cannot enforce an egress network policy — `@e2b/desktop` bundles an e2b version that predates the native firewall. " + "e2b-desktop is registry-only and must not run with a confined policy.");
|
|
4959
|
+
files: {
|
|
4960
|
+
async write(path, content) {
|
|
4961
|
+
await fs2.mkdir(dirname2(path), { recursive: true });
|
|
4962
|
+
await fs2.writeFile(path, content);
|
|
4963
|
+
},
|
|
4964
|
+
async read(path) {
|
|
4965
|
+
return await fs2.readFile(path, "utf8");
|
|
4099
4966
|
}
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4967
|
+
},
|
|
4968
|
+
async kill() {}
|
|
4969
|
+
};
|
|
4970
|
+
}
|
|
4971
|
+
// src/sandbox/registry.ts
|
|
4972
|
+
import { SandboxNotFoundError, RateLimitError } from "e2b";
|
|
4973
|
+
import pRetry3 from "p-retry";
|
|
4974
|
+
var SANDBOX_PROVIDERS = {
|
|
4975
|
+
vercel: vercelProviderDef,
|
|
4976
|
+
e2b: e2bProviderDef,
|
|
4977
|
+
"e2b-desktop": e2bDesktopProviderDef
|
|
4106
4978
|
};
|
|
4979
|
+
function isTransientSandboxError(error) {
|
|
4980
|
+
if (error instanceof SandboxNotFoundError || error instanceof RateLimitError)
|
|
4981
|
+
return true;
|
|
4982
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
4983
|
+
return /paus|stopp|resum|transition|status(?:\s+code)?[:\s]+5\d\d\b|\b5\d\d\s+(?:internal|bad gateway|service|gateway)|\b429\b|ECONNRESET|ECONNREFUSED|ETIMEDOUT|socket hang up|fetch failed/i.test(message);
|
|
4984
|
+
}
|
|
4985
|
+
function withSandboxRetry(fn, shouldRetry = isTransientSandboxError) {
|
|
4986
|
+
return pRetry3(fn, { retries: 4, minTimeout: 400, factor: 2, shouldRetry });
|
|
4987
|
+
}
|
|
4988
|
+
function withSnapshotRetry(p) {
|
|
4989
|
+
if (!p.snapshot)
|
|
4990
|
+
return p;
|
|
4991
|
+
const snapshot = p.snapshot.bind(p);
|
|
4992
|
+
return { ...p, snapshot: () => withSandboxRetry(snapshot) };
|
|
4993
|
+
}
|
|
4107
4994
|
async function createSandbox(provider, opts) {
|
|
4108
4995
|
const def = SANDBOX_PROVIDERS[provider];
|
|
4109
4996
|
if (!def)
|
|
@@ -4274,7 +5161,7 @@ class StepObservabilityCollector {
|
|
|
4274
5161
|
await this.drainInFlight();
|
|
4275
5162
|
const hasMetadata = Object.keys(this.metadata).length > 0;
|
|
4276
5163
|
const hasSubSteps = this.subSteps.length > 0;
|
|
4277
|
-
const remainingEvents = this.events.
|
|
5164
|
+
const remainingEvents = this.events.map((event, seq) => ({ ...event, seq })).filter(({ seq }) => !this.ackedSeqs.has(seq));
|
|
4278
5165
|
const hasEvents = remainingEvents.length > 0;
|
|
4279
5166
|
if (!hasMetadata && !hasEvents && !hasSubSteps)
|
|
4280
5167
|
return;
|
|
@@ -4307,6 +5194,8 @@ function scopedMemoize(scope) {
|
|
|
4307
5194
|
}
|
|
4308
5195
|
|
|
4309
5196
|
// src/pause/wrappers.ts
|
|
5197
|
+
import { z as z11 } from "zod";
|
|
5198
|
+
var DecisionResultSchema = z11.object({ decision: z11.string() });
|
|
4310
5199
|
function buildPauseWrappers(pause) {
|
|
4311
5200
|
return {
|
|
4312
5201
|
sleep(durationMs) {
|
|
@@ -4324,6 +5213,20 @@ function buildPauseWrappers(pause) {
|
|
|
4324
5213
|
...req.schema !== undefined ? { schema: req.schema } : {},
|
|
4325
5214
|
...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {}
|
|
4326
5215
|
}, "event");
|
|
5216
|
+
},
|
|
5217
|
+
requestDecision(req) {
|
|
5218
|
+
return pause({
|
|
5219
|
+
reason: req.prompt,
|
|
5220
|
+
payload: {
|
|
5221
|
+
prompt: req.prompt,
|
|
5222
|
+
...req.options !== undefined ? { options: req.options } : {},
|
|
5223
|
+
...req.approvers !== undefined ? { approvers: req.approvers } : {},
|
|
5224
|
+
...req.draft !== undefined ? { draft: req.draft } : {}
|
|
5225
|
+
},
|
|
5226
|
+
schema: DecisionResultSchema,
|
|
5227
|
+
...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {},
|
|
5228
|
+
...req.correlationKey !== undefined ? { correlationKey: req.correlationKey } : {}
|
|
5229
|
+
}, "decision");
|
|
4327
5230
|
}
|
|
4328
5231
|
};
|
|
4329
5232
|
}
|
|
@@ -4626,14 +5529,35 @@ function buildInvokeChild(runId, opts = {}) {
|
|
|
4626
5529
|
});
|
|
4627
5530
|
}
|
|
4628
5531
|
// src/workflow-steps/step.ts
|
|
5532
|
+
var CONTROL_CHARS = /[\u0000-\u001f\u007f]/;
|
|
4629
5533
|
function defineStep(opts) {
|
|
4630
5534
|
if (!opts.name)
|
|
4631
5535
|
throw new Error("defineStep: 'name' is required");
|
|
5536
|
+
if (opts.summary !== undefined) {
|
|
5537
|
+
if (opts.summary.length === 0 || opts.summary.length > 200 || CONTROL_CHARS.test(opts.summary)) {
|
|
5538
|
+
throw new Error(`defineStep("${opts.name}"): 'summary' must be one plain sentence, 1-200 characters, no control characters`);
|
|
5539
|
+
}
|
|
5540
|
+
}
|
|
5541
|
+
if (opts.deliverables !== undefined) {
|
|
5542
|
+
if (opts.deliverables.length > 8) {
|
|
5543
|
+
throw new Error(`defineStep("${opts.name}"): at most 8 deliverables`);
|
|
5544
|
+
}
|
|
5545
|
+
for (const d of opts.deliverables) {
|
|
5546
|
+
if (!d.path || d.path.length > 200 || CONTROL_CHARS.test(d.path)) {
|
|
5547
|
+
throw new Error(`defineStep("${opts.name}"): each deliverable needs a 'path' of 1-200 characters, no control characters`);
|
|
5548
|
+
}
|
|
5549
|
+
if (d.description !== undefined && (d.description.length === 0 || d.description.length > 200 || CONTROL_CHARS.test(d.description))) {
|
|
5550
|
+
throw new Error(`defineStep("${opts.name}"): deliverable descriptions must be 1-200 characters, no control characters`);
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
4632
5554
|
return {
|
|
4633
5555
|
name: opts.name,
|
|
4634
5556
|
input: opts.input,
|
|
4635
5557
|
output: opts.output,
|
|
4636
|
-
run: opts.run
|
|
5558
|
+
run: opts.run,
|
|
5559
|
+
...opts.summary !== undefined ? { summary: opts.summary } : {},
|
|
5560
|
+
...opts.deliverables !== undefined ? { deliverables: Object.freeze(opts.deliverables.map((d) => ({ ...d }))) } : {}
|
|
4637
5561
|
};
|
|
4638
5562
|
}
|
|
4639
5563
|
// src/step-invocation/protocol.ts
|
|
@@ -4665,37 +5589,40 @@ function requestContextPath(stepIndex) {
|
|
|
4665
5589
|
function stepResultFilePath(token) {
|
|
4666
5590
|
return `/tmp/wf/step-result-${token}.json`;
|
|
4667
5591
|
}
|
|
5592
|
+
function stepPidFilePath(token) {
|
|
5593
|
+
return `/tmp/wf/step-pid-${token}.pid`;
|
|
5594
|
+
}
|
|
4668
5595
|
function stepLogFilePath(token) {
|
|
4669
5596
|
return `/tmp/wf/step-log-${token}.log`;
|
|
4670
5597
|
}
|
|
4671
5598
|
// src/step-invocation/invoker.ts
|
|
4672
5599
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
4673
|
-
import { z as
|
|
4674
|
-
var StepObservabilitySchema =
|
|
4675
|
-
metadata:
|
|
4676
|
-
events:
|
|
4677
|
-
subSteps:
|
|
4678
|
-
name:
|
|
4679
|
-
startedAt:
|
|
4680
|
-
durationMs:
|
|
4681
|
-
status:
|
|
4682
|
-
error:
|
|
5600
|
+
import { z as z12 } from "zod";
|
|
5601
|
+
var StepObservabilitySchema = z12.object({
|
|
5602
|
+
metadata: z12.record(z12.string(), z12.unknown()).optional(),
|
|
5603
|
+
events: z12.array(z12.unknown()).optional(),
|
|
5604
|
+
subSteps: z12.array(z12.object({
|
|
5605
|
+
name: z12.string(),
|
|
5606
|
+
startedAt: z12.number(),
|
|
5607
|
+
durationMs: z12.number(),
|
|
5608
|
+
status: z12.enum(["completed", "failed"]),
|
|
5609
|
+
error: z12.string().optional()
|
|
4683
5610
|
})).optional()
|
|
4684
5611
|
}).passthrough();
|
|
4685
|
-
var StepResultEnvelopeSchema =
|
|
4686
|
-
|
|
4687
|
-
ok:
|
|
4688
|
-
output:
|
|
5612
|
+
var StepResultEnvelopeSchema = z12.discriminatedUnion("ok", [
|
|
5613
|
+
z12.object({
|
|
5614
|
+
ok: z12.literal(true),
|
|
5615
|
+
output: z12.unknown().optional(),
|
|
4689
5616
|
observability: StepObservabilitySchema.optional()
|
|
4690
5617
|
}),
|
|
4691
|
-
|
|
4692
|
-
ok:
|
|
4693
|
-
kind:
|
|
4694
|
-
error:
|
|
5618
|
+
z12.object({
|
|
5619
|
+
ok: z12.literal(false),
|
|
5620
|
+
kind: z12.enum(["protocol", "user-step"]),
|
|
5621
|
+
error: z12.string().optional()
|
|
4695
5622
|
})
|
|
4696
5623
|
]);
|
|
4697
|
-
var StepPauseEnvelopeSchema =
|
|
4698
|
-
pauseId:
|
|
5624
|
+
var StepPauseEnvelopeSchema = z12.object({
|
|
5625
|
+
pauseId: z12.string().min(1),
|
|
4699
5626
|
pauseRequest: StepPauseRequestSchema,
|
|
4700
5627
|
observability: StepObservabilitySchema.optional()
|
|
4701
5628
|
});
|
|
@@ -4863,28 +5790,50 @@ ${tail}` : ""}`
|
|
|
4863
5790
|
}
|
|
4864
5791
|
};
|
|
4865
5792
|
}
|
|
4866
|
-
async function pollDurableResult(sandbox, runnerPid, resultToken,
|
|
5793
|
+
async function pollDurableResult(sandbox, runnerPid, resultToken, opts) {
|
|
5794
|
+
const signal = opts?.signal;
|
|
4867
5795
|
const resultFile = stepResultFilePath(resultToken);
|
|
4868
5796
|
const POLL_MS = 1000;
|
|
4869
|
-
const
|
|
5797
|
+
const deadlineMs = opts?.deadlineMs ?? 45 * 60000;
|
|
5798
|
+
const DEADLINE = Date.now() + deadlineMs;
|
|
5799
|
+
const CMD_TIMEOUT_MS = 1e4;
|
|
5800
|
+
const UNREACHABLE_LIMIT = 20;
|
|
5801
|
+
let unreachableStreak = 0;
|
|
4870
5802
|
const readResult = async () => {
|
|
4871
|
-
const r = await sandbox.commands.run(`cat ${resultFile} 2>/dev/null || true
|
|
4872
|
-
|
|
5803
|
+
const r = await sandbox.commands.run(`cat ${resultFile} 2>/dev/null || true`, { timeoutMs: CMD_TIMEOUT_MS }).catch(() => "unreachable");
|
|
5804
|
+
if (r === "unreachable")
|
|
5805
|
+
return "unreachable";
|
|
5806
|
+
return r.stdout ? parseStepResult(r.stdout, resultToken) ?? null : null;
|
|
4873
5807
|
};
|
|
4874
5808
|
for (let attempt = 1;; attempt++) {
|
|
4875
5809
|
if (signal?.aborted) {
|
|
4876
5810
|
return { ok: false, error: { kind: "runner-exit", message: "reconnectStep aborted (run re-paused)", exitCode: 1 } };
|
|
4877
5811
|
}
|
|
4878
5812
|
const fromFile = await readResult();
|
|
4879
|
-
if (fromFile)
|
|
5813
|
+
if (fromFile !== null && fromFile !== "unreachable")
|
|
4880
5814
|
return fromFile;
|
|
4881
|
-
const probe = await sandbox.commands.run(`test -d /proc/${runnerPid} && echo alive || echo dead
|
|
5815
|
+
const probe = await sandbox.commands.run(`test -d /proc/${runnerPid} && echo alive || echo dead`, { timeoutMs: CMD_TIMEOUT_MS }).catch(() => null);
|
|
5816
|
+
if (fromFile === "unreachable" && probe === null) {
|
|
5817
|
+
unreachableStreak++;
|
|
5818
|
+
if (unreachableStreak >= UNREACHABLE_LIMIT) {
|
|
5819
|
+
return {
|
|
5820
|
+
ok: false,
|
|
5821
|
+
error: {
|
|
5822
|
+
kind: "runner-exit",
|
|
5823
|
+
message: `sandbox stopped responding while waiting for runner pid ${runnerPid} to emit a result (${UNREACHABLE_LIMIT} consecutive failed polls)`,
|
|
5824
|
+
exitCode: 1
|
|
5825
|
+
}
|
|
5826
|
+
};
|
|
5827
|
+
}
|
|
5828
|
+
} else {
|
|
5829
|
+
unreachableStreak = 0;
|
|
5830
|
+
}
|
|
4882
5831
|
const dead = (probe?.stdout ?? "").includes("dead");
|
|
4883
5832
|
process.stderr.write(`[recover] poll ${attempt}: result-file=absent runner=${dead ? "dead" : "alive"} pid=${runnerPid}
|
|
4884
5833
|
`);
|
|
4885
5834
|
if (dead) {
|
|
4886
5835
|
const finalRead = await readResult();
|
|
4887
|
-
if (finalRead)
|
|
5836
|
+
if (finalRead !== null && finalRead !== "unreachable")
|
|
4888
5837
|
return finalRead;
|
|
4889
5838
|
return {
|
|
4890
5839
|
ok: false,
|
|
@@ -4894,14 +5843,18 @@ async function pollDurableResult(sandbox, runnerPid, resultToken, signal) {
|
|
|
4894
5843
|
if (Date.now() > DEADLINE) {
|
|
4895
5844
|
return {
|
|
4896
5845
|
ok: false,
|
|
4897
|
-
error: {
|
|
5846
|
+
error: {
|
|
5847
|
+
kind: "runner-exit",
|
|
5848
|
+
message: `timed out after ${Math.round(deadlineMs / 60000)}m waiting for runner pid ${runnerPid} to emit a result`,
|
|
5849
|
+
exitCode: 1
|
|
5850
|
+
}
|
|
4898
5851
|
};
|
|
4899
5852
|
}
|
|
4900
5853
|
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
4901
5854
|
}
|
|
4902
5855
|
}
|
|
4903
|
-
async function recoverLogsAndResult(sandbox, runnerPid, resultToken, liveSplitters, opts) {
|
|
4904
|
-
const result = await pollDurableResult(sandbox, runnerPid, resultToken);
|
|
5856
|
+
async function recoverLogsAndResult(sandbox, runnerPid, resultToken, liveSplitters, opts, pollOpts) {
|
|
5857
|
+
const result = await pollDurableResult(sandbox, runnerPid, resultToken, pollOpts);
|
|
4905
5858
|
if (sandbox.files.read && opts?.onStdout) {
|
|
4906
5859
|
const fullLog = await sandbox.files.read(stepLogFilePath(resultToken)).catch(() => null);
|
|
4907
5860
|
if (fullLog != null) {
|
|
@@ -4916,6 +5869,21 @@ async function recoverLogsAndResult(sandbox, runnerPid, resultToken, liveSplitte
|
|
|
4916
5869
|
}
|
|
4917
5870
|
return result;
|
|
4918
5871
|
}
|
|
5872
|
+
async function readRunnerPid(sandbox, resultToken) {
|
|
5873
|
+
const ATTEMPTS = 3;
|
|
5874
|
+
const RETRY_DELAY_MS = 2000;
|
|
5875
|
+
for (let attempt = 1;attempt <= ATTEMPTS; attempt++) {
|
|
5876
|
+
try {
|
|
5877
|
+
const r = await sandbox.commands.run(`cat ${stepPidFilePath(resultToken)} 2>/dev/null || true`, { timeoutMs: 1e4 });
|
|
5878
|
+
const pid = Number.parseInt(r.stdout.trim(), 10);
|
|
5879
|
+
if (Number.isInteger(pid) && pid > 0)
|
|
5880
|
+
return pid;
|
|
5881
|
+
} catch {}
|
|
5882
|
+
if (attempt < ATTEMPTS)
|
|
5883
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
5884
|
+
}
|
|
5885
|
+
return null;
|
|
5886
|
+
}
|
|
4919
5887
|
async function prepareStepLaunch(sandbox, request, opts) {
|
|
4920
5888
|
const resultToken = randomBytes2(16).toString("hex");
|
|
4921
5889
|
await Promise.all([
|
|
@@ -4933,7 +5901,7 @@ async function invokeStep(sandbox, request, opts) {
|
|
|
4933
5901
|
const splitters = makeStreamSplitters(opts, resultToken);
|
|
4934
5902
|
let result;
|
|
4935
5903
|
try {
|
|
4936
|
-
result = await sandbox.commands.run(RUNNER_COMMAND
|
|
5904
|
+
result = await sandbox.commands.run(`set -o pipefail; echo $$ > ${stepPidFilePath(resultToken)}; ${RUNNER_COMMAND} | tee ${stepLogFilePath(resultToken)}`, {
|
|
4937
5905
|
envs: { ...envs, HOME: "/root", IS_SANDBOX: "1" },
|
|
4938
5906
|
sudo: true,
|
|
4939
5907
|
timeoutMs: 0,
|
|
@@ -4941,8 +5909,15 @@ async function invokeStep(sandbox, request, opts) {
|
|
|
4941
5909
|
...splitters.onStderr ? { onStderr: splitters.onStderr } : {}
|
|
4942
5910
|
});
|
|
4943
5911
|
} catch (e) {
|
|
4944
|
-
if (e instanceof SandboxUnavailableError)
|
|
4945
|
-
|
|
5912
|
+
if (e instanceof SandboxUnavailableError) {
|
|
5913
|
+
if (e.retryable)
|
|
5914
|
+
throw e;
|
|
5915
|
+
const runnerPid = await readRunnerPid(sandbox, resultToken);
|
|
5916
|
+
if (runnerPid === null)
|
|
5917
|
+
throw e;
|
|
5918
|
+
opts?.onStreamDegraded?.({ error: e, runnerPid, resultToken });
|
|
5919
|
+
return recoverLogsAndResult(sandbox, runnerPid, resultToken, splitters, opts, opts?.recoveryDeadlineMs !== undefined ? { deadlineMs: opts.recoveryDeadlineMs } : undefined);
|
|
5920
|
+
}
|
|
4946
5921
|
const ce = e;
|
|
4947
5922
|
result = {
|
|
4948
5923
|
stdout: ce.stdout ?? ce.result?.stdout ?? "",
|
|
@@ -4990,6 +5965,7 @@ async function reconnectStep(sandbox, resume, opts) {
|
|
|
4990
5965
|
const { runnerPid, resultToken } = resume;
|
|
4991
5966
|
const splitters = makeStreamSplitters(opts, resultToken);
|
|
4992
5967
|
const signal = opts?.signal;
|
|
5968
|
+
const pollDeadlineMs = opts?.pollDeadlineMs;
|
|
4993
5969
|
let attached = null;
|
|
4994
5970
|
try {
|
|
4995
5971
|
attached = await sandbox.commands.connectProcess(runnerPid, {
|
|
@@ -5005,7 +5981,10 @@ async function reconnectStep(sandbox, resume, opts) {
|
|
|
5005
5981
|
resultToken,
|
|
5006
5982
|
async wait() {
|
|
5007
5983
|
try {
|
|
5008
|
-
const result = await pollDurableResult(sandbox, runnerPid, resultToken,
|
|
5984
|
+
const result = await pollDurableResult(sandbox, runnerPid, resultToken, {
|
|
5985
|
+
...signal ? { signal } : {},
|
|
5986
|
+
...pollDeadlineMs !== undefined ? { deadlineMs: pollDeadlineMs } : {}
|
|
5987
|
+
});
|
|
5009
5988
|
splitters.flush();
|
|
5010
5989
|
return result;
|
|
5011
5990
|
} finally {
|
|
@@ -5121,7 +6100,7 @@ async function serveStep(handler) {
|
|
|
5121
6100
|
process.exit(exitCode);
|
|
5122
6101
|
}
|
|
5123
6102
|
// src/agent/run-agent.ts
|
|
5124
|
-
import { z as
|
|
6103
|
+
import { z as z13 } from "zod";
|
|
5125
6104
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5126
6105
|
|
|
5127
6106
|
// src/agent/agent-context.ts
|
|
@@ -5181,9 +6160,9 @@ step) instead of writing source from memory — the skill scaffolds the correct,
|
|
|
5181
6160
|
current shape. Then \`agentc register <file.ts>\` (or \`/ac:register\`).
|
|
5182
6161
|
|
|
5183
6162
|
The skill writes **step-form** (a builder of discrete, durable \`.step()\`s).
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
6163
|
+
The legacy run-form (\`defineWorkflow({ run(ctx, sandbox) { … } })\`) has been
|
|
6164
|
+
REMOVED from the SDK — registering one fails with an error. Step-form is the
|
|
6165
|
+
only shape: durable per-step replay, and pause only works there.
|
|
5187
6166
|
|
|
5188
6167
|
## Pausing to ask the human
|
|
5189
6168
|
|
|
@@ -5229,13 +6208,174 @@ request **without** an Authorization header and the platform adds it. Don't try
|
|
|
5229
6208
|
to read or exfiltrate tokens; they aren't here. The "Connectors & access"
|
|
5230
6209
|
section below (when present) lists exactly which providers this run can reach.
|
|
5231
6210
|
|
|
6211
|
+
## Computer Use — you have a real desktop, and it is already running
|
|
6212
|
+
|
|
6213
|
+
**This machine has a graphical desktop.** Every session machine does — terminal
|
|
6214
|
+
sessions included — and the platform brings it UP AT BOOT, before your first
|
|
6215
|
+
turn: an X server on \`DISPLAY=:0\`, the openbox window manager, wallpaper and a
|
|
6216
|
+
panel. You do not start it, you do not wait for a human to open it, and you do
|
|
6217
|
+
not need a viewer. Go straight to driving it.
|
|
6218
|
+
|
|
6219
|
+
(The one exception, and it is rare: an image built without the GUI stack has no
|
|
6220
|
+
display at all, and \`DISPLAY=:0 xdotool getdisplaygeometry\` errors outright.
|
|
6221
|
+
That single case is the only one where this section does not apply — a
|
|
6222
|
+
screenshot showing only wallpaper is NOT it, and neither is an app that failed
|
|
6223
|
+
to start.)
|
|
6224
|
+
|
|
6225
|
+
**This is how you SEE anything.** Any question of the form "does it render?",
|
|
6226
|
+
"is the page actually working?", "did the markers show up?", "what does it look
|
|
6227
|
+
like?" is answered by opening it on this desktop and screenshotting it — not by
|
|
6228
|
+
reasoning about the code, and not by a headless render (which proves the process
|
|
6229
|
+
starts, not that the thing draws). Verify visually before you report visually.
|
|
6230
|
+
|
|
6231
|
+
- **Input** — \`xdotool\` against \`DISPLAY=:0\`: \`DISPLAY=:0 xdotool mousemove <x> <y>\`,
|
|
6232
|
+
\`DISPLAY=:0 xdotool click 1\` (1=left, 3=right), \`DISPLAY=:0 xdotool type 'text'\`,
|
|
6233
|
+
\`DISPLAY=:0 xdotool key Return\` (also \`ctrl+c\`, \`Tab\`, \`super\`, …).
|
|
6234
|
+
- **Screenshots** — \`scrot\` (or ImageMagick's \`import\`):
|
|
6235
|
+
\`DISPLAY=:0 scrot /tmp/screen.png\`, then READ the PNG to see the screen,
|
|
6236
|
+
before and after you act. A screenshot is your only eyes here.
|
|
6237
|
+
- **Apps + windows** — a plain X session. Launch in the background:
|
|
6238
|
+
\`DISPLAY=:0 <app> &\`. Two things that trip agents up, both normal:
|
|
6239
|
+
- a GUI app needs a **beat to map its window** — screenshot, and if you see
|
|
6240
|
+
only wallpaper, wait a couple of seconds and screenshot again before
|
|
6241
|
+
concluding anything;
|
|
6242
|
+
- **Chromium needs \`--no-sandbox\`** in this environment (nested sandbox).
|
|
6243
|
+
The whole recipe for looking at a local page:
|
|
6244
|
+
\`DISPLAY=:0 chromium --no-sandbox --disable-gpu --start-maximized <url> &\`
|
|
6245
|
+
then \`sleep 5\`, then \`DISPLAY=:0 scrot /tmp/screen.png\` and read it.
|
|
6246
|
+
If a window still never appears, read the app's own log (\`/tmp/*.log\`) — the
|
|
6247
|
+
desktop is not the thing that failed. Do NOT abandon it for a headless
|
|
6248
|
+
screenshot: headless cannot tell you what the human will see.
|
|
6249
|
+
- **A human can watch** — the session header carries a **Desktop** button in the
|
|
6250
|
+
dashboard, and what a teammate sees there is exactly this display. The desktop
|
|
6251
|
+
runs whether or not anyone is looking; never wait for a viewer.
|
|
6252
|
+
|
|
6253
|
+
Nothing here changes the credentials rule above: tokens are injected at the
|
|
6254
|
+
network layer, never present on the desktop or in any file you can read — so
|
|
6255
|
+
there is nothing to type, paste, or screenshot a credential from.
|
|
6256
|
+
|
|
6257
|
+
## Recording a demo — the desktop, captured to a video the human can play
|
|
6258
|
+
|
|
6259
|
+
"Record a demo of you using X" is a normal ask, and this machine does it:
|
|
6260
|
+
start a screen recording, drive the app with \`xdotool\` exactly as in Computer
|
|
6261
|
+
Use, stop the recording, and report the file. (For a LIVE view no recording is
|
|
6262
|
+
needed — the session header's **Desktop** button already streams this display
|
|
6263
|
+
to any teammate watching; a recording is the durable, replayable artifact.
|
|
6264
|
+
Both modes exist; say so when it matters.)
|
|
6265
|
+
|
|
6266
|
+
**ffmpeg is NOT pre-installed** — install it first, once per machine:
|
|
6267
|
+
|
|
6268
|
+
sudo apt-get update -q && sudo apt-get install -y -q ffmpeg
|
|
6269
|
+
|
|
6270
|
+
(drop \`sudo\` if you are already root). Then the whole recipe:
|
|
6271
|
+
|
|
6272
|
+
DISPLAY=:0 ffmpeg -f x11grab \\
|
|
6273
|
+
-video_size "$(DISPLAY=:0 xdotool getdisplaygeometry | tr ' ' x)" \\
|
|
6274
|
+
-framerate 10 -i :0 -c:v libvpx -b:v 1M -deadline realtime -cpu-used 8 \\
|
|
6275
|
+
demo.webm &
|
|
6276
|
+
FFMPEG_PID=$!
|
|
6277
|
+
# ... drive the app with xdotool, screenshotting as you go ...
|
|
6278
|
+
kill -INT "$FFMPEG_PID" && wait "$FFMPEG_PID"
|
|
6279
|
+
|
|
6280
|
+
The gotchas, each one earned:
|
|
6281
|
+
- **Stop with SIGINT (\`kill -INT\`), never SIGKILL** — ffmpeg finalizes the
|
|
6282
|
+
file on SIGINT; a hard kill truncates the encode mid-write.
|
|
6283
|
+
- **Record WebM (matroska-family), not MP4** — mp4 writes its moov atom at the
|
|
6284
|
+
END, so a killed or crashed encode leaves an UNPLAYABLE file; webm stays
|
|
6285
|
+
playable up to the last written frame and plays natively in the browser.
|
|
6286
|
+
MP4's only edge is compatibility with some external players — transcode
|
|
6287
|
+
afterwards if you truly need it, never record straight to it.
|
|
6288
|
+
- **\`-video_size\` must match the real screen** — x11grab does not default to
|
|
6289
|
+
it; read the geometry from \`xdotool getdisplaygeometry\` as above.
|
|
6290
|
+
- **10–12 fps is right for a screen demo** — small files, legible UI motion;
|
|
6291
|
+
this is not video production.
|
|
6292
|
+
- **Write to the drive, not /tmp** — the recording must land in your working
|
|
6293
|
+
directory to persist and show up in Files; a file in /tmp dies with the
|
|
6294
|
+
sandbox.
|
|
6295
|
+
- When you stop, **TELL the human the exact drive path** of the video — a
|
|
6296
|
+
recording they cannot find might as well not exist.
|
|
6297
|
+
|
|
6298
|
+
## Previews — register every server you serve (cloud sessions)
|
|
6299
|
+
|
|
6300
|
+
In a cloud session, a dev server listening on a port becomes a hosted,
|
|
6301
|
+
member-gated URL the human can open — but ONLY if you register it:
|
|
6302
|
+
|
|
6303
|
+
agentc preview open <port> [--name <label>] [--path </landing>]
|
|
6304
|
+
# hosted URL + an "Open preview" card
|
|
6305
|
+
agentc preview list # the registry — what is live right now
|
|
6306
|
+
agentc preview close <port> # take one down
|
|
6307
|
+
|
|
6308
|
+
(\`agentc preview announce\` is the same verb as \`open\` — announce what you
|
|
6309
|
+
serve.) \`--name\` is the human-readable label; \`--path\` is where the app
|
|
6310
|
+
should open (e.g. \`/dashboard\`) — the card and every chip land the human
|
|
6311
|
+
there instead of a bare \`/\`.
|
|
6312
|
+
|
|
6313
|
+
Register EVERY server you start for a human, the moment it is listening, and
|
|
6314
|
+
tell them the URL the command printed. The registry is the only discoverable
|
|
6315
|
+
record of what this machine serves: an unregistered server keeps running, but
|
|
6316
|
+
nobody — not the human, not the assistant — can find its URL, and when the
|
|
6317
|
+
sandbox recycles it is gone without a trace. Never guess or hand out a raw
|
|
6318
|
+
port; the hosted URL from \`agentc preview open\` is the only address that
|
|
6319
|
+
works outside this machine. (Outside a cloud session the command errors
|
|
6320
|
+
honestly — there is no session sandbox to expose.)
|
|
6321
|
+
|
|
6322
|
+
What registration buys you: the human sees each registered preview as a card
|
|
6323
|
+
in the conversation and a row in the session's Previews menu — MANY at once,
|
|
6324
|
+
one per port — and the assistant resolves "open the preview" from this same
|
|
6325
|
+
registry (its \`list_previews\` read), so what you register is exactly what
|
|
6326
|
+
gets opened. On deployments with subdomain previews the hosted URL is a real
|
|
6327
|
+
origin of its own — absolute asset paths and client-side routing work, the
|
|
6328
|
+
whole app is navigable — so serve normally and let the platform address it;
|
|
6329
|
+
never rewrite your app to a path prefix.
|
|
6330
|
+
|
|
5232
6331
|
## Tools in this environment
|
|
5233
6332
|
|
|
5234
6333
|
- \`agentc\` — Agent Compose CLI (your primary interface; authed from env)
|
|
5235
6334
|
- \`@agent-compose/sdk\` — installed in /workspace for writing workflows
|
|
5236
6335
|
- \`/ac:*\` Claude Code skills — slash commands for the above
|
|
5237
6336
|
- \`archil\` (factory drive), \`rtk\`, \`bun\`
|
|
6337
|
+
- \`xdotool\` / \`scrot\` — drive + screenshot the desktop (if this machine has one; see Computer Use)
|
|
5238
6338
|
- A world-writable \`/workspace\` working directory`;
|
|
6339
|
+
function buildAddedSessionBrief(p) {
|
|
6340
|
+
const dashboardSection = p.dashboardUrl === null ? "" : `
|
|
6341
|
+
|
|
6342
|
+
## Dashboard
|
|
6343
|
+
|
|
6344
|
+
Teammates follow this conversation (and the rest of the factory) in the
|
|
6345
|
+
Agent Compose dashboard: ${p.dashboardUrl}`;
|
|
6346
|
+
return `# Connected to Agent Compose
|
|
6347
|
+
|
|
6348
|
+
Agent Compose is your team's agent platform: durable conversations,
|
|
6349
|
+
workflow runs, and shared factory drives where humans and agents work
|
|
6350
|
+
together. THIS terminal's claude-code session is connected to Agent
|
|
6351
|
+
Compose conversation \`${p.conversationId}\` on ${p.serverUrl}.
|
|
6352
|
+
|
|
6353
|
+
That conversation is a LIVE, READ-ONLY MIRROR of this terminal session:
|
|
6354
|
+
teammates read along in the dashboard as the work happens, but they cannot
|
|
6355
|
+
message you through it — anything posted there is answered by the server
|
|
6356
|
+
with a notice and never reaches this terminal. Everything you do here is
|
|
6357
|
+
mirrored automatically; you have an audience, not a channel.
|
|
6358
|
+
|
|
6359
|
+
## The \`agentc\` toolbelt
|
|
6360
|
+
|
|
6361
|
+
The \`agentc\` CLI works from this shell. It is already authenticated on
|
|
6362
|
+
this machine via the bridge credential fallback — no keys to manage,
|
|
6363
|
+
commands just work:
|
|
6364
|
+
|
|
6365
|
+
agentc list # registered workflows
|
|
6366
|
+
agentc logs <run-id> # a run's logs
|
|
6367
|
+
agentc invoke <workflow> -i '<json>' # dispatch a workflow
|
|
6368
|
+
agentc events list # read the factory timeline
|
|
6369
|
+
|
|
6370
|
+
## Scope — this is your LOCAL machine
|
|
6371
|
+
|
|
6372
|
+
The files here are YOURS: no factory drive is mounted in this session,
|
|
6373
|
+
and nothing you write locally lands on a shared drive by itself. Cloud
|
|
6374
|
+
drive/branch semantics (per-run directories on the factory drive, drive
|
|
6375
|
+
branches, persist-by-default outputs) apply only to cloud sessions —
|
|
6376
|
+
not here.${dashboardSection}
|
|
6377
|
+
`;
|
|
6378
|
+
}
|
|
5239
6379
|
function renderConnectorsSection(connectors) {
|
|
5240
6380
|
if (connectors.length === 0)
|
|
5241
6381
|
return "";
|
|
@@ -5341,29 +6481,29 @@ function stripFrontmatter(content) {
|
|
|
5341
6481
|
return end === -1 ? content : content.slice(end + 4).trimStart();
|
|
5342
6482
|
}
|
|
5343
6483
|
function zodTypeName(schema) {
|
|
5344
|
-
if (schema instanceof
|
|
6484
|
+
if (schema instanceof z13.ZodString)
|
|
5345
6485
|
return "string";
|
|
5346
|
-
if (schema instanceof
|
|
6486
|
+
if (schema instanceof z13.ZodNumber)
|
|
5347
6487
|
return "number";
|
|
5348
|
-
if (schema instanceof
|
|
6488
|
+
if (schema instanceof z13.ZodBoolean)
|
|
5349
6489
|
return "boolean";
|
|
5350
|
-
if (schema instanceof
|
|
6490
|
+
if (schema instanceof z13.ZodArray)
|
|
5351
6491
|
return `${zodTypeName(schema.element)}[]`;
|
|
5352
|
-
if (schema instanceof
|
|
6492
|
+
if (schema instanceof z13.ZodEnum)
|
|
5353
6493
|
return schema.options.map((o) => `"${o}"`).join(" | ");
|
|
5354
|
-
if (schema instanceof
|
|
6494
|
+
if (schema instanceof z13.ZodOptional)
|
|
5355
6495
|
return `${zodTypeName(schema.unwrap())} (optional)`;
|
|
5356
|
-
if (schema instanceof
|
|
6496
|
+
if (schema instanceof z13.ZodNullable)
|
|
5357
6497
|
return `${zodTypeName(schema.unwrap())} | null`;
|
|
5358
|
-
if (schema instanceof
|
|
6498
|
+
if (schema instanceof z13.ZodLiteral)
|
|
5359
6499
|
return `"${String(schema.value)}"`;
|
|
5360
|
-
if (schema instanceof
|
|
6500
|
+
if (schema instanceof z13.ZodObject)
|
|
5361
6501
|
return "object";
|
|
5362
6502
|
return "any";
|
|
5363
6503
|
}
|
|
5364
6504
|
function buildResponseFormatAppendix(schema) {
|
|
5365
6505
|
const header = ["## Response Format", "", "When you emit `exit_signal: true` in your `<status>` block, also include a `<response>` block containing JSON immediately after `<status>`:", ""];
|
|
5366
|
-
const shape = schema instanceof
|
|
6506
|
+
const shape = schema instanceof z13.ZodObject ? schema.shape : null;
|
|
5367
6507
|
const body = shape ? (() => {
|
|
5368
6508
|
const entries = Object.entries(shape);
|
|
5369
6509
|
const example = Object.fromEntries(entries.map(([k, v]) => [k, `<${zodTypeName(v)}>`]));
|
|
@@ -5501,6 +6641,692 @@ async function agent(opts) {
|
|
|
5501
6641
|
inboxAbort.abort();
|
|
5502
6642
|
}
|
|
5503
6643
|
}
|
|
6644
|
+
// src/display.ts
|
|
6645
|
+
var DISPLAY_MARKER_KEY = "acDisplay";
|
|
6646
|
+
var DISPLAY_MARKER_VERSION = "v1";
|
|
6647
|
+
var PLAN_MAX_ENTRIES = 64;
|
|
6648
|
+
var PLAN_ENTRY_MAX_CHARS = 1000;
|
|
6649
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6650
|
+
var PLAN_STATUSES = new Set(["pending", "in_progress", "completed"]);
|
|
6651
|
+
function clampPlanEntries(raw) {
|
|
6652
|
+
if (!Array.isArray(raw))
|
|
6653
|
+
return [];
|
|
6654
|
+
const entries = [];
|
|
6655
|
+
for (const e of raw) {
|
|
6656
|
+
if (typeof e !== "object" || e === null)
|
|
6657
|
+
continue;
|
|
6658
|
+
const { content, status } = e;
|
|
6659
|
+
if (typeof content !== "string" || content.trim().length === 0)
|
|
6660
|
+
continue;
|
|
6661
|
+
if (typeof status !== "string" || !PLAN_STATUSES.has(status))
|
|
6662
|
+
continue;
|
|
6663
|
+
entries.push({ content: content.slice(0, PLAN_ENTRY_MAX_CHARS), status });
|
|
6664
|
+
if (entries.length >= PLAN_MAX_ENTRIES)
|
|
6665
|
+
break;
|
|
6666
|
+
}
|
|
6667
|
+
return entries;
|
|
6668
|
+
}
|
|
6669
|
+
var TABLE_MAX_COLUMNS = 12;
|
|
6670
|
+
var TABLE_MAX_ROWS = 100;
|
|
6671
|
+
var TABLE_CELL_MAX_CHARS = 200;
|
|
6672
|
+
var IMAGE_PATH_MAX_CHARS = 512;
|
|
6673
|
+
function clampCellString(s) {
|
|
6674
|
+
return s.length > TABLE_CELL_MAX_CHARS ? `${s.slice(0, TABLE_CELL_MAX_CHARS - 1)}…` : s;
|
|
6675
|
+
}
|
|
6676
|
+
function clampCell(value) {
|
|
6677
|
+
if (typeof value === "string")
|
|
6678
|
+
return clampCellString(value);
|
|
6679
|
+
if (typeof value === "number")
|
|
6680
|
+
return Number.isFinite(value) ? value : null;
|
|
6681
|
+
if (typeof value === "boolean")
|
|
6682
|
+
return value;
|
|
6683
|
+
return null;
|
|
6684
|
+
}
|
|
6685
|
+
function clampTableData(raw) {
|
|
6686
|
+
let rawColumns;
|
|
6687
|
+
let rawRows;
|
|
6688
|
+
if (Array.isArray(raw)) {
|
|
6689
|
+
rawRows = raw;
|
|
6690
|
+
} else if (typeof raw === "object" && raw !== null) {
|
|
6691
|
+
rawColumns = raw.columns;
|
|
6692
|
+
rawRows = raw.rows;
|
|
6693
|
+
} else {
|
|
6694
|
+
return null;
|
|
6695
|
+
}
|
|
6696
|
+
if (!Array.isArray(rawRows))
|
|
6697
|
+
return null;
|
|
6698
|
+
let truncated = false;
|
|
6699
|
+
const columns = [];
|
|
6700
|
+
const seenKeys = new Set;
|
|
6701
|
+
const pushColumn = (key, label) => {
|
|
6702
|
+
if (seenKeys.has(key))
|
|
6703
|
+
return;
|
|
6704
|
+
seenKeys.add(key);
|
|
6705
|
+
if (columns.length >= TABLE_MAX_COLUMNS) {
|
|
6706
|
+
truncated = true;
|
|
6707
|
+
return;
|
|
6708
|
+
}
|
|
6709
|
+
columns.push({ sourceKey: key, key: clampCellString(key), label: clampCellString(label) });
|
|
6710
|
+
};
|
|
6711
|
+
if (rawColumns !== undefined) {
|
|
6712
|
+
if (!Array.isArray(rawColumns))
|
|
6713
|
+
return null;
|
|
6714
|
+
for (const c of rawColumns) {
|
|
6715
|
+
if (typeof c !== "object" || c === null)
|
|
6716
|
+
continue;
|
|
6717
|
+
const { key, label } = c;
|
|
6718
|
+
if (typeof key !== "string" || key.length === 0)
|
|
6719
|
+
continue;
|
|
6720
|
+
pushColumn(key, typeof label === "string" && label.length > 0 ? label : key);
|
|
6721
|
+
}
|
|
6722
|
+
} else {
|
|
6723
|
+
for (const row of rawRows) {
|
|
6724
|
+
if (typeof row !== "object" || row === null || Array.isArray(row))
|
|
6725
|
+
continue;
|
|
6726
|
+
for (const key of Object.keys(row))
|
|
6727
|
+
pushColumn(key, key);
|
|
6728
|
+
}
|
|
6729
|
+
}
|
|
6730
|
+
if (columns.length === 0)
|
|
6731
|
+
return null;
|
|
6732
|
+
const rows = [];
|
|
6733
|
+
for (const rawRow of rawRows) {
|
|
6734
|
+
if (typeof rawRow !== "object" || rawRow === null || Array.isArray(rawRow))
|
|
6735
|
+
continue;
|
|
6736
|
+
if (rows.length >= TABLE_MAX_ROWS) {
|
|
6737
|
+
truncated = true;
|
|
6738
|
+
break;
|
|
6739
|
+
}
|
|
6740
|
+
const row = {};
|
|
6741
|
+
for (const c of columns)
|
|
6742
|
+
row[c.key] = clampCell(rawRow[c.sourceKey]);
|
|
6743
|
+
rows.push(row);
|
|
6744
|
+
}
|
|
6745
|
+
if (rows.length === 0)
|
|
6746
|
+
return null;
|
|
6747
|
+
return { columns: columns.map(({ key, label }) => ({ key, label })), rows, truncated };
|
|
6748
|
+
}
|
|
6749
|
+
var CHART_MAX_SERIES = 4;
|
|
6750
|
+
var CHART_MAX_POINTS_PER_SERIES = 60;
|
|
6751
|
+
var CHART_LABEL_MAX_CHARS = 40;
|
|
6752
|
+
function clampChartSeries(raw) {
|
|
6753
|
+
if (!Array.isArray(raw))
|
|
6754
|
+
return [];
|
|
6755
|
+
const series = [];
|
|
6756
|
+
for (const s of raw) {
|
|
6757
|
+
if (series.length >= CHART_MAX_SERIES)
|
|
6758
|
+
break;
|
|
6759
|
+
if (typeof s !== "object" || s === null)
|
|
6760
|
+
continue;
|
|
6761
|
+
const { label, points } = s;
|
|
6762
|
+
if (typeof label !== "string" || label.trim().length === 0)
|
|
6763
|
+
continue;
|
|
6764
|
+
if (!Array.isArray(points))
|
|
6765
|
+
continue;
|
|
6766
|
+
const clamped = [];
|
|
6767
|
+
for (const p of points) {
|
|
6768
|
+
if (clamped.length >= CHART_MAX_POINTS_PER_SERIES)
|
|
6769
|
+
break;
|
|
6770
|
+
if (typeof p !== "object" || p === null)
|
|
6771
|
+
continue;
|
|
6772
|
+
const { x, y } = p;
|
|
6773
|
+
if (typeof y !== "number" || !Number.isFinite(y))
|
|
6774
|
+
continue;
|
|
6775
|
+
if (typeof x === "number" && Number.isFinite(x))
|
|
6776
|
+
clamped.push({ x, y });
|
|
6777
|
+
else if (typeof x === "string" && x.length > 0)
|
|
6778
|
+
clamped.push({ x: x.slice(0, CHART_LABEL_MAX_CHARS), y });
|
|
6779
|
+
}
|
|
6780
|
+
if (clamped.length === 0)
|
|
6781
|
+
continue;
|
|
6782
|
+
series.push({ label: label.slice(0, CHART_LABEL_MAX_CHARS), points: clamped });
|
|
6783
|
+
}
|
|
6784
|
+
return series;
|
|
6785
|
+
}
|
|
6786
|
+
function clampChartAxisLabel(raw) {
|
|
6787
|
+
return typeof raw === "string" && raw.trim().length > 0 ? raw.slice(0, CHART_LABEL_MAX_CHARS) : undefined;
|
|
6788
|
+
}
|
|
6789
|
+
var ASK_PROMPT_MAX_CHARS = 1000;
|
|
6790
|
+
var ASK_MAX_OPTIONS = 8;
|
|
6791
|
+
var ASK_OPTION_ID_MAX_CHARS = 100;
|
|
6792
|
+
var ASK_OPTION_LABEL_MAX_CHARS = 200;
|
|
6793
|
+
function clampAskOptions(raw) {
|
|
6794
|
+
if (!Array.isArray(raw))
|
|
6795
|
+
return [];
|
|
6796
|
+
const options = [];
|
|
6797
|
+
const seen = new Set;
|
|
6798
|
+
for (const o of raw) {
|
|
6799
|
+
if (options.length >= ASK_MAX_OPTIONS)
|
|
6800
|
+
break;
|
|
6801
|
+
let id = null;
|
|
6802
|
+
let label = null;
|
|
6803
|
+
if (typeof o === "string" && o.trim().length > 0) {
|
|
6804
|
+
id = o;
|
|
6805
|
+
label = o;
|
|
6806
|
+
} else if (typeof o === "object" && o !== null) {
|
|
6807
|
+
const cand = o;
|
|
6808
|
+
if (typeof cand.id === "string" && cand.id.length > 0 && typeof cand.label === "string" && cand.label.length > 0) {
|
|
6809
|
+
id = cand.id;
|
|
6810
|
+
label = cand.label;
|
|
6811
|
+
}
|
|
6812
|
+
}
|
|
6813
|
+
if (id === null || label === null)
|
|
6814
|
+
continue;
|
|
6815
|
+
const clampedId = id.slice(0, ASK_OPTION_ID_MAX_CHARS);
|
|
6816
|
+
if (seen.has(clampedId))
|
|
6817
|
+
continue;
|
|
6818
|
+
seen.add(clampedId);
|
|
6819
|
+
options.push({ id: clampedId, label: label.slice(0, ASK_OPTION_LABEL_MAX_CHARS) });
|
|
6820
|
+
}
|
|
6821
|
+
return options;
|
|
6822
|
+
}
|
|
6823
|
+
function serializeDisplayMarker(marker) {
|
|
6824
|
+
return JSON.stringify({ [DISPLAY_MARKER_KEY]: DISPLAY_MARKER_VERSION, ...marker });
|
|
6825
|
+
}
|
|
6826
|
+
function parseDisplayMarker(line) {
|
|
6827
|
+
const trimmed = line.trim();
|
|
6828
|
+
if (!trimmed.startsWith("{") || !trimmed.includes(DISPLAY_MARKER_KEY))
|
|
6829
|
+
return null;
|
|
6830
|
+
let obj;
|
|
6831
|
+
try {
|
|
6832
|
+
const parsed = JSON.parse(trimmed);
|
|
6833
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
6834
|
+
return null;
|
|
6835
|
+
obj = parsed;
|
|
6836
|
+
} catch {
|
|
6837
|
+
return null;
|
|
6838
|
+
}
|
|
6839
|
+
if (obj[DISPLAY_MARKER_KEY] !== DISPLAY_MARKER_VERSION)
|
|
6840
|
+
return null;
|
|
6841
|
+
const str = (key) => {
|
|
6842
|
+
const v = obj[key];
|
|
6843
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
6844
|
+
};
|
|
6845
|
+
switch (obj.kind) {
|
|
6846
|
+
case "run": {
|
|
6847
|
+
const runId = str("runId");
|
|
6848
|
+
return runId && UUID_RE.test(runId) ? { kind: "run", runId } : null;
|
|
6849
|
+
}
|
|
6850
|
+
case "changes": {
|
|
6851
|
+
const runId = str("runId");
|
|
6852
|
+
if (!runId || !UUID_RE.test(runId))
|
|
6853
|
+
return null;
|
|
6854
|
+
return {
|
|
6855
|
+
kind: "changes",
|
|
6856
|
+
runId,
|
|
6857
|
+
...str("factorySlug") ? { factorySlug: str("factorySlug") } : {},
|
|
6858
|
+
endedAt: str("endedAt") ?? null
|
|
6859
|
+
};
|
|
6860
|
+
}
|
|
6861
|
+
case "document": {
|
|
6862
|
+
const path = str("path");
|
|
6863
|
+
if (!path)
|
|
6864
|
+
return null;
|
|
6865
|
+
return {
|
|
6866
|
+
kind: "document",
|
|
6867
|
+
path,
|
|
6868
|
+
...str("factorySlug") ? { factorySlug: str("factorySlug") } : {},
|
|
6869
|
+
preview: str("preview") ?? null
|
|
6870
|
+
};
|
|
6871
|
+
}
|
|
6872
|
+
case "plan": {
|
|
6873
|
+
const entries = clampPlanEntries(obj.entries);
|
|
6874
|
+
return entries.length > 0 ? { kind: "plan", entries } : null;
|
|
6875
|
+
}
|
|
6876
|
+
case "image": {
|
|
6877
|
+
const path = str("path");
|
|
6878
|
+
if (!path || path.length > IMAGE_PATH_MAX_CHARS)
|
|
6879
|
+
return null;
|
|
6880
|
+
return {
|
|
6881
|
+
kind: "image",
|
|
6882
|
+
path,
|
|
6883
|
+
...str("factorySlug") ? { factorySlug: str("factorySlug") } : {}
|
|
6884
|
+
};
|
|
6885
|
+
}
|
|
6886
|
+
case "table": {
|
|
6887
|
+
const data = clampTableData({ columns: obj.columns, rows: obj.rows });
|
|
6888
|
+
if (!data)
|
|
6889
|
+
return null;
|
|
6890
|
+
return { kind: "table", ...data, truncated: data.truncated || obj.truncated === true };
|
|
6891
|
+
}
|
|
6892
|
+
case "chart": {
|
|
6893
|
+
if (obj.chartKind !== "bar" && obj.chartKind !== "line")
|
|
6894
|
+
return null;
|
|
6895
|
+
const series = clampChartSeries(obj.series);
|
|
6896
|
+
if (series.length === 0)
|
|
6897
|
+
return null;
|
|
6898
|
+
const xLabel = clampChartAxisLabel(obj.xLabel);
|
|
6899
|
+
const yLabel = clampChartAxisLabel(obj.yLabel);
|
|
6900
|
+
return {
|
|
6901
|
+
kind: "chart",
|
|
6902
|
+
chartKind: obj.chartKind,
|
|
6903
|
+
series,
|
|
6904
|
+
...xLabel !== undefined ? { xLabel } : {},
|
|
6905
|
+
...yLabel !== undefined ? { yLabel } : {}
|
|
6906
|
+
};
|
|
6907
|
+
}
|
|
6908
|
+
case "ask": {
|
|
6909
|
+
const askId = str("askId");
|
|
6910
|
+
const rawPrompt = str("prompt");
|
|
6911
|
+
if (!askId || !UUID_RE.test(askId) || !rawPrompt)
|
|
6912
|
+
return null;
|
|
6913
|
+
const options = clampAskOptions(obj.options);
|
|
6914
|
+
return {
|
|
6915
|
+
kind: "ask",
|
|
6916
|
+
askId,
|
|
6917
|
+
prompt: rawPrompt.slice(0, ASK_PROMPT_MAX_CHARS),
|
|
6918
|
+
...options.length > 0 ? { options } : {},
|
|
6919
|
+
freeText: true
|
|
6920
|
+
};
|
|
6921
|
+
}
|
|
6922
|
+
default:
|
|
6923
|
+
return null;
|
|
6924
|
+
}
|
|
6925
|
+
}
|
|
6926
|
+
function findDisplayMarker(text) {
|
|
6927
|
+
for (const line of text.split(`
|
|
6928
|
+
`)) {
|
|
6929
|
+
const marker = parseDisplayMarker(line);
|
|
6930
|
+
if (marker)
|
|
6931
|
+
return marker;
|
|
6932
|
+
}
|
|
6933
|
+
return null;
|
|
6934
|
+
}
|
|
6935
|
+
var SHELL_STOPPERS = new Set(["|", "||", "&&", ";", ">", ">>", "<", "2>", "2>>", "&"]);
|
|
6936
|
+
function shellWords(command) {
|
|
6937
|
+
const words = [];
|
|
6938
|
+
let current = "";
|
|
6939
|
+
let started = false;
|
|
6940
|
+
let quote2 = null;
|
|
6941
|
+
for (let i = 0;i < command.length; i += 1) {
|
|
6942
|
+
const ch = command[i];
|
|
6943
|
+
if (quote2) {
|
|
6944
|
+
if (ch === quote2)
|
|
6945
|
+
quote2 = null;
|
|
6946
|
+
else
|
|
6947
|
+
current += ch;
|
|
6948
|
+
continue;
|
|
6949
|
+
}
|
|
6950
|
+
if (ch === "'" || ch === '"') {
|
|
6951
|
+
quote2 = ch;
|
|
6952
|
+
started = true;
|
|
6953
|
+
continue;
|
|
6954
|
+
}
|
|
6955
|
+
if (ch === " " || ch === "\t" || ch === `
|
|
6956
|
+
`) {
|
|
6957
|
+
if (started) {
|
|
6958
|
+
words.push(current);
|
|
6959
|
+
current = "";
|
|
6960
|
+
started = false;
|
|
6961
|
+
}
|
|
6962
|
+
continue;
|
|
6963
|
+
}
|
|
6964
|
+
current += ch;
|
|
6965
|
+
started = true;
|
|
6966
|
+
}
|
|
6967
|
+
if (started)
|
|
6968
|
+
words.push(current);
|
|
6969
|
+
return words;
|
|
6970
|
+
}
|
|
6971
|
+
var VALUE_FLAGS = new Set([
|
|
6972
|
+
"--factory",
|
|
6973
|
+
"--input",
|
|
6974
|
+
"--entries",
|
|
6975
|
+
"--url",
|
|
6976
|
+
"--api-key",
|
|
6977
|
+
"--limit",
|
|
6978
|
+
"--thread",
|
|
6979
|
+
"--content-type",
|
|
6980
|
+
"--file",
|
|
6981
|
+
"--out",
|
|
6982
|
+
"--revision",
|
|
6983
|
+
"--ended-at",
|
|
6984
|
+
"--data",
|
|
6985
|
+
"--kind",
|
|
6986
|
+
"--from",
|
|
6987
|
+
"--to",
|
|
6988
|
+
"--prompt",
|
|
6989
|
+
"--options",
|
|
6990
|
+
"--approver"
|
|
6991
|
+
]);
|
|
6992
|
+
function isShellControlWord(w) {
|
|
6993
|
+
if (/^\d*>&\d+$/.test(w))
|
|
6994
|
+
return false;
|
|
6995
|
+
return SHELL_STOPPERS.has(w) || /^\d*>>?(&\d+)?$/.test(w) || w === "<" || w.startsWith("$(") || w.startsWith("`");
|
|
6996
|
+
}
|
|
6997
|
+
function detectAgentcInvocation(command) {
|
|
6998
|
+
const words = shellWords(command);
|
|
6999
|
+
const start = words.findIndex((w) => w === "agentc" || w.endsWith("/agentc"));
|
|
7000
|
+
if (start < 0)
|
|
7001
|
+
return null;
|
|
7002
|
+
if (words.some(isShellControlWord))
|
|
7003
|
+
return null;
|
|
7004
|
+
for (let i = 0;i < start; i += 1) {
|
|
7005
|
+
const w = words[i];
|
|
7006
|
+
const isEnvAssign = /^[A-Za-z_][A-Za-z0-9_]*=/.test(w);
|
|
7007
|
+
const isShellInvoker = /^(?:[\w./-]*\/)?(?:sh|bash|zsh|dash)$/.test(w);
|
|
7008
|
+
if (!isEnvAssign && !isShellInvoker && !w.startsWith("-"))
|
|
7009
|
+
return null;
|
|
7010
|
+
}
|
|
7011
|
+
const args = [];
|
|
7012
|
+
const flags = new Map;
|
|
7013
|
+
for (let i = start + 1;i < words.length; i += 1) {
|
|
7014
|
+
const w = words[i];
|
|
7015
|
+
if (SHELL_STOPPERS.has(w))
|
|
7016
|
+
break;
|
|
7017
|
+
if (w.startsWith("--")) {
|
|
7018
|
+
if (VALUE_FLAGS.has(w) && i + 1 < words.length && !SHELL_STOPPERS.has(words[i + 1])) {
|
|
7019
|
+
flags.set(w, words[i + 1]);
|
|
7020
|
+
i += 1;
|
|
7021
|
+
}
|
|
7022
|
+
continue;
|
|
7023
|
+
}
|
|
7024
|
+
args.push(w);
|
|
7025
|
+
}
|
|
7026
|
+
const factorySlug = flags.get("--factory");
|
|
7027
|
+
const withSlug = factorySlug ? { factorySlug } : {};
|
|
7028
|
+
const uuid = (v) => v && UUID_RE.test(v) ? v : null;
|
|
7029
|
+
if (args[0] === "display") {
|
|
7030
|
+
switch (args[1]) {
|
|
7031
|
+
case "run": {
|
|
7032
|
+
const runId = uuid(args[2]);
|
|
7033
|
+
return runId ? { verb: "display-run", runId } : null;
|
|
7034
|
+
}
|
|
7035
|
+
case "changes": {
|
|
7036
|
+
const runId = uuid(args[2]);
|
|
7037
|
+
return runId ? { verb: "display-changes", runId, ...withSlug } : null;
|
|
7038
|
+
}
|
|
7039
|
+
case "document":
|
|
7040
|
+
return args[2] ? { verb: "display-document", path: args[2], ...withSlug } : null;
|
|
7041
|
+
case "plan":
|
|
7042
|
+
return { verb: "display-plan" };
|
|
7043
|
+
case "image":
|
|
7044
|
+
return args[2] ? { verb: "display-image", path: args[2], ...withSlug } : null;
|
|
7045
|
+
case "preview":
|
|
7046
|
+
return args[2] ? { verb: "display-preview", path: args[2] } : null;
|
|
7047
|
+
case "table": {
|
|
7048
|
+
const file = flags.get("--file");
|
|
7049
|
+
const hasData = flags.has("--data");
|
|
7050
|
+
if (file && !hasData)
|
|
7051
|
+
return { verb: "display-table", source: "file", path: file };
|
|
7052
|
+
if (hasData && !file)
|
|
7053
|
+
return { verb: "display-table", source: "inline" };
|
|
7054
|
+
return null;
|
|
7055
|
+
}
|
|
7056
|
+
case "chart": {
|
|
7057
|
+
const chartKind = flags.get("--kind");
|
|
7058
|
+
return chartKind === "bar" || chartKind === "line" ? { verb: "display-chart", chartKind } : null;
|
|
7059
|
+
}
|
|
7060
|
+
case "diff": {
|
|
7061
|
+
const from = flags.get("--from");
|
|
7062
|
+
const to = flags.get("--to");
|
|
7063
|
+
if (!args[2] || !from || !to)
|
|
7064
|
+
return null;
|
|
7065
|
+
if (!REVISION_SELECTOR_RE.test(from) || !REVISION_SELECTOR_RE.test(to))
|
|
7066
|
+
return null;
|
|
7067
|
+
return { verb: "display-diff", path: args[2], from, to };
|
|
7068
|
+
}
|
|
7069
|
+
case "ask": {
|
|
7070
|
+
const prompt = flags.get("--prompt");
|
|
7071
|
+
if (!prompt || prompt.trim().length === 0)
|
|
7072
|
+
return null;
|
|
7073
|
+
const approver = flags.get("--approver");
|
|
7074
|
+
return {
|
|
7075
|
+
verb: "display-ask",
|
|
7076
|
+
prompt: prompt.slice(0, ASK_PROMPT_MAX_CHARS),
|
|
7077
|
+
...approver ? { approver } : {}
|
|
7078
|
+
};
|
|
7079
|
+
}
|
|
7080
|
+
default:
|
|
7081
|
+
return null;
|
|
7082
|
+
}
|
|
7083
|
+
}
|
|
7084
|
+
if (args[0] === "run" && args[1] === "dispatch" && args[2]) {
|
|
7085
|
+
let input;
|
|
7086
|
+
const rawInput = flags.get("--input");
|
|
7087
|
+
if (rawInput !== undefined) {
|
|
7088
|
+
try {
|
|
7089
|
+
input = JSON.parse(rawInput);
|
|
7090
|
+
} catch {}
|
|
7091
|
+
}
|
|
7092
|
+
return { verb: "run-dispatch", templateName: args[2], ...input !== undefined ? { input } : {} };
|
|
7093
|
+
}
|
|
7094
|
+
if (args[0] === "run" && args[1] === "get") {
|
|
7095
|
+
const runId = uuid(args[2]);
|
|
7096
|
+
return runId ? { verb: "run-get", runId } : null;
|
|
7097
|
+
}
|
|
7098
|
+
if (args[0] === "files" && args[1] === "read" && args[2]) {
|
|
7099
|
+
return { verb: "files-read", path: args[2], ...withSlug };
|
|
7100
|
+
}
|
|
7101
|
+
return null;
|
|
7102
|
+
}
|
|
7103
|
+
var DOCUMENT_PREVIEW_CHARS = 240;
|
|
7104
|
+
var DOCUMENT_CONTENT_CHARS = 4000;
|
|
7105
|
+
function commandOf(toolInput) {
|
|
7106
|
+
if (typeof toolInput !== "object" || toolInput === null)
|
|
7107
|
+
return null;
|
|
7108
|
+
const cmd = toolInput.command;
|
|
7109
|
+
if (typeof cmd === "string")
|
|
7110
|
+
return cmd;
|
|
7111
|
+
if (Array.isArray(cmd) && cmd.every((c) => typeof c === "string"))
|
|
7112
|
+
return cmd.join(" ");
|
|
7113
|
+
return null;
|
|
7114
|
+
}
|
|
7115
|
+
function textOf(output) {
|
|
7116
|
+
if (typeof output === "string")
|
|
7117
|
+
return output;
|
|
7118
|
+
if (typeof output !== "object" || output === null)
|
|
7119
|
+
return null;
|
|
7120
|
+
const o = output;
|
|
7121
|
+
if (typeof o.output === "string")
|
|
7122
|
+
return o.output;
|
|
7123
|
+
if (typeof o.text === "string")
|
|
7124
|
+
return o.text;
|
|
7125
|
+
if (typeof o.content === "string")
|
|
7126
|
+
return o.content;
|
|
7127
|
+
return null;
|
|
7128
|
+
}
|
|
7129
|
+
function runIdIn(text) {
|
|
7130
|
+
for (const line of [...text.split(`
|
|
7131
|
+
`)].reverse()) {
|
|
7132
|
+
const trimmed = line.trim();
|
|
7133
|
+
if (trimmed.startsWith("{")) {
|
|
7134
|
+
try {
|
|
7135
|
+
const parsed = JSON.parse(trimmed);
|
|
7136
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
7137
|
+
const runId = parsed.runId;
|
|
7138
|
+
if (typeof runId === "string" && UUID_RE.test(runId)) {
|
|
7139
|
+
return { runId, parsed };
|
|
7140
|
+
}
|
|
7141
|
+
}
|
|
7142
|
+
} catch {}
|
|
7143
|
+
}
|
|
7144
|
+
}
|
|
7145
|
+
const m = /run ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i.exec(text);
|
|
7146
|
+
return m ? { runId: m[1], parsed: null } : null;
|
|
7147
|
+
}
|
|
7148
|
+
function createDisplayPromoter(ctx = {}) {
|
|
7149
|
+
const pending = new Map;
|
|
7150
|
+
const factoryRef = (slug) => {
|
|
7151
|
+
if (slug)
|
|
7152
|
+
return { factorySlug: slug };
|
|
7153
|
+
if (ctx.factoryId)
|
|
7154
|
+
return { factoryId: ctx.factoryId };
|
|
7155
|
+
return {};
|
|
7156
|
+
};
|
|
7157
|
+
return {
|
|
7158
|
+
promoteToolUse(msg) {
|
|
7159
|
+
if (msg.toolName === "TodoWrite") {
|
|
7160
|
+
const todos = msg.toolInput?.todos;
|
|
7161
|
+
const entries = clampPlanEntries(todos);
|
|
7162
|
+
if (entries.length === 0)
|
|
7163
|
+
return null;
|
|
7164
|
+
return [
|
|
7165
|
+
{ type: "tool_call", toolName: msg.toolName, input: msg.toolInput, toolCallId: msg.toolUseId },
|
|
7166
|
+
{ type: "plan", entries }
|
|
7167
|
+
];
|
|
7168
|
+
}
|
|
7169
|
+
const command = commandOf(msg.toolInput);
|
|
7170
|
+
if (!command || !command.includes("agentc"))
|
|
7171
|
+
return null;
|
|
7172
|
+
const invocation = detectAgentcInvocation(command);
|
|
7173
|
+
if (!invocation)
|
|
7174
|
+
return null;
|
|
7175
|
+
pending.set(msg.toolUseId, { invocation });
|
|
7176
|
+
const call = (toolName, input) => [
|
|
7177
|
+
{ type: "tool_call", toolName, input: { ...input, command }, toolCallId: msg.toolUseId }
|
|
7178
|
+
];
|
|
7179
|
+
switch (invocation.verb) {
|
|
7180
|
+
case "display-run":
|
|
7181
|
+
case "run-get":
|
|
7182
|
+
return call("display_run", { runId: invocation.runId });
|
|
7183
|
+
case "display-changes":
|
|
7184
|
+
return call("display_changes", { runId: invocation.runId, ...factoryRef(invocation.factorySlug) });
|
|
7185
|
+
case "display-document":
|
|
7186
|
+
case "files-read":
|
|
7187
|
+
return call("display_document", { path: invocation.path, ...factoryRef(invocation.factorySlug) });
|
|
7188
|
+
case "display-plan":
|
|
7189
|
+
return call("display_plan", {});
|
|
7190
|
+
case "display-image":
|
|
7191
|
+
return call("display_image", { path: invocation.path, ...factoryRef(invocation.factorySlug) });
|
|
7192
|
+
case "display-preview":
|
|
7193
|
+
return call("display_preview", { path: invocation.path, ...factoryRef(undefined) });
|
|
7194
|
+
case "display-table":
|
|
7195
|
+
return call("display_table", {
|
|
7196
|
+
source: invocation.source,
|
|
7197
|
+
...invocation.path !== undefined ? { path: invocation.path } : {},
|
|
7198
|
+
...invocation.source === "file" ? factoryRef(undefined) : {}
|
|
7199
|
+
});
|
|
7200
|
+
case "display-chart":
|
|
7201
|
+
return call("display_chart", { chartKind: invocation.chartKind });
|
|
7202
|
+
case "display-diff":
|
|
7203
|
+
return call("display_diff", {
|
|
7204
|
+
path: invocation.path,
|
|
7205
|
+
from: invocation.from,
|
|
7206
|
+
to: invocation.to,
|
|
7207
|
+
...factoryRef(undefined)
|
|
7208
|
+
});
|
|
7209
|
+
case "display-ask":
|
|
7210
|
+
return call("display_ask", { prompt: invocation.prompt });
|
|
7211
|
+
case "run-dispatch":
|
|
7212
|
+
return call("dispatch_workflow", {
|
|
7213
|
+
templateName: invocation.templateName,
|
|
7214
|
+
...invocation.input !== undefined ? { input: invocation.input } : {}
|
|
7215
|
+
});
|
|
7216
|
+
}
|
|
7217
|
+
},
|
|
7218
|
+
promoteToolResult(msg) {
|
|
7219
|
+
const entry = pending.get(msg.toolUseId);
|
|
7220
|
+
if (!entry)
|
|
7221
|
+
return null;
|
|
7222
|
+
pending.delete(msg.toolUseId);
|
|
7223
|
+
const isError = msg.isError === true;
|
|
7224
|
+
const result = (output) => ({
|
|
7225
|
+
type: "tool_result",
|
|
7226
|
+
toolCallId: msg.toolUseId,
|
|
7227
|
+
output,
|
|
7228
|
+
isError
|
|
7229
|
+
});
|
|
7230
|
+
const text = textOf(msg.output);
|
|
7231
|
+
if (isError || text === null)
|
|
7232
|
+
return [result(msg.output)];
|
|
7233
|
+
const inv = entry.invocation;
|
|
7234
|
+
switch (inv.verb) {
|
|
7235
|
+
case "display-run": {
|
|
7236
|
+
const marker = findDisplayMarker(text);
|
|
7237
|
+
return [result(marker?.kind === "run" ? { runId: marker.runId } : { runId: inv.runId })];
|
|
7238
|
+
}
|
|
7239
|
+
case "run-get": {
|
|
7240
|
+
const found = runIdIn(text);
|
|
7241
|
+
return [result(found?.parsed ?? { runId: found?.runId ?? inv.runId })];
|
|
7242
|
+
}
|
|
7243
|
+
case "display-changes": {
|
|
7244
|
+
const marker = findDisplayMarker(text);
|
|
7245
|
+
const m = marker?.kind === "changes" ? marker : null;
|
|
7246
|
+
return [result({
|
|
7247
|
+
runId: m?.runId ?? inv.runId,
|
|
7248
|
+
...factoryRef(m?.factorySlug ?? inv.factorySlug),
|
|
7249
|
+
endedAt: m?.endedAt ?? null
|
|
7250
|
+
})];
|
|
7251
|
+
}
|
|
7252
|
+
case "display-document": {
|
|
7253
|
+
const marker = findDisplayMarker(text);
|
|
7254
|
+
const m = marker?.kind === "document" ? marker : null;
|
|
7255
|
+
return [result({
|
|
7256
|
+
path: m?.path ?? inv.path,
|
|
7257
|
+
...factoryRef(m?.factorySlug ?? inv.factorySlug),
|
|
7258
|
+
preview: m?.preview ?? null
|
|
7259
|
+
})];
|
|
7260
|
+
}
|
|
7261
|
+
case "files-read":
|
|
7262
|
+
return [result({
|
|
7263
|
+
path: inv.path,
|
|
7264
|
+
...factoryRef(inv.factorySlug),
|
|
7265
|
+
preview: text.slice(0, DOCUMENT_PREVIEW_CHARS) || null,
|
|
7266
|
+
content: text.slice(0, DOCUMENT_CONTENT_CHARS)
|
|
7267
|
+
})];
|
|
7268
|
+
case "display-plan": {
|
|
7269
|
+
const marker = findDisplayMarker(text);
|
|
7270
|
+
if (marker?.kind !== "plan")
|
|
7271
|
+
return [result(msg.output)];
|
|
7272
|
+
return [
|
|
7273
|
+
result({ entries: marker.entries.length }),
|
|
7274
|
+
{ type: "plan", entries: marker.entries }
|
|
7275
|
+
];
|
|
7276
|
+
}
|
|
7277
|
+
case "display-image": {
|
|
7278
|
+
const marker = findDisplayMarker(text);
|
|
7279
|
+
const m = marker?.kind === "image" ? marker : null;
|
|
7280
|
+
return [result({
|
|
7281
|
+
path: m?.path ?? inv.path,
|
|
7282
|
+
...factoryRef(m?.factorySlug ?? inv.factorySlug)
|
|
7283
|
+
})];
|
|
7284
|
+
}
|
|
7285
|
+
case "display-table": {
|
|
7286
|
+
if (inv.source === "file")
|
|
7287
|
+
return [result(msg.output)];
|
|
7288
|
+
const marker = findDisplayMarker(text);
|
|
7289
|
+
if (marker?.kind !== "table")
|
|
7290
|
+
return [result(msg.output)];
|
|
7291
|
+
return [result({ columns: marker.columns, rows: marker.rows, truncated: marker.truncated })];
|
|
7292
|
+
}
|
|
7293
|
+
case "display-chart": {
|
|
7294
|
+
const marker = findDisplayMarker(text);
|
|
7295
|
+
if (marker?.kind !== "chart")
|
|
7296
|
+
return [result(msg.output)];
|
|
7297
|
+
return [result({
|
|
7298
|
+
chartKind: marker.chartKind,
|
|
7299
|
+
series: marker.series,
|
|
7300
|
+
...marker.xLabel !== undefined ? { xLabel: marker.xLabel } : {},
|
|
7301
|
+
...marker.yLabel !== undefined ? { yLabel: marker.yLabel } : {}
|
|
7302
|
+
})];
|
|
7303
|
+
}
|
|
7304
|
+
case "display-ask": {
|
|
7305
|
+
if (inv.approver !== undefined)
|
|
7306
|
+
return [result(msg.output)];
|
|
7307
|
+
const marker = findDisplayMarker(text);
|
|
7308
|
+
if (marker?.kind !== "ask")
|
|
7309
|
+
return [result(msg.output)];
|
|
7310
|
+
return [result({
|
|
7311
|
+
askId: marker.askId,
|
|
7312
|
+
prompt: marker.prompt,
|
|
7313
|
+
...marker.options !== undefined ? { options: marker.options } : {},
|
|
7314
|
+
freeText: marker.freeText
|
|
7315
|
+
})];
|
|
7316
|
+
}
|
|
7317
|
+
case "display-preview":
|
|
7318
|
+
case "display-diff":
|
|
7319
|
+
return [result(msg.output)];
|
|
7320
|
+
case "run-dispatch": {
|
|
7321
|
+
const found = runIdIn(text);
|
|
7322
|
+
if (!found)
|
|
7323
|
+
return [result(msg.output)];
|
|
7324
|
+
return [result(found.parsed ?? { runId: found.runId })];
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
}
|
|
7328
|
+
};
|
|
7329
|
+
}
|
|
5504
7330
|
// src/runtimes/openai-desktop.ts
|
|
5505
7331
|
import OpenAI from "openai";
|
|
5506
7332
|
import sharp from "sharp";
|
|
@@ -5534,9 +7360,10 @@ class OpenAIDesktopRunner {
|
|
|
5534
7360
|
{ type: "input_text", text: opts.prompt }
|
|
5535
7361
|
]
|
|
5536
7362
|
}];
|
|
7363
|
+
const responses = this.openai.responses;
|
|
5537
7364
|
let previousResponseId;
|
|
5538
7365
|
for (let turn = 0;turn < MAX_TURNS; turn++) {
|
|
5539
|
-
const response = await
|
|
7366
|
+
const response = await responses.create({
|
|
5540
7367
|
model: "computer-use-preview",
|
|
5541
7368
|
tools: [{ type: "computer_use_preview", name: "computer", display_width: DISPLAY_WIDTH, display_height: DISPLAY_HEIGHT, environment: "linux" }],
|
|
5542
7369
|
input: messages,
|