@agent-compose/sdk 0.8.0 → 0.8.1

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.
@@ -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
  }
@@ -2466,6 +2526,7 @@ class AcpClientPeer {
2466
2526
  }
2467
2527
  // src/utils/bundler.ts
2468
2528
  import { createHash as createHash2 } from "node:crypto";
2529
+ import { dirname as dirname2 } from "node:path";
2469
2530
  import { parse as babelParse } from "@babel/parser";
2470
2531
 
2471
2532
  // src/utils/source-loader.ts
@@ -2495,15 +2556,119 @@ class WorkflowSourceValidationError extends Error {
2495
2556
  this.name = "WorkflowSourceValidationError";
2496
2557
  }
2497
2558
  }
2559
+ var SDK_PACKAGE = "@agent-compose/sdk";
2560
+ var SDK_SPECIFIER_ALIASES = [
2561
+ "agentc/sdk",
2562
+ "@agentc/sdk",
2563
+ "agentc-sdk",
2564
+ "agent-compose/sdk",
2565
+ "agentcompose/sdk",
2566
+ "@agentcompose/sdk",
2567
+ "agent-compose-sdk"
2568
+ ];
2569
+ var SDK_ALIAS_SET = new Set(SDK_SPECIFIER_ALIASES);
2570
+ function resolveSdkAlias(specifier) {
2571
+ return SDK_ALIAS_SET.has(specifier) ? SDK_PACKAGE : null;
2572
+ }
2573
+ function aliasFilter() {
2574
+ const alternation = SDK_SPECIFIER_ALIASES.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
2575
+ return new RegExp(`^(?:${alternation})$`);
2576
+ }
2577
+ function sdkAliasPlugin(bun) {
2578
+ return {
2579
+ name: "agent-compose-sdk-alias",
2580
+ setup(build) {
2581
+ build.onResolve({ filter: aliasFilter() }, (args) => {
2582
+ const target = resolveSdkAlias(args.path);
2583
+ if (!target)
2584
+ return;
2585
+ const from = args.importer ? dirname2(args.importer) : args.resolveDir;
2586
+ try {
2587
+ return { path: bun.resolveSync(target, from && from.length > 0 ? from : process.cwd()) };
2588
+ } catch {
2589
+ return;
2590
+ }
2591
+ });
2592
+ }
2593
+ };
2594
+ }
2595
+ function readUnresolvedSpecifier(diag) {
2596
+ if (!diag || typeof diag !== "object")
2597
+ return null;
2598
+ const d = diag;
2599
+ if (typeof d.specifier === "string" && d.specifier.length > 0)
2600
+ return d.specifier;
2601
+ const message = typeof d.message === "string" ? d.message : "";
2602
+ const m = /Could not resolve:?\s*"([^"]+)"/.exec(message);
2603
+ return m ? m[1] : null;
2604
+ }
2605
+ function flattenDiagnostics(failure) {
2606
+ const out = [];
2607
+ const seen = new Set;
2608
+ const walk = (node, depth) => {
2609
+ if (!node || depth > 4 || seen.has(node))
2610
+ return;
2611
+ seen.add(node);
2612
+ if (Array.isArray(node)) {
2613
+ for (const sub of node)
2614
+ walk(sub, depth + 1);
2615
+ return;
2616
+ }
2617
+ out.push(node);
2618
+ if (typeof node === "object") {
2619
+ const n = node;
2620
+ if (Array.isArray(n.errors))
2621
+ for (const sub of n.errors)
2622
+ walk(sub, depth + 1);
2623
+ if (n.cause)
2624
+ walk(n.cause, depth + 1);
2625
+ }
2626
+ };
2627
+ walk(failure, 0);
2628
+ return out;
2629
+ }
2630
+ function diagnosticText(diag) {
2631
+ if (diag instanceof Error)
2632
+ return diag.message || String(diag);
2633
+ if (diag && typeof diag === "object" && typeof diag.message === "string") {
2634
+ return diag.message;
2635
+ }
2636
+ return String(diag);
2637
+ }
2638
+ function explainBundleFailure(err, label) {
2639
+ const diagnostics = flattenDiagnostics(err);
2640
+ const unresolved = [];
2641
+ for (const diag of diagnostics) {
2642
+ const specifier = readUnresolvedSpecifier(diag);
2643
+ if (specifier && !unresolved.includes(specifier))
2644
+ unresolved.push(specifier);
2645
+ }
2646
+ if (unresolved.length > 0) {
2647
+ const quoted = unresolved.map((s) => `"${s}"`).join(", ");
2648
+ 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.`;
2649
+ }
2650
+ const detail = diagnostics.map(diagnosticText).filter((t) => t.length > 0 && t !== "Bundle failed").join(`
2651
+ `);
2652
+ return `Failed to bundle ${label}:
2653
+ ${detail || "the bundler reported no diagnostics"}`;
2654
+ }
2498
2655
  async function bundle(path, label) {
2499
2656
  const bun = globalThis;
2500
2657
  if (!bun.Bun?.build)
2501
2658
  throw new Error("bundleWorkflow requires the Bun runtime (Bun.build)");
2502
- const result = await bun.Bun.build({ entrypoints: [path], format: "esm", target: "node" });
2659
+ let result;
2660
+ try {
2661
+ result = await bun.Bun.build({
2662
+ entrypoints: [path],
2663
+ format: "esm",
2664
+ target: "node",
2665
+ plugins: [sdkAliasPlugin(bun.Bun)]
2666
+ });
2667
+ } catch (err) {
2668
+ throw new WorkflowSourceValidationError(explainBundleFailure(err, label));
2669
+ }
2503
2670
  if (!result.success) {
2504
- throw new Error(`Failed to bundle ${label}:
2505
- ${result.logs.map((l) => l.message).join(`
2506
- `)}`);
2671
+ throw new WorkflowSourceValidationError(explainBundleFailure(result.logs, label));
2507
2672
  }
2508
2673
  return result.outputs[0].text();
2509
2674
  }
@@ -2621,7 +2786,8 @@ async function bundleWorkflow(workflowPath, overrides) {
2621
2786
  ...metadata.connectors !== undefined ? { connectors: metadata.connectors } : {},
2622
2787
  ...metadata.connectorOperation !== undefined ? { connectorOperation: metadata.connectorOperation } : {},
2623
2788
  ...metadata.invokePolicy !== undefined ? { invokePolicy: metadata.invokePolicy } : {},
2624
- ...metadata.environmentBuild !== undefined ? { environmentBuild: metadata.environmentBuild } : {}
2789
+ ...metadata.environmentBuild !== undefined ? { environmentBuild: metadata.environmentBuild } : {},
2790
+ ...metadata.factoryDrive !== undefined ? { factoryDrive: metadata.factoryDrive } : {}
2625
2791
  };
2626
2792
  }
2627
2793
  function extractIOSchema(zodSchema) {
@@ -3019,7 +3185,8 @@ async function agentLoop(opts) {
3019
3185
  label,
3020
3186
  allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS,
3021
3187
  ...client.model != null ? { model: client.model } : {},
3022
- ...client.kind != null ? { runtimeKind: client.kind } : {}
3188
+ ...client.kind != null ? { runtimeKind: client.kind } : {},
3189
+ ...opts.phase != null ? { phase: opts.phase } : {}
3023
3190
  });
3024
3191
  }
3025
3192
  const flushState = async (currentIteration) => {
@@ -3986,7 +4153,7 @@ var codexSpec = {
3986
4153
  "--skip-git-repo-check",
3987
4154
  "--dangerously-bypass-approvals-and-sandbox",
3988
4155
  ...model ? ["-m", shellQuote(model)] : [],
3989
- ...effort ? ["-c", `model_reasoning_effort=${effort}`] : [],
4156
+ ...effort ? ["-c", `model_reasoning_effort=${effort === "max" ? "xhigh" : effort}`] : [],
3990
4157
  ...cwd ? ["-C", shellQuote(cwd)] : []
3991
4158
  ].join(" ");
3992
4159
  const exec = sessionId ? `codex exec resume ${shellQuote(sessionId)} ${flags}` : `codex exec ${flags}`;
@@ -4208,7 +4375,7 @@ function toolResultText(content) {
4208
4375
  }
4209
4376
  return content == null ? "" : JSON.stringify(content) ?? "";
4210
4377
  }
4211
- var CLAUDE_CODE_THINKING_TOKENS = { low: 2048, medium: 8192, high: 16384 };
4378
+ var CLAUDE_CODE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
4212
4379
  var claudeCodeSpec = {
4213
4380
  kind: "claude-code",
4214
4381
  authEnv: "ANTHROPIC_API_KEY",
@@ -4224,14 +4391,15 @@ var claudeCodeSpec = {
4224
4391
  "--include-partial-messages",
4225
4392
  "--dangerously-skip-permissions",
4226
4393
  ...model ? [`--model ${shellQuote(model)}`] : [],
4394
+ ...effort ? [`--effort ${effort}`] : [],
4227
4395
  ...sessionId ? [`--resume ${shellQuote(sessionId)}`] : []
4228
4396
  ].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)}`;
4397
+ return `${cwd ? `cd ${shellQuote(cwd)} && ` : ""}IS_SANDBOX=1 claude ${flags} < ${shellQuote(promptPath)}`;
4231
4398
  },
4232
4399
  extractSessionId: (p) => typeof p.session_id === "string" ? p.session_id : undefined,
4233
4400
  mapEvent: (p) => {
4234
4401
  const ts = now10();
4402
+ const parent = typeof p.parent_tool_use_id === "string" && p.parent_tool_use_id.length > 0 ? { parentToolUseId: p.parent_tool_use_id } : {};
4235
4403
  switch (p.type) {
4236
4404
  case "assistant": {
4237
4405
  const message = p.message;
@@ -4249,6 +4417,7 @@ var claudeCodeSpec = {
4249
4417
  toolName: String(b.name ?? "tool"),
4250
4418
  toolInput: b.input ?? {},
4251
4419
  toolUseId: String(b.id ?? ""),
4420
+ ...parent,
4252
4421
  timestamp: ts
4253
4422
  }];
4254
4423
  }
@@ -4263,6 +4432,7 @@ var claudeCodeSpec = {
4263
4432
  toolUseId: String(b.tool_use_id ?? ""),
4264
4433
  output: toolResultText(b.content),
4265
4434
  isError: b.is_error === true,
4435
+ ...parent,
4266
4436
  timestamp: ts
4267
4437
  }] : []);
4268
4438
  }
@@ -4892,7 +5062,7 @@ var vercelProviderDef = {
4892
5062
 
4893
5063
  // src/sandbox/providers/local.ts
4894
5064
  import { promises as fs2 } from "node:fs";
4895
- import { dirname as dirname2 } from "node:path";
5065
+ import { dirname as dirname3 } from "node:path";
4896
5066
  import { spawn } from "node:child_process";
4897
5067
  import { Readable, Writable } from "node:stream";
4898
5068
  function makeLocalSandboxProvider() {
@@ -4958,7 +5128,7 @@ function makeLocalSandboxProvider() {
4958
5128
  },
4959
5129
  files: {
4960
5130
  async write(path, content) {
4961
- await fs2.mkdir(dirname2(path), { recursive: true });
5131
+ await fs2.mkdir(dirname3(path), { recursive: true });
4962
5132
  await fs2.writeFile(path, content);
4963
5133
  },
4964
5134
  async read(path) {
@@ -5509,6 +5679,15 @@ async function runWorkflow(wf, ctx, opts) {
5509
5679
  }
5510
5680
  }
5511
5681
  // src/workflows/invoke-child.ts
5682
+ function deriveInvokeChildIdempotencyKey(parentRunId, childName) {
5683
+ const step = getActiveStep();
5684
+ if (!step)
5685
+ return null;
5686
+ const ordinal = nextPauseOrdinalInActiveStep(`invoke-child:${childName}`);
5687
+ if (ordinal === null)
5688
+ return null;
5689
+ return `invoke-child:${parentRunId}:step${step.stepIndex}:${childName}:${ordinal}`;
5690
+ }
5512
5691
  function buildInvokeChild(runId, opts = {}) {
5513
5692
  let childClient = null;
5514
5693
  const getChildClient = () => {
@@ -5522,11 +5701,15 @@ function buildInvokeChild(runId, opts = {}) {
5522
5701
  childClient = new AgentComposeClient({ apiKey, baseUrl });
5523
5702
  return childClient;
5524
5703
  };
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
- });
5704
+ return (name, input, childOpts) => {
5705
+ const idempotencyKey = childOpts?.idempotencyKey ?? deriveInvokeChildIdempotencyKey(runId, name) ?? undefined;
5706
+ return getChildClient().invokeAndWait(name, input, {
5707
+ ...childOpts,
5708
+ ...idempotencyKey !== undefined ? { idempotencyKey } : {},
5709
+ parentRunId: runId,
5710
+ factorySlug: childOpts?.factorySlug ?? opts.defaultFactorySlug ?? process.env.AGENT_COMPOSE_FACTORY ?? process.env.AGENT_COMPOSE_FACTORY_SLUG ?? "default"
5711
+ });
5712
+ };
5530
5713
  }
5531
5714
  // src/workflow-steps/step.ts
5532
5715
  var CONTROL_CHARS = /[\u0000-\u001f\u007f]/;
@@ -6599,6 +6782,7 @@ async function agent(opts) {
6599
6782
  runtime: (runtimeOpts) => opts.runtime.create(opts.sandbox, { ...runtimeOpts, agentManual: buildAgentContextDoc(process.env) }),
6600
6783
  agentId,
6601
6784
  ...opts.label !== undefined ? { label: opts.label } : {},
6785
+ ...opts.phase !== undefined ? { phase: opts.phase } : {},
6602
6786
  ...opts.budget?.turnsPerIteration !== undefined ? { turnsPerIteration: opts.budget.turnsPerIteration } : {},
6603
6787
  ...opts.budget?.maxIterations !== undefined ? { maxIterations: opts.budget.maxIterations } : {},
6604
6788
  ...opts.tools !== undefined ? { allowedTools: opts.tools } : {},
@@ -6669,7 +6853,21 @@ function clampPlanEntries(raw) {
6669
6853
  var TABLE_MAX_COLUMNS = 12;
6670
6854
  var TABLE_MAX_ROWS = 100;
6671
6855
  var TABLE_CELL_MAX_CHARS = 200;
6672
- var IMAGE_PATH_MAX_CHARS = 512;
6856
+ var DRIVE_PATH_MAX_CHARS = 512;
6857
+ function isPlausibleDrivePath(raw) {
6858
+ if (raw.trim().length === 0 || raw.length > DRIVE_PATH_MAX_CHARS)
6859
+ return false;
6860
+ if (raw.startsWith("-"))
6861
+ return false;
6862
+ if (/[\u0000-\u001f\u007f\\]/.test(raw))
6863
+ return false;
6864
+ if (/[|<>;`]|\$\(/.test(raw))
6865
+ return false;
6866
+ const trimmed = raw.replace(/^\/+|\/+$/g, "");
6867
+ if (trimmed.length === 0)
6868
+ return false;
6869
+ return trimmed.split("/").every((seg) => seg !== "" && seg !== "." && seg !== "..");
6870
+ }
6673
6871
  function clampCellString(s) {
6674
6872
  return s.length > TABLE_CELL_MAX_CHARS ? `${s.slice(0, TABLE_CELL_MAX_CHARS - 1)}…` : s;
6675
6873
  }
@@ -6860,7 +7058,7 @@ function parseDisplayMarker(line) {
6860
7058
  }
6861
7059
  case "document": {
6862
7060
  const path = str("path");
6863
- if (!path)
7061
+ if (!path || !isPlausibleDrivePath(path))
6864
7062
  return null;
6865
7063
  return {
6866
7064
  kind: "document",
@@ -6875,7 +7073,7 @@ function parseDisplayMarker(line) {
6875
7073
  }
6876
7074
  case "image": {
6877
7075
  const path = str("path");
6878
- if (!path || path.length > IMAGE_PATH_MAX_CHARS)
7076
+ if (!path || !isPlausibleDrivePath(path))
6879
7077
  return null;
6880
7078
  return {
6881
7079
  kind: "image",
@@ -7014,6 +7212,8 @@ function detectAgentcInvocation(command) {
7014
7212
  const w = words[i];
7015
7213
  if (SHELL_STOPPERS.has(w))
7016
7214
  break;
7215
+ if (/^\d*>&\d+$/.test(w))
7216
+ continue;
7017
7217
  if (w.startsWith("--")) {
7018
7218
  if (VALUE_FLAGS.has(w) && i + 1 < words.length && !SHELL_STOPPERS.has(words[i + 1])) {
7019
7219
  flags.set(w, words[i + 1]);
@@ -7026,6 +7226,7 @@ function detectAgentcInvocation(command) {
7026
7226
  const factorySlug = flags.get("--factory");
7027
7227
  const withSlug = factorySlug ? { factorySlug } : {};
7028
7228
  const uuid = (v) => v && UUID_RE.test(v) ? v : null;
7229
+ const drivePath = (v) => v && isPlausibleDrivePath(v) ? v : null;
7029
7230
  if (args[0] === "display") {
7030
7231
  switch (args[1]) {
7031
7232
  case "run": {
@@ -7036,19 +7237,27 @@ function detectAgentcInvocation(command) {
7036
7237
  const runId = uuid(args[2]);
7037
7238
  return runId ? { verb: "display-changes", runId, ...withSlug } : null;
7038
7239
  }
7039
- case "document":
7040
- return args[2] ? { verb: "display-document", path: args[2], ...withSlug } : null;
7240
+ case "document": {
7241
+ const path = drivePath(args[2]);
7242
+ return path ? { verb: "display-document", path, ...withSlug } : null;
7243
+ }
7041
7244
  case "plan":
7042
7245
  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;
7246
+ case "image": {
7247
+ const path = drivePath(args[2]);
7248
+ return path ? { verb: "display-image", path, ...withSlug } : null;
7249
+ }
7250
+ case "preview": {
7251
+ const path = drivePath(args[2]);
7252
+ return path ? { verb: "display-preview", path } : null;
7253
+ }
7047
7254
  case "table": {
7048
7255
  const file = flags.get("--file");
7049
7256
  const hasData = flags.has("--data");
7050
- if (file && !hasData)
7051
- return { verb: "display-table", source: "file", path: file };
7257
+ if (file && !hasData) {
7258
+ const path = drivePath(file);
7259
+ return path ? { verb: "display-table", source: "file", path } : null;
7260
+ }
7052
7261
  if (hasData && !file)
7053
7262
  return { verb: "display-table", source: "inline" };
7054
7263
  return null;
@@ -7060,11 +7269,12 @@ function detectAgentcInvocation(command) {
7060
7269
  case "diff": {
7061
7270
  const from = flags.get("--from");
7062
7271
  const to = flags.get("--to");
7063
- if (!args[2] || !from || !to)
7272
+ const path = drivePath(args[2]);
7273
+ if (!path || !from || !to)
7064
7274
  return null;
7065
7275
  if (!REVISION_SELECTOR_RE.test(from) || !REVISION_SELECTOR_RE.test(to))
7066
7276
  return null;
7067
- return { verb: "display-diff", path: args[2], from, to };
7277
+ return { verb: "display-diff", path, from, to };
7068
7278
  }
7069
7279
  case "ask": {
7070
7280
  const prompt = flags.get("--prompt");
@@ -7095,8 +7305,9 @@ function detectAgentcInvocation(command) {
7095
7305
  const runId = uuid(args[2]);
7096
7306
  return runId ? { verb: "run-get", runId } : null;
7097
7307
  }
7098
- if (args[0] === "files" && args[1] === "read" && args[2]) {
7099
- return { verb: "files-read", path: args[2], ...withSlug };
7308
+ if (args[0] === "files" && args[1] === "read") {
7309
+ const path = drivePath(args[2]);
7310
+ return path ? { verb: "files-read", path, ...withSlug } : null;
7100
7311
  }
7101
7312
  return null;
7102
7313
  }
@@ -157,6 +157,16 @@ export interface ConversationDetail {
157
157
  name: string;
158
158
  description: string;
159
159
  }> | null;
160
+ /** Where this conversation's UNMERGED work lives: a cloud session writes
161
+ * its own private drive branch, and every drive read a client makes for
162
+ * THAT factory must carry `?branch=` or it resolves against `main`,
163
+ * where session-born files do not exist. Non-null only for a session
164
+ * with a drive branch and a resolvable factory; null/absent = main
165
+ * only (read loosely — older servers omit it). */
166
+ sessionDrive?: {
167
+ factorySlug: string;
168
+ branch: string;
169
+ } | null;
160
170
  viewerLastReadAt: string | null;
161
171
  /** The caller's role in this conversation (ADR-0045) — drives client
162
172
  * affordances only; the server remains the authority on every action.
@@ -226,6 +236,27 @@ export interface CreateCloudSessionInput {
226
236
  * profile ids, or "none" for a bare session. Omitted = the owner's
227
237
  * default profile. */
228
238
  connectorProfileId?: string;
239
+ /** Idempotent dependency-setup command run by every fresh sandbox acquire
240
+ * after the drive mounts (`bun install`, `npm ci`, …). Strict charset —
241
+ * no shell metacharacters (400). Mutually exclusive with `templateId`
242
+ * (a snapshot carries its own installed state). */
243
+ setupCommand?: string;
244
+ /** Provenance of a session born by `agentc session import`: where the
245
+ * imported local session lived and which harness thread seeded it. */
246
+ handoffOrigin?: {
247
+ cwd: string;
248
+ sourceAcpSessionId: string | null;
249
+ agentKind: string;
250
+ mirrorConversationId: string | null;
251
+ };
252
+ /** Imported claude memories (session-import spec §memories): guest-home
253
+ * seeds re-applied on every fresh acquire. Paths under .claude/ only;
254
+ * strict base64; 256KB decoded total (server-capped). */
255
+ seedFiles?: Array<{
256
+ path: string;
257
+ contentB64: string;
258
+ mode: "write" | "append";
259
+ }>;
229
260
  }
230
261
  export interface CloudSessionCreated {
231
262
  conversation: ConversationRow;
@@ -89,6 +89,11 @@ export interface RegisterWorkflowInput {
89
89
  * server skips the /factory mount for its runs (#13). See
90
90
  * `WorkflowMetadata.environmentBuild`. */
91
91
  environmentBuild?: boolean;
92
+ /** Drive requirement declared via `defineWorkflow({ factoryDrive })`.
93
+ * Omitted ⇒ `"required"` on the server (a failed /factory mount fails
94
+ * the run); `"none"` = explicit no-drive opt-out. See
95
+ * `WorkflowMetadata.factoryDrive`. */
96
+ factoryDrive?: "required" | "none";
92
97
  /** Factory slug. Defaults to `"default"`. */
93
98
  factorySlug?: string;
94
99
  }
@@ -181,6 +186,26 @@ export interface FactoryFileWriteResult {
181
186
  sizeBytes: number;
182
187
  created: boolean;
183
188
  }
189
+ /** A minted local-mount grant: the user branch, the signed gateway token, and
190
+ * where to dial. The backing `conversationId` is the mount's review surface
191
+ * (its branch changes list + merge ride the session-changes routes). */
192
+ export interface DriveMountSession {
193
+ conversationId: string;
194
+ branch: string;
195
+ /** Signed gateway mount token (user principal, exclusive mode). Treat as a
196
+ * secret: write it to a 0600 token file, never argv/env of children. */
197
+ token: string;
198
+ gatewayWsUrl: string;
199
+ /** Token expiry, unix seconds — re-mint (same `conversationId`) before it. */
200
+ expiresAtS: number;
201
+ diskId: string;
202
+ }
203
+ export interface CreateDriveMountSessionInput {
204
+ /** Re-mint for an existing mount session (remount / token refresh). */
205
+ conversationId?: string;
206
+ /** The mounting machine's hostname — carried in the mount's title. */
207
+ host?: string;
208
+ }
184
209
  /** A factory: a project-level grouping of workflows inside a team. */
185
210
  export interface FactoryRow {
186
211
  id: string;
@@ -9,7 +9,18 @@
9
9
  import type { SandboxNetworkPolicy, SandboxSize } from "../sandbox.js";
10
10
  import type { SnapshotConfig } from "./workflow-metadata.js";
11
11
  import type { RunContext } from "./api-scopes.js";
12
+ import type { BundledWorkflow } from "../utils/bundler.js";
12
13
  export type RunState = "running" | "success" | "failed" | "abandoned" | "canceled";
14
+ /** INLINE invoke payload (`POST /factories/:slug/invoke`): everything
15
+ * `bundleWorkflow` produced — source, the REQUIRED manifest binding the
16
+ * bytes, the workflow plan, and the build fields — plus the name the run
17
+ * reports as its workflow. The server runs it through the same
18
+ * validate/build core as registration but writes NO registry row: the run
19
+ * snapshots the validated source (auditable, replayable) and the name
20
+ * stays free for real registrations. */
21
+ export type InlineWorkflowPayload = BundledWorkflow & {
22
+ name: string;
23
+ };
13
24
  export interface InvokeWorkflowOptions {
14
25
  /** Per-invocation snapshot config override. `snapshots.bootFrom`
15
26
  * replaces the template's boot source; `snapshots.saveLatest` and
@@ -38,11 +49,45 @@ export interface InvokeWorkflowOptions {
38
49
  * with the same key inside the server's dedup window returns the original
39
50
  * run instead of starting a new one (matches `resumePause`'s pattern). */
40
51
  idempotencyKey?: string;
41
- }
52
+ /** Who pays for this run's model calls:
53
+ *
54
+ * - `"default"` (Auto) — the initiating human's connected subscription
55
+ * when one is enabled for workflows, else platform credits;
56
+ * - `"platform"` — always platform credits (metered);
57
+ * - `"subscription"` — REQUIRE the initiating human's plan. If it
58
+ * cannot be honored the invoke FAILS (412) rather than quietly
59
+ * spending credits;
60
+ * - `"byok"` — a factory secret named by `fundingSecret`, injected as
61
+ * the runtime's raw provider key. Never metered.
62
+ *
63
+ * Omitted → the workflow's own default (its `settings.funding`), else
64
+ * Auto. */
65
+ funding?: FundingChoice;
66
+ /** `funding: "byok"` only — the FACTORY SECRET NAME whose value funds the
67
+ * run. Must be a provider key variable (ANTHROPIC_API_KEY,
68
+ * OPENAI_API_KEY, CODEX_API_KEY, OPENROUTER_API_KEY) — that is where the
69
+ * sandbox's runtimes read it. The name only; the value never leaves the
70
+ * server's secret store. */
71
+ fundingSecret?: string;
72
+ }
73
+ /** Inference-funding choice for a run — a lane the caller PINS, or
74
+ * `"default"` (Auto, the automatic ladder). Mirrors the same vocabulary
75
+ * cloud sessions use. */
76
+ export type FundingChoice = "default" | "platform" | "subscription" | "byok";
42
77
  export interface InvokeAndWaitOptions extends InvokeWorkflowOptions {
43
78
  timeoutMs?: number;
44
79
  pollIntervalMs?: number;
45
80
  }
81
+ /** Options for `invokeInline` — the named-invoke options plus the run-title
82
+ * override (an inline run has no registered template to inherit one from). */
83
+ export interface InvokeInlineOptions extends InvokeWorkflowOptions {
84
+ /** Run title override — defaults to the workflow name. */
85
+ title?: string;
86
+ }
87
+ export interface InvokeInlineAndWaitOptions extends InvokeInlineOptions {
88
+ timeoutMs?: number;
89
+ pollIntervalMs?: number;
90
+ }
46
91
  export interface InvokeResult {
47
92
  id: string;
48
93
  }