@agent-compose/sdk 0.5.6 → 0.5.8
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 +4 -4
- package/dist/agent/__tests__/run-agent-liveness.test.d.ts +17 -0
- package/dist/agent/agent-context.d.ts +67 -0
- package/dist/agent/agent-loop-contract.test.d.ts +1 -0
- package/dist/agent/agent-loop.d.ts +2 -1
- package/dist/client.d.ts +129 -24
- package/dist/index.d.ts +9 -4
- package/dist/index.js +553 -89
- package/dist/pause/wrappers.d.ts +7 -11
- package/dist/runtimes/claude.d.ts +9 -1
- package/dist/runtimes/openai-desktop.js +548 -89
- package/dist/sandbox-errors.d.ts +49 -0
- package/dist/sandbox.d.ts +92 -13
- package/dist/step-invocation/protocol.d.ts +6 -0
- package/dist/step-invocation/types.d.ts +1 -1
- package/dist/types/execution-context.d.ts +1 -3
- package/dist/types/sandbox-environment.d.ts +1 -10
- package/dist/types/sandbox.d.ts +27 -3
- package/dist/types/workflow-metadata.d.ts +81 -13
- package/dist/types/workflow.d.ts +45 -10
- package/dist/utils/bundler.d.ts +40 -9
- package/dist/workflow-steps/workflow.d.ts +4 -3
- package/package.json +2 -2
- package/src/agent/agent-context.ts +212 -0
- package/src/agent/agent-loop.ts +78 -10
- package/src/agent/run-agent.ts +37 -1
- package/src/client.ts +232 -26
- package/src/index.ts +9 -4
- package/src/pause/wrappers.ts +7 -21
- package/src/runtimes/claude.ts +66 -8
- package/src/sandbox-errors.ts +53 -0
- package/src/sandbox.ts +438 -61
- package/src/step-invocation/invoker.ts +66 -8
- package/src/step-invocation/protocol.ts +9 -0
- package/src/step-invocation/server.ts +27 -4
- package/src/step-invocation/types.ts +1 -1
- package/src/types/execution-context.ts +1 -3
- package/src/types/sandbox-environment.ts +1 -11
- package/src/types/sandbox.ts +28 -3
- package/src/types/workflow-metadata.ts +91 -16
- package/src/types/workflow.ts +45 -12
- package/src/utils/bundler.ts +46 -13
- package/src/workflow-steps/workflow.ts +4 -3
- package/src/workflows/invoke-child.ts +7 -1
package/dist/index.js
CHANGED
|
@@ -540,12 +540,16 @@ function extractMetadata(source) {
|
|
|
540
540
|
out.outputSchema = freezeMetadataValue(source.outputSchema);
|
|
541
541
|
if (source.snapshots !== undefined)
|
|
542
542
|
out.snapshots = Object.freeze({ ...source.snapshots });
|
|
543
|
+
if (source.resources !== undefined)
|
|
544
|
+
out.resources = Object.freeze({ ...source.resources });
|
|
543
545
|
if (source.processors !== undefined)
|
|
544
546
|
out.processors = Object.freeze([...source.processors]);
|
|
545
|
-
if (source.
|
|
546
|
-
out.
|
|
547
|
-
if (source.
|
|
548
|
-
out.
|
|
547
|
+
if (source.connectors !== undefined)
|
|
548
|
+
out.connectors = freezeMetadataValue(source.connectors);
|
|
549
|
+
if (source.connectorOperation !== undefined)
|
|
550
|
+
out.connectorOperation = Object.freeze({ ...source.connectorOperation });
|
|
551
|
+
if (source.invokePolicy !== undefined)
|
|
552
|
+
out.invokePolicy = freezeMetadataValue(source.invokePolicy);
|
|
549
553
|
return Object.freeze(out);
|
|
550
554
|
}
|
|
551
555
|
function freezeMetadataValue(value) {
|
|
@@ -615,7 +619,6 @@ function compileRunForm(def, metadata) {
|
|
|
615
619
|
agentEvents: stepCtx.agentEvents,
|
|
616
620
|
checkpoint: stepCtx.checkpoint,
|
|
617
621
|
pause: stepCtx.pause,
|
|
618
|
-
requestDecision: stepCtx.requestDecision,
|
|
619
622
|
sleep: stepCtx.sleep,
|
|
620
623
|
waitForEvent: stepCtx.waitForEvent,
|
|
621
624
|
processors: metadata.processors ?? []
|
|
@@ -649,7 +652,6 @@ function defineSandboxEnvironment(env) {
|
|
|
649
652
|
return defineWorkflow({
|
|
650
653
|
...env.description !== undefined ? { description: env.description } : {},
|
|
651
654
|
snapshots: env.snapshots ?? { saveLatest: true },
|
|
652
|
-
memory: env.memory ?? false,
|
|
653
655
|
run: async (_ctx, sandbox) => env.setup(sandbox)
|
|
654
656
|
});
|
|
655
657
|
}
|
|
@@ -800,15 +802,18 @@ class AgentComposeClient {
|
|
|
800
802
|
invoke(name, input, opts) {
|
|
801
803
|
const parentRunId = opts?.parentRunId === undefined ? detectAmbientParentRunId() : opts.parentRunId;
|
|
802
804
|
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
805
|
+
const headers = {};
|
|
806
|
+
if (opts?.idempotencyKey)
|
|
807
|
+
headers["Idempotency-Key"] = opts.idempotencyKey;
|
|
803
808
|
return this.fetch(templatePath(factorySlug, name, "invoke"), {
|
|
804
809
|
method: "POST",
|
|
810
|
+
...opts?.idempotencyKey ? { headers } : {},
|
|
805
811
|
body: {
|
|
806
812
|
input,
|
|
807
813
|
...opts?.snapshots !== undefined ? { snapshots: opts.snapshots } : {},
|
|
808
814
|
...opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {},
|
|
809
815
|
...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {},
|
|
810
|
-
...opts?.
|
|
811
|
-
...opts?.postRunHooks !== undefined ? { postRunHooks: opts.postRunHooks } : {},
|
|
816
|
+
...opts?.size !== undefined ? { size: opts.size } : {},
|
|
812
817
|
...parentRunId ? { parentRunId } : {},
|
|
813
818
|
...opts?.agentId ? { agentId: opts.agentId } : {}
|
|
814
819
|
}
|
|
@@ -933,6 +938,56 @@ class AgentComposeClient {
|
|
|
933
938
|
const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/events`);
|
|
934
939
|
return body.events;
|
|
935
940
|
}
|
|
941
|
+
async listRunArtifacts(runId) {
|
|
942
|
+
const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/artifacts`);
|
|
943
|
+
return body.artifacts.map((a) => ({
|
|
944
|
+
path: a.path,
|
|
945
|
+
factorySlug: a.factory_slug,
|
|
946
|
+
sizeBytes: a.size_bytes,
|
|
947
|
+
lastWriteAt: a.last_write_at,
|
|
948
|
+
preview: a.preview
|
|
949
|
+
}));
|
|
950
|
+
}
|
|
951
|
+
async putFactoryFile(path, content, opts) {
|
|
952
|
+
const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
|
|
953
|
+
const runToken = typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_RUN_TOKEN : undefined;
|
|
954
|
+
const wire = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?path=${encodeURIComponent(path)}`, {
|
|
955
|
+
method: "PUT",
|
|
956
|
+
body: content,
|
|
957
|
+
headers: {
|
|
958
|
+
"content-type": opts?.contentType ?? "text/plain; charset=utf-8",
|
|
959
|
+
...runToken ? { "x-run-token": runToken } : {}
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
return {
|
|
963
|
+
path: wire.path,
|
|
964
|
+
contentHash: wire.content_hash,
|
|
965
|
+
sizeBytes: wire.size_bytes,
|
|
966
|
+
created: wire.created
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
async getFactoryFile(path, opts) {
|
|
970
|
+
const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
|
|
971
|
+
const q2 = new URLSearchParams({ path });
|
|
972
|
+
if (opts?.revision !== undefined)
|
|
973
|
+
q2.set("revision", String(opts.revision));
|
|
974
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?${q2}`, { responseType: "text" });
|
|
975
|
+
}
|
|
976
|
+
async listMembers() {
|
|
977
|
+
const body = await this.fetch("/api/v1/team/members");
|
|
978
|
+
return body.members;
|
|
979
|
+
}
|
|
980
|
+
async createMentions(input) {
|
|
981
|
+
const factorySlug = input.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
|
|
982
|
+
const runToken = typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_RUN_TOKEN : undefined;
|
|
983
|
+
const { factorySlug: _omit, ...payload } = input;
|
|
984
|
+
const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/mentions`, {
|
|
985
|
+
method: "POST",
|
|
986
|
+
body: payload,
|
|
987
|
+
...runToken ? { headers: { "x-run-token": runToken } } : {}
|
|
988
|
+
});
|
|
989
|
+
return body.mentions;
|
|
990
|
+
}
|
|
936
991
|
async listFactoryEvents(opts) {
|
|
937
992
|
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
938
993
|
const q2 = new URLSearchParams;
|
|
@@ -1068,7 +1123,7 @@ function workflowPlan(steps) {
|
|
|
1068
1123
|
}
|
|
1069
1124
|
|
|
1070
1125
|
// src/utils/bundler.ts
|
|
1071
|
-
var BUNDLER_VERSION =
|
|
1126
|
+
var BUNDLER_VERSION = 2;
|
|
1072
1127
|
|
|
1073
1128
|
class WorkflowSourceValidationError extends Error {
|
|
1074
1129
|
constructor(message) {
|
|
@@ -1184,8 +1239,10 @@ async function bundleWorkflow(workflowPath, overrides) {
|
|
|
1184
1239
|
...inputSchema !== undefined ? { inputSchema } : {},
|
|
1185
1240
|
...outputSchema !== undefined ? { outputSchema } : {},
|
|
1186
1241
|
...metadata.snapshots !== undefined ? { snapshots: metadata.snapshots } : {},
|
|
1187
|
-
...metadata.
|
|
1188
|
-
...metadata.
|
|
1242
|
+
...metadata.resources !== undefined ? { resources: metadata.resources } : {},
|
|
1243
|
+
...metadata.connectors !== undefined ? { connectors: metadata.connectors } : {},
|
|
1244
|
+
...metadata.connectorOperation !== undefined ? { connectorOperation: metadata.connectorOperation } : {},
|
|
1245
|
+
...metadata.invokePolicy !== undefined ? { invokePolicy: metadata.invokePolicy } : {}
|
|
1189
1246
|
};
|
|
1190
1247
|
}
|
|
1191
1248
|
function extractIOSchema(zodSchema) {
|
|
@@ -1812,9 +1869,9 @@ async function runControlPoller(opts) {
|
|
|
1812
1869
|
}
|
|
1813
1870
|
|
|
1814
1871
|
// src/agent/agent-loop.ts
|
|
1815
|
-
var DEFAULT_CLAUDE_MODEL = "claude-
|
|
1872
|
+
var DEFAULT_CLAUDE_MODEL = "claude-fable-5";
|
|
1816
1873
|
var SAME_BLOCKER_ITERATIONS = 3;
|
|
1817
|
-
var STALL_ITERATIONS =
|
|
1874
|
+
var STALL_ITERATIONS = 6;
|
|
1818
1875
|
var MESSAGE_PREVIEW_CHARS = 400;
|
|
1819
1876
|
function parseAgentStatus(text) {
|
|
1820
1877
|
const match = text.match(/<status>([\s\S]*?)<\/status>/);
|
|
@@ -1871,8 +1928,9 @@ async function agentLoop(opts) {
|
|
|
1871
1928
|
const label = opts.label ?? "agent";
|
|
1872
1929
|
const logLabel = opts.label ?? "[Agent Loop]";
|
|
1873
1930
|
const startedAt = Date.now();
|
|
1874
|
-
const turnsPerIteration = opts.turnsPerIteration
|
|
1875
|
-
const maxIterations = opts.maxIterations ?? 8;
|
|
1931
|
+
const turnsPerIteration = opts.turnsPerIteration;
|
|
1932
|
+
const maxIterations = opts.maxIterations ?? (turnsPerIteration === undefined ? 1 : 8);
|
|
1933
|
+
let schemaRetriesLeft = 10;
|
|
1876
1934
|
const processors = opts.processors ?? [];
|
|
1877
1935
|
const requestContext = opts.requestContext ?? RequestContext.fromReserved({
|
|
1878
1936
|
teamId: "",
|
|
@@ -1893,7 +1951,7 @@ async function agentLoop(opts) {
|
|
|
1893
1951
|
if (!opts.runtime)
|
|
1894
1952
|
throw new Error("agentLoop: opts.runtime is required");
|
|
1895
1953
|
const client = opts.runtime({
|
|
1896
|
-
maxTurns: turnsPerIteration,
|
|
1954
|
+
...turnsPerIteration !== undefined ? { maxTurns: turnsPerIteration } : {},
|
|
1897
1955
|
allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS,
|
|
1898
1956
|
label: logLabel,
|
|
1899
1957
|
cwd: opts.cwd,
|
|
@@ -2002,6 +2060,15 @@ async function agentLoop(opts) {
|
|
|
2002
2060
|
}
|
|
2003
2061
|
const procCtx = buildProcCtx(iteration + 1);
|
|
2004
2062
|
let initialPrompt = opts.buildPrompt(lastStatus, iteration);
|
|
2063
|
+
if (lastResponseValidationError) {
|
|
2064
|
+
initialPrompt = `${initialPrompt}
|
|
2065
|
+
|
|
2066
|
+
[response contract violation — fix and re-emit]
|
|
2067
|
+
Your previous <response> failed schema validation with these errors:
|
|
2068
|
+
${lastResponseValidationError.slice(0, 2000)}
|
|
2069
|
+
Re-emit the COMPLETE corrected <response> JSON now: every required field present, correctly named and typed (no omissions, no renames).`;
|
|
2070
|
+
lastResponseValidationError = "";
|
|
2071
|
+
}
|
|
2005
2072
|
if (steerDecision) {
|
|
2006
2073
|
const who = steerDecision.actor ? ` from ${steerDecision.actor}` : "";
|
|
2007
2074
|
initialPrompt = `${initialPrompt}
|
|
@@ -2014,12 +2081,12 @@ async function agentLoop(opts) {
|
|
|
2014
2081
|
throw new Error(`${logLabel} input ${inputVerdict.kind === "deny" ? "rejected" : "aborted"}: ${inputVerdict.reason}`);
|
|
2015
2082
|
}
|
|
2016
2083
|
const prompt = inputVerdict.value;
|
|
2017
|
-
process.stdout.write(`${logLabel} iteration ${iteration + 1}/${maxIterations} · ${turnsPerIteration} turns
|
|
2084
|
+
process.stdout.write(`${logLabel} iteration ${iteration + 1}/${maxIterations} · ${turnsPerIteration !== undefined ? `${turnsPerIteration} turns` : "harness-decided turns"}
|
|
2018
2085
|
`);
|
|
2019
2086
|
let responseText = "";
|
|
2020
2087
|
for await (const rawMsg of client.sendMessage({
|
|
2021
2088
|
prompt,
|
|
2022
|
-
sessionId:
|
|
2089
|
+
sessionId: lastSessionId ?? undefined,
|
|
2023
2090
|
iteration: iteration + 1,
|
|
2024
2091
|
...opts.inbox ? { inboxStream: opts.inbox } : {}
|
|
2025
2092
|
})) {
|
|
@@ -2035,7 +2102,15 @@ async function agentLoop(opts) {
|
|
|
2035
2102
|
}
|
|
2036
2103
|
const msg = outputVerdict.value;
|
|
2037
2104
|
opts.onAgentEvent?.(iteration, msg);
|
|
2038
|
-
|
|
2105
|
+
const summary = summarizeAgentMessage(msg);
|
|
2106
|
+
opts.onAgentLifecycleEvent?.({
|
|
2107
|
+
event: "agent.message",
|
|
2108
|
+
at: Date.now(),
|
|
2109
|
+
agentId,
|
|
2110
|
+
label,
|
|
2111
|
+
iteration: iteration + 1,
|
|
2112
|
+
message: summary.type === "usage" && client.model != null ? { ...summary, model: client.model } : summary
|
|
2113
|
+
});
|
|
2039
2114
|
if (msg.type === "init")
|
|
2040
2115
|
lastSessionId = msg.sessionId;
|
|
2041
2116
|
if (msg.type === "text")
|
|
@@ -2078,6 +2153,8 @@ async function agentLoop(opts) {
|
|
|
2078
2153
|
`);
|
|
2079
2154
|
status = { ...status, exit_signal: false, blockers: ["No <response> block found — emit a <response> block with the required JSON fields before setting exit_signal: true"] };
|
|
2080
2155
|
opts.onIteration?.(iteration + 1, status);
|
|
2156
|
+
if (schemaRetriesLeft-- > 0)
|
|
2157
|
+
iteration--;
|
|
2081
2158
|
continue;
|
|
2082
2159
|
}
|
|
2083
2160
|
const parsed = validateResponse({ ...status, ...rawResponse });
|
|
@@ -2087,6 +2164,8 @@ raw: ${JSON.stringify(rawResponse).slice(0, 400)}
|
|
|
2087
2164
|
`);
|
|
2088
2165
|
status = { ...status, exit_signal: false, blockers: [`<response> schema validation failed: ${parsed.error.message}`] };
|
|
2089
2166
|
opts.onIteration?.(iteration + 1, status);
|
|
2167
|
+
if (schemaRetriesLeft-- > 0)
|
|
2168
|
+
iteration--;
|
|
2090
2169
|
continue;
|
|
2091
2170
|
}
|
|
2092
2171
|
response = parsed.data;
|
|
@@ -2107,9 +2186,16 @@ raw: ${JSON.stringify(rawResponse).slice(0, 400)}
|
|
|
2107
2186
|
blockerStreak = null;
|
|
2108
2187
|
continue;
|
|
2109
2188
|
}
|
|
2110
|
-
if (!status) {
|
|
2189
|
+
if (!status && rawResponse === null) {
|
|
2111
2190
|
if (++iterationsWithoutStatus >= STALL_ITERATIONS)
|
|
2112
2191
|
throw new Error(`${logLabel} stalled: no <status> block after ${iterationsWithoutStatus} iterations`);
|
|
2192
|
+
if (schemaRetriesLeft-- > 0) {
|
|
2193
|
+
lastResponseValidationError = "Your turn ended with no <status> block" + (opts.responseSchema ? " (and no <response> block)" : "") + ". " + "If you launched a background job (`… &`) and are waiting to be notified when it finishes — STOP: nothing will notify you here. " + "Poll it NOW (read its output / wait for it synchronously to completion), then emit your <status>" + (opts.responseSchema ? " and the complete <response> JSON." : ".");
|
|
2194
|
+
process.stdout.write(`${logLabel} empty turn (no <status>) — corrective re-prompt (${schemaRetriesLeft} retries left)
|
|
2195
|
+
`);
|
|
2196
|
+
iteration--;
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2113
2199
|
} else {
|
|
2114
2200
|
iterationsWithoutStatus = 0;
|
|
2115
2201
|
}
|
|
@@ -2124,6 +2210,12 @@ raw: ${JSON.stringify(rawResponse).slice(0, 400)}
|
|
|
2124
2210
|
} else {
|
|
2125
2211
|
blockerStreak = null;
|
|
2126
2212
|
}
|
|
2213
|
+
if (opts.responseSchema && rawResponse !== null && lastResponseValidationError !== "" && status?.exit_signal !== false && schemaRetriesLeft-- > 0) {
|
|
2214
|
+
process.stdout.write(`${logLabel} response contract not yet satisfied — corrective re-prompt (${schemaRetriesLeft} retries left)
|
|
2215
|
+
`);
|
|
2216
|
+
iteration--;
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2127
2219
|
if (iteration + 1 < maxIterations)
|
|
2128
2220
|
process.stdout.write(`${logLabel} continuing to iteration ${iteration + 2}/${maxIterations}
|
|
2129
2221
|
`);
|
|
@@ -2249,14 +2341,34 @@ function translateMessage(message) {
|
|
|
2249
2341
|
numTurns: Number(message.num_turns ?? 0),
|
|
2250
2342
|
timestamp: ts
|
|
2251
2343
|
});
|
|
2252
|
-
if (message.
|
|
2253
|
-
msgs.push({ type: "
|
|
2344
|
+
if (message.subtype === "error_max_turns") {
|
|
2345
|
+
msgs.push({ type: "done", sessionId: String(message.session_id ?? ""), timestamp: ts });
|
|
2346
|
+
} else if (message.is_error || message.subtype === "error_during_execution") {
|
|
2347
|
+
const detail = message.error ?? message.result ?? message.message;
|
|
2348
|
+
const text = detail !== undefined ? formatError(detail) : `${String(message.subtype ?? "unknown_error")} — raw result: ${JSON.stringify({ ...message, usage: undefined }).slice(0, 600)}`;
|
|
2349
|
+
msgs.push({ type: "error", text, timestamp: ts });
|
|
2254
2350
|
} else {
|
|
2255
2351
|
msgs.push({ type: "done", sessionId: String(message.session_id ?? ""), timestamp: ts });
|
|
2256
2352
|
}
|
|
2257
2353
|
}
|
|
2258
2354
|
return msgs;
|
|
2259
2355
|
}
|
|
2356
|
+
var MODEL_TIERS = {
|
|
2357
|
+
fable: "claude-fable-5",
|
|
2358
|
+
opus: "claude-opus-4-8",
|
|
2359
|
+
sonnet: "claude-sonnet-4-6",
|
|
2360
|
+
haiku: "claude-haiku-4-5"
|
|
2361
|
+
};
|
|
2362
|
+
function resolveClaudeModel(model) {
|
|
2363
|
+
if (!model)
|
|
2364
|
+
return DEFAULT_CLAUDE_MODEL;
|
|
2365
|
+
return MODEL_TIERS[model.toLowerCase()] ?? model;
|
|
2366
|
+
}
|
|
2367
|
+
function resolveThinking(model, thinking) {
|
|
2368
|
+
if (model.startsWith("claude-fable") && thinking && thinking.type === "disabled")
|
|
2369
|
+
return;
|
|
2370
|
+
return thinking;
|
|
2371
|
+
}
|
|
2260
2372
|
var DEFAULT_CLAUDE_PATH = "/usr/local/bin/claude";
|
|
2261
2373
|
|
|
2262
2374
|
class ClaudeRunner {
|
|
@@ -2269,7 +2381,7 @@ class ClaudeRunner {
|
|
|
2269
2381
|
this.config = config;
|
|
2270
2382
|
}
|
|
2271
2383
|
get model() {
|
|
2272
|
-
return this.config.model ?? this.options.model
|
|
2384
|
+
return resolveClaudeModel(this.config.model ?? this.options.model);
|
|
2273
2385
|
}
|
|
2274
2386
|
async gateToolCall(call, ctx) {
|
|
2275
2387
|
const verdict = await runProcessorChain(this.options.processors ?? [], (p) => p.processToolCall, call, ctx);
|
|
@@ -2337,14 +2449,15 @@ class ClaudeRunner {
|
|
|
2337
2449
|
tools: this.options.allowedTools,
|
|
2338
2450
|
allowedTools: this.options.allowedTools,
|
|
2339
2451
|
maxTurns: this.options.maxTurns,
|
|
2340
|
-
model: this.
|
|
2452
|
+
model: this.model,
|
|
2341
2453
|
outputFormat: this.options.outputFormat,
|
|
2342
|
-
thinking: this.config.thinking,
|
|
2454
|
+
thinking: resolveThinking(this.model, this.config.thinking),
|
|
2343
2455
|
effort: this.config.effort,
|
|
2344
2456
|
cwd: this.options.cwd,
|
|
2345
2457
|
env: { ...process.env, ...this.config.env ?? {} },
|
|
2346
2458
|
pathToClaudeCodeExecutable: this.config.pathToClaudeCodeExecutable ?? process.env.CLAUDE_CODE_EXECUTABLE ?? DEFAULT_CLAUDE_PATH,
|
|
2347
2459
|
...this.config.claudeMdContent ? { systemPrompt: { type: "preset", preset: "claude_code", append: this.config.claudeMdContent } } : {},
|
|
2460
|
+
skills: this.config.skills ?? "all",
|
|
2348
2461
|
resume: opts.sessionId,
|
|
2349
2462
|
mcpServers: this.config.mcpServers,
|
|
2350
2463
|
permissionMode: "acceptEdits",
|
|
@@ -2364,7 +2477,12 @@ class ClaudeRunner {
|
|
|
2364
2477
|
inboxQueue.close();
|
|
2365
2478
|
}
|
|
2366
2479
|
} catch (err) {
|
|
2367
|
-
|
|
2480
|
+
const text = formatError(err);
|
|
2481
|
+
if (/maximum number of turns/i.test(text)) {
|
|
2482
|
+
yield { type: "done", sessionId: opts.sessionId ?? "", timestamp: now2() };
|
|
2483
|
+
} else {
|
|
2484
|
+
yield { type: "error", text, timestamp: now2() };
|
|
2485
|
+
}
|
|
2368
2486
|
} finally {
|
|
2369
2487
|
inboxQueue?.close();
|
|
2370
2488
|
}
|
|
@@ -2633,11 +2751,34 @@ var amp_default = createAmpRuntime();
|
|
|
2633
2751
|
import { promises as fs2 } from "node:fs";
|
|
2634
2752
|
import { dirname as dirname2 } from "node:path";
|
|
2635
2753
|
import { spawn } from "node:child_process";
|
|
2636
|
-
import { Sandbox } from "e2b";
|
|
2754
|
+
import { Sandbox, SandboxNotFoundError, RateLimitError } from "e2b";
|
|
2637
2755
|
import { Sandbox as Desktop } from "@e2b/desktop";
|
|
2638
2756
|
import pRetry from "p-retry";
|
|
2757
|
+
|
|
2758
|
+
// src/sandbox-errors.ts
|
|
2759
|
+
var SANDBOX_UNAVAILABLE_PREFIX = "[sandbox-unavailable";
|
|
2760
|
+
|
|
2761
|
+
class SandboxUnavailableError extends Error {
|
|
2762
|
+
retryable;
|
|
2763
|
+
sandboxId;
|
|
2764
|
+
constructor(detail, opts) {
|
|
2765
|
+
super(`${SANDBOX_UNAVAILABLE_PREFIX}:${opts.retryable ? "retryable" : "terminal"}] ${detail}`);
|
|
2766
|
+
this.name = "SandboxUnavailableError";
|
|
2767
|
+
this.retryable = opts.retryable;
|
|
2768
|
+
this.sandboxId = opts.sandboxId;
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
// src/sandbox.ts
|
|
2639
2773
|
var AGENT_COMPOSE_TAG = process.env.AGENT_COMPOSE_TAG ?? `agent-compose-${process.env.AGENT_COMPOSE_ENV ?? "dev"}`;
|
|
2640
2774
|
var VERCEL_VM_LIFETIME_WINDOW_MS = 6 * 60 * 60 * 1000;
|
|
2775
|
+
var SANDBOX_VCPUS = {
|
|
2776
|
+
"2vcpu-4gb": 2,
|
|
2777
|
+
"4vcpu-8gb": 4,
|
|
2778
|
+
"8vcpu-16gb": 8,
|
|
2779
|
+
"32vcpu-64gb": 32
|
|
2780
|
+
};
|
|
2781
|
+
var DEFAULT_SANDBOX_SIZE = "2vcpu-4gb";
|
|
2641
2782
|
function makeSandboxProvider(sb) {
|
|
2642
2783
|
const commands = sb.commands;
|
|
2643
2784
|
return {
|
|
@@ -2667,6 +2808,33 @@ function makeSandboxProvider(sb) {
|
|
|
2667
2808
|
kill: () => sb.kill()
|
|
2668
2809
|
};
|
|
2669
2810
|
}
|
|
2811
|
+
function e2bMaxSandboxMs() {
|
|
2812
|
+
return Number(process.env.E2B_MAX_SANDBOX_MS) || 60 * 60 * 1000;
|
|
2813
|
+
}
|
|
2814
|
+
function isTransientSandboxError(error) {
|
|
2815
|
+
if (error instanceof SandboxNotFoundError || error instanceof RateLimitError)
|
|
2816
|
+
return true;
|
|
2817
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
2818
|
+
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);
|
|
2819
|
+
}
|
|
2820
|
+
function withSandboxRetry(fn, shouldRetry = isTransientSandboxError) {
|
|
2821
|
+
return pRetry(fn, { retries: 4, minTimeout: 400, factor: 2, shouldRetry });
|
|
2822
|
+
}
|
|
2823
|
+
function withSnapshotRetry(p) {
|
|
2824
|
+
if (!p.snapshot)
|
|
2825
|
+
return p;
|
|
2826
|
+
const snapshot = p.snapshot.bind(p);
|
|
2827
|
+
return { ...p, snapshot: () => withSandboxRetry(snapshot) };
|
|
2828
|
+
}
|
|
2829
|
+
function makeE2bSandboxProvider(sb) {
|
|
2830
|
+
return {
|
|
2831
|
+
...makeSandboxProvider(sb),
|
|
2832
|
+
async snapshot() {
|
|
2833
|
+
const { snapshotId } = await sb.createSnapshot();
|
|
2834
|
+
return { snapshotId };
|
|
2835
|
+
}
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2670
2838
|
function makeDesktopSandboxProvider(sb) {
|
|
2671
2839
|
return {
|
|
2672
2840
|
...makeSandboxProvider(sb),
|
|
@@ -2724,43 +2892,104 @@ async function parseSseExecStream(body, opts) {
|
|
|
2724
2892
|
throw new Error("Sandbox exec stream ended without an exit event");
|
|
2725
2893
|
return { exitCode, stdout, stderr };
|
|
2726
2894
|
}
|
|
2895
|
+
var DOT_SEGMENT_PATH_RE2 = ".*(?:^|/|%2[fF])(?:\\.|%2[eE]){2}(?:/|%2[fF]|$).*";
|
|
2896
|
+
var DOT_SEGMENT_RE = /(?:^|\/|%2f)(?:\.|%2e){2}(?:\/|%2f|$)/i;
|
|
2897
|
+
function assertSafePathPrefix(domain, prefix) {
|
|
2898
|
+
if (!prefix.startsWith("/") || DOT_SEGMENT_RE.test(prefix)) {
|
|
2899
|
+
throw new Error(`Invalid Tier-2 pathPrefix ${JSON.stringify(prefix)} for "${domain}": prefixes must start with "/" and contain no dot-segments`);
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
function toVercelNetworkPolicy(policy) {
|
|
2903
|
+
if (typeof policy === "string" || !policy.allow || Array.isArray(policy.allow)) {
|
|
2904
|
+
return policy;
|
|
2905
|
+
}
|
|
2906
|
+
const allow = {};
|
|
2907
|
+
for (const [domain, rules] of Object.entries(policy.allow)) {
|
|
2908
|
+
const translated = rules.flatMap((rule) => {
|
|
2909
|
+
const { requestRules, ...rest } = rule;
|
|
2910
|
+
const methods = requestRules?.methods ?? [];
|
|
2911
|
+
const prefixes = requestRules?.pathPrefixes ?? [];
|
|
2912
|
+
if (methods.length === 0 && prefixes.length === 0)
|
|
2913
|
+
return [rest];
|
|
2914
|
+
const methodMatch = methods.length > 0 ? { method: methods } : {};
|
|
2915
|
+
if (prefixes.length === 0)
|
|
2916
|
+
return [{ ...rest, match: methodMatch }];
|
|
2917
|
+
for (const prefix of prefixes)
|
|
2918
|
+
assertSafePathPrefix(domain, prefix);
|
|
2919
|
+
return prefixes.map((prefix) => ({ ...rest, match: { ...methodMatch, path: { startsWith: prefix } } }));
|
|
2920
|
+
});
|
|
2921
|
+
const hasPathGate = rules.some((rule) => (rule.requestRules?.pathPrefixes?.length ?? 0) > 0);
|
|
2922
|
+
allow[domain] = hasPathGate ? [{ match: { path: { regex: DOT_SEGMENT_PATH_RE2 } } }, ...translated] : translated;
|
|
2923
|
+
}
|
|
2924
|
+
return { ...policy, allow };
|
|
2925
|
+
}
|
|
2926
|
+
var VERCEL_MAX_TAGS = 5;
|
|
2927
|
+
function buildVercelTags(metadata) {
|
|
2928
|
+
const tags = { ...metadata, executor: AGENT_COMPOSE_TAG };
|
|
2929
|
+
const extras = Object.keys(tags).filter((k) => k !== "executor").sort();
|
|
2930
|
+
for (const key of extras.slice(VERCEL_MAX_TAGS - 1))
|
|
2931
|
+
delete tags[key];
|
|
2932
|
+
return tags;
|
|
2933
|
+
}
|
|
2727
2934
|
function makeVercelSandboxProvider(sb, globalEnvs) {
|
|
2728
2935
|
const mergeEnvs = (cmdEnvs) => globalEnvs ? { ...globalEnvs, ...cmdEnvs } : cmdEnvs;
|
|
2936
|
+
const asUnavailable = (err, retryable) => {
|
|
2937
|
+
throw new SandboxUnavailableError(err instanceof Error ? err.message : String(err), { retryable, sandboxId: sb.name });
|
|
2938
|
+
};
|
|
2729
2939
|
return {
|
|
2730
|
-
sandboxId: sb.
|
|
2940
|
+
sandboxId: sb.name,
|
|
2731
2941
|
commands: {
|
|
2732
2942
|
async run(cmd, opts) {
|
|
2733
2943
|
const signal = opts?.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined;
|
|
2734
2944
|
let stdout = "";
|
|
2735
2945
|
let stderr = "";
|
|
2736
|
-
const handle = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal, ...opts?.sudo ? { sudo: true } : {} });
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
for await (const log of h.logs()) {
|
|
2744
|
-
if (log.stream === "stdout") {
|
|
2745
|
-
stdout += log.data;
|
|
2746
|
-
opts?.onStdout?.(log.data);
|
|
2747
|
-
} else {
|
|
2748
|
-
stderr += log.data;
|
|
2749
|
-
opts?.onStderr?.(log.data);
|
|
2946
|
+
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));
|
|
2947
|
+
const collect = async () => {
|
|
2948
|
+
await pRetry(async (attempt) => {
|
|
2949
|
+
const h = attempt === 1 ? handle : await sb.getCommand(handle.cmdId);
|
|
2950
|
+
if (attempt > 1) {
|
|
2951
|
+
stdout = "";
|
|
2952
|
+
stderr = "";
|
|
2750
2953
|
}
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2954
|
+
for await (const log of h.logs()) {
|
|
2955
|
+
if (log.stream === "stdout") {
|
|
2956
|
+
stdout += log.data;
|
|
2957
|
+
opts?.onStdout?.(log.data);
|
|
2958
|
+
} else {
|
|
2959
|
+
stderr += log.data;
|
|
2960
|
+
opts?.onStderr?.(log.data);
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
}, { retries: 3, minTimeout: 1000, factor: 2 });
|
|
2964
|
+
const finished = await handle.wait();
|
|
2965
|
+
return { exitCode: finished.exitCode, stdout, stderr };
|
|
2966
|
+
};
|
|
2967
|
+
let timer;
|
|
2968
|
+
const deadline = opts?.timeoutMs ? new Promise((_, reject) => {
|
|
2969
|
+
timer = setTimeout(() => reject(new Error(`command timed out after ${opts.timeoutMs}ms: ${cmd.slice(0, 200)}`)), opts.timeoutMs);
|
|
2970
|
+
}) : undefined;
|
|
2971
|
+
try {
|
|
2972
|
+
return await (deadline ? Promise.race([collect(), deadline]) : collect());
|
|
2973
|
+
} catch (err) {
|
|
2974
|
+
if (err instanceof Error && err.message.startsWith("command timed out after"))
|
|
2975
|
+
throw err;
|
|
2976
|
+
if (signal?.aborted)
|
|
2977
|
+
throw new Error(`command timed out after ${opts?.timeoutMs}ms: ${cmd.slice(0, 200)}`);
|
|
2978
|
+
return asUnavailable(err, false);
|
|
2979
|
+
} finally {
|
|
2980
|
+
if (timer)
|
|
2981
|
+
clearTimeout(timer);
|
|
2982
|
+
}
|
|
2755
2983
|
}
|
|
2756
2984
|
},
|
|
2757
2985
|
files: {
|
|
2758
2986
|
async write(path, content) {
|
|
2759
|
-
await sb.writeFiles([{ path, content }]);
|
|
2987
|
+
await sb.writeFiles([{ path, content }]).catch((err) => asUnavailable(err, true));
|
|
2760
2988
|
}
|
|
2761
2989
|
},
|
|
2762
2990
|
async kill() {
|
|
2763
2991
|
await sb.stop();
|
|
2992
|
+
await sb.delete();
|
|
2764
2993
|
},
|
|
2765
2994
|
async snapshot() {
|
|
2766
2995
|
const res = await sb.snapshot({ expiration: 0 });
|
|
@@ -2769,6 +2998,9 @@ function makeVercelSandboxProvider(sb, globalEnvs) {
|
|
|
2769
2998
|
snapshotId: res.snapshotId,
|
|
2770
2999
|
...sizeBytes !== undefined ? { sizeBytes } : {}
|
|
2771
3000
|
};
|
|
3001
|
+
},
|
|
3002
|
+
async updateNetworkPolicy(policy) {
|
|
3003
|
+
await sb.update({ networkPolicy: toVercelNetworkPolicy(policy) });
|
|
2772
3004
|
}
|
|
2773
3005
|
};
|
|
2774
3006
|
}
|
|
@@ -2820,8 +3052,11 @@ var SANDBOX_PROVIDERS = {
|
|
|
2820
3052
|
create: async (opts, env) => {
|
|
2821
3053
|
const { Sandbox: VercelSandbox } = await import("@vercel/sandbox");
|
|
2822
3054
|
const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
|
|
2823
|
-
const np = opts.networkPolicy;
|
|
2824
|
-
const
|
|
3055
|
+
const np = opts.networkPolicy ? { networkPolicy: toVercelNetworkPolicy(opts.networkPolicy) } : {};
|
|
3056
|
+
const tags = buildVercelTags(opts.metadata);
|
|
3057
|
+
const tmpl = opts.template ?? process.env.VERCEL_DEFAULT_SNAPSHOT;
|
|
3058
|
+
const resources = { vcpus: SANDBOX_VCPUS[opts.size ?? DEFAULT_SANDBOX_SIZE] };
|
|
3059
|
+
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 });
|
|
2825
3060
|
return makeVercelSandboxProvider(sb, opts.envs);
|
|
2826
3061
|
},
|
|
2827
3062
|
reconnect: async (sandboxId) => {
|
|
@@ -2831,7 +3066,10 @@ var SANDBOX_PROVIDERS = {
|
|
|
2831
3066
|
teamId: process.env.VERCEL_TEAM_ID ?? "",
|
|
2832
3067
|
projectId: process.env.VERCEL_PROJECT_ID ?? ""
|
|
2833
3068
|
};
|
|
2834
|
-
|
|
3069
|
+
const sb = await VercelSandbox.get({ name: sandboxId, ...creds }).catch((err) => {
|
|
3070
|
+
throw new SandboxUnavailableError(err instanceof Error ? err.message : String(err), { retryable: true, sandboxId });
|
|
3071
|
+
});
|
|
3072
|
+
return makeVercelSandboxProvider(sb);
|
|
2835
3073
|
},
|
|
2836
3074
|
getActiveCount: async (env) => (await SANDBOX_PROVIDERS.vercel.listOwned(env)).length,
|
|
2837
3075
|
listOwned: async (env) => {
|
|
@@ -2839,18 +3077,21 @@ var SANDBOX_PROVIDERS = {
|
|
|
2839
3077
|
const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
|
|
2840
3078
|
const since = Date.now() - VERCEL_VM_LIFETIME_WINDOW_MS;
|
|
2841
3079
|
const out = [];
|
|
2842
|
-
let
|
|
2843
|
-
for (let page = 0;page <
|
|
2844
|
-
const
|
|
2845
|
-
for (const sb of
|
|
3080
|
+
let cursor;
|
|
3081
|
+
for (let page = 0;page < 200; page++) {
|
|
3082
|
+
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 });
|
|
3083
|
+
for (const sb of result.sandboxes) {
|
|
3084
|
+
if (sb.createdAt < since)
|
|
3085
|
+
return out;
|
|
2846
3086
|
if (sb.status === "running") {
|
|
2847
|
-
out.push({ sandboxId: sb.
|
|
3087
|
+
out.push({ sandboxId: sb.name, createdAt: new Date(sb.createdAt), metadata: sb.tags ?? {} });
|
|
2848
3088
|
}
|
|
2849
|
-
|
|
3089
|
+
}
|
|
3090
|
+
if (result.pagination.next === null)
|
|
2850
3091
|
return out;
|
|
2851
|
-
|
|
3092
|
+
cursor = result.pagination.next;
|
|
2852
3093
|
}
|
|
2853
|
-
throw new Error("Vercel listSandboxes exceeded
|
|
3094
|
+
throw new Error("Vercel listSandboxes exceeded 200 pages (10k sandboxes) within the lifetime window — refuse to silently truncate");
|
|
2854
3095
|
},
|
|
2855
3096
|
deleteSnapshot: async (snapshotId, env) => {
|
|
2856
3097
|
const { Snapshot } = await import("@vercel/sandbox");
|
|
@@ -2861,12 +3102,16 @@ var SANDBOX_PROVIDERS = {
|
|
|
2861
3102
|
},
|
|
2862
3103
|
e2b: {
|
|
2863
3104
|
requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
|
|
2864
|
-
create: async ({ template, ...
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
3105
|
+
create: async ({ template, timeoutMs, networkPolicy: _np, size: _size, ...rest }) => {
|
|
3106
|
+
const maxMs = e2bMaxSandboxMs();
|
|
3107
|
+
if (timeoutMs > maxMs) {
|
|
3108
|
+
console.warn(`[sandbox] e2b create timeout clamped: requested ${timeoutMs}ms exceeds the plan cap ${maxMs}ms — ` + `the sandbox dies at the cap, not the requested deadline. Raise E2B_MAX_SANDBOX_MS on plans that allow more.`);
|
|
3109
|
+
}
|
|
3110
|
+
const sandboxOpts = { ...rest, timeoutMs: Math.min(timeoutMs, maxMs) };
|
|
3111
|
+
const tmpl = template ?? process.env.E2B_DEFAULT_TEMPLATE;
|
|
3112
|
+
return makeE2bSandboxProvider(await (tmpl ? Sandbox.create(tmpl, sandboxOpts) : Sandbox.create(sandboxOpts)));
|
|
2868
3113
|
},
|
|
2869
|
-
reconnect: async (sandboxId) =>
|
|
3114
|
+
reconnect: async (sandboxId) => makeE2bSandboxProvider(await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY ?? "", timeoutMs: 3600000 })),
|
|
2870
3115
|
killAll: async () => {
|
|
2871
3116
|
const paginator = Sandbox.list({ query: { metadata: { executor: AGENT_COMPOSE_TAG } } });
|
|
2872
3117
|
const sandboxes = [];
|
|
@@ -2892,14 +3137,17 @@ var SANDBOX_PROVIDERS = {
|
|
|
2892
3137
|
}
|
|
2893
3138
|
}
|
|
2894
3139
|
return out;
|
|
3140
|
+
},
|
|
3141
|
+
deleteSnapshot: async (snapshotId, env) => {
|
|
3142
|
+
await Sandbox.deleteSnapshot(snapshotId, { apiKey: env.E2B_API_KEY });
|
|
2895
3143
|
}
|
|
2896
3144
|
},
|
|
2897
3145
|
"e2b-desktop": {
|
|
2898
3146
|
requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
|
|
2899
|
-
create: async ({ template, ...
|
|
3147
|
+
create: async ({ template, timeoutMs, networkPolicy: _np, size: _size, ...rest }) => {
|
|
2900
3148
|
if (!template)
|
|
2901
3149
|
throw new Error("E2B Desktop provider requires an explicit `template` (Dockerfile-based — no default base image)");
|
|
2902
|
-
return makeDesktopSandboxProvider(await Desktop.create(template,
|
|
3150
|
+
return makeDesktopSandboxProvider(await Desktop.create(template, { ...rest, timeoutMs: Math.min(timeoutMs, e2bMaxSandboxMs()) }));
|
|
2903
3151
|
}
|
|
2904
3152
|
}
|
|
2905
3153
|
};
|
|
@@ -2912,13 +3160,14 @@ async function createSandbox(provider, opts) {
|
|
|
2912
3160
|
throw new Error(`Sandbox provider "${provider}" requires env vars:
|
|
2913
3161
|
${missing.join(`
|
|
2914
3162
|
`)}`);
|
|
2915
|
-
|
|
3163
|
+
const env = Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k]]));
|
|
3164
|
+
return withSnapshotRetry(await def.create(opts, env));
|
|
2916
3165
|
}
|
|
2917
3166
|
async function reconnectSandbox(provider, sandboxId) {
|
|
2918
3167
|
const def = SANDBOX_PROVIDERS[provider];
|
|
2919
3168
|
if (!def?.reconnect)
|
|
2920
3169
|
throw new Error(`Provider "${provider}" does not support reconnect`);
|
|
2921
|
-
return def.reconnect(sandboxId);
|
|
3170
|
+
return withSnapshotRetry(await withSandboxRetry(() => def.reconnect(sandboxId), (error) => error instanceof SandboxNotFoundError ? error.attemptNumber <= 1 : isTransientSandboxError(error)));
|
|
2922
3171
|
}
|
|
2923
3172
|
async function deleteSandboxSnapshot(provider, snapshotId) {
|
|
2924
3173
|
const def = SANDBOX_PROVIDERS[provider];
|
|
@@ -3079,14 +3328,6 @@ function scopedCheckpoint(scope) {
|
|
|
3079
3328
|
// src/pause/wrappers.ts
|
|
3080
3329
|
function buildPauseWrappers(pause) {
|
|
3081
3330
|
return {
|
|
3082
|
-
requestDecision(req) {
|
|
3083
|
-
return pause({
|
|
3084
|
-
reason: req.reason,
|
|
3085
|
-
schema: req.schema,
|
|
3086
|
-
...req.payload !== undefined ? { payload: req.payload } : {},
|
|
3087
|
-
...req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {}
|
|
3088
|
-
}, "decision");
|
|
3089
|
-
},
|
|
3090
3331
|
sleep(durationMs) {
|
|
3091
3332
|
return pause({
|
|
3092
3333
|
reason: `sleep ${durationMs}ms`,
|
|
@@ -3396,7 +3637,7 @@ function buildInvokeChild(runId, opts = {}) {
|
|
|
3396
3637
|
return (name, input, childOpts) => getChildClient().invokeAndWait(name, input, {
|
|
3397
3638
|
...childOpts,
|
|
3398
3639
|
parentRunId: runId,
|
|
3399
|
-
factorySlug: childOpts?.factorySlug ?? opts.defaultFactorySlug ?? process.env.AGENT_COMPOSE_FACTORY_SLUG ?? "default"
|
|
3640
|
+
factorySlug: childOpts?.factorySlug ?? opts.defaultFactorySlug ?? process.env.AGENT_COMPOSE_FACTORY ?? process.env.AGENT_COMPOSE_FACTORY_SLUG ?? "default"
|
|
3400
3641
|
});
|
|
3401
3642
|
}
|
|
3402
3643
|
// src/workflow-steps/step.ts
|
|
@@ -3436,6 +3677,9 @@ function stepInputPath(stepIndex) {
|
|
|
3436
3677
|
function requestContextPath(stepIndex) {
|
|
3437
3678
|
return `/tmp/wf/request-context-${stepIndex}.json`;
|
|
3438
3679
|
}
|
|
3680
|
+
function stepResultFilePath(token) {
|
|
3681
|
+
return `/tmp/wf/step-result-${token}.json`;
|
|
3682
|
+
}
|
|
3439
3683
|
// src/step-invocation/invoker.ts
|
|
3440
3684
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
3441
3685
|
import { z as z10 } from "zod";
|
|
@@ -3491,6 +3735,18 @@ function parseStepResult(stdout, resultToken) {
|
|
|
3491
3735
|
if (pauseLine && resultLine)
|
|
3492
3736
|
break;
|
|
3493
3737
|
}
|
|
3738
|
+
const midLine = (prefix) => {
|
|
3739
|
+
const at = stdout.indexOf(prefix);
|
|
3740
|
+
if (at === -1)
|
|
3741
|
+
return;
|
|
3742
|
+
const end = stdout.indexOf(`
|
|
3743
|
+
`, at);
|
|
3744
|
+
return stdout.slice(at, end === -1 ? undefined : end);
|
|
3745
|
+
};
|
|
3746
|
+
if (!pauseLine)
|
|
3747
|
+
pauseLine = midLine(pausePrefix);
|
|
3748
|
+
if (!resultLine)
|
|
3749
|
+
resultLine = midLine(resultPrefix);
|
|
3494
3750
|
if (pauseLine) {
|
|
3495
3751
|
try {
|
|
3496
3752
|
const raw = JSON.parse(pauseLine.slice(pausePrefix.length));
|
|
@@ -3578,17 +3834,35 @@ async function invokeStep(sandbox, request, opts) {
|
|
|
3578
3834
|
};
|
|
3579
3835
|
const stdoutSplitter = makeLineSplitter(opts?.onStdout, true);
|
|
3580
3836
|
const stderrSplitter = makeLineSplitter(opts?.onStderr, false);
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3837
|
+
let result;
|
|
3838
|
+
try {
|
|
3839
|
+
result = await sandbox.commands.run(RUNNER_COMMAND, {
|
|
3840
|
+
envs,
|
|
3841
|
+
timeoutMs: 0,
|
|
3842
|
+
...stdoutSplitter.onChunk ? { onStdout: stdoutSplitter.onChunk } : {},
|
|
3843
|
+
...stderrSplitter.onChunk ? { onStderr: stderrSplitter.onChunk } : {}
|
|
3844
|
+
});
|
|
3845
|
+
} catch (e) {
|
|
3846
|
+
if (e instanceof SandboxUnavailableError)
|
|
3847
|
+
throw e;
|
|
3848
|
+
const ce = e;
|
|
3849
|
+
result = {
|
|
3850
|
+
stdout: ce.stdout ?? ce.result?.stdout ?? "",
|
|
3851
|
+
stderr: ce.stderr ?? ce.result?.stderr ?? (e instanceof Error ? e.message : String(e)),
|
|
3852
|
+
exitCode: ce.exitCode ?? ce.result?.exitCode ?? 1
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3587
3855
|
stdoutSplitter.flush();
|
|
3588
3856
|
stderrSplitter.flush();
|
|
3589
3857
|
const parsed = parseStepResult(result.stdout, resultToken);
|
|
3590
3858
|
if (parsed)
|
|
3591
3859
|
return parsed;
|
|
3860
|
+
const fileRead = await sandbox.commands.run(`cat ${stepResultFilePath(resultToken)} 2>/dev/null || true`).catch(() => null);
|
|
3861
|
+
if (fileRead?.stdout) {
|
|
3862
|
+
const fromFile = parseStepResult(fileRead.stdout, resultToken);
|
|
3863
|
+
if (fromFile)
|
|
3864
|
+
return fromFile;
|
|
3865
|
+
}
|
|
3592
3866
|
if (result.exitCode !== 0) {
|
|
3593
3867
|
const stderrTail = result.stderr.trim().slice(-2000);
|
|
3594
3868
|
const stdoutTail = result.stdout.trim().slice(-2000);
|
|
@@ -3609,13 +3883,19 @@ ${details}` : ""}`,
|
|
|
3609
3883
|
}
|
|
3610
3884
|
};
|
|
3611
3885
|
}
|
|
3886
|
+
const tail = result.stdout.trim().slice(-500);
|
|
3612
3887
|
return {
|
|
3613
3888
|
ok: false,
|
|
3614
|
-
error: {
|
|
3889
|
+
error: {
|
|
3890
|
+
kind: "protocol",
|
|
3891
|
+
message: `no tokenised step result on stdout or in the result file (runner exited 0 without emitting)${tail ? `
|
|
3892
|
+
stdout tail:
|
|
3893
|
+
${tail}` : ""}`
|
|
3894
|
+
}
|
|
3615
3895
|
};
|
|
3616
3896
|
}
|
|
3617
3897
|
// src/step-invocation/server.ts
|
|
3618
|
-
import { readFileSync } from "node:fs";
|
|
3898
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3619
3899
|
function writeSentinelLine(line) {
|
|
3620
3900
|
return new Promise((resolve, reject) => {
|
|
3621
3901
|
process.stdout.write(line, (err) => {
|
|
@@ -3626,9 +3906,21 @@ function writeSentinelLine(line) {
|
|
|
3626
3906
|
});
|
|
3627
3907
|
});
|
|
3628
3908
|
}
|
|
3629
|
-
function
|
|
3630
|
-
|
|
3909
|
+
function persistSentinelFile(token, line) {
|
|
3910
|
+
try {
|
|
3911
|
+
mkdirSync("/tmp/wf", { recursive: true });
|
|
3912
|
+
writeFileSync(stepResultFilePath(token), line);
|
|
3913
|
+
} catch (err) {
|
|
3914
|
+
process.stderr.write(`[step-server] result-file persist failed: ${err instanceof Error ? err.message : String(err)}
|
|
3631
3915
|
`);
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
function emitResult(token, payload) {
|
|
3919
|
+
const line = `${STEP_RESULT_PREFIX}${token}:${JSON.stringify(payload)}
|
|
3920
|
+
`;
|
|
3921
|
+
persistSentinelFile(token, line);
|
|
3922
|
+
return writeSentinelLine(`
|
|
3923
|
+
${line}`);
|
|
3632
3924
|
}
|
|
3633
3925
|
function emitPause(token, signal) {
|
|
3634
3926
|
const body = JSON.stringify({
|
|
@@ -3636,8 +3928,11 @@ function emitPause(token, signal) {
|
|
|
3636
3928
|
pauseRequest: signal.pauseRequest,
|
|
3637
3929
|
...signal.observability ? { observability: signal.observability } : {}
|
|
3638
3930
|
});
|
|
3639
|
-
|
|
3640
|
-
|
|
3931
|
+
const line = `${STEP_PAUSE_PREFIX}${token}:${body}
|
|
3932
|
+
`;
|
|
3933
|
+
persistSentinelFile(token, line);
|
|
3934
|
+
return writeSentinelLine(`
|
|
3935
|
+
${line}`);
|
|
3641
3936
|
}
|
|
3642
3937
|
function scrubProtocolEnvs() {
|
|
3643
3938
|
for (const key of [
|
|
@@ -3709,6 +4004,149 @@ async function serveStep(handler) {
|
|
|
3709
4004
|
import { z as z11 } from "zod";
|
|
3710
4005
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3711
4006
|
|
|
4007
|
+
// src/agent/agent-context.ts
|
|
4008
|
+
var AGENT_COMPOSE_MANUAL = `# Working inside an Agent Compose sandbox
|
|
4009
|
+
|
|
4010
|
+
You are an agent running in a per-run sandbox on the Agent Compose platform.
|
|
4011
|
+
Use the **\`agentc\` CLI** and the **\`@agent-compose/sdk\`** for everything below —
|
|
4012
|
+
do NOT hand-roll raw HTTP/curl calls against the platform API. The CLI is on
|
|
4013
|
+
your PATH and already authenticated from the environment
|
|
4014
|
+
(\`AGENT_COMPOSE_URL\` / \`AGENT_COMPOSE_API_KEY\` / \`AGENT_COMPOSE_FACTORY\` are
|
|
4015
|
+
injected for this run), so commands just work — no login, no keys to manage.
|
|
4016
|
+
|
|
4017
|
+
The \`/ac:*\` skills are installed as Claude Code slash commands (\`/ac:invoke\`,
|
|
4018
|
+
\`/ac:events\`, \`/ac:logs\`, \`/ac:register\`, …) — reach for them too.
|
|
4019
|
+
|
|
4020
|
+
## Files — your outputs persist by default
|
|
4021
|
+
|
|
4022
|
+
Your working directory defaults to **\`$AGENT_COMPOSE_RUN_DIR\`** — a per-run
|
|
4023
|
+
directory on the shared factory drive
|
|
4024
|
+
(\`$AGENT_COMPOSE_FACTORY_DIR/<workflow>/<version>/<run-id>/\`) the platform
|
|
4025
|
+
creates and attributes to this run. **Files you write here persist by
|
|
4026
|
+
default** — they show up in the dashboard's Files tab and the run's Artifacts
|
|
4027
|
+
card, with no API calls to save them. The dir already exists and is writable.
|
|
4028
|
+
|
|
4029
|
+
Need throwaway scratch — heavy build output, package caches, temp files?
|
|
4030
|
+
\`cd /tmp\` (or any path outside \`/factory\`): anything off the factory drive is
|
|
4031
|
+
ephemeral and discarded when the sandbox ends. In short: **stay in your working
|
|
4032
|
+
dir to keep something, \`cd\` out to throw it away.**
|
|
4033
|
+
|
|
4034
|
+
The whole shared drive is POSIX-mounted at \`/factory\`; the dashboard-visible
|
|
4035
|
+
root is \`$AGENT_COMPOSE_FACTORY_DIR\` (\`/factory/files\`). Earlier versions and
|
|
4036
|
+
runs live in sibling dirs under
|
|
4037
|
+
\`$AGENT_COMPOSE_FACTORY_DIR/$AGENT_COMPOSE_WORKFLOW/\` — read them for prior
|
|
4038
|
+
context. Other workflows' dirs are present but not your concern.
|
|
4039
|
+
|
|
4040
|
+
## Events — the factory timeline
|
|
4041
|
+
|
|
4042
|
+
Record something on the run/factory timeline (the dashboard renders these)
|
|
4043
|
+
with the CLI — your run id is \`$RUN_ID\`:
|
|
4044
|
+
|
|
4045
|
+
agentc events send "$RUN_ID" <name> --summary "<one line>" [--body '<json>']
|
|
4046
|
+
|
|
4047
|
+
Names like \`note.created\` / \`brief.posted\` surface in the Workbench;
|
|
4048
|
+
\`agentc events list\` reads them back. \`/ac:events\` is the skill equivalent.
|
|
4049
|
+
|
|
4050
|
+
## Runs
|
|
4051
|
+
|
|
4052
|
+
agentc list # registered workflows (/ac:list)
|
|
4053
|
+
agentc logs "$RUN_ID" # a run's logs (/ac:logs)
|
|
4054
|
+
agentc invoke <workflow> -i '<json>' # dispatch a workflow (/ac:invoke)
|
|
4055
|
+
|
|
4056
|
+
## Writing workflow / agent code — the SDK
|
|
4057
|
+
|
|
4058
|
+
\`@agent-compose/sdk\` is installed in \`/workspace\` — import it from any script
|
|
4059
|
+
you write there:
|
|
4060
|
+
|
|
4061
|
+
import { defineWorkflow, agent, AgentComposeClient } from "@agent-compose/sdk";
|
|
4062
|
+
|
|
4063
|
+
Use \`/ac:generate-workflow\` / \`/ac:generate-agent\` to scaffold, then
|
|
4064
|
+
\`agentc register <file.ts>\` (or \`/ac:register\`).
|
|
4065
|
+
|
|
4066
|
+
## Pausing to ask the human — \`agentc pause\`
|
|
4067
|
+
|
|
4068
|
+
When you can't or shouldn't proceed without a human, run \`agentc pause\`. It
|
|
4069
|
+
blocks until they answer on the dashboard, then prints their answer to stdout:
|
|
4070
|
+
|
|
4071
|
+
ANSWER=$(agentc pause --reason "Notion returned 401 — connect Notion to continue" \\
|
|
4072
|
+
--option retry --option skip)
|
|
4073
|
+
|
|
4074
|
+
Reach for it the moment you hit — or foresee — any of these:
|
|
4075
|
+
- **A wall only a human can clear:** a 401/403, a missing credential, an
|
|
4076
|
+
unconnected provider, a host the network refuses. Do NOT retry blindly or try
|
|
4077
|
+
to work around it — pause and say what needs enabling.
|
|
4078
|
+
- **A durable or outward-facing action that needs sign-off:** registering a
|
|
4079
|
+
workflow, deploying, sending email/messages, deleting or overwriting shared
|
|
4080
|
+
data, spending money. Prepare everything, then pause for approval BEFORE you
|
|
4081
|
+
commit it.
|
|
4082
|
+
- **A judgment call only the human can settle:** an under-specified request,
|
|
4083
|
+
several valid paths, a conflict with existing state, missing input only they have.
|
|
4084
|
+
|
|
4085
|
+
You compose the \`--reason\` (the ask) yourself; pass \`--option\` choices when
|
|
4086
|
+
there are clear ones, omit them for a free-form answer. Read the printed answer
|
|
4087
|
+
and act on it. Each agent pauses independently — pausing doesn't stop the others.
|
|
4088
|
+
|
|
4089
|
+
## Credentials
|
|
4090
|
+
|
|
4091
|
+
Connector credentials (Google, GitHub, …) are NEVER in your environment.
|
|
4092
|
+
They're injected at the network layer when you call an allowed host — make the
|
|
4093
|
+
request **without** an Authorization header and the platform adds it. Don't try
|
|
4094
|
+
to read or exfiltrate tokens; they aren't here. The "Connectors & access"
|
|
4095
|
+
section below (when present) lists exactly which providers this run can reach.
|
|
4096
|
+
|
|
4097
|
+
## Tools in this environment
|
|
4098
|
+
|
|
4099
|
+
- \`agentc\` — Agent Compose CLI (your primary interface; authed from env)
|
|
4100
|
+
- \`@agent-compose/sdk\` — installed in /workspace for writing workflows
|
|
4101
|
+
- \`/ac:*\` Claude Code skills — slash commands for the above
|
|
4102
|
+
- \`archil\` (factory drive), \`rtk\`, \`bun\`
|
|
4103
|
+
- A world-writable \`/workspace\` working directory`;
|
|
4104
|
+
function renderConnectorsSection(connectors) {
|
|
4105
|
+
if (connectors.length === 0)
|
|
4106
|
+
return "";
|
|
4107
|
+
const rows = connectors.map((c) => {
|
|
4108
|
+
const host = c.hosts?.length ? c.hosts.join(", ") : "(host set by the platform)";
|
|
4109
|
+
const verbs = c.methods?.length ? c.methods.join("/") : "any method";
|
|
4110
|
+
const paths = c.pathPrefixes?.length ? ` under ${c.pathPrefixes.join(", ")}` : "";
|
|
4111
|
+
const repo = c.repository ? ` — repo \`${c.repository}\` (${c.access ?? "read"})` : "";
|
|
4112
|
+
const why = c.scopes?.length ? `
|
|
4113
|
+
_scopes: ${c.scopes.join(", ")}_` : "";
|
|
4114
|
+
return `- **${c.name ?? c.provider}** → \`${host}\` — ${verbs}${paths}${repo}${why}`;
|
|
4115
|
+
});
|
|
4116
|
+
return `
|
|
4117
|
+
|
|
4118
|
+
## Connectors & access — what this run can reach
|
|
4119
|
+
|
|
4120
|
+
These providers are connected for this run. Call their APIs with plain
|
|
4121
|
+
fetch/SDKs and **no Authorization header** — the platform injects the
|
|
4122
|
+
credential at the network layer. Requests outside the listed method/path are
|
|
4123
|
+
refused (403) and the token withheld. Anything NOT listed is unreachable; if
|
|
4124
|
+
you need it, \`agentc pause\` and ask for it to be connected.
|
|
4125
|
+
|
|
4126
|
+
${rows.join(`
|
|
4127
|
+
`)}
|
|
4128
|
+
`;
|
|
4129
|
+
}
|
|
4130
|
+
function buildAgentContextDoc(env) {
|
|
4131
|
+
let connectors = [];
|
|
4132
|
+
const raw = env.AGENT_COMPOSE_CONNECTORS;
|
|
4133
|
+
if (raw) {
|
|
4134
|
+
try {
|
|
4135
|
+
const parsed = JSON.parse(raw);
|
|
4136
|
+
if (Array.isArray(parsed))
|
|
4137
|
+
connectors = parsed;
|
|
4138
|
+
} catch {}
|
|
4139
|
+
}
|
|
4140
|
+
return AGENT_COMPOSE_MANUAL + renderConnectorsSection(connectors);
|
|
4141
|
+
}
|
|
4142
|
+
async function writeAgentContext(args) {
|
|
4143
|
+
const doc = buildAgentContextDoc(args.env);
|
|
4144
|
+
const dir = args.cwd.replace(/\/+$/, "") || "/workspace";
|
|
4145
|
+
for (const name of ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]) {
|
|
4146
|
+
await args.sandbox.files.write(`${dir}/${name}`, doc);
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
|
|
3712
4150
|
// src/agent/protocol-suffix.md
|
|
3713
4151
|
var protocol_suffix_default = '## Status Signal\n\nWhen you have finished your work or are blocked, emit a `<status>` block at the end of your response:\n\n```json\n<status>\n{\n "summary": "one sentence describing what was done or what is blocking",\n "completed": ["each acceptance criterion that is now fully met"],\n "blockers": [],\n "changed_files": ["relative/path/to/file"],\n "tests_run": true,\n "exit_signal": true\n}\n</status>\n```\n\n**Field semantics:**\n- `summary`: one sentence — what was accomplished or what is blocking\n- `completed`: acceptance criteria items that are fully done — be specific\n- `blockers`: non-empty when `exit_signal: false` — describe the exact obstacle\n- `changed_files`: relative paths of files you created or modified\n- `tests_run`: `true` if you ran any test suite (pass or fail); `false` if no tests exist or you skipped them\n- `exit_signal: true` — set when ALL acceptance criteria are met and no blockers remain\n- `exit_signal: false` — set when blocked or unfinished; `blockers` must be non-empty\n\n**If you are still actively working** and have not reached a natural stopping point, do NOT emit a `<status>` block — just keep working.\n\n**Example (done):**\n\n```json\n<status>\n{\n "summary": "Added input validation middleware to /api/tasks with tests",\n "completed": ["POST /api/tasks validates required fields", "Returns 400 with details on invalid input", "Unit tests passing"],\n "blockers": [],\n "changed_files": ["src/middleware/validate.ts", "src/routes/tasks.ts", "tests/validate.test.ts"],\n "tests_run": true,\n "exit_signal": true\n}\n</status>\n```\n\n**Example (blocked):**\n\n```json\n<status>\n{\n "summary": "Implemented middleware but tests are failing due to module resolution",\n "completed": ["Middleware created and wired into route"],\n "blockers": ["Tests fail: cannot resolve import \'./validate\' — module resolution config unclear"],\n "changed_files": ["src/middleware/validate.ts"],\n "tests_run": true,\n "exit_signal": false\n}\n</status>\n```\n';
|
|
3714
4152
|
|
|
@@ -3824,7 +4262,28 @@ function resolveAgentId(explicitId) {
|
|
|
3824
4262
|
return `step${activeCall.stepIndex}-agent-${activeCall.callIndex}`;
|
|
3825
4263
|
}
|
|
3826
4264
|
async function agent(opts) {
|
|
3827
|
-
|
|
4265
|
+
let workingDir = opts.workingDir || process.env.AGENT_COMPOSE_RUN_DIR || "/workspace";
|
|
4266
|
+
const CONTEXT_WRITE_DEADLINE_MS = 15000;
|
|
4267
|
+
const tryWriteContext = (cwd) => {
|
|
4268
|
+
let timer;
|
|
4269
|
+
return Promise.race([
|
|
4270
|
+
writeAgentContext({ sandbox: opts.sandbox, cwd, env: process.env }).then(() => true),
|
|
4271
|
+
new Promise((resolve) => {
|
|
4272
|
+
timer = setTimeout(() => resolve(false), CONTEXT_WRITE_DEADLINE_MS);
|
|
4273
|
+
})
|
|
4274
|
+
]).catch((err) => {
|
|
4275
|
+
console.error(`[agent] writeAgentContext(${cwd}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4276
|
+
return false;
|
|
4277
|
+
}).finally(() => {
|
|
4278
|
+
if (timer)
|
|
4279
|
+
clearTimeout(timer);
|
|
4280
|
+
});
|
|
4281
|
+
};
|
|
4282
|
+
if (!await tryWriteContext(workingDir) && workingDir !== "/workspace") {
|
|
4283
|
+
console.error(`[agent] working dir ${workingDir} not writable within ${CONTEXT_WRITE_DEADLINE_MS}ms ` + `(factory drive degraded?) — falling back to /workspace so the agent can run`);
|
|
4284
|
+
workingDir = "/workspace";
|
|
4285
|
+
await tryWriteContext(workingDir);
|
|
4286
|
+
}
|
|
3828
4287
|
const agentId = resolveAgentId(opts.agentId);
|
|
3829
4288
|
const callbackUrl = process.env.AGENT_COMPOSE_URL;
|
|
3830
4289
|
const callbackToken = process.env.AGENT_COMPOSE_RUN_TOKEN;
|
|
@@ -3908,6 +4367,7 @@ async function agent(opts) {
|
|
|
3908
4367
|
}
|
|
3909
4368
|
export {
|
|
3910
4369
|
writeTool,
|
|
4370
|
+
writeAgentContext,
|
|
3911
4371
|
stepInputPath,
|
|
3912
4372
|
serveStep,
|
|
3913
4373
|
runWorkflowSteps,
|
|
@@ -3955,6 +4415,7 @@ export {
|
|
|
3955
4415
|
bundleWorkflow,
|
|
3956
4416
|
buildStepEnvs,
|
|
3957
4417
|
buildInvokeChild,
|
|
4418
|
+
buildAgentContextDoc,
|
|
3958
4419
|
bashTool,
|
|
3959
4420
|
assertDefaultExportIsDefineWorkflow,
|
|
3960
4421
|
amp_default as ampRuntime,
|
|
@@ -3969,9 +4430,11 @@ export {
|
|
|
3969
4430
|
TMP_DIR,
|
|
3970
4431
|
StepValidationError,
|
|
3971
4432
|
StepExecutionError,
|
|
4433
|
+
SandboxUnavailableError,
|
|
3972
4434
|
STEP_RESULT_PREFIX,
|
|
3973
4435
|
STEP_PAUSE_PREFIX,
|
|
3974
4436
|
STEP_ENV,
|
|
4437
|
+
SANDBOX_UNAVAILABLE_PREFIX,
|
|
3975
4438
|
ReservedKeyError,
|
|
3976
4439
|
RequestContext,
|
|
3977
4440
|
RUNNER_COMMAND,
|
|
@@ -3991,6 +4454,7 @@ export {
|
|
|
3991
4454
|
AgentComposeError,
|
|
3992
4455
|
AgentComposeClient,
|
|
3993
4456
|
AGENT_COMPOSE_TAG,
|
|
4457
|
+
AGENT_COMPOSE_MANUAL,
|
|
3994
4458
|
AC_WORKFLOW_ID,
|
|
3995
4459
|
AC_TEAM_ID,
|
|
3996
4460
|
AC_RUN_ID,
|