@agent-compose/sdk 0.8.0 → 0.8.2

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.
Files changed (44) hide show
  1. package/dist/agent/agent-context.d.ts +1 -1
  2. package/dist/agent/agent-loop.d.ts +8 -0
  3. package/dist/agent/run-agent.d.ts +4 -0
  4. package/dist/client.d.ts +77 -15
  5. package/dist/display.d.ts +16 -0
  6. package/dist/index.d.ts +6 -6
  7. package/dist/index.js +522 -123
  8. package/dist/runtimes/_cli-agent.d.ts +34 -7
  9. package/dist/runtimes/claude-code.d.ts +10 -8
  10. package/dist/runtimes/codex.buildcommand.test.d.ts +9 -0
  11. package/dist/runtimes/codex.d.ts +4 -1
  12. package/dist/runtimes/openai-desktop.js +507 -122
  13. package/dist/sandbox/sizes.d.ts +120 -30
  14. package/dist/sandbox.d.ts +1 -1
  15. package/dist/types/api-conversations.d.ts +198 -0
  16. package/dist/types/api-factory.d.ts +84 -7
  17. package/dist/types/api-runs.d.ts +48 -2
  18. package/dist/types/protocol.d.ts +8 -0
  19. package/dist/types/workflow-metadata.d.ts +14 -5
  20. package/dist/utils/bundler.d.ts +56 -0
  21. package/dist/workflow-steps/workflow.d.ts +7 -0
  22. package/dist/workflows/invoke-child.d.ts +18 -0
  23. package/dist/workflows/invoke-child.test.d.ts +9 -0
  24. package/package.json +2 -2
  25. package/src/agent/agent-context.ts +28 -17
  26. package/src/agent/agent-loop.ts +9 -0
  27. package/src/agent/run-agent.ts +5 -0
  28. package/src/client.ts +201 -30
  29. package/src/display.ts +61 -15
  30. package/src/index.ts +22 -9
  31. package/src/runtimes/_cli-agent.ts +302 -63
  32. package/src/runtimes/claude-code.ts +25 -15
  33. package/src/runtimes/codex.ts +19 -5
  34. package/src/sandbox/providers/e2b.ts +8 -4
  35. package/src/sandbox/sizes.ts +127 -44
  36. package/src/sandbox.ts +8 -0
  37. package/src/types/api-conversations.ts +180 -0
  38. package/src/types/api-factory.ts +89 -7
  39. package/src/types/api-runs.ts +50 -2
  40. package/src/types/protocol.ts +8 -0
  41. package/src/types/workflow-metadata.ts +15 -5
  42. package/src/utils/bundler.ts +213 -3
  43. package/src/workflow-steps/workflow.ts +7 -0
  44. package/src/workflows/invoke-child.ts +47 -11
package/dist/index.js CHANGED
@@ -959,6 +959,8 @@ function extractMetadata(source) {
959
959
  out.invokePolicy = freezeMetadataValue(source.invokePolicy);
960
960
  if (source.environmentBuild !== undefined)
961
961
  out.environmentBuild = source.environmentBuild;
962
+ if (source.factoryDrive !== undefined)
963
+ out.factoryDrive = source.factoryDrive;
962
964
  return Object.freeze(out);
963
965
  }
964
966
  function freezeMetadataValue(value) {
@@ -1446,14 +1448,47 @@ class AgentComposeClient {
1446
1448
  ...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {},
1447
1449
  ...opts?.size !== undefined ? { size: opts.size } : {},
1448
1450
  ...parentRunId ? { parentRunId } : {},
1449
- ...opts?.agentId ? { agentId: opts.agentId } : {}
1451
+ ...opts?.agentId ? { agentId: opts.agentId } : {},
1452
+ ...opts?.funding !== undefined ? { funding: opts.funding } : {},
1453
+ ...opts?.fundingSecret !== undefined ? { fundingSecret: opts.fundingSecret } : {}
1454
+ }
1455
+ });
1456
+ }
1457
+ invokeInline(workflow, input, opts) {
1458
+ const parentRunId = opts?.parentRunId === undefined ? detectAmbientParentRunId() : opts.parentRunId;
1459
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
1460
+ const headers = {};
1461
+ if (opts?.idempotencyKey)
1462
+ headers["Idempotency-Key"] = opts.idempotencyKey;
1463
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/invoke`, {
1464
+ method: "POST",
1465
+ ...opts?.idempotencyKey ? { headers } : {},
1466
+ body: {
1467
+ workflow,
1468
+ input,
1469
+ ...opts?.title !== undefined ? { title: opts.title } : {},
1470
+ ...opts?.snapshots !== undefined ? { snapshots: opts.snapshots } : {},
1471
+ ...opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {},
1472
+ ...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {},
1473
+ ...opts?.size !== undefined ? { size: opts.size } : {},
1474
+ ...parentRunId ? { parentRunId } : {},
1475
+ ...opts?.agentId ? { agentId: opts.agentId } : {},
1476
+ ...opts?.funding !== undefined ? { funding: opts.funding } : {},
1477
+ ...opts?.fundingSecret !== undefined ? { fundingSecret: opts.fundingSecret } : {}
1450
1478
  }
1451
1479
  });
1452
1480
  }
1453
1481
  async invokeAndWait(name, input, opts) {
1482
+ const { id: runId } = await this.invoke(name, input, opts);
1483
+ return this.waitForRun(runId, opts);
1484
+ }
1485
+ async invokeInlineAndWait(workflow, input, opts) {
1486
+ const { id: runId } = await this.invokeInline(workflow, input, opts);
1487
+ return this.waitForRun(runId, opts);
1488
+ }
1489
+ async waitForRun(runId, opts) {
1454
1490
  const timeoutMs = opts?.timeoutMs ?? 30 * 60 * 1000;
1455
1491
  const pollMs = opts?.pollIntervalMs ?? 1000;
1456
- const { id: runId } = await this.invoke(name, input, opts);
1457
1492
  const deadline = Date.now() + timeoutMs;
1458
1493
  while (Date.now() < deadline) {
1459
1494
  const status = await this.getStatus(runId);
@@ -1462,7 +1497,7 @@ class AgentComposeClient {
1462
1497
  }
1463
1498
  await new Promise((r) => setTimeout(r, pollMs));
1464
1499
  }
1465
- throw new AgentComposeError(504, `invokeAndWait: run ${runId} did not settle within ${timeoutMs}ms`);
1500
+ throw new AgentComposeError(504, `waitForRun: run ${runId} did not settle within ${timeoutMs}ms`);
1466
1501
  }
1467
1502
  async listSnapshots(opts) {
1468
1503
  const body = await this.listSnapshotsPage(opts);
@@ -1598,6 +1633,11 @@ class AgentComposeClient {
1598
1633
  preview: a.preview
1599
1634
  }));
1600
1635
  }
1636
+ async getRunArtifactBytes(runId, path) {
1637
+ const q2 = new URLSearchParams({ path });
1638
+ const buf = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/artifacts/content?${q2}`, { responseType: "arrayBuffer" });
1639
+ return new Uint8Array(buf);
1640
+ }
1601
1641
  async putFactoryFile(path, content, opts) {
1602
1642
  const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
1603
1643
  const runToken = typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_RUN_TOKEN : undefined;
@@ -1621,12 +1661,28 @@ class AgentComposeClient {
1621
1661
  const q2 = new URLSearchParams({ path });
1622
1662
  if (opts?.revision !== undefined)
1623
1663
  q2.set("revision", String(opts.revision));
1664
+ if (opts?.branch !== undefined)
1665
+ q2.set("branch", opts.branch);
1624
1666
  const buf = await this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?${q2}`, { responseType: "arrayBuffer" });
1625
1667
  return new Uint8Array(buf);
1626
1668
  }
1627
1669
  async getFactoryFile(path, opts) {
1628
1670
  return new TextDecoder().decode(await this.getFactoryFileBytes(path, opts));
1629
1671
  }
1672
+ async createDriveMountSession(opts) {
1673
+ const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
1674
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/mount-sessions`, {
1675
+ method: "POST",
1676
+ body: {
1677
+ ...opts?.conversationId ? { conversationId: opts.conversationId } : {},
1678
+ ...opts?.host ? { host: opts.host } : {}
1679
+ }
1680
+ });
1681
+ }
1682
+ async releaseDriveMountSession(conversationId, opts) {
1683
+ const factorySlug = opts?.factorySlug ?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined) ?? DEFAULT_FACTORY;
1684
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/mount-sessions/${encodeURIComponent(conversationId)}/release`, { method: "POST", body: {} });
1685
+ }
1630
1686
  listConversations(opts) {
1631
1687
  const q2 = new URLSearchParams;
1632
1688
  if (opts?.cursor)
@@ -1639,8 +1695,9 @@ class AgentComposeClient {
1639
1695
  q2.set("cursor", opts.cursor);
1640
1696
  return this.fetch(`/api/v1/sessions?${q2}`);
1641
1697
  }
1642
- getConversation(id) {
1643
- return this.fetch(`/api/v1/conversations/${encodeURIComponent(id)}`);
1698
+ getConversation(id, opts) {
1699
+ const q2 = opts?.limit !== undefined ? `?limit=${opts.limit}` : "";
1700
+ return this.fetch(`/api/v1/conversations/${encodeURIComponent(id)}${q2}`);
1644
1701
  }
1645
1702
  createCloudSession(input) {
1646
1703
  return this.fetch(`/api/v1/sessions/cloud`, {
@@ -1658,7 +1715,10 @@ class AgentComposeClient {
1658
1715
  ...input.templateId ? { templateId: input.templateId } : {},
1659
1716
  ...input.sandboxSize ? { sandboxSize: input.sandboxSize } : {},
1660
1717
  ...input.networkPolicy !== undefined ? { networkPolicy: input.networkPolicy } : {},
1661
- ...input.connectorProfileId !== undefined ? { connectorProfileId: input.connectorProfileId } : {}
1718
+ ...input.connectorProfileId !== undefined ? { connectorProfileId: input.connectorProfileId } : {},
1719
+ ...input.setupCommand !== undefined ? { setupCommand: input.setupCommand } : {},
1720
+ ...input.handoffOrigin !== undefined ? { handoffOrigin: input.handoffOrigin } : {},
1721
+ ...input.seedFiles !== undefined ? { seedFiles: input.seedFiles } : {}
1662
1722
  }
1663
1723
  });
1664
1724
  }
@@ -1696,6 +1756,13 @@ class AgentComposeClient {
1696
1756
  const body = await this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/preview/${port}`, { method: "DELETE" });
1697
1757
  return body.closed;
1698
1758
  }
1759
+ holdBackgroundWork(conversationId, input) {
1760
+ return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/background-work`, { method: "POST", body: { ...input?.minutes !== undefined ? { minutes: input.minutes } : {} } });
1761
+ }
1762
+ async releaseBackgroundWork(conversationId) {
1763
+ const body = await this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/background-work`, { method: "DELETE" });
1764
+ return body.released;
1765
+ }
1699
1766
  forkSession(conversationId, input) {
1700
1767
  return this.fetch(`/api/v1/conversations/${encodeURIComponent(conversationId)}/fork`, { method: "POST", body: input?.title ? { title: input.title } : {} });
1701
1768
  }
@@ -1945,7 +2012,7 @@ class AgentComposeClient {
1945
2012
  async createRepoLink(input, opts) {
1946
2013
  const slug = opts?.factorySlug ?? DEFAULT_FACTORY;
1947
2014
  const body = await this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}/repo-links`, { method: "POST", body: input });
1948
- return body.link;
2015
+ return body.links;
1949
2016
  }
1950
2017
  deleteRepoLink(linkId, opts) {
1951
2018
  const slug = opts?.factorySlug ?? DEFAULT_FACTORY;
@@ -1969,9 +2036,17 @@ class AgentComposeClient {
1969
2036
  createApiKey(input) {
1970
2037
  return this.fetch("/api-keys", { method: "POST", body: input });
1971
2038
  }
1972
- async listApiKeys() {
1973
- const body = await this.fetch("/api-keys");
1974
- return body.data;
2039
+ async listApiKeys(opts) {
2040
+ const qs = new URLSearchParams;
2041
+ if (opts?.limit !== undefined)
2042
+ qs.set("limit", String(opts.limit));
2043
+ if (opts?.status !== undefined)
2044
+ qs.set("status", opts.status);
2045
+ if (opts?.cursor !== undefined)
2046
+ qs.set("cursor", opts.cursor);
2047
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
2048
+ const body = await this.fetch(`/api-keys${suffix}`);
2049
+ return { data: body.data, hasMore: body.has_more, nextCursor: body.next_cursor ?? null };
1975
2050
  }
1976
2051
  getUsage(from, to) {
1977
2052
  const qs = `?from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`;
@@ -2466,6 +2541,7 @@ class AcpClientPeer {
2466
2541
  }
2467
2542
  // src/utils/bundler.ts
2468
2543
  import { createHash as createHash2 } from "node:crypto";
2544
+ import { dirname as dirname2 } from "node:path";
2469
2545
  import { parse as babelParse } from "@babel/parser";
2470
2546
 
2471
2547
  // src/utils/source-loader.ts
@@ -2495,15 +2571,119 @@ class WorkflowSourceValidationError extends Error {
2495
2571
  this.name = "WorkflowSourceValidationError";
2496
2572
  }
2497
2573
  }
2574
+ var SDK_PACKAGE = "@agent-compose/sdk";
2575
+ var SDK_SPECIFIER_ALIASES = [
2576
+ "agentc/sdk",
2577
+ "@agentc/sdk",
2578
+ "agentc-sdk",
2579
+ "agent-compose/sdk",
2580
+ "agentcompose/sdk",
2581
+ "@agentcompose/sdk",
2582
+ "agent-compose-sdk"
2583
+ ];
2584
+ var SDK_ALIAS_SET = new Set(SDK_SPECIFIER_ALIASES);
2585
+ function resolveSdkAlias(specifier) {
2586
+ return SDK_ALIAS_SET.has(specifier) ? SDK_PACKAGE : null;
2587
+ }
2588
+ function aliasFilter() {
2589
+ const alternation = SDK_SPECIFIER_ALIASES.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
2590
+ return new RegExp(`^(?:${alternation})$`);
2591
+ }
2592
+ function sdkAliasPlugin(bun) {
2593
+ return {
2594
+ name: "agent-compose-sdk-alias",
2595
+ setup(build) {
2596
+ build.onResolve({ filter: aliasFilter() }, (args) => {
2597
+ const target = resolveSdkAlias(args.path);
2598
+ if (!target)
2599
+ return;
2600
+ const from = args.importer ? dirname2(args.importer) : args.resolveDir;
2601
+ try {
2602
+ return { path: bun.resolveSync(target, from && from.length > 0 ? from : process.cwd()) };
2603
+ } catch {
2604
+ return;
2605
+ }
2606
+ });
2607
+ }
2608
+ };
2609
+ }
2610
+ function readUnresolvedSpecifier(diag) {
2611
+ if (!diag || typeof diag !== "object")
2612
+ return null;
2613
+ const d = diag;
2614
+ if (typeof d.specifier === "string" && d.specifier.length > 0)
2615
+ return d.specifier;
2616
+ const message = typeof d.message === "string" ? d.message : "";
2617
+ const m = /Could not resolve:?\s*"([^"]+)"/.exec(message);
2618
+ return m ? m[1] : null;
2619
+ }
2620
+ function flattenDiagnostics(failure) {
2621
+ const out = [];
2622
+ const seen = new Set;
2623
+ const walk = (node, depth) => {
2624
+ if (!node || depth > 4 || seen.has(node))
2625
+ return;
2626
+ seen.add(node);
2627
+ if (Array.isArray(node)) {
2628
+ for (const sub of node)
2629
+ walk(sub, depth + 1);
2630
+ return;
2631
+ }
2632
+ out.push(node);
2633
+ if (typeof node === "object") {
2634
+ const n = node;
2635
+ if (Array.isArray(n.errors))
2636
+ for (const sub of n.errors)
2637
+ walk(sub, depth + 1);
2638
+ if (n.cause)
2639
+ walk(n.cause, depth + 1);
2640
+ }
2641
+ };
2642
+ walk(failure, 0);
2643
+ return out;
2644
+ }
2645
+ function diagnosticText(diag) {
2646
+ if (diag instanceof Error)
2647
+ return diag.message || String(diag);
2648
+ if (diag && typeof diag === "object" && typeof diag.message === "string") {
2649
+ return diag.message;
2650
+ }
2651
+ return String(diag);
2652
+ }
2653
+ function explainBundleFailure(err, label) {
2654
+ const diagnostics = flattenDiagnostics(err);
2655
+ const unresolved = [];
2656
+ for (const diag of diagnostics) {
2657
+ const specifier = readUnresolvedSpecifier(diag);
2658
+ if (specifier && !unresolved.includes(specifier))
2659
+ unresolved.push(specifier);
2660
+ }
2661
+ if (unresolved.length > 0) {
2662
+ const quoted = unresolved.map((s) => `"${s}"`).join(", ");
2663
+ return `Could not resolve ${quoted} — workflow source imports the platform SDK as "${SDK_PACKAGE}". ` + `Fix the import specifier in ${label}; anything that is genuinely a third-party ` + `package must be installed where the source is bundled.`;
2664
+ }
2665
+ const detail = diagnostics.map(diagnosticText).filter((t) => t.length > 0 && t !== "Bundle failed").join(`
2666
+ `);
2667
+ return `Failed to bundle ${label}:
2668
+ ${detail || "the bundler reported no diagnostics"}`;
2669
+ }
2498
2670
  async function bundle(path, label) {
2499
2671
  const bun = globalThis;
2500
2672
  if (!bun.Bun?.build)
2501
2673
  throw new Error("bundleWorkflow requires the Bun runtime (Bun.build)");
2502
- const result = await bun.Bun.build({ entrypoints: [path], format: "esm", target: "node" });
2674
+ let result;
2675
+ try {
2676
+ result = await bun.Bun.build({
2677
+ entrypoints: [path],
2678
+ format: "esm",
2679
+ target: "node",
2680
+ plugins: [sdkAliasPlugin(bun.Bun)]
2681
+ });
2682
+ } catch (err) {
2683
+ throw new WorkflowSourceValidationError(explainBundleFailure(err, label));
2684
+ }
2503
2685
  if (!result.success) {
2504
- throw new Error(`Failed to bundle ${label}:
2505
- ${result.logs.map((l) => l.message).join(`
2506
- `)}`);
2686
+ throw new WorkflowSourceValidationError(explainBundleFailure(result.logs, label));
2507
2687
  }
2508
2688
  return result.outputs[0].text();
2509
2689
  }
@@ -2621,7 +2801,8 @@ async function bundleWorkflow(workflowPath, overrides) {
2621
2801
  ...metadata.connectors !== undefined ? { connectors: metadata.connectors } : {},
2622
2802
  ...metadata.connectorOperation !== undefined ? { connectorOperation: metadata.connectorOperation } : {},
2623
2803
  ...metadata.invokePolicy !== undefined ? { invokePolicy: metadata.invokePolicy } : {},
2624
- ...metadata.environmentBuild !== undefined ? { environmentBuild: metadata.environmentBuild } : {}
2804
+ ...metadata.environmentBuild !== undefined ? { environmentBuild: metadata.environmentBuild } : {},
2805
+ ...metadata.factoryDrive !== undefined ? { factoryDrive: metadata.factoryDrive } : {}
2625
2806
  };
2626
2807
  }
2627
2808
  function extractIOSchema(zodSchema) {
@@ -3019,7 +3200,8 @@ async function agentLoop(opts) {
3019
3200
  label,
3020
3201
  allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS,
3021
3202
  ...client.model != null ? { model: client.model } : {},
3022
- ...client.kind != null ? { runtimeKind: client.kind } : {}
3203
+ ...client.kind != null ? { runtimeKind: client.kind } : {},
3204
+ ...opts.phase != null ? { phase: opts.phase } : {}
3023
3205
  });
3024
3206
  }
3025
3207
  const flushState = async (currentIteration) => {
@@ -3616,6 +3798,29 @@ function wrapJsonlCommand(args) {
3616
3798
  function now4() {
3617
3799
  return new Date().toISOString();
3618
3800
  }
3801
+ var TAIL_REATTACH_DELAY_MS = 2000;
3802
+ var LAUNCH_EXEC_TIMEOUT_MS = 30000;
3803
+ var LAUNCH_PID_RECOVERY_ATTEMPTS = 3;
3804
+ var LAUNCH_PID_RECOVERY_DELAY_MS = 1000;
3805
+ var INSTALL_PROBE_TIMEOUT_MS = 30000;
3806
+ function parsePid(raw) {
3807
+ const pid = Number.parseInt(raw.trim().split(`
3808
+ `).pop() ?? "", 10);
3809
+ return Number.isInteger(pid) && pid > 0 ? pid : Number.NaN;
3810
+ }
3811
+ async function recoverLaunchPid(read, pidPath) {
3812
+ for (let attempt = 0;attempt < LAUNCH_PID_RECOVERY_ATTEMPTS; attempt++) {
3813
+ try {
3814
+ const pid = parsePid(await read(pidPath));
3815
+ if (!Number.isNaN(pid))
3816
+ return pid;
3817
+ } catch {}
3818
+ if (attempt < LAUNCH_PID_RECOVERY_ATTEMPTS - 1) {
3819
+ await new Promise((r) => setTimeout(r, LAUNCH_PID_RECOVERY_DELAY_MS));
3820
+ }
3821
+ }
3822
+ return Number.NaN;
3823
+ }
3619
3824
  function shellQuote(value) {
3620
3825
  return `'${value.replace(/'/g, `'\\''`)}'`;
3621
3826
  }
@@ -3701,7 +3906,7 @@ class CliAgentRunner {
3701
3906
  async ensureInstalled() {
3702
3907
  if (this.installed)
3703
3908
  return;
3704
- const probe = await this.sandbox.commands.run(`command -v ${this.spec.bin}`);
3909
+ const probe = await this.sandbox.commands.run(`command -v ${this.spec.bin}`, { timeoutMs: INSTALL_PROBE_TIMEOUT_MS });
3705
3910
  if (probe.exitCode === 0) {
3706
3911
  this.installed = true;
3707
3912
  return;
@@ -3866,51 +4071,153 @@ class CliAgentRunner {
3866
4071
  keepBytes: JSONL_GUARD_KEEP_BYTES
3867
4072
  });
3868
4073
  const lines = new AsyncQueue;
3869
- let buf = "";
3870
- const onStdout = (data) => {
3871
- buf += data;
3872
- let nl;
3873
- while ((nl = buf.indexOf(`
3874
- `)) >= 0) {
3875
- const line = buf.slice(0, nl).trim();
3876
- buf = buf.slice(nl + 1);
3877
- if (line)
3878
- lines.push(line);
3879
- }
3880
- };
3881
- const runOpts = {
3882
- ...this.options.cwd ? { cwd: this.options.cwd } : {},
3883
- onStdout,
3884
- timeoutMs: 0
3885
- };
3886
- let exited = false;
3887
- const settle = (res) => {
3888
- exited = true;
3889
- const tail = buf.trim();
3890
- if (tail)
3891
- lines.push(tail);
3892
- lines.close();
3893
- return res;
3894
- };
3895
- const fail = (err) => {
3896
- exited = true;
3897
- lines.close();
3898
- throw err;
3899
- };
3900
- let kill;
3901
- let runPromise;
4074
+ let exitCode = null;
4075
+ let exitStderr = "";
4076
+ let transportError = null;
4077
+ let consumerStopped = false;
4078
+ let reap = () => {};
4079
+ let transport;
4080
+ const durableRead = this.sandbox.files.read?.bind(this.sandbox.files);
3902
4081
  if (this.sandbox.commands.runBackground) {
3903
- const handle = await this.sandbox.commands.runBackground(guardedCmd, runOpts);
3904
- kill = () => handle.kill();
3905
- runPromise = handle.wait().then(settle, fail);
4082
+ const outPath = `${promptPath}.out`;
4083
+ const errPath = `${promptPath}.err`;
4084
+ const detachedScript = `{ ${guardedCmd}; } >> ${shellQuote(outPath)} 2>> ${shellQuote(errPath)} </dev/null; ` + `printf '\\n%s %s\\n' ${shellQuote(sentinel)} "$?" >> ${shellQuote(outPath)}`;
4085
+ const pidPath = `${promptPath}.pid`;
4086
+ const launchCmd = `: > ${shellQuote(outPath)}; : > ${shellQuote(errPath)}; ` + `if command -v setsid >/dev/null 2>&1; then setsid sh -c ${shellQuote(detachedScript)} >/dev/null 2>&1 </dev/null & ` + `else sh -c ${shellQuote(detachedScript)} >/dev/null 2>&1 </dev/null & fi; ` + `echo "$!" > ${shellQuote(pidPath)}; echo "$!"`;
4087
+ let pid = Number.NaN;
4088
+ let launched = null;
4089
+ let launchErr = null;
4090
+ try {
4091
+ launched = await this.sandbox.commands.run(launchCmd, {
4092
+ ...this.options.cwd ? { cwd: this.options.cwd } : {},
4093
+ timeoutMs: LAUNCH_EXEC_TIMEOUT_MS
4094
+ });
4095
+ } catch (err) {
4096
+ launchErr = err;
4097
+ }
4098
+ if (launched) {
4099
+ pid = parsePid(launched.stdout ?? "");
4100
+ if (launched.exitCode !== 0 || Number.isNaN(pid)) {
4101
+ throw new Error(`failed to launch ${this.spec.kind}: ` + `exit ${launched.exitCode}${launched.stderr ? `: ${launched.stderr.slice(-500)}` : ""}`);
4102
+ }
4103
+ } else {
4104
+ pid = durableRead ? await recoverLaunchPid(durableRead, pidPath) : Number.NaN;
4105
+ if (Number.isNaN(pid))
4106
+ throw launchErr;
4107
+ console.warn(`[cli-agent] ${this.spec.kind} launch exec stream died but the detached runner is up ` + `(pid ${pid} via ${pidPath}) — continuing on the durable transport: ${formatError(launchErr)}`);
4108
+ }
4109
+ let offset = 0;
4110
+ let currentTail = null;
4111
+ const consumeLine = (raw) => {
4112
+ offset += Buffer.byteLength(raw, "utf8") + 1;
4113
+ const line = raw.trim();
4114
+ if (!line)
4115
+ return;
4116
+ if (line.startsWith(sentinel)) {
4117
+ const code = Number(line.slice(sentinel.length).trim());
4118
+ exitCode = Number.isFinite(code) ? code : 0;
4119
+ return;
4120
+ }
4121
+ lines.push(line);
4122
+ };
4123
+ reap = () => {
4124
+ this.sandbox.commands.run(`kill -TERM -- -${pid} 2>/dev/null; kill -TERM ${pid} 2>/dev/null; true`, { timeoutMs: 1e4 }).catch(() => {});
4125
+ currentTail?.kill().catch(() => {});
4126
+ };
4127
+ transport = (async () => {
4128
+ while (exitCode === null && !consumerStopped && !opts.signal?.aborted) {
4129
+ let buf = "";
4130
+ let handle = null;
4131
+ const onStdout = (data) => {
4132
+ buf += data;
4133
+ let nl;
4134
+ while ((nl = buf.indexOf(`
4135
+ `)) >= 0) {
4136
+ consumeLine(buf.slice(0, nl));
4137
+ buf = buf.slice(nl + 1);
4138
+ if (exitCode !== null) {
4139
+ handle?.kill().catch(() => {});
4140
+ return;
4141
+ }
4142
+ }
4143
+ };
4144
+ try {
4145
+ handle = await this.sandbox.commands.runBackground(`tail -c +${offset + 1} -f ${shellQuote(outPath)}`, { timeoutMs: 0, onStdout });
4146
+ } catch {}
4147
+ if (handle) {
4148
+ if (exitCode !== null)
4149
+ handle.kill().catch(() => {});
4150
+ currentTail = handle;
4151
+ await handle.wait().catch(() => {
4152
+ return;
4153
+ });
4154
+ currentTail = null;
4155
+ }
4156
+ if (exitCode !== null || consumerStopped || opts.signal?.aborted)
4157
+ break;
4158
+ if (durableRead) {
4159
+ try {
4160
+ const bytes = Buffer.from(await durableRead(outPath), "utf8");
4161
+ let rest = bytes.subarray(offset).toString("utf8");
4162
+ let nl;
4163
+ while (exitCode === null && (nl = rest.indexOf(`
4164
+ `)) >= 0) {
4165
+ consumeLine(rest.slice(0, nl));
4166
+ rest = rest.slice(nl + 1);
4167
+ }
4168
+ } catch {}
4169
+ }
4170
+ if (exitCode !== null)
4171
+ break;
4172
+ let alive = null;
4173
+ try {
4174
+ const probe = await this.sandbox.commands.run(`kill -0 ${pid} 2>/dev/null`, { timeoutMs: 1e4 });
4175
+ alive = probe.exitCode === 0;
4176
+ } catch {
4177
+ alive = null;
4178
+ }
4179
+ if (alive === false) {
4180
+ exitCode = JSONL_GUARD_NO_SENTINEL_EXIT;
4181
+ break;
4182
+ }
4183
+ console.warn(`[cli-agent] ${this.spec.kind} turn stream lost with the runner alive — re-tailing ${outPath} from byte ${offset}`);
4184
+ await new Promise((r) => setTimeout(r, TAIL_REATTACH_DELAY_MS));
4185
+ }
4186
+ if (exitCode !== null && exitCode !== 0 && durableRead) {
4187
+ try {
4188
+ exitStderr = (await durableRead(errPath)).slice(-2000);
4189
+ } catch {}
4190
+ }
4191
+ })().finally(() => lines.close());
3906
4192
  } else {
3907
- runPromise = this.sandbox.commands.run(guardedCmd, runOpts).then(settle, fail);
4193
+ let buf = "";
4194
+ const onStdout = (data) => {
4195
+ buf += data;
4196
+ let nl;
4197
+ while ((nl = buf.indexOf(`
4198
+ `)) >= 0) {
4199
+ const line = buf.slice(0, nl).trim();
4200
+ buf = buf.slice(nl + 1);
4201
+ if (line)
4202
+ lines.push(line);
4203
+ }
4204
+ };
4205
+ const runOpts = {
4206
+ ...this.options.cwd ? { cwd: this.options.cwd } : {},
4207
+ onStdout,
4208
+ timeoutMs: 0
4209
+ };
4210
+ transport = this.sandbox.commands.run(guardedCmd, runOpts).then((res) => {
4211
+ const tail = buf.trim();
4212
+ if (tail)
4213
+ lines.push(tail);
4214
+ exitCode = res.exitCode;
4215
+ exitStderr = (res.stderr ?? "").slice(-2000);
4216
+ }, (err) => {
4217
+ transportError = err;
4218
+ }).finally(() => lines.close());
3908
4219
  }
3909
- runPromise.catch(() => {});
3910
- const reap = () => {
3911
- if (!exited)
3912
- kill?.().catch(() => {});
3913
- };
4220
+ transport.catch(() => {});
3914
4221
  if (opts.signal) {
3915
4222
  if (opts.signal.aborted)
3916
4223
  reap();
@@ -3934,18 +4241,28 @@ class CliAgentRunner {
3934
4241
  yield msg;
3935
4242
  }
3936
4243
  }
3937
- const res = await runPromise;
3938
- if (res.exitCode !== 0 && !sawError) {
3939
- const tail = (res.stderr ?? "").slice(-2000);
3940
- yield { type: "error", text: `${this.spec.kind} exited with code ${res.exitCode}${tail ? `: ${tail}` : ""}`, timestamp: now4() };
4244
+ await transport;
4245
+ if (transportError)
4246
+ throw transportError;
4247
+ if (exitCode === null) {
4248
+ if (!sawError) {
4249
+ yield { type: "error", text: `${this.spec.kind} aborted — the in-sandbox runner was terminated`, timestamp: now4() };
4250
+ }
4251
+ return;
4252
+ }
4253
+ if (exitCode !== 0 && !sawError) {
4254
+ const detail = exitCode === JSONL_GUARD_NO_SENTINEL_EXIT ? " (the runner died without reporting an exit code)" : "";
4255
+ yield { type: "error", text: `${this.spec.kind} exited with code ${exitCode}${detail}${exitStderr ? `: ${exitStderr}` : ""}`, timestamp: now4() };
3941
4256
  return;
3942
4257
  }
3943
4258
  if (!sawError)
3944
4259
  yield { type: "done", sessionId: sessionId ?? "", timestamp: now4() };
3945
4260
  } finally {
3946
4261
  opts.signal?.removeEventListener("abort", reap);
3947
- reap();
3948
- this.sandbox.commands.run(`rm -f ${shellQuote(promptPath)} ${shellQuote(guardPath)}`, { timeoutMs: 1e4 }).catch(() => {});
4262
+ consumerStopped = true;
4263
+ if (exitCode === null)
4264
+ reap();
4265
+ this.sandbox.commands.run(`rm -f ${shellQuote(promptPath)} ${shellQuote(guardPath)} ${shellQuote(`${promptPath}.out`)} ${shellQuote(`${promptPath}.err`)} ${shellQuote(`${promptPath}.pid`)}`, { timeoutMs: 1e4 }).catch(() => {});
3949
4266
  }
3950
4267
  } catch (err) {
3951
4268
  yield { type: "error", text: formatError(err), timestamp: now4() };
@@ -3986,11 +4303,11 @@ var codexSpec = {
3986
4303
  "--skip-git-repo-check",
3987
4304
  "--dangerously-bypass-approvals-and-sandbox",
3988
4305
  ...model ? ["-m", shellQuote(model)] : [],
3989
- ...effort ? ["-c", `model_reasoning_effort=${effort}`] : [],
3990
- ...cwd ? ["-C", shellQuote(cwd)] : []
4306
+ ...effort ? ["-c", `model_reasoning_effort=${effort === "max" ? "xhigh" : effort}`] : []
3991
4307
  ].join(" ");
4308
+ const cd = cwd ? `cd ${shellQuote(cwd)} && ` : "";
3992
4309
  const exec = sessionId ? `codex exec resume ${shellQuote(sessionId)} ${flags}` : `codex exec ${flags}`;
3993
- return `${exec} - < ${shellQuote(promptPath)}`;
4310
+ return `${cd}${exec} - < ${shellQuote(promptPath)}`;
3994
4311
  },
3995
4312
  extractSessionId: (p) => p.type === "thread.started" && typeof p.thread_id === "string" ? p.thread_id : undefined,
3996
4313
  mapEvent: (p) => {
@@ -4208,7 +4525,7 @@ function toolResultText(content) {
4208
4525
  }
4209
4526
  return content == null ? "" : JSON.stringify(content) ?? "";
4210
4527
  }
4211
- var CLAUDE_CODE_THINKING_TOKENS = { low: 2048, medium: 8192, high: 16384 };
4528
+ var CLAUDE_CODE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
4212
4529
  var claudeCodeSpec = {
4213
4530
  kind: "claude-code",
4214
4531
  authEnv: "ANTHROPIC_API_KEY",
@@ -4224,14 +4541,15 @@ var claudeCodeSpec = {
4224
4541
  "--include-partial-messages",
4225
4542
  "--dangerously-skip-permissions",
4226
4543
  ...model ? [`--model ${shellQuote(model)}`] : [],
4544
+ ...effort ? [`--effort ${effort}`] : [],
4227
4545
  ...sessionId ? [`--resume ${shellQuote(sessionId)}`] : []
4228
4546
  ].join(" ");
4229
- const thinking = effort ? `MAX_THINKING_TOKENS=${CLAUDE_CODE_THINKING_TOKENS[effort]} ` : "";
4230
- return `${cwd ? `cd ${shellQuote(cwd)} && ` : ""}${thinking}IS_SANDBOX=1 claude ${flags} < ${shellQuote(promptPath)}`;
4547
+ return `${cwd ? `cd ${shellQuote(cwd)} && ` : ""}IS_SANDBOX=1 claude ${flags} < ${shellQuote(promptPath)}`;
4231
4548
  },
4232
4549
  extractSessionId: (p) => typeof p.session_id === "string" ? p.session_id : undefined,
4233
4550
  mapEvent: (p) => {
4234
4551
  const ts = now10();
4552
+ const parent = typeof p.parent_tool_use_id === "string" && p.parent_tool_use_id.length > 0 ? { parentToolUseId: p.parent_tool_use_id } : {};
4235
4553
  switch (p.type) {
4236
4554
  case "assistant": {
4237
4555
  const message = p.message;
@@ -4249,6 +4567,7 @@ var claudeCodeSpec = {
4249
4567
  toolName: String(b.name ?? "tool"),
4250
4568
  toolInput: b.input ?? {},
4251
4569
  toolUseId: String(b.id ?? ""),
4570
+ ...parent,
4252
4571
  timestamp: ts
4253
4572
  }];
4254
4573
  }
@@ -4263,6 +4582,7 @@ var claudeCodeSpec = {
4263
4582
  toolUseId: String(b.tool_use_id ?? ""),
4264
4583
  output: toolResultText(b.content),
4265
4584
  isError: b.is_error === true,
4585
+ ...parent,
4266
4586
  timestamp: ts
4267
4587
  }] : []);
4268
4588
  }
@@ -4382,23 +4702,36 @@ function toE2bNetwork(policy) {
4382
4702
  }
4383
4703
 
4384
4704
  // src/sandbox/sizes.ts
4385
- var SANDBOX_VCPUS = {
4386
- "2vcpu-4gb": 2,
4387
- "4vcpu-8gb": 4,
4388
- "8vcpu-16gb": 8,
4389
- "32vcpu-64gb": 32
4705
+ var SANDBOX_MACHINES = {
4706
+ "1vcpu-2gb": { vcpus: 1, memoryMB: 2048 },
4707
+ "2vcpu-4gb": { vcpus: 2, memoryMB: 4096 },
4708
+ "4vcpu-8gb": { vcpus: 4, memoryMB: 8192 },
4709
+ "8vcpu-8gb": { vcpus: 8, memoryMB: 8192 },
4710
+ "8vcpu-16gb": { vcpus: 8, memoryMB: 16384 },
4711
+ "32vcpu-64gb": { vcpus: 32, memoryMB: 65536 }
4390
4712
  };
4713
+ var SANDBOX_SIZES = Object.keys(SANDBOX_MACHINES);
4714
+ var SANDBOX_VCPUS = Object.fromEntries(SANDBOX_SIZES.map((s) => [s, SANDBOX_MACHINES[s].vcpus]));
4391
4715
  var DEFAULT_SANDBOX_SIZE = "2vcpu-4gb";
4392
- var E2B_TEMPLATE_SIZES = [
4393
- "2vcpu-4gb",
4394
- "4vcpu-8gb"
4395
- ];
4716
+ var SESSION_DEFAULT_SANDBOX_SIZE = "4vcpu-8gb";
4717
+ var E2B_MAX_VCPUS = 8;
4718
+ var E2B_MAX_MEMORY_MB = 8192;
4719
+ var E2B_TEMPLATE_SIZES = SANDBOX_SIZES.filter((s) => SANDBOX_MACHINES[s].vcpus <= E2B_MAX_VCPUS && SANDBOX_MACHINES[s].memoryMB <= E2B_MAX_MEMORY_MB);
4396
4720
  function isE2bSupportedSize(size) {
4397
4721
  return E2B_TEMPLATE_SIZES.includes(size);
4398
4722
  }
4723
+ var VERCEL_MEMORY_MB_PER_VCPU = 2048;
4724
+ function isVercelSupportedSize(size) {
4725
+ const m = SANDBOX_MACHINES[size];
4726
+ return m.memoryMB === m.vcpus * VERCEL_MEMORY_MB_PER_VCPU;
4727
+ }
4399
4728
  function e2bMachineSpec(size) {
4400
- const cpuCount = SANDBOX_VCPUS[size];
4401
- return { cpuCount, memoryMB: cpuCount * 2048 };
4729
+ const m = SANDBOX_MACHINES[size];
4730
+ return { cpuCount: m.vcpus, memoryMB: m.memoryMB };
4731
+ }
4732
+ function sandboxSizeLabel(size) {
4733
+ const m = SANDBOX_MACHINES[size];
4734
+ return `${m.vcpus} vCPU · ${Math.round(m.memoryMB / 1024)} GB`;
4402
4735
  }
4403
4736
  function e2bBaseTemplate(size) {
4404
4737
  return `agent-compose-base-${size}`;
@@ -4615,7 +4948,7 @@ function makeE2bSandboxProvider(sb) {
4615
4948
  return { resumeHandle: sb.sandboxId };
4616
4949
  },
4617
4950
  async extendLifetime(ms) {
4618
- await sb.setTimeout(ms);
4951
+ await sb.setTimeout(Math.min(ms, e2bMaxSandboxMs()));
4619
4952
  },
4620
4953
  async updateNetworkPolicy(policy) {
4621
4954
  await sb.updateNetwork(toE2bNetwork(policy));
@@ -4892,7 +5225,7 @@ var vercelProviderDef = {
4892
5225
 
4893
5226
  // src/sandbox/providers/local.ts
4894
5227
  import { promises as fs2 } from "node:fs";
4895
- import { dirname as dirname2 } from "node:path";
5228
+ import { dirname as dirname3 } from "node:path";
4896
5229
  import { spawn } from "node:child_process";
4897
5230
  import { Readable, Writable } from "node:stream";
4898
5231
  function makeLocalSandboxProvider() {
@@ -4958,7 +5291,7 @@ function makeLocalSandboxProvider() {
4958
5291
  },
4959
5292
  files: {
4960
5293
  async write(path, content) {
4961
- await fs2.mkdir(dirname2(path), { recursive: true });
5294
+ await fs2.mkdir(dirname3(path), { recursive: true });
4962
5295
  await fs2.writeFile(path, content);
4963
5296
  },
4964
5297
  async read(path) {
@@ -5509,6 +5842,15 @@ async function runWorkflow(wf, ctx, opts) {
5509
5842
  }
5510
5843
  }
5511
5844
  // src/workflows/invoke-child.ts
5845
+ function deriveInvokeChildIdempotencyKey(parentRunId, childName) {
5846
+ const step = getActiveStep();
5847
+ if (!step)
5848
+ return null;
5849
+ const ordinal = nextPauseOrdinalInActiveStep(`invoke-child:${childName}`);
5850
+ if (ordinal === null)
5851
+ return null;
5852
+ return `invoke-child:${parentRunId}:step${step.stepIndex}:${childName}:${ordinal}`;
5853
+ }
5512
5854
  function buildInvokeChild(runId, opts = {}) {
5513
5855
  let childClient = null;
5514
5856
  const getChildClient = () => {
@@ -5522,11 +5864,15 @@ function buildInvokeChild(runId, opts = {}) {
5522
5864
  childClient = new AgentComposeClient({ apiKey, baseUrl });
5523
5865
  return childClient;
5524
5866
  };
5525
- return (name, input, childOpts) => getChildClient().invokeAndWait(name, input, {
5526
- ...childOpts,
5527
- parentRunId: runId,
5528
- factorySlug: childOpts?.factorySlug ?? opts.defaultFactorySlug ?? process.env.AGENT_COMPOSE_FACTORY ?? process.env.AGENT_COMPOSE_FACTORY_SLUG ?? "default"
5529
- });
5867
+ return (name, input, childOpts) => {
5868
+ const idempotencyKey = childOpts?.idempotencyKey ?? deriveInvokeChildIdempotencyKey(runId, name) ?? undefined;
5869
+ return getChildClient().invokeAndWait(name, input, {
5870
+ ...childOpts,
5871
+ ...idempotencyKey !== undefined ? { idempotencyKey } : {},
5872
+ parentRunId: runId,
5873
+ factorySlug: childOpts?.factorySlug ?? opts.defaultFactorySlug ?? process.env.AGENT_COMPOSE_FACTORY ?? process.env.AGENT_COMPOSE_FACTORY_SLUG ?? "default"
5874
+ });
5875
+ };
5530
5876
  }
5531
5877
  // src/workflow-steps/step.ts
5532
5878
  var CONTROL_CHARS = /[\u0000-\u001f\u007f]/;
@@ -6256,38 +6602,49 @@ there is nothing to type, paste, or screenshot a credential from.
6256
6602
 
6257
6603
  ## Recording a demo — the desktop, captured to a video the human can play
6258
6604
 
6259
- "Record a demo of you using X" is a normal ask, and this machine does it:
6260
- start a screen recording, drive the app with \`xdotool\` exactly as in Computer
6261
- Use, stop the recording, and report the file. (For a LIVE view no recording is
6262
- needed — the session header's **Desktop** button already streams this display
6263
- to any teammate watching; a recording is the durable, replayable artifact.
6264
- Both modes exist; say so when it matters.)
6605
+ "Record a demo of you using X" is a normal ask, and this machine does it.
6606
+ (For a LIVE view no recording is needed — the session header's **Desktop**
6607
+ button already streams this display to any teammate watching; a recording is
6608
+ the durable, replayable artifact. Both modes exist; say so when it matters.)
6265
6609
 
6266
- **ffmpeg is NOT pre-installed** — install it first, once per machine:
6610
+ **Use \`ac-record\` — the platform recorder is already on PATH** (cloud
6611
+ sessions; \`command -v ac-record\` to confirm on older machines):
6267
6612
 
6268
- sudo apt-get update -q && sudo apt-get install -y -q ffmpeg
6613
+ ac-record start # begins capturing the desktop (display :0)
6614
+ # ... drive the app with xdotool, screenshotting as you go ...
6615
+ ac-record stop # finishes + saves to recordings/ in your workspace
6616
+ ac-record status # one JSON line: {"recording":true,...}
6269
6617
 
6270
- (drop \`sudo\` if you are already root). Then the whole recipe:
6618
+ It records the whole display (with desktop audio when the machine has a
6619
+ PulseAudio monitor), enforces sane caps (5 min / 200 MB — start a fresh
6620
+ recording per scene rather than one long take), keeps the file playable even
6621
+ if the machine dies mid-take, and \`stop\` prints the saved path — the file
6622
+ lands ON THE DRIVE in \`recordings/\`, visible in Files and playable in the
6623
+ dashboard. A human watching the Desktop pane sees the recording indicator
6624
+ while you record.
6625
+
6626
+ If \`ac-record\` is missing (older machine), record by hand.
6627
+ **ffmpeg IS pre-installed** on platform images (\`command -v ffmpeg\`; only
6628
+ if absent: \`sudo apt-get update -q && sudo apt-get install -y -q ffmpeg\`):
6271
6629
 
6272
6630
  DISPLAY=:0 ffmpeg -f x11grab \\
6273
6631
  -video_size "$(DISPLAY=:0 xdotool getdisplaygeometry | tr ' ' x)" \\
6274
6632
  -framerate 10 -i :0 -c:v libvpx -b:v 1M -deadline realtime -cpu-used 8 \\
6275
6633
  demo.webm &
6276
6634
  FFMPEG_PID=$!
6277
- # ... drive the app with xdotool, screenshotting as you go ...
6635
+ # ... drive the app with xdotool ...
6278
6636
  kill -INT "$FFMPEG_PID" && wait "$FFMPEG_PID"
6279
6637
 
6280
- The gotchas, each one earned:
6638
+ The hand-rolled gotchas, each one earned:
6281
6639
  - **Stop with SIGINT (\`kill -INT\`), never SIGKILL** — ffmpeg finalizes the
6282
6640
  file on SIGINT; a hard kill truncates the encode mid-write.
6283
- - **Record WebM (matroska-family), not MP4** — mp4 writes its moov atom at the
6284
- END, so a killed or crashed encode leaves an UNPLAYABLE file; webm stays
6285
- playable up to the last written frame and plays natively in the browser.
6286
- MP4's only edge is compatibility with some external players — transcode
6287
- afterwards if you truly need it, never record straight to it.
6641
+ - **Record WebM (matroska-family), not plain MP4** — mp4 writes its moov atom
6642
+ at the END, so a killed or crashed encode leaves an UNPLAYABLE file; webm
6643
+ stays playable up to the last written frame and plays natively in the
6644
+ browser. (\`ac-record\` sidesteps this with fragmented mp4.)
6288
6645
  - **\`-video_size\` must match the real screen** — x11grab does not default to
6289
6646
  it; read the geometry from \`xdotool getdisplaygeometry\` as above.
6290
- - **10–12 fps is right for a screen demo** — small files, legible UI motion;
6647
+ - **10–15 fps is right for a screen demo** — small files, legible UI motion;
6291
6648
  this is not video production.
6292
6649
  - **Write to the drive, not /tmp** — the recording must land in your working
6293
6650
  directory to persist and show up in Files; a file in /tmp dies with the
@@ -6599,6 +6956,7 @@ async function agent(opts) {
6599
6956
  runtime: (runtimeOpts) => opts.runtime.create(opts.sandbox, { ...runtimeOpts, agentManual: buildAgentContextDoc(process.env) }),
6600
6957
  agentId,
6601
6958
  ...opts.label !== undefined ? { label: opts.label } : {},
6959
+ ...opts.phase !== undefined ? { phase: opts.phase } : {},
6602
6960
  ...opts.budget?.turnsPerIteration !== undefined ? { turnsPerIteration: opts.budget.turnsPerIteration } : {},
6603
6961
  ...opts.budget?.maxIterations !== undefined ? { maxIterations: opts.budget.maxIterations } : {},
6604
6962
  ...opts.tools !== undefined ? { allowedTools: opts.tools } : {},
@@ -6669,7 +7027,21 @@ function clampPlanEntries(raw) {
6669
7027
  var TABLE_MAX_COLUMNS = 12;
6670
7028
  var TABLE_MAX_ROWS = 100;
6671
7029
  var TABLE_CELL_MAX_CHARS = 200;
6672
- var IMAGE_PATH_MAX_CHARS = 512;
7030
+ var DRIVE_PATH_MAX_CHARS = 512;
7031
+ function isPlausibleDrivePath(raw) {
7032
+ if (raw.trim().length === 0 || raw.length > DRIVE_PATH_MAX_CHARS)
7033
+ return false;
7034
+ if (raw.startsWith("-"))
7035
+ return false;
7036
+ if (/[\u0000-\u001f\u007f\\]/.test(raw))
7037
+ return false;
7038
+ if (/[|<>;`]|\$\(/.test(raw))
7039
+ return false;
7040
+ const trimmed = raw.replace(/^\/+|\/+$/g, "");
7041
+ if (trimmed.length === 0)
7042
+ return false;
7043
+ return trimmed.split("/").every((seg) => seg !== "" && seg !== "." && seg !== "..");
7044
+ }
6673
7045
  function clampCellString(s) {
6674
7046
  return s.length > TABLE_CELL_MAX_CHARS ? `${s.slice(0, TABLE_CELL_MAX_CHARS - 1)}…` : s;
6675
7047
  }
@@ -6860,7 +7232,7 @@ function parseDisplayMarker(line) {
6860
7232
  }
6861
7233
  case "document": {
6862
7234
  const path = str("path");
6863
- if (!path)
7235
+ if (!path || !isPlausibleDrivePath(path))
6864
7236
  return null;
6865
7237
  return {
6866
7238
  kind: "document",
@@ -6875,7 +7247,7 @@ function parseDisplayMarker(line) {
6875
7247
  }
6876
7248
  case "image": {
6877
7249
  const path = str("path");
6878
- if (!path || path.length > IMAGE_PATH_MAX_CHARS)
7250
+ if (!path || !isPlausibleDrivePath(path))
6879
7251
  return null;
6880
7252
  return {
6881
7253
  kind: "image",
@@ -7014,6 +7386,8 @@ function detectAgentcInvocation(command) {
7014
7386
  const w = words[i];
7015
7387
  if (SHELL_STOPPERS.has(w))
7016
7388
  break;
7389
+ if (/^\d*>&\d+$/.test(w))
7390
+ continue;
7017
7391
  if (w.startsWith("--")) {
7018
7392
  if (VALUE_FLAGS.has(w) && i + 1 < words.length && !SHELL_STOPPERS.has(words[i + 1])) {
7019
7393
  flags.set(w, words[i + 1]);
@@ -7026,6 +7400,7 @@ function detectAgentcInvocation(command) {
7026
7400
  const factorySlug = flags.get("--factory");
7027
7401
  const withSlug = factorySlug ? { factorySlug } : {};
7028
7402
  const uuid = (v) => v && UUID_RE.test(v) ? v : null;
7403
+ const drivePath = (v) => v && isPlausibleDrivePath(v) ? v : null;
7029
7404
  if (args[0] === "display") {
7030
7405
  switch (args[1]) {
7031
7406
  case "run": {
@@ -7036,19 +7411,27 @@ function detectAgentcInvocation(command) {
7036
7411
  const runId = uuid(args[2]);
7037
7412
  return runId ? { verb: "display-changes", runId, ...withSlug } : null;
7038
7413
  }
7039
- case "document":
7040
- return args[2] ? { verb: "display-document", path: args[2], ...withSlug } : null;
7414
+ case "document": {
7415
+ const path = drivePath(args[2]);
7416
+ return path ? { verb: "display-document", path, ...withSlug } : null;
7417
+ }
7041
7418
  case "plan":
7042
7419
  return { verb: "display-plan" };
7043
- case "image":
7044
- return args[2] ? { verb: "display-image", path: args[2], ...withSlug } : null;
7045
- case "preview":
7046
- return args[2] ? { verb: "display-preview", path: args[2] } : null;
7420
+ case "image": {
7421
+ const path = drivePath(args[2]);
7422
+ return path ? { verb: "display-image", path, ...withSlug } : null;
7423
+ }
7424
+ case "preview": {
7425
+ const path = drivePath(args[2]);
7426
+ return path ? { verb: "display-preview", path } : null;
7427
+ }
7047
7428
  case "table": {
7048
7429
  const file = flags.get("--file");
7049
7430
  const hasData = flags.has("--data");
7050
- if (file && !hasData)
7051
- return { verb: "display-table", source: "file", path: file };
7431
+ if (file && !hasData) {
7432
+ const path = drivePath(file);
7433
+ return path ? { verb: "display-table", source: "file", path } : null;
7434
+ }
7052
7435
  if (hasData && !file)
7053
7436
  return { verb: "display-table", source: "inline" };
7054
7437
  return null;
@@ -7060,11 +7443,12 @@ function detectAgentcInvocation(command) {
7060
7443
  case "diff": {
7061
7444
  const from = flags.get("--from");
7062
7445
  const to = flags.get("--to");
7063
- if (!args[2] || !from || !to)
7446
+ const path = drivePath(args[2]);
7447
+ if (!path || !from || !to)
7064
7448
  return null;
7065
7449
  if (!REVISION_SELECTOR_RE.test(from) || !REVISION_SELECTOR_RE.test(to))
7066
7450
  return null;
7067
- return { verb: "display-diff", path: args[2], from, to };
7451
+ return { verb: "display-diff", path, from, to };
7068
7452
  }
7069
7453
  case "ask": {
7070
7454
  const prompt = flags.get("--prompt");
@@ -7095,8 +7479,9 @@ function detectAgentcInvocation(command) {
7095
7479
  const runId = uuid(args[2]);
7096
7480
  return runId ? { verb: "run-get", runId } : null;
7097
7481
  }
7098
- if (args[0] === "files" && args[1] === "read" && args[2]) {
7099
- return { verb: "files-read", path: args[2], ...withSlug };
7482
+ if (args[0] === "files" && args[1] === "read") {
7483
+ const path = drivePath(args[2]);
7484
+ return path ? { verb: "files-read", path, ...withSlug } : null;
7100
7485
  }
7101
7486
  return null;
7102
7487
  }
@@ -7335,10 +7720,12 @@ export {
7335
7720
  shellWords,
7336
7721
  serveStep,
7337
7722
  serializeDisplayMarker,
7723
+ sandboxSizeLabel,
7338
7724
  runWorkflowSteps,
7339
7725
  runWorkflowSingleStep,
7340
7726
  runWorkflow,
7341
7727
  runProcessorChain,
7728
+ resolveSdkAlias,
7342
7729
  requireScope,
7343
7730
  requestPauseAndAwait,
7344
7731
  requestContextPath,
@@ -7367,6 +7754,8 @@ export {
7367
7754
  killSandboxById,
7368
7755
  killAllSandboxes,
7369
7756
  isWorkflow,
7757
+ isVercelSupportedSize,
7758
+ isPlausibleDrivePath,
7370
7759
  isPlatformE2bTemplateAlias,
7371
7760
  isE2bSupportedSize,
7372
7761
  invokeStep,
@@ -7375,6 +7764,7 @@ export {
7375
7764
  getSandboxQuotas,
7376
7765
  formatError,
7377
7766
  findDisplayMarker,
7767
+ explainBundleFailure,
7378
7768
  editTool,
7379
7769
  e2bMachineSpec,
7380
7770
  e2bDevboxTemplateRef,
@@ -7431,6 +7821,7 @@ export {
7431
7821
  WorkflowError,
7432
7822
  Verdict,
7433
7823
  VercelRunner,
7824
+ VERCEL_MEMORY_MB_PER_VCPU,
7434
7825
  TMP_DIR,
7435
7826
  TABLE_MAX_ROWS,
7436
7827
  TABLE_MAX_COLUMNS,
@@ -7441,8 +7832,13 @@ export {
7441
7832
  STEP_RESULT_PREFIX,
7442
7833
  STEP_PAUSE_PREFIX,
7443
7834
  STEP_ENV,
7835
+ SESSION_DEFAULT_SANDBOX_SIZE,
7836
+ SDK_SPECIFIER_ALIASES,
7837
+ SDK_PACKAGE,
7444
7838
  SANDBOX_VCPUS,
7445
7839
  SANDBOX_UNAVAILABLE_PREFIX,
7840
+ SANDBOX_SIZES,
7841
+ SANDBOX_MACHINES,
7446
7842
  ReservedKeyError,
7447
7843
  RequestContext,
7448
7844
  RUNNER_COMMAND,
@@ -7459,9 +7855,12 @@ export {
7459
7855
  LATEST_VERSION,
7460
7856
  EngineError,
7461
7857
  E2B_TEMPLATE_SIZES,
7858
+ E2B_MAX_VCPUS,
7859
+ E2B_MAX_MEMORY_MB,
7462
7860
  E2B_DEVBOX_TEMPLATE,
7463
7861
  E2B_DEVBOX_SPEC,
7464
7862
  E2B_DEVBOX_RECIPE_VERSION,
7863
+ DRIVE_PATH_MAX_CHARS,
7465
7864
  DISPLAY_MARKER_VERSION,
7466
7865
  DISPLAY_MARKER_KEY,
7467
7866
  DIRECTIVE_MARKER_SUFFIX,
@@ -7469,7 +7868,7 @@ export {
7469
7868
  DEFAULT_SANDBOX_SIZE,
7470
7869
  DEFAULT_CLAUDE_MODEL,
7471
7870
  ClaudeRunner,
7472
- CLAUDE_CODE_THINKING_TOKENS,
7871
+ CLAUDE_CODE_EFFORT_LEVELS,
7473
7872
  CLAUDE_CODE_ACP_ADAPTER,
7474
7873
  CHART_MAX_SERIES,
7475
7874
  CHART_MAX_POINTS_PER_SERIES,