@agent-compose/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1114 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
+
20
+ // src/types/runtime.ts
21
+ function defineRuntime(pkg) {
22
+ if (typeof pkg.create !== "function")
23
+ throw new Error("defineRuntime: 'create' is required");
24
+ return pkg;
25
+ }
26
+ // src/types/workflow.ts
27
+ function defineWorkflow(def) {
28
+ return Object.assign(def.run, {
29
+ networkPolicy: def.networkPolicy,
30
+ placeholders: def.placeholders,
31
+ sandboxEnvironment: def.sandboxEnvironment,
32
+ snapshot: def.snapshot
33
+ });
34
+ }
35
+ // src/sandbox.ts
36
+ import { promises as fs } from "node:fs";
37
+ import { dirname } from "node:path";
38
+ import { spawn } from "node:child_process";
39
+ import { Sandbox } from "e2b";
40
+ import { Sandbox as Desktop } from "@e2b/desktop";
41
+ import pRetry from "p-retry";
42
+ var AGENT_COMPOSE_TAG = process.env.AGENT_COMPOSE_TAG ?? `agent-compose-${process.env.AGENT_COMPOSE_ENV ?? "dev"}`;
43
+ var VERCEL_VM_LIFETIME_WINDOW_MS = 6 * 60 * 60 * 1000;
44
+ function makeSandboxProvider(sb) {
45
+ return { sandboxId: sb.sandboxId, commands: sb.commands, files: sb.files, kill: () => sb.kill() };
46
+ }
47
+ function makeDesktopSandboxProvider(sb) {
48
+ return {
49
+ ...makeSandboxProvider(sb),
50
+ screenshot: () => sb.screenshot(),
51
+ leftClick: (x, y) => sb.leftClick(x, y),
52
+ doubleClick: (x, y) => sb.doubleClick(x, y),
53
+ rightClick: (x, y) => sb.rightClick(x, y),
54
+ middleClick: (x, y) => sb.middleClick(x, y),
55
+ moveMouse: (x, y) => sb.moveMouse(x, y),
56
+ write: (text) => sb.write(text),
57
+ press: (key) => sb.press(key),
58
+ scroll: (dir, ticks) => sb.scroll(dir, ticks),
59
+ drag: (from, to) => sb.drag(from, to)
60
+ };
61
+ }
62
+ async function parseSseExecStream(body, opts) {
63
+ let stdout = "", exitCode = 0, exited = false;
64
+ const reader = body.getReader(), decoder = new TextDecoder;
65
+ let buf = "";
66
+ while (true) {
67
+ const { done, value } = await reader.read();
68
+ if (done)
69
+ break;
70
+ buf += decoder.decode(value, { stream: true });
71
+ const lines = buf.replace(/\r\n/g, `
72
+ `).split(`
73
+ `);
74
+ buf = lines.pop() ?? "";
75
+ for (const line of lines) {
76
+ if (!line.startsWith("data: "))
77
+ continue;
78
+ let event;
79
+ try {
80
+ event = JSON.parse(line.slice(6));
81
+ } catch {
82
+ continue;
83
+ }
84
+ if (event.type === "stdout") {
85
+ stdout += event.data ?? "";
86
+ opts?.onStdout?.(event.data ?? "");
87
+ } else if (event.type === "stderr") {
88
+ opts?.onStderr?.(event.data ?? "");
89
+ } else if (event.type === "exit") {
90
+ exitCode = event.exitCode ?? 0;
91
+ exited = true;
92
+ } else if (event.type === "error") {
93
+ throw new Error(`Sandbox exec error: ${event.data}`);
94
+ }
95
+ }
96
+ if (exited)
97
+ break;
98
+ }
99
+ return { exitCode, stdout };
100
+ }
101
+ function makeVercelSandboxProvider(sb, globalEnvs) {
102
+ const mergeEnvs = (cmdEnvs) => globalEnvs ? { ...globalEnvs, ...cmdEnvs } : cmdEnvs;
103
+ return {
104
+ sandboxId: sb.sandboxId,
105
+ commands: {
106
+ async run(cmd, opts) {
107
+ if (opts?.background) {
108
+ sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts.cwd, env: mergeEnvs(opts.envs), detached: true }).catch(() => {});
109
+ return { exitCode: 0, stdout: "" };
110
+ }
111
+ const signal = opts?.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined;
112
+ let stdout = "";
113
+ const handle = await sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts?.cwd, env: mergeEnvs(opts?.envs), detached: true, signal });
114
+ await pRetry(async (attempt) => {
115
+ const h = attempt === 1 ? handle : await sb.getCommand(handle.cmdId);
116
+ for await (const log of h.logs()) {
117
+ if (log.stream === "stdout") {
118
+ stdout += log.data;
119
+ opts?.onStdout?.(log.data);
120
+ } else {
121
+ opts?.onStderr?.(log.data);
122
+ }
123
+ }
124
+ }, { retries: 3, minTimeout: 1000, factor: 2 });
125
+ const finished = await handle.wait();
126
+ return { exitCode: finished.exitCode, stdout };
127
+ }
128
+ },
129
+ files: {
130
+ async write(path, content) {
131
+ await sb.writeFiles([{ path, content }]);
132
+ }
133
+ },
134
+ async kill() {
135
+ await sb.stop();
136
+ },
137
+ async snapshot() {
138
+ const res = await sb.snapshot({ expiration: 0 });
139
+ return { snapshotId: res.snapshotId };
140
+ }
141
+ };
142
+ }
143
+ function makeLocalSandboxProvider() {
144
+ return {
145
+ sandboxId: "local",
146
+ commands: {
147
+ run(cmd, opts) {
148
+ return new Promise((resolve, reject) => {
149
+ const proc = spawn("sh", ["-c", cmd], {
150
+ ...opts?.cwd ? { cwd: opts.cwd } : {},
151
+ env: { ...process.env, ...opts?.envs ?? {} },
152
+ stdio: ["ignore", "pipe", "pipe"]
153
+ });
154
+ let stdout = "";
155
+ proc.stdout?.setEncoding("utf8");
156
+ proc.stderr?.setEncoding("utf8");
157
+ proc.stdout?.on("data", (chunk) => {
158
+ stdout += chunk;
159
+ opts?.onStdout?.(chunk);
160
+ });
161
+ proc.stderr?.on("data", (chunk) => {
162
+ opts?.onStderr?.(chunk);
163
+ });
164
+ proc.on("error", reject);
165
+ proc.on("close", (code) => resolve({ exitCode: code ?? 0, stdout }));
166
+ });
167
+ }
168
+ },
169
+ files: {
170
+ async write(path, content) {
171
+ await fs.mkdir(dirname(path), { recursive: true });
172
+ await fs.writeFile(path, content);
173
+ }
174
+ },
175
+ async kill() {}
176
+ };
177
+ }
178
+ var SANDBOX_PROVIDERS = {
179
+ vercel: {
180
+ requiredEnv: {
181
+ VERCEL_ACCESS_TOKEN: "Vercel access token — vercel.com/account/tokens",
182
+ VERCEL_TEAM_ID: "Vercel team ID — vercel.com/account/settings",
183
+ VERCEL_PROJECT_ID: "Vercel project ID — vercel.com/[team]/[project]/settings"
184
+ },
185
+ create: async (opts, env) => {
186
+ const { Sandbox: VercelSandbox } = await import("@vercel/sandbox");
187
+ const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
188
+ const np = opts.networkPolicy;
189
+ const sb = await VercelSandbox.create(opts.template ? { source: { type: "snapshot", snapshotId: opts.template }, timeout: opts.timeoutMs, env: opts.envs, ...np ? { networkPolicy: np } : {}, ...creds } : { runtime: "node24", timeout: opts.timeoutMs, env: opts.envs, ...np ? { networkPolicy: np } : {}, ...creds });
190
+ return makeVercelSandboxProvider(sb, opts.envs);
191
+ },
192
+ reconnect: async (sandboxId) => {
193
+ const { Sandbox: VercelSandbox } = await import("@vercel/sandbox");
194
+ const creds = {
195
+ token: process.env.VERCEL_ACCESS_TOKEN ?? "",
196
+ teamId: process.env.VERCEL_TEAM_ID ?? "",
197
+ projectId: process.env.VERCEL_PROJECT_ID ?? ""
198
+ };
199
+ return makeVercelSandboxProvider(await VercelSandbox.get({ sandboxId, ...creds }));
200
+ },
201
+ getActiveCount: async (env) => (await SANDBOX_PROVIDERS.vercel.listOwned(env)).length,
202
+ listOwned: async (env) => {
203
+ const { Sandbox: VercelSandbox } = await import("@vercel/sandbox");
204
+ const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
205
+ const since = Date.now() - VERCEL_VM_LIFETIME_WINDOW_MS;
206
+ const out = [];
207
+ let until;
208
+ for (let page = 0;page < 50; page++) {
209
+ const { json } = await pRetry(() => VercelSandbox.list({ ...creds, limit: 200, since, ...until !== undefined ? { until } : {} }), { retries: 3, minTimeout: 500, factor: 2 });
210
+ for (const sb of json.sandboxes)
211
+ if (sb.status === "running") {
212
+ out.push({ sandboxId: sb.id, createdAt: new Date(sb.createdAt), metadata: {} });
213
+ }
214
+ if (json.pagination.next === null)
215
+ return out;
216
+ until = json.pagination.next;
217
+ }
218
+ throw new Error("Vercel listSandboxes exceeded 50 pages within the lifetime window — refuse to silently truncate");
219
+ },
220
+ deleteSnapshot: async (snapshotId, env) => {
221
+ const { Snapshot } = await import("@vercel/sandbox");
222
+ const creds = { token: env.VERCEL_ACCESS_TOKEN, teamId: env.VERCEL_TEAM_ID, projectId: env.VERCEL_PROJECT_ID };
223
+ const snap = await Snapshot.get({ snapshotId, ...creds });
224
+ await snap.delete();
225
+ }
226
+ },
227
+ e2b: {
228
+ requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
229
+ create: async ({ template, ...sandboxOpts }) => {
230
+ if (!template)
231
+ throw new Error("E2B provider requires an explicit `template` (Dockerfile-based — no default base image)");
232
+ return makeSandboxProvider(await Sandbox.create(template, sandboxOpts));
233
+ },
234
+ reconnect: async (sandboxId) => makeSandboxProvider(await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY ?? "", timeoutMs: 3600000 })),
235
+ killAll: async () => {
236
+ const paginator = Sandbox.list({ query: { metadata: { executor: AGENT_COMPOSE_TAG } } });
237
+ const sandboxes = [];
238
+ while (paginator.hasNext)
239
+ sandboxes.push(...await paginator.nextItems());
240
+ if (sandboxes.length === 0)
241
+ return;
242
+ await Promise.all(sandboxes.map((s) => Sandbox.kill(s.sandboxId).catch(() => {})));
243
+ console.info(`[sandbox] killed ${sandboxes.length} stale E2B sandboxes (tag: ${AGENT_COMPOSE_TAG})`);
244
+ },
245
+ getActiveCount: async () => (await SANDBOX_PROVIDERS.e2b.listOwned({})).length,
246
+ listOwned: async () => {
247
+ const paginator = Sandbox.list({ query: { metadata: { executor: AGENT_COMPOSE_TAG }, state: ["running"] } });
248
+ const out = [];
249
+ while (paginator.hasNext) {
250
+ const items = await pRetry(() => paginator.nextItems(), { retries: 3, minTimeout: 500, factor: 2 });
251
+ for (const sb of items) {
252
+ out.push({
253
+ sandboxId: sb.sandboxId,
254
+ createdAt: new Date(sb.startedAt),
255
+ metadata: sb.metadata ?? {}
256
+ });
257
+ }
258
+ }
259
+ return out;
260
+ }
261
+ },
262
+ "e2b-desktop": {
263
+ requiredEnv: { E2B_API_KEY: "E2B API key — e2b.dev/dashboard" },
264
+ create: async ({ template, ...sandboxOpts }) => {
265
+ if (!template)
266
+ throw new Error("E2B Desktop provider requires an explicit `template` (Dockerfile-based — no default base image)");
267
+ return makeDesktopSandboxProvider(await Desktop.create(template, sandboxOpts));
268
+ }
269
+ }
270
+ };
271
+ async function createSandbox(provider, opts) {
272
+ const def = SANDBOX_PROVIDERS[provider];
273
+ if (!def)
274
+ throw new Error(`Unknown sandbox provider: "${provider}". Known: ${Object.keys(SANDBOX_PROVIDERS).join(", ")}`);
275
+ const missing = Object.entries(def.requiredEnv).filter(([key]) => !process.env[key]).map(([key, desc]) => ` ${key} — ${desc}`);
276
+ if (missing.length > 0)
277
+ throw new Error(`Sandbox provider "${provider}" requires env vars:
278
+ ${missing.join(`
279
+ `)}`);
280
+ return def.create(opts, Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k]])));
281
+ }
282
+ async function reconnectSandbox(provider, sandboxId) {
283
+ const def = SANDBOX_PROVIDERS[provider];
284
+ if (!def?.reconnect)
285
+ throw new Error(`Provider "${provider}" does not support reconnect`);
286
+ return def.reconnect(sandboxId);
287
+ }
288
+ async function deleteSandboxSnapshot(provider, snapshotId) {
289
+ const def = SANDBOX_PROVIDERS[provider];
290
+ if (!def?.deleteSnapshot)
291
+ throw new Error(`Provider "${provider}" does not support deleteSnapshot`);
292
+ const missing = Object.entries(def.requiredEnv).filter(([k]) => !process.env[k]).map(([k, desc]) => ` ${k} — ${desc}`);
293
+ if (missing.length > 0)
294
+ throw new Error(`Sandbox provider "${provider}" requires env vars:
295
+ ${missing.join(`
296
+ `)}`);
297
+ return def.deleteSnapshot(snapshotId, Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k]])));
298
+ }
299
+ async function getSandboxQuotas() {
300
+ const out = {};
301
+ await Promise.all(Object.entries(SANDBOX_PROVIDERS).map(async ([name, def]) => {
302
+ if (!def.getActiveCount)
303
+ return;
304
+ if (Object.keys(def.requiredEnv).some((k) => !process.env[k]))
305
+ return;
306
+ const env = Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k]]));
307
+ try {
308
+ out[name] = await def.getActiveCount(env);
309
+ } catch (err) {
310
+ out[name] = err instanceof Error ? err : new Error(String(err));
311
+ }
312
+ }));
313
+ return out;
314
+ }
315
+ async function listOwnedSandboxes() {
316
+ const out = {};
317
+ await Promise.all(Object.entries(SANDBOX_PROVIDERS).map(async ([name, def]) => {
318
+ if (!def.listOwned)
319
+ return;
320
+ if (Object.keys(def.requiredEnv).some((k) => !process.env[k]))
321
+ return;
322
+ const env = Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k]]));
323
+ try {
324
+ out[name] = await def.listOwned(env);
325
+ } catch (err) {
326
+ out[name] = err instanceof Error ? err : new Error(String(err));
327
+ }
328
+ }));
329
+ return out;
330
+ }
331
+ async function killSandboxById(provider, sandboxId) {
332
+ const def = SANDBOX_PROVIDERS[provider];
333
+ if (!def?.reconnect)
334
+ throw new Error(`Provider "${provider}" does not support reconnect (required for kill-by-id)`);
335
+ const sb = await def.reconnect(sandboxId);
336
+ await sb.kill();
337
+ }
338
+ async function killAllSandboxes(onError) {
339
+ await Promise.allSettled(Object.entries(SANDBOX_PROVIDERS).filter(([, def]) => def.killAll).map(([name, def]) => {
340
+ const env = Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k] ?? ""]));
341
+ if (Object.keys(def.requiredEnv).some((k) => !process.env[k]))
342
+ return Promise.resolve();
343
+ return def.killAll(env).catch((err) => onError?.(name, err));
344
+ }));
345
+ }
346
+
347
+ // src/types/sandbox-environment.ts
348
+ function defineSandboxEnvironment(env) {
349
+ if (typeof env.setup !== "function") {
350
+ throw new Error(`defineSandboxEnvironment(${env.name}): 'setup' must be a function`);
351
+ }
352
+ return defineWorkflow({
353
+ snapshot: env.snapshot ?? true,
354
+ run: () => env.setup(makeLocalSandboxProvider())
355
+ });
356
+ }
357
+ // src/client.ts
358
+ import { ofetch } from "ofetch";
359
+
360
+ // src/errors.ts
361
+ class AgentComposeError extends Error {
362
+ status;
363
+ constructor(status, message) {
364
+ super(message);
365
+ this.status = status;
366
+ this.name = "AgentComposeError";
367
+ }
368
+ }
369
+
370
+ // src/client.ts
371
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
372
+ function detectAmbientParentRunId() {
373
+ const envRunId = typeof process !== "undefined" ? process.env?.RUN_ID : undefined;
374
+ return envRunId && UUID_REGEX.test(envRunId) ? envRunId : null;
375
+ }
376
+
377
+ class AgentComposeClient {
378
+ fetch;
379
+ constructor(baseUrl, apiKey) {
380
+ this.fetch = ofetch.create({
381
+ baseURL: baseUrl.replace(/\/$/, ""),
382
+ headers: { Authorization: `Bearer ${apiKey}` },
383
+ async onResponseError({ response }) {
384
+ const body = response._data;
385
+ throw new AgentComposeError(response.status, body?.error ?? response.statusText);
386
+ }
387
+ });
388
+ }
389
+ register(payload) {
390
+ return this.fetch("/api/v1/templates", { method: "POST", body: payload });
391
+ }
392
+ invoke(name, input, opts) {
393
+ const parentRunId = opts?.parentRunId === undefined ? detectAmbientParentRunId() : opts.parentRunId;
394
+ return this.fetch(`/api/v1/templates/${name}/invoke`, {
395
+ method: "POST",
396
+ body: {
397
+ input,
398
+ ...opts?.snapshot !== undefined ? { snapshot: opts.snapshot } : {},
399
+ ...parentRunId ? { parentRunId } : {}
400
+ }
401
+ });
402
+ }
403
+ async invokeAndWait(name, input, opts) {
404
+ const timeoutMs = opts?.timeoutMs ?? 30 * 60 * 1000;
405
+ const pollMs = opts?.pollIntervalMs ?? 1000;
406
+ const { id: runId } = await this.invoke(name, input, {
407
+ ...opts?.snapshot !== undefined ? { snapshot: opts.snapshot } : {},
408
+ ...opts?.parentRunId !== undefined ? { parentRunId: opts.parentRunId } : {}
409
+ });
410
+ const deadline = Date.now() + timeoutMs;
411
+ while (Date.now() < deadline) {
412
+ const status = await this.getStatus(runId);
413
+ if (status.status === "success" || status.status === "failed" || status.status === "abandoned") {
414
+ return status;
415
+ }
416
+ await new Promise((r) => setTimeout(r, pollMs));
417
+ }
418
+ throw new AgentComposeError(504, `invokeAndWait: run ${runId} did not settle within ${timeoutMs}ms`);
419
+ }
420
+ async listSnapshots(opts) {
421
+ const q = new URLSearchParams;
422
+ if (opts?.workflow)
423
+ q.set("workflow", opts.workflow);
424
+ if (opts?.limit != null)
425
+ q.set("limit", String(opts.limit));
426
+ const body = await this.fetch(`/api/v1/snapshots${q.toString() ? `?${q}` : ""}`);
427
+ return body.data;
428
+ }
429
+ deleteSnapshot(runId) {
430
+ return this.fetch(`/api/v1/workflows/${runId}/snapshot`, { method: "DELETE" });
431
+ }
432
+ getStatus(runId) {
433
+ return this.fetch(`/api/v1/workflows/${runId}/status`);
434
+ }
435
+ async listTemplates() {
436
+ const body = await this.fetch("/api/v1/templates");
437
+ return body.templates;
438
+ }
439
+ setSecret(workflowName, key, value) {
440
+ return this.fetch(`/api/v1/templates/${workflowName}/secrets`, { method: "POST", body: { key, value } });
441
+ }
442
+ async listSecrets(workflowName) {
443
+ const body = await this.fetch(`/api/v1/templates/${workflowName}/secrets`);
444
+ return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
445
+ }
446
+ deleteSecret(workflowName, key) {
447
+ return this.fetch(`/api/v1/templates/${workflowName}/secrets/${key}`, { method: "DELETE" });
448
+ }
449
+ }
450
+ // src/utils/errors.ts
451
+ function formatError(err) {
452
+ return err instanceof Error ? err.message : String(err);
453
+ }
454
+ // src/utils/discovery.ts
455
+ function discoverRuntimeName(source) {
456
+ return source.match(/runtime:\s*["']([^"']+)["']/)?.[1] ?? null;
457
+ }
458
+ // src/utils/source-loader.ts
459
+ import { dirname as dirname2 } from "path";
460
+ import { writeFile, unlink, mkdir } from "node:fs/promises";
461
+ var TMP_DIR = process.env.TMP_DIR ?? "/tmp/agent-compose";
462
+ var LATEST_VERSION = "$LATEST";
463
+ async function importSourceModule(source, tmpPath) {
464
+ await mkdir(dirname2(tmpPath), { recursive: true });
465
+ await writeFile(tmpPath, source, "utf8");
466
+ const mod = await import(tmpPath);
467
+ const cleanup = () => unlink(tmpPath).catch(() => {});
468
+ return { mod, cleanup };
469
+ }
470
+
471
+ // src/utils/bundler.ts
472
+ async function bundle(path, label) {
473
+ const bun = globalThis;
474
+ if (!bun.Bun?.build)
475
+ throw new Error("bundleWorkflow requires the Bun runtime (Bun.build)");
476
+ const result = await bun.Bun.build({ entrypoints: [path], format: "esm", target: "bun" });
477
+ if (!result.success) {
478
+ throw new Error(`Failed to bundle ${label}:
479
+ ${result.logs.map((l) => l.message).join(`
480
+ `)}`);
481
+ }
482
+ return result.outputs[0].text();
483
+ }
484
+ async function extractFromBundle(source, label, pick) {
485
+ const tmpPath = `/tmp/agentc-${label}-${Date.now()}-${Math.random().toString(36).slice(2)}.js`;
486
+ try {
487
+ const loaded = await importSourceModule(source, tmpPath);
488
+ try {
489
+ return pick(loaded.mod.default);
490
+ } finally {
491
+ await loaded.cleanup();
492
+ }
493
+ } catch {
494
+ return;
495
+ }
496
+ }
497
+ async function bundleWorkflow(workflowPath, overrides) {
498
+ const source = await bundle(workflowPath, `workflow "${workflowPath}"`);
499
+ const wfBits = await extractFromBundle(source, "workflow", (wf) => wf ? {
500
+ networkPolicy: wf.networkPolicy,
501
+ placeholders: wf.placeholders,
502
+ sandboxEnvironment: wf.sandboxEnvironment,
503
+ snapshot: wf.snapshot
504
+ } : undefined);
505
+ const { networkPolicy, placeholders, sandboxEnvironment, snapshot } = wfBits ?? {};
506
+ return {
507
+ source,
508
+ networkPolicy: overrides?.networkPolicy ?? networkPolicy,
509
+ placeholders: overrides?.placeholders ?? placeholders,
510
+ ...sandboxEnvironment ? { sandboxEnvironment } : {},
511
+ ...snapshot !== undefined ? { snapshot } : {}
512
+ };
513
+ }
514
+ // src/utils/schemas.ts
515
+ import { z } from "zod";
516
+ var AgentStatusSchema = z.object({
517
+ summary: z.string(),
518
+ completed: z.array(z.string()),
519
+ blockers: z.array(z.string()),
520
+ exit_signal: z.boolean()
521
+ });
522
+ // src/agent/protocol.ts
523
+ import { z as z2 } from "zod";
524
+ var AgentMessageSchema = z2.object({
525
+ type: z2.enum(["init", "text", "thinking", "tool_use", "tool_result", "done", "error", "usage"]),
526
+ timestamp: z2.string()
527
+ }).passthrough();
528
+ function parseAgentResponse(text) {
529
+ const match = text.match(/<response>([\s\S]*?)<\/response>/);
530
+ if (!match)
531
+ return null;
532
+ try {
533
+ return JSON.parse(match[1].trim());
534
+ } catch {
535
+ return null;
536
+ }
537
+ }
538
+
539
+ // src/agent/agent-loop.ts
540
+ var DEFAULT_CLAUDE_MODEL = "claude-opus-4-7";
541
+ var SAME_BLOCKER_ITERATIONS = 3;
542
+ var STALL_ITERATIONS = 3;
543
+ function parseAgentStatus(text) {
544
+ const match = text.match(/<status>([\s\S]*?)<\/status>/);
545
+ if (!match)
546
+ return null;
547
+ try {
548
+ const result = AgentStatusSchema.safeParse(JSON.parse(match[1].trim()));
549
+ return result.success ? result.data : null;
550
+ } catch {
551
+ return null;
552
+ }
553
+ }
554
+ var DEFAULT_ALLOWED_TOOLS = ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebFetch"];
555
+ async function agentLoop(opts) {
556
+ const label = opts.label ?? "[Agent Loop]";
557
+ const turnsPerIteration = opts.turnsPerIteration ?? 40;
558
+ const maxIterations = opts.maxIterations ?? 8;
559
+ if (!opts.runtime)
560
+ throw new Error("agentLoop: opts.runtime is required");
561
+ const client = opts.runtime({
562
+ maxTurns: turnsPerIteration,
563
+ allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS,
564
+ label,
565
+ cwd: opts.cwd
566
+ });
567
+ let lastSessionId = "";
568
+ let lastStatus = null;
569
+ let iterationsWithoutStatus = 0;
570
+ let blockerStreak = null;
571
+ for (let iteration = 0;iteration < maxIterations; iteration++) {
572
+ const prompt = opts.buildPrompt(lastStatus, iteration);
573
+ process.stderr.write(`${label} iteration ${iteration + 1}/${maxIterations} · ${turnsPerIteration} turns
574
+ `);
575
+ let responseText = "";
576
+ for await (const msg of client.sendMessage({ prompt, sessionId: iteration > 0 ? lastSessionId : undefined })) {
577
+ opts.onAgentEvent?.(iteration, msg);
578
+ if (msg.type === "init")
579
+ lastSessionId = msg.sessionId;
580
+ if (msg.type === "text")
581
+ responseText += msg.text;
582
+ if (msg.type === "error")
583
+ throw new Error(`Agent error: ${msg.text}`);
584
+ }
585
+ process.stdout.write(`
586
+ `);
587
+ let status = parseAgentStatus(responseText);
588
+ process.stderr.write(`
589
+ ${label} iteration ${iteration + 1} status: exit_signal=${status?.exit_signal ?? "(no status)"} blockers=${JSON.stringify(status?.blockers ?? [])}
590
+ `);
591
+ lastStatus = status ?? lastStatus;
592
+ opts.onIteration?.(iteration + 1, status);
593
+ if (status?.exit_signal && (status.blockers?.length ?? 0) === 0) {
594
+ let response = status;
595
+ if (opts.responseSchema) {
596
+ const raw = parseAgentResponse(responseText);
597
+ if (raw === null) {
598
+ process.stderr.write(`
599
+ ${label} NO <response> BLOCK — response tail: ${responseText.slice(-400)}
600
+ `);
601
+ status = { ...status, exit_signal: false, blockers: ["No <response> block found — emit a <response> block with the required JSON fields before setting exit_signal: true"] };
602
+ opts.onIteration?.(iteration + 1, status);
603
+ continue;
604
+ }
605
+ const parsed = opts.responseSchema.safeParse({ ...status, ...raw });
606
+ if (!parsed.success) {
607
+ process.stderr.write(`
608
+ ${label} <response> SCHEMA FAILED: ${parsed.error.message}
609
+ raw: ${JSON.stringify(raw).slice(0, 400)}
610
+ `);
611
+ status = { ...status, exit_signal: false, blockers: [`<response> schema validation failed: ${parsed.error.message}`] };
612
+ opts.onIteration?.(iteration + 1, status);
613
+ continue;
614
+ }
615
+ response = parsed.data;
616
+ }
617
+ process.stderr.write(`${label} done after ${iteration + 1}/${maxIterations} iterations
618
+ `);
619
+ return { sessionId: lastSessionId, lastStatus: status, iterations: iteration + 1, response };
620
+ }
621
+ if (!status) {
622
+ if (++iterationsWithoutStatus >= STALL_ITERATIONS)
623
+ throw new Error(`${label} stalled: no <status> block after ${iterationsWithoutStatus} iterations`);
624
+ } else {
625
+ iterationsWithoutStatus = 0;
626
+ }
627
+ if (status?.blockers?.length) {
628
+ const key = status.blockers.join("|");
629
+ if (blockerStreak !== null && blockerStreak.key === key) {
630
+ if (++blockerStreak.count >= SAME_BLOCKER_ITERATIONS)
631
+ throw new Error(`${label} circuit break: same blocker repeated ${blockerStreak.count}x — "${status.blockers[0]}"`);
632
+ } else {
633
+ blockerStreak = { key, count: 1 };
634
+ }
635
+ } else {
636
+ blockerStreak = null;
637
+ }
638
+ if (iteration + 1 < maxIterations)
639
+ process.stderr.write(`${label} continuing to iteration ${iteration + 2}/${maxIterations}
640
+ `);
641
+ }
642
+ if (opts.responseSchema)
643
+ throw new Error(`${label} did not produce a valid <response> after ${maxIterations} iterations`);
644
+ process.stderr.write(`${label} exhausted ${maxIterations} iterations, proceeding with available work
645
+ `);
646
+ return { sessionId: lastSessionId, lastStatus, iterations: maxIterations };
647
+ }
648
+
649
+ // src/runtimes/claude.ts
650
+ var NO_TIMEOUT = 0;
651
+ function translateEvent(event) {
652
+ const msgs = [];
653
+ const ts = new Date().toISOString();
654
+ if (event.type === "system" && event.subtype === "init") {
655
+ msgs.push({ type: "init", sessionId: String(event.session_id ?? ""), timestamp: ts });
656
+ return msgs;
657
+ }
658
+ if (event.type === "assistant") {
659
+ const message = event.message;
660
+ for (const block of message?.content ?? []) {
661
+ const b = block;
662
+ if (b.type === "text")
663
+ msgs.push({ type: "text", text: String(b.text ?? ""), timestamp: ts });
664
+ if (b.type === "thinking")
665
+ msgs.push({ type: "thinking", text: String(b.thinking ?? ""), timestamp: ts });
666
+ if (b.type === "tool_use")
667
+ msgs.push({ type: "tool_use", toolName: String(b.name ?? ""), toolInput: b.input ?? {}, toolUseId: String(b.id ?? ""), timestamp: ts });
668
+ }
669
+ return msgs;
670
+ }
671
+ if (event.type === "user") {
672
+ const message = event.message;
673
+ for (const block of message?.content ?? []) {
674
+ const b = block;
675
+ if (b.type === "tool_result") {
676
+ const raw = b.content;
677
+ msgs.push({ type: "tool_result", toolUseId: String(b.tool_use_id ?? ""), output: typeof raw === "string" ? raw : JSON.stringify(raw ?? ""), isError: Boolean(b.is_error), timestamp: ts });
678
+ }
679
+ }
680
+ return msgs;
681
+ }
682
+ if (event.type === "result") {
683
+ const usage = event.usage;
684
+ if (usage) {
685
+ msgs.push({ type: "usage", inputTokens: usage.input_tokens ?? 0, outputTokens: usage.output_tokens ?? 0, cacheReadTokens: usage.cache_read_input_tokens ?? 0, cacheCreationTokens: usage.cache_creation_input_tokens ?? 0, durationMs: Number(event.duration_ms ?? 0), numTurns: Number(event.num_turns ?? 0), timestamp: ts });
686
+ }
687
+ if (event.is_error || event.subtype === "error_during_execution") {
688
+ const raw = event.error ?? event.result ?? event.message;
689
+ msgs.push({ type: "error", text: raw == null ? "Unknown error" : typeof raw === "object" ? JSON.stringify(raw) : String(raw), timestamp: ts });
690
+ } else {
691
+ msgs.push({ type: "done", sessionId: String(event.session_id ?? ""), timestamp: ts });
692
+ }
693
+ return msgs;
694
+ }
695
+ return msgs;
696
+ }
697
+
698
+ class ClaudeRunner {
699
+ sandbox;
700
+ options;
701
+ claudeMdContent;
702
+ configEnv;
703
+ configModel;
704
+ configMcpServers;
705
+ claudeMdWritten = false;
706
+ homeDir = null;
707
+ label;
708
+ constructor(sandbox, options = {}, claudeMdContent = "", configEnv = {}, configModel, configMcpServers) {
709
+ this.sandbox = sandbox;
710
+ this.options = options;
711
+ this.claudeMdContent = claudeMdContent;
712
+ this.configEnv = configEnv;
713
+ this.configModel = configModel;
714
+ this.configMcpServers = configMcpServers;
715
+ this.label = options.label ?? "[Agent]";
716
+ }
717
+ async getHomeDir() {
718
+ if (!this.homeDir) {
719
+ const { stdout } = await this.sandbox.commands.run("echo $HOME", { timeoutMs: 30000 });
720
+ this.homeDir = stdout.trim() || "/home/user";
721
+ }
722
+ return this.homeDir;
723
+ }
724
+ async deployGlobalConfig() {
725
+ if (this.claudeMdWritten)
726
+ return;
727
+ const home = await this.getHomeDir();
728
+ await this.sandbox.commands.run(`mkdir -p "${home}/.claude" && find "${home}/.claude" -name "*.json" -not -name "CLAUDE.md" -delete 2>/dev/null || true`, { timeoutMs: 5000 });
729
+ await this.sandbox.files.write(`${home}/.claude/CLAUDE.md`, this.claudeMdContent);
730
+ if (Object.keys(this.configEnv).length) {
731
+ const settings = JSON.stringify({ env: this.configEnv });
732
+ const b64 = Buffer.from(settings).toString("base64");
733
+ await this.sandbox.commands.run(`echo "${b64}" | base64 -d > "${home}/.claude/settings.json"`, { timeoutMs: 5000 });
734
+ }
735
+ const mcpServers = this.configMcpServers;
736
+ if (mcpServers && Object.keys(mcpServers).length) {
737
+ const b64 = Buffer.from(JSON.stringify({ mcpServers })).toString("base64");
738
+ await this.sandbox.commands.run(`echo "${b64}" | base64 -d > "${home}/.claude.json"`, { timeoutMs: 5000 });
739
+ process.stderr.write(`${this.label}[mcp] wrote ${Object.keys(mcpServers).join(",")} to ~/.claude.json
740
+ `);
741
+ }
742
+ const placeholderB64 = Buffer.from("x-access-token:placeholder").toString("base64");
743
+ await this.sandbox.commands.run([
744
+ `git config --global core.askPass ""`,
745
+ `git config --global credential.interactive false`,
746
+ `git config --global credential.helper ""`,
747
+ `git config --global "http.https://github.com/.extraHeader" "Authorization: Basic ${placeholderB64}"`
748
+ ].join(" && "), { timeoutMs: 5000 }).catch((e) => console.warn(`${this.label}[init] git config failed (non-fatal): ${formatError(e)}`));
749
+ try {
750
+ await this.sandbox.commands.run("rtk init --global --hook-only --auto-patch", { timeoutMs: 1e4 });
751
+ } catch {
752
+ console.warn("[Transport] rtk init failed (non-fatal) — token compression disabled");
753
+ }
754
+ this.claudeMdWritten = true;
755
+ }
756
+ async* _runCli(cmd, label, signal) {
757
+ const queue = [];
758
+ let notify = null;
759
+ function enqueue(items) {
760
+ queue.push(...items);
761
+ notify?.();
762
+ notify = null;
763
+ }
764
+ let lineBuffer = "";
765
+ let stderrBuffer = "";
766
+ const maxTurns = this.options.maxTurns ?? 40;
767
+ function handleStdout(data) {
768
+ lineBuffer += data;
769
+ const lines = lineBuffer.split(`
770
+ `);
771
+ lineBuffer = lines.pop() ?? "";
772
+ for (const line of lines) {
773
+ const trimmed = line.trim();
774
+ if (!trimmed)
775
+ continue;
776
+ try {
777
+ const event = JSON.parse(trimmed);
778
+ if (event.type === "assistant") {
779
+ const content = event.message?.content ?? [];
780
+ for (const block of content) {
781
+ const b = block;
782
+ if (b.type === "text")
783
+ process.stdout.write(".");
784
+ if (b.type === "tool_use")
785
+ process.stderr.write(`
786
+ ${label} ${b.name}(${JSON.stringify(b.input).slice(0, 80)})`);
787
+ }
788
+ } else if (event.type === "result") {
789
+ process.stderr.write(`
790
+ ${label}[result:${event.num_turns}/${maxTurns}/${event.subtype}]
791
+ `);
792
+ } else if (event.type === "system") {
793
+ const ev = event;
794
+ const mcpSrvs = ev.mcp_servers ?? [];
795
+ const mcpStr = mcpSrvs.length ? ` mcp=[${mcpSrvs.map((s) => `${s.name}:${s.status}`).join(",")}]` : " mcp=[]";
796
+ process.stderr.write(`
797
+ ${label}[init:${ev.session_id}]${mcpStr}
798
+ `);
799
+ }
800
+ enqueue(translateEvent(event));
801
+ } catch {}
802
+ }
803
+ }
804
+ process.stderr.write(`${label}[cwd:${this.options.cwd ?? "(none)"}]
805
+ `);
806
+ const runPromise = this.sandbox.commands.run(cmd, {
807
+ cwd: this.options.cwd,
808
+ timeoutMs: NO_TIMEOUT,
809
+ onStdout: handleStdout,
810
+ onStderr: (data) => {
811
+ stderrBuffer += data;
812
+ process.stderr.write(`${label}[stderr] ${data.trimEnd()}
813
+ `);
814
+ }
815
+ });
816
+ runPromise.catch((err) => {
817
+ const e = err;
818
+ if (e && typeof e === "object" && "exitCode" in e)
819
+ process.stderr.write(`${label}[transport] command failed — exitCode=${e.exitCode} error=${JSON.stringify(e.error)} stdout_tail=${String(e.stdout ?? "").slice(-200)} stderr_tail=${String(e.stderr ?? "").slice(-200)}
820
+ `);
821
+ queue.push({ sentinel: "error", text: stderrBuffer.trim() ? `Agent process failed: ${formatError(err)}
822
+
823
+ ${stderrBuffer.trim()}` : `Agent process failed: ${formatError(err)}` });
824
+ }).finally(() => {
825
+ if (lineBuffer.trim()) {
826
+ try {
827
+ enqueue(translateEvent(JSON.parse(lineBuffer.trim())));
828
+ } catch {}
829
+ }
830
+ queue.push(null);
831
+ notify?.();
832
+ notify = null;
833
+ });
834
+ while (true) {
835
+ while (queue.length > 0) {
836
+ const item = queue.shift();
837
+ if (item === null)
838
+ return;
839
+ if ("sentinel" in item) {
840
+ yield { type: "error", text: item.text, timestamp: new Date().toISOString() };
841
+ return;
842
+ }
843
+ if (signal?.aborted)
844
+ return;
845
+ const msg = item;
846
+ yield msg.type === "error" && stderrBuffer.trim() && !msg.text.includes(stderrBuffer.trim()) ? { ...msg, text: `${msg.text}
847
+
848
+ ${stderrBuffer.trim()}` } : msg;
849
+ }
850
+ await new Promise((r) => {
851
+ notify = r;
852
+ });
853
+ }
854
+ }
855
+ async* sendMessage(opts) {
856
+ await this.deployGlobalConfig();
857
+ if (opts.sessionId) {
858
+ await this.sandbox.commands.run(`pkill -x claude 2>/dev/null || true`, { timeoutMs: 1e4 });
859
+ }
860
+ const maxTurns = this.options.maxTurns ?? 40;
861
+ const model = this.configModel ?? this.options.model ?? DEFAULT_CLAUDE_MODEL;
862
+ const label = this.label;
863
+ const promptFile = `/tmp/agent-prompt-${Date.now()}.txt`;
864
+ await this.sandbox.files.write(promptFile, opts.prompt);
865
+ const hasMcp = this.configMcpServers && Object.keys(this.configMcpServers).length > 0;
866
+ const allowedToolsFlag = !hasMcp && this.options.allowedTools?.length ? ["--allowedTools", this.options.allowedTools.join(",")] : [];
867
+ const buildCmd = (sessionId) => [
868
+ "claude",
869
+ "--dangerously-skip-permissions",
870
+ "--verbose",
871
+ "--output-format",
872
+ "stream-json",
873
+ "--max-turns",
874
+ String(maxTurns),
875
+ "--model",
876
+ model,
877
+ ...allowedToolsFlag,
878
+ ...sessionId ? ["--resume", sessionId] : [],
879
+ "-p",
880
+ `"$(cat ${promptFile})"`
881
+ ].join(" ");
882
+ const MAX_529_RETRIES = 5;
883
+ const BACKOFF_MS = [60000, 120000, 240000, 480000, 600000];
884
+ let resumeSessionId = opts.sessionId;
885
+ for (let attempt = 0;attempt <= MAX_529_RETRIES; attempt++) {
886
+ let got529 = false;
887
+ let newSession = "";
888
+ for await (const msg of this._runCli(buildCmd(resumeSessionId), label, opts.signal)) {
889
+ if (msg.type === "init")
890
+ newSession = msg.sessionId;
891
+ if (msg.type === "error" && msg.text.includes("529")) {
892
+ got529 = true;
893
+ break;
894
+ }
895
+ yield msg;
896
+ if (msg.type === "done" || msg.type === "error")
897
+ return;
898
+ }
899
+ if (!got529)
900
+ return;
901
+ if (attempt === MAX_529_RETRIES) {
902
+ yield { type: "error", text: "Agent process failed: Repeated 529 Overloaded errors — all retries exhausted", timestamp: new Date().toISOString() };
903
+ return;
904
+ }
905
+ if (newSession)
906
+ resumeSessionId = newSession;
907
+ const wait = BACKOFF_MS[attempt];
908
+ process.stderr.write(`
909
+ ${label}[529] Overloaded — resuming in ${wait / 1000}s (attempt ${attempt + 1}/${MAX_529_RETRIES})${resumeSessionId ? " with session resume" : ""}
910
+ `);
911
+ await new Promise((r) => setTimeout(r, wait));
912
+ }
913
+ }
914
+ }
915
+ function createClaudeRuntime(config = {}) {
916
+ return defineRuntime({
917
+ create: (sandbox, opts) => new ClaudeRunner(sandbox, opts, config.claudeMdContent ?? "", config.env ?? {}, config.model, config.mcpServers)
918
+ });
919
+ }
920
+ var claude_default = createClaudeRuntime();
921
+ // src/workflows/engine.ts
922
+ class WorkflowError extends Error {
923
+ kind = "workflow";
924
+ constructor(message, options) {
925
+ super(message, options);
926
+ this.name = "WorkflowError";
927
+ }
928
+ }
929
+
930
+ class EngineError extends Error {
931
+ kind = "engine";
932
+ subsystem;
933
+ constructor(message, subsystem = "unknown", options) {
934
+ super(message, options);
935
+ this.name = "EngineError";
936
+ this.subsystem = subsystem;
937
+ }
938
+ }
939
+ function classifyError(err) {
940
+ if (err instanceof WorkflowError)
941
+ return "workflow";
942
+ if (err instanceof EngineError)
943
+ return "engine";
944
+ return "engine";
945
+ }
946
+ function parseNameVersion(ref) {
947
+ const atIdx = ref.lastIndexOf("@");
948
+ return atIdx > 0 ? { name: ref.slice(0, atIdx), version: ref.slice(atIdx + 1) } : { name: ref, version: undefined };
949
+ }
950
+ async function runWorkflow(wf, ctx, opts) {
951
+ const hooks = opts?.hooks ?? {};
952
+ const setMetadata = opts?.setMetadata ?? (async () => {});
953
+ const sandbox = makeLocalSandboxProvider();
954
+ let workflowErr;
955
+ let wfOutput;
956
+ try {
957
+ wfOutput = await wf({
958
+ ...ctx,
959
+ setMetadata,
960
+ step: async (name, fn) => {
961
+ const startedAt = Date.now();
962
+ hooks.onStepStart?.(name);
963
+ try {
964
+ const r = await fn();
965
+ hooks.onStepComplete?.(name, Date.now() - startedAt);
966
+ return r;
967
+ } catch (err) {
968
+ hooks.onStepFail?.(name, Date.now() - startedAt, formatError(err));
969
+ throw err;
970
+ }
971
+ }
972
+ }, sandbox);
973
+ } catch (err) {
974
+ workflowErr = err;
975
+ }
976
+ if (workflowErr !== undefined) {
977
+ if (workflowErr instanceof WorkflowError)
978
+ throw workflowErr;
979
+ throw new WorkflowError(formatError(workflowErr), { cause: workflowErr });
980
+ }
981
+ return { response: wfOutput ?? null };
982
+ }
983
+ // src/agent/run-agent.ts
984
+ import { z as z3 } from "zod";
985
+
986
+ // src/agent/protocol-suffix.md
987
+ 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';
988
+
989
+ // src/agent/run-agent.ts
990
+ var PROTOCOL_SUFFIX = stripFrontmatter(protocol_suffix_default);
991
+ function stripFrontmatter(content) {
992
+ if (!content.startsWith("---"))
993
+ return content;
994
+ const end = content.indexOf(`
995
+ ---`, 3);
996
+ return end === -1 ? content : content.slice(end + 4).trimStart();
997
+ }
998
+ function inject(template, vars) {
999
+ return Object.entries(vars).reduce((t, [k, v]) => t.replaceAll(`{{${k}}}`, v), template);
1000
+ }
1001
+ function zodTypeName(schema) {
1002
+ if (schema instanceof z3.ZodString)
1003
+ return "string";
1004
+ if (schema instanceof z3.ZodNumber)
1005
+ return "number";
1006
+ if (schema instanceof z3.ZodBoolean)
1007
+ return "boolean";
1008
+ if (schema instanceof z3.ZodArray)
1009
+ return `${zodTypeName(schema.element)}[]`;
1010
+ if (schema instanceof z3.ZodEnum)
1011
+ return schema.options.map((o) => `"${o}"`).join(" | ");
1012
+ if (schema instanceof z3.ZodOptional)
1013
+ return `${zodTypeName(schema.unwrap())} (optional)`;
1014
+ if (schema instanceof z3.ZodNullable)
1015
+ return `${zodTypeName(schema.unwrap())} | null`;
1016
+ if (schema instanceof z3.ZodLiteral)
1017
+ return `"${String(schema.value)}"`;
1018
+ if (schema instanceof z3.ZodObject)
1019
+ return "object";
1020
+ return "any";
1021
+ }
1022
+ function buildResponseFormatAppendix(schema) {
1023
+ const header = ["## Response Format", "", "When you emit `exit_signal: true` in your `<status>` block, also include a `<response>` block containing JSON immediately after `<status>`:", ""];
1024
+ const shape = schema instanceof z3.ZodObject ? schema.shape : null;
1025
+ const body = shape ? (() => {
1026
+ const entries = Object.entries(shape);
1027
+ const example = Object.fromEntries(entries.map(([k, v]) => [k, `<${zodTypeName(v)}>`]));
1028
+ return [
1029
+ "Fields:",
1030
+ ...entries.map(([k, v]) => `- \`${k}\` (${zodTypeName(v)})`),
1031
+ "",
1032
+ "Example:",
1033
+ "<response>",
1034
+ JSON.stringify(example, null, 2),
1035
+ "</response>"
1036
+ ];
1037
+ })() : [
1038
+ "The response must match this schema:",
1039
+ `Expected type: ${zodTypeName(schema)}`,
1040
+ "",
1041
+ "Example:",
1042
+ "<response>",
1043
+ `<${zodTypeName(schema)}>`,
1044
+ "</response>"
1045
+ ];
1046
+ return [...header, ...body].join(`
1047
+ `);
1048
+ }
1049
+ async function runAgent(opts) {
1050
+ const workingDir = opts.workingDir ?? "";
1051
+ const promptVars = { WORKING_DIR: workingDir, DIFF_BASE: "HEAD~1", ...opts.promptVars };
1052
+ return agentLoop({
1053
+ runtime: (runtimeOpts) => opts.runtime.create(opts.sandbox, runtimeOpts),
1054
+ ...opts.label !== undefined ? { label: opts.label } : {},
1055
+ ...opts.budget?.turnsPerIteration !== undefined ? { turnsPerIteration: opts.budget.turnsPerIteration } : {},
1056
+ ...opts.budget?.maxIterations !== undefined ? { maxIterations: opts.budget.maxIterations } : {},
1057
+ ...opts.tools !== undefined ? { allowedTools: opts.tools } : {},
1058
+ ...opts.responseSchema !== undefined ? { responseSchema: opts.responseSchema } : {},
1059
+ ...workingDir ? { cwd: workingDir } : {},
1060
+ buildPrompt: (_lastStatus, iteration) => {
1061
+ if (iteration > 0)
1062
+ return PROTOCOL_SUFFIX;
1063
+ const base = inject(stripFrontmatter(opts.prompt), promptVars);
1064
+ return opts.responseSchema ? `${base}
1065
+
1066
+ ${PROTOCOL_SUFFIX}
1067
+
1068
+ ${buildResponseFormatAppendix(opts.responseSchema)}` : `${base}
1069
+
1070
+ ${PROTOCOL_SUFFIX}`;
1071
+ },
1072
+ ...opts.onAgentEvent ? { onAgentEvent: opts.onAgentEvent } : {},
1073
+ ...opts.onIteration ? { onIteration: opts.onIteration } : {}
1074
+ });
1075
+ }
1076
+ export {
1077
+ runWorkflow,
1078
+ runAgent,
1079
+ reconnectSandbox,
1080
+ parseSseExecStream,
1081
+ parseNameVersion,
1082
+ parseAgentStatus,
1083
+ parseAgentResponse,
1084
+ makeSandboxProvider,
1085
+ makeDesktopSandboxProvider,
1086
+ listOwnedSandboxes,
1087
+ killSandboxById,
1088
+ killAllSandboxes,
1089
+ importSourceModule,
1090
+ getSandboxQuotas,
1091
+ formatError,
1092
+ discoverRuntimeName,
1093
+ deleteSandboxSnapshot,
1094
+ defineWorkflow,
1095
+ defineSandboxEnvironment,
1096
+ defineRuntime,
1097
+ createSandbox,
1098
+ createClaudeRuntime,
1099
+ claude_default as claudeRuntime,
1100
+ classifyError,
1101
+ bundleWorkflow,
1102
+ agentLoop,
1103
+ WorkflowError,
1104
+ TMP_DIR,
1105
+ LATEST_VERSION,
1106
+ EngineError,
1107
+ DEFAULT_CLAUDE_MODEL,
1108
+ ClaudeRunner,
1109
+ AgentStatusSchema,
1110
+ AgentMessageSchema,
1111
+ AgentComposeError,
1112
+ AgentComposeClient,
1113
+ AGENT_COMPOSE_TAG
1114
+ };