@odla-ai/cli 0.42.2 → 0.43.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.
package/dist/index.cjs CHANGED
@@ -452,10 +452,10 @@ function isManagedDevVar(line2) {
452
452
  const match = line2.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
453
453
  return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
454
454
  }
455
- function writePrivateText(path, text3) {
455
+ function writePrivateText(path, text4) {
456
456
  (0, import_node_fs4.mkdirSync)((0, import_node_path4.dirname)(path), { recursive: true });
457
457
  const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
458
- (0, import_node_fs4.writeFileSync)(temporary, text3, { mode: 384 });
458
+ (0, import_node_fs4.writeFileSync)(temporary, text4, { mode: 384 });
459
459
  (0, import_node_fs4.chmodSync)(temporary, 384);
460
460
  (0, import_node_fs4.renameSync)(temporary, path);
461
461
  }
@@ -895,12 +895,12 @@ async function readAdminAiAudit(request3) {
895
895
  }
896
896
  }
897
897
  async function responseBody(response2) {
898
- const text3 = await response2.text();
899
- if (!text3) return {};
898
+ const text4 = await response2.text();
899
+ if (!text4) return {};
900
900
  try {
901
- return JSON.parse(text3);
901
+ return JSON.parse(text4);
902
902
  } catch {
903
- return { message: text3.slice(0, 300) };
903
+ return { message: text4.slice(0, 300) };
904
904
  }
905
905
  }
906
906
  function apiError(status, body) {
@@ -1002,12 +1002,12 @@ function timestamp2(value2) {
1002
1002
  return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
1003
1003
  }
1004
1004
  async function responseBody2(res) {
1005
- const text3 = await res.text();
1006
- if (!text3) return {};
1005
+ const text4 = await res.text();
1006
+ if (!text4) return {};
1007
1007
  try {
1008
- return JSON.parse(text3);
1008
+ return JSON.parse(text4);
1009
1009
  } catch {
1010
- return { message: text3.slice(0, 300) };
1010
+ return { message: text4.slice(0, 300) };
1011
1011
  }
1012
1012
  }
1013
1013
  function apiError2(action2, status, body) {
@@ -1183,12 +1183,12 @@ function catalogModels(body) {
1183
1183
  return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
1184
1184
  }
1185
1185
  async function responseBody3(res) {
1186
- const text3 = await res.text();
1187
- if (!text3) return {};
1186
+ const text4 = await res.text();
1187
+ if (!text4) return {};
1188
1188
  try {
1189
- return JSON.parse(text3);
1189
+ return JSON.parse(text4);
1190
1190
  } catch {
1191
- return { message: text3.slice(0, 300) };
1191
+ return { message: text4.slice(0, 300) };
1192
1192
  }
1193
1193
  }
1194
1194
  function apiError3(action2, status, body) {
@@ -2557,8 +2557,8 @@ async function appImport(options) {
2557
2557
  const out = options.stdout ?? console;
2558
2558
  const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
2559
2559
  const { tenant } = resolveTenant(cfg, options.env);
2560
- const text3 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs9.readFileSync)(0, "utf8")))() : (0, import_node_fs9.readFileSync)(options.file, "utf8");
2561
- const { format, sources } = (0, import_import.parseImport)(text3, options.ns);
2560
+ const text4 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs9.readFileSync)(0, "utf8")))() : (0, import_node_fs9.readFileSync)(options.file, "utf8");
2561
+ const { format, sources } = (0, import_import.parseImport)(text4, options.ns);
2562
2562
  if (format === "namespace-map" && options.ns) {
2563
2563
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
2564
2564
  }
@@ -2769,7 +2769,7 @@ var EXTENSIONS = {
2769
2769
  "text/html": "html",
2770
2770
  "application/json": "json"
2771
2771
  };
2772
- var encode = (text3) => new TextEncoder().encode(text3);
2772
+ var encode = (text4) => new TextEncoder().encode(text4);
2773
2773
  function assetFileName(uuid, mime) {
2774
2774
  const ext = EXTENSIONS[mime.split(";")[0].trim().toLowerCase()] ?? "bin";
2775
2775
  return `${uuid.replace(/[^a-zA-Z0-9._-]/g, "_")}.${ext}`;
@@ -3679,9 +3679,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
3679
3679
  }
3680
3680
  throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
3681
3681
  }
3682
- function errorCode(text3) {
3682
+ function errorCode(text4) {
3683
3683
  try {
3684
- const body = JSON.parse(text3);
3684
+ const body = JSON.parse(text4);
3685
3685
  return typeof body.error?.code === "string" ? body.error.code : null;
3686
3686
  } catch {
3687
3687
  return null;
@@ -4416,15 +4416,15 @@ function readWranglerConfig(path) {
4416
4416
  return null;
4417
4417
  }
4418
4418
  }
4419
- function stripJsonComments(text3) {
4419
+ function stripJsonComments(text4) {
4420
4420
  let result = "";
4421
4421
  let inString = false;
4422
- for (let i = 0; i < text3.length; i++) {
4423
- const ch = text3[i];
4422
+ for (let i = 0; i < text4.length; i++) {
4423
+ const ch = text4[i];
4424
4424
  if (inString) {
4425
4425
  result += ch;
4426
4426
  if (ch === "\\") {
4427
- result += text3[i + 1] ?? "";
4427
+ result += text4[i + 1] ?? "";
4428
4428
  i++;
4429
4429
  } else if (ch === '"') {
4430
4430
  inString = false;
@@ -4436,14 +4436,14 @@ function stripJsonComments(text3) {
4436
4436
  result += ch;
4437
4437
  continue;
4438
4438
  }
4439
- if (ch === "/" && text3[i + 1] === "/") {
4440
- while (i < text3.length && text3[i] !== "\n") i++;
4439
+ if (ch === "/" && text4[i + 1] === "/") {
4440
+ while (i < text4.length && text4[i] !== "\n") i++;
4441
4441
  result += "\n";
4442
4442
  continue;
4443
4443
  }
4444
- if (ch === "/" && text3[i + 1] === "*") {
4444
+ if (ch === "/" && text4[i + 1] === "*") {
4445
4445
  i += 2;
4446
- while (i < text3.length && !(text3[i] === "*" && text3[i + 1] === "/")) i++;
4446
+ while (i < text4.length && !(text4[i] === "*" && text4[i + 1] === "/")) i++;
4447
4447
  i++;
4448
4448
  continue;
4449
4449
  }
@@ -4957,9 +4957,9 @@ function initProject(options) {
4957
4957
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
4958
4958
  out.log("updated .gitignore for local odla credentials");
4959
4959
  }
4960
- function writeIfMissing(path, text3) {
4960
+ function writeIfMissing(path, text4) {
4961
4961
  if ((0, import_node_fs14.existsSync)(path)) return;
4962
- (0, import_node_fs14.writeFileSync)(path, text3);
4962
+ (0, import_node_fs14.writeFileSync)(path, text4);
4963
4963
  }
4964
4964
  function configTemplate(input) {
4965
4965
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -5171,13 +5171,13 @@ async function secretsSetClerkKey(options) {
5171
5171
  body: JSON.stringify({ value: value2 })
5172
5172
  });
5173
5173
  if (!res.ok) {
5174
- const text3 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
5175
- throw new Error(`store Clerk secret key failed (${res.status}): ${text3 || "request failed"}`);
5174
+ const text4 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
5175
+ throw new Error(`store Clerk secret key failed (${res.status}): ${text4 || "request failed"}`);
5176
5176
  }
5177
5177
  out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
5178
5178
  }
5179
- function scrubValue(text3, value2) {
5180
- return redactSecrets(text3).split(value2).join("[value redacted]");
5179
+ function scrubValue(text4, value2) {
5180
+ return redactSecrets(text4).split(value2).join("[value redacted]");
5181
5181
  }
5182
5182
  async function resolveVaultWrite(options) {
5183
5183
  const out = options.stdout ?? console;
@@ -5469,8 +5469,8 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
5469
5469
  harnesses: installations
5470
5470
  };
5471
5471
  }
5472
- function pathsUnder(root, paths) {
5473
- return [...paths].map((path) => (0, import_node_path15.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path15.sep}`) && !(0, import_node_path15.isAbsolute)(path)).sort();
5472
+ function pathsUnder(root, paths2) {
5473
+ return [...paths2].map((path) => (0, import_node_path15.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path15.sep}`) && !(0, import_node_path15.isAbsolute)(path)).sort();
5474
5474
  }
5475
5475
  function normalizeHarnesses(values, global) {
5476
5476
  const requested = values?.length ? values : ["claude"];
@@ -5899,10 +5899,10 @@ var import_node_fs16 = require("fs");
5899
5899
  var import_node_os5 = require("os");
5900
5900
  var import_node_path16 = require("path");
5901
5901
 
5902
- // ../harness/dist/chunk-LNQNFGQC.js
5902
+ // ../harness/dist/chunk-RXNHCGWE.js
5903
5903
  var HARNESS_PROTOCOL_VERSION = 1;
5904
5904
 
5905
- // ../harness/dist/chunk-K76I2TCQ.js
5905
+ // ../harness/dist/chunk-CR6RE3A2.js
5906
5906
  var import_child_process = require("child_process");
5907
5907
  var import_fs = require("fs");
5908
5908
  var import_promises2 = require("fs/promises");
@@ -6154,12 +6154,12 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
6154
6154
  });
6155
6155
  if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
6156
6156
  if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
6157
- const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
6158
- if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
6157
+ const paths2 = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
6158
+ if (paths2.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
6159
6159
  const root = (0, import_path4.resolve)(sourceDir);
6160
6160
  const files = [];
6161
6161
  let bytes = 0;
6162
- for (const relativePath of paths) {
6162
+ for (const relativePath of paths2) {
6163
6163
  if (!allowedWorkspacePath(relativePath)) continue;
6164
6164
  const source = (0, import_path4.resolve)(root, relativePath);
6165
6165
  if (!source.startsWith(`${root}${import_path4.sep}`)) throw new TypeError("git file path escapes workspace");
@@ -6275,7 +6275,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
6275
6275
  }
6276
6276
  }
6277
6277
 
6278
- // ../harness/dist/chunk-GYWQM76X.js
6278
+ // ../harness/dist/chunk-ISR434K7.js
6279
6279
  var import_crypto = require("crypto");
6280
6280
  var import_promises5 = require("fs/promises");
6281
6281
  var import_path5 = require("path");
@@ -6598,13 +6598,13 @@ function validateSnapshot(snapshot, limits) {
6598
6598
  if (!Number.isSafeInteger(limits.maximumFiles) || limits.maximumFiles < 1 || !Number.isSafeInteger(limits.maximumBytes) || limits.maximumBytes < 1 || snapshot.files.length > limits.maximumFiles) {
6599
6599
  throw new CamelError("limit_exceeded", "Code snapshot exceeds its registered limits.");
6600
6600
  }
6601
- const paths = /* @__PURE__ */ new Set();
6601
+ const paths2 = /* @__PURE__ */ new Set();
6602
6602
  let bytes = 0;
6603
6603
  for (const file of snapshot.files) {
6604
- if (!file.path || file.path.startsWith("/") || file.path.includes("\\") || file.path.split("/").some((part) => !part || part === "." || part === "..") || paths.has(file.path) || typeof file.content !== "string") {
6604
+ if (!file.path || file.path.startsWith("/") || file.path.includes("\\") || file.path.split("/").some((part) => !part || part === "." || part === "..") || paths2.has(file.path) || typeof file.content !== "string") {
6605
6605
  throw new CamelError("state_conflict", "Code snapshot contains an invalid or duplicate path.");
6606
6606
  }
6607
- paths.add(file.path);
6607
+ paths2.add(file.path);
6608
6608
  bytes += utf8Length(file.path) + utf8Length(file.content);
6609
6609
  }
6610
6610
  if (bytes > limits.maximumBytes) {
@@ -6612,7 +6612,7 @@ function validateSnapshot(snapshot, limits) {
6612
6612
  }
6613
6613
  }
6614
6614
 
6615
- // ../harness/dist/chunk-GYWQM76X.js
6615
+ // ../harness/dist/chunk-ISR434K7.js
6616
6616
  var import_child_process4 = require("child_process");
6617
6617
  var import_promises6 = require("fs/promises");
6618
6618
  var import_path6 = require("path");
@@ -6755,8 +6755,8 @@ function boundedInteger(value2, spec) {
6755
6755
  }
6756
6756
  function boundedNumber(value2, spec) {
6757
6757
  if (spec.kind !== "finite_number" || typeof value2 !== "number" || !Number.isFinite(value2) || value2 < spec.minimum || value2 > spec.maximum) throw new CamelError("conversion_rejected", "Finite-number conversion rejected the structured value.");
6758
- const text3 = String(value2);
6759
- if (/e/i.test(text3) || (text3.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
6758
+ const text4 = String(value2);
6759
+ if (/e/i.test(text4) || (text4.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
6760
6760
  return value2;
6761
6761
  }
6762
6762
  function enumMember(value2, spec) {
@@ -6912,11 +6912,11 @@ function validateUnsafeSelector(path, value2, tool) {
6912
6912
  return void 0;
6913
6913
  }
6914
6914
  function looksLikeDestination(value2) {
6915
- const text3 = value2.trim();
6916
- return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
6915
+ const text4 = value2.trim();
6916
+ return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text4) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text4);
6917
6917
  }
6918
6918
 
6919
- // ../harness/dist/chunk-GYWQM76X.js
6919
+ // ../harness/dist/chunk-ISR434K7.js
6920
6920
  var import_promises10 = require("fs/promises");
6921
6921
  var import_promises11 = require("fs/promises");
6922
6922
  var import_path10 = require("path");
@@ -7081,9 +7081,9 @@ async function extractImports(builder, input) {
7081
7081
  const sources = input.paths.filter(isSourcePath);
7082
7082
  const known = new Set(sources);
7083
7083
  for (const path of sources) {
7084
- let text3;
7084
+ let text4;
7085
7085
  try {
7086
- text3 = await input.read(path);
7086
+ text4 = await input.read(path);
7087
7087
  } catch {
7088
7088
  continue;
7089
7089
  }
@@ -7091,13 +7091,13 @@ async function extractImports(builder, input) {
7091
7091
  const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
7092
7092
  if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
7093
7093
  const specifiers = /* @__PURE__ */ new Set();
7094
- for (const match of text3.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
7095
- for (const match of text3.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
7094
+ for (const match of text4.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
7095
+ for (const match of text4.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
7096
7096
  for (const specifier of specifiers) {
7097
7097
  const resolved = resolveImport(path, specifier, known);
7098
7098
  if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
7099
7099
  }
7100
- for (const name of exportedNames(text3)) {
7100
+ for (const name of exportedNames(text4)) {
7101
7101
  builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
7102
7102
  }
7103
7103
  }
@@ -7138,16 +7138,16 @@ async function extractData(builder, input) {
7138
7138
  };
7139
7139
  for (const path of input.paths) {
7140
7140
  if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
7141
- let text3;
7141
+ let text4;
7142
7142
  try {
7143
- text3 = await input.read(path);
7143
+ text4 = await input.read(path);
7144
7144
  } catch {
7145
7145
  continue;
7146
7146
  }
7147
- for (const statement of text3.matchAll(STATEMENT)) {
7147
+ for (const statement of text4.matchAll(STATEMENT)) {
7148
7148
  const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
7149
7149
  const start = statement.index ?? 0;
7150
- const rest = text3.slice(start + statement[0].length, start + STATEMENT_WINDOW);
7150
+ const rest = text4.slice(start + statement[0].length, start + STATEMENT_WINDOW);
7151
7151
  if (verb === "SELECT") {
7152
7152
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
7153
7153
  continue;
@@ -7163,16 +7163,16 @@ async function extractData(builder, input) {
7163
7163
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
7164
7164
  }
7165
7165
  }
7166
- for (const match of text3.matchAll(NS_CONST)) {
7167
- touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text3, match.index ?? 0));
7166
+ for (const match of text4.matchAll(NS_CONST)) {
7167
+ touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text4, match.index ?? 0));
7168
7168
  }
7169
- for (const match of text3.matchAll(NS_LITERAL)) {
7170
- touch(path, match[1], NAMESPACE, accessFor(text3, match.index ?? 0));
7169
+ for (const match of text4.matchAll(NS_LITERAL)) {
7170
+ touch(path, match[1], NAMESPACE, accessFor(text4, match.index ?? 0));
7171
7171
  }
7172
7172
  }
7173
7173
  }
7174
- function accessFor(text3, index) {
7175
- const window = text3.slice(Math.max(0, index - 160), index + 40);
7174
+ function accessFor(text4, index) {
7175
+ const window = text4.slice(Math.max(0, index - 160), index + 40);
7176
7176
  return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
7177
7177
  }
7178
7178
  async function buildCodeGraph(input) {
@@ -7184,7 +7184,7 @@ async function buildCodeGraph(input) {
7184
7184
  return builder.build();
7185
7185
  }
7186
7186
 
7187
- // ../harness/dist/chunk-GYWQM76X.js
7187
+ // ../harness/dist/chunk-ISR434K7.js
7188
7188
  var import_crypto4 = require("crypto");
7189
7189
  async function digestStagedWorkspace(root, limits) {
7190
7190
  const files = [];
@@ -7512,7 +7512,7 @@ function validateCodePatch(rawPatch, maxBytes) {
7512
7512
  if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
7513
7513
  throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
7514
7514
  }
7515
- const paths = [];
7515
+ const paths2 = [];
7516
7516
  const lines = patch2.split("\n");
7517
7517
  for (let index = 0; index < lines.length; index += 1) {
7518
7518
  const line2 = lines[index];
@@ -7528,10 +7528,10 @@ function validateCodePatch(rawPatch, maxBytes) {
7528
7528
  if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
7529
7529
  throw new TypeError("patch file headers do not match the declared path");
7530
7530
  }
7531
- paths.push(path);
7531
+ paths2.push(path);
7532
7532
  }
7533
- if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
7534
- return paths;
7533
+ if (!paths2.length || new Set(paths2).size !== paths2.length) throw new TypeError("patch has no diffs or repeats a path");
7534
+ return paths2;
7535
7535
  }
7536
7536
  function validHeaderPath(value2, path, prefix) {
7537
7537
  return value2 === "/dev/null" || value2 === `${prefix}/${path}`;
@@ -7556,11 +7556,11 @@ function describePatchFailure(patch2, detail) {
7556
7556
  const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
7557
7557
  return `patch did not apply: ${detail}${hint}`;
7558
7558
  }
7559
- async function applyCodePatch(workspaceDir, rawPatch, paths) {
7559
+ async function applyCodePatch(workspaceDir, rawPatch, paths2) {
7560
7560
  const patch2 = stripPatchEnvelope(rawPatch);
7561
7561
  await gitApply(workspaceDir, patch2, true);
7562
7562
  await gitApply(workspaceDir, patch2, false);
7563
- for (const path of paths) {
7563
+ for (const path of paths2) {
7564
7564
  try {
7565
7565
  const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
7566
7566
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
@@ -7582,8 +7582,8 @@ function gitApply(cwd, patch2, check) {
7582
7582
  });
7583
7583
  let stderr2 = "";
7584
7584
  child.stderr.setEncoding("utf8");
7585
- child.stderr.on("data", (text3) => {
7586
- if (stderr2.length < 4e3) stderr2 += text3.slice(0, 4e3);
7585
+ child.stderr.on("data", (text22) => {
7586
+ if (stderr2.length < 4e3) stderr2 += text22.slice(0, 4e3);
7587
7587
  });
7588
7588
  child.once("error", reject);
7589
7589
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
@@ -7607,8 +7607,8 @@ async function restoreCodeWorkspaceCheckpoint(input) {
7607
7607
  const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
7608
7608
  try {
7609
7609
  if (checkpoint.patch) {
7610
- const paths = validateCodePatch(checkpoint.patch, 256 * 1024);
7611
- await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths);
7610
+ const paths2 = validateCodePatch(checkpoint.patch, 256 * 1024);
7611
+ await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths2);
7612
7612
  }
7613
7613
  return { workspace, checkpoint };
7614
7614
  } catch (error) {
@@ -7757,8 +7757,8 @@ async function verifyCodeCandidate(input) {
7757
7757
  try {
7758
7758
  const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
7759
7759
  if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
7760
- const paths = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
7761
- await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths);
7760
+ const paths2 = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
7761
+ await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths2);
7762
7762
  const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
7763
7763
  const policyDigest = digestPolicy(policy);
7764
7764
  const patchDigest = digestBytes(input.candidatePatch);
@@ -7767,7 +7767,7 @@ async function verifyCodeCandidate(input) {
7767
7767
  trustedBaseDigest: input.trustedBaseDigest,
7768
7768
  patchDigest
7769
7769
  });
7770
- const changedTests = changedTestPaths(paths, policy);
7770
+ const changedTests = changedTestPaths(paths2, policy);
7771
7771
  if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
7772
7772
  const recipes = [];
7773
7773
  const logs = [];
@@ -7834,8 +7834,8 @@ function validate2(input) {
7834
7834
  }
7835
7835
  return result;
7836
7836
  }
7837
- function changedTestPaths(paths, policy) {
7838
- return paths.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
7837
+ function changedTestPaths(paths2, policy) {
7838
+ return paths2.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
7839
7839
  }
7840
7840
  function recipeReceipt(recipe2, result, artifacts) {
7841
7841
  const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
@@ -8566,7 +8566,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
8566
8566
  files(root) {
8567
8567
  const existing = cache2.get(root);
8568
8568
  if (existing) return existing;
8569
- const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
8569
+ const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
8570
8570
  cache2.set(root, pending);
8571
8571
  void pending.catch(() => {
8572
8572
  if (cache2.get(root) === pending) cache2.delete(root);
@@ -8579,7 +8579,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
8579
8579
  };
8580
8580
  }
8581
8581
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
8582
- const paths = [];
8582
+ const paths2 = [];
8583
8583
  const walk = async (directory) => {
8584
8584
  for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
8585
8585
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
@@ -8593,26 +8593,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
8593
8593
  } catch {
8594
8594
  continue;
8595
8595
  }
8596
- paths.push(path);
8597
- if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
8596
+ paths2.push(path);
8597
+ if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
8598
8598
  }
8599
8599
  }
8600
8600
  };
8601
8601
  await walk((0, import_path9.resolve)(root));
8602
- return paths.sort();
8602
+ return paths2.sort();
8603
8603
  }
8604
- function listWorkspace(paths, options = {}) {
8604
+ function listWorkspace(paths2, options = {}) {
8605
8605
  const max = options.maxEntries ?? 1e3;
8606
8606
  const prefix = options.prefix?.replace(/\/+$/, "");
8607
- const scoped = prefix ? paths.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths];
8607
+ const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
8608
8608
  return scoped.slice(0, max);
8609
8609
  }
8610
- async function searchWorkspace(root, paths, options) {
8610
+ async function searchWorkspace(root, paths2, options) {
8611
8611
  options.signal?.throwIfAborted();
8612
8612
  if (!options.query) throw new TypeError("search query must be a non-empty string");
8613
8613
  const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
8614
8614
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
8615
- const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
8615
+ const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
8616
8616
  if (scoped.length === 0) return [];
8617
8617
  try {
8618
8618
  return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
@@ -8622,11 +8622,11 @@ async function searchWorkspace(root, paths, options) {
8622
8622
  }
8623
8623
  }
8624
8624
  var MAX_NATIVE_ARG_BYTES = 96 * 1024;
8625
- async function nativeSearch(root, paths, options) {
8625
+ async function nativeSearch(root, paths2, options) {
8626
8626
  const batches = [];
8627
8627
  let batch = [];
8628
8628
  let bytes = 0;
8629
- for (const path of paths) {
8629
+ for (const path of paths2) {
8630
8630
  const size = Buffer.byteLength(path) + 1;
8631
8631
  if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
8632
8632
  batches.push(batch);
@@ -8645,7 +8645,7 @@ async function nativeSearch(root, paths, options) {
8645
8645
  }
8646
8646
  return matches;
8647
8647
  }
8648
- function nativeSearchBatch(root, paths, options, remaining) {
8648
+ function nativeSearchBatch(root, paths2, options, remaining) {
8649
8649
  return new Promise((resolveMatches, reject) => {
8650
8650
  const args = [
8651
8651
  "--fixed-strings",
@@ -8658,7 +8658,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
8658
8658
  options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
8659
8659
  "--",
8660
8660
  options.query,
8661
- ...paths
8661
+ ...paths2
8662
8662
  ];
8663
8663
  const child = (0, import_child_process6.spawn)("rg", args, {
8664
8664
  cwd: root,
@@ -8844,16 +8844,16 @@ function createCodePolicyGate(options) {
8844
8844
  }
8845
8845
  };
8846
8846
  }
8847
- function directoryPrefixes(paths) {
8847
+ function directoryPrefixes(paths2) {
8848
8848
  const prefixes = /* @__PURE__ */ new Set(["."]);
8849
- for (const path of paths) {
8849
+ for (const path of paths2) {
8850
8850
  const parts = path.split("/");
8851
8851
  for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
8852
8852
  }
8853
8853
  return [...prefixes].sort();
8854
8854
  }
8855
- async function safePrefix(base, paths, prefix) {
8856
- const prefixes = directoryPrefixes(paths);
8855
+ async function safePrefix(base, paths2, prefix) {
8856
+ const prefixes = directoryPrefixes(paths2);
8857
8857
  const conversions = await conversionRegistry(
8858
8858
  [await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
8859
8859
  { "code.prefixes.v1": prefixes }
@@ -8965,7 +8965,7 @@ function response(request3, ok, content2, details) {
8965
8965
  return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
8966
8966
  }
8967
8967
  var cache = /* @__PURE__ */ new Map();
8968
- function workspaceGraphs(workspaceDir, paths) {
8968
+ function workspaceGraphs(workspaceDir, paths2) {
8969
8969
  const existing = cache.get(workspaceDir);
8970
8970
  if (existing) return existing;
8971
8971
  const read22 = (path) => (0, import_promises11.readFile)((0, import_path10.join)(workspaceDir, path), "utf8");
@@ -8973,7 +8973,7 @@ function workspaceGraphs(workspaceDir, paths) {
8973
8973
  // No knownTables: a staged workspace may not carry migrations, and a filter
8974
8974
  // that silently drops every table is worse than an unfiltered one. Callers
8975
8975
  // with ground truth should build the graph themselves.
8976
- graph: await buildCodeGraph({ paths, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
8976
+ graph: await buildCodeGraph({ paths: paths2, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
8977
8977
  }))();
8978
8978
  cache.set(workspaceDir, built);
8979
8979
  return built;
@@ -9033,11 +9033,11 @@ async function read(context, request3, options, policy, registry) {
9033
9033
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
9034
9034
  throw new TypeError("requested line range exceeds its bound");
9035
9035
  }
9036
- const paths = await registry.files(context.workspaceDir);
9037
- if (!paths.includes(path)) {
9036
+ const paths2 = await registry.files(context.workspaceDir);
9037
+ if (!paths2.includes(path)) {
9038
9038
  throw new TypeError(`no such file in the staged workspace: "${path}". Use sandbox.overview, sandbox.where_is or sandbox.search to find the correct path.`);
9039
9039
  }
9040
- const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
9040
+ const allowed = await policy.read(policyContext(context, request3, options, { paths: paths2, path, startLine, endLine }));
9041
9041
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9042
9042
  const target = resolveCodePath(context.workspaceDir, path);
9043
9043
  const info = await (0, import_promises10.stat)(target);
@@ -9059,16 +9059,16 @@ async function list(context, request3, options, policy, registry) {
9059
9059
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
9060
9060
  const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
9061
9061
  if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
9062
- const paths = await registry.files(context.workspaceDir);
9063
- const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
9062
+ const paths2 = await registry.files(context.workspaceDir);
9063
+ const allowed = await policy.list(policyContext(context, request3, options, { paths: paths2, ...prefix ? { prefix } : {} }));
9064
9064
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9065
- const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
9065
+ const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
9066
9066
  if (!entries.length) {
9067
9067
  return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
9068
9068
  }
9069
- const truncated = entries.length < paths.length && entries.length === maxEntries;
9070
- const hint = !prefix && paths.length > 500 ? `
9071
- \u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
9069
+ const truncated = entries.length < paths2.length && entries.length === maxEntries;
9070
+ const hint = !prefix && paths2.length > 500 ? `
9071
+ \u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
9072
9072
  return response(
9073
9073
  request3,
9074
9074
  true,
@@ -9086,10 +9086,10 @@ async function search(context, request3, options, policy, registry) {
9086
9086
  const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
9087
9087
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
9088
9088
  const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
9089
- const paths = await registry.files(context.workspaceDir);
9090
- const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
9089
+ const paths2 = await registry.files(context.workspaceDir);
9090
+ const allowed = await policy.search(policyContext(context, request3, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
9091
9091
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9092
- const matches = await searchWorkspace(context.workspaceDir, paths, {
9092
+ const matches = await searchWorkspace(context.workspaceDir, paths2, {
9093
9093
  query,
9094
9094
  maxResults,
9095
9095
  caseSensitive,
@@ -9111,8 +9111,8 @@ async function graphQuery(context, request3, options, policy, registry) {
9111
9111
  selector: query
9112
9112
  }));
9113
9113
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9114
- const paths = await registry.files(context.workspaceDir);
9115
- const graphs = await workspaceGraphs(context.workspaceDir, paths);
9114
+ const paths2 = await registry.files(context.workspaceDir);
9115
+ const graphs = await workspaceGraphs(context.workspaceDir, paths2);
9116
9116
  if (request3.tool === "sandbox.overview") {
9117
9117
  return response(request3, true, renderOverview(graphs, query || void 0));
9118
9118
  }
@@ -9174,16 +9174,16 @@ function toolFailureMessage(reason) {
9174
9174
  async function patch(context, request3, options, policy, registry) {
9175
9175
  exactKeys(request3.input, ["patch"]);
9176
9176
  const value2 = stringField(request3.input, "patch");
9177
- const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
9178
- if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
9177
+ const paths2 = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
9178
+ if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
9179
9179
  throw new TypeError("patch targets a read-only reference source");
9180
9180
  }
9181
9181
  const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
9182
9182
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9183
- await applyCodePatch(context.workspaceDir, value2, paths);
9183
+ await applyCodePatch(context.workspaceDir, value2, paths2);
9184
9184
  registry.invalidate(context.workspaceDir);
9185
9185
  forgetWorkspaceGraphs(context.workspaceDir);
9186
- return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
9186
+ return response(request3, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
9187
9187
  }
9188
9188
  async function recipe(context, request3, options, recipes, policy) {
9189
9189
  exactKeys(request3.input, ["recipeId"]);
@@ -9532,11 +9532,126 @@ async function startGoalPursuit(input) {
9532
9532
  async function appendCodeRuntimeEvent(control, command, event, refs) {
9533
9533
  const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
9534
9534
  refs.push(eventId);
9535
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
9535
+ const attributed = { ...event, interactionId: command.commandId };
9536
+ const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
9536
9537
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
9537
9538
  }
9538
9539
  var digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
9539
9540
  var runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
9541
+ var text3 = (value2, maximum) => {
9542
+ if (typeof value2 !== "string") return void 0;
9543
+ const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
9544
+ return bounded ? bounded.slice(0, maximum) : void 0;
9545
+ };
9546
+ var integer2 = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
9547
+ var record32 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
9548
+ var excerpt = (value2, tail = false) => {
9549
+ if (typeof value2 !== "string") return void 0;
9550
+ const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
9551
+ const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
9552
+ if (!source) return void 0;
9553
+ const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
9554
+ const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
9555
+ return text3(selected.join("\n"), 2400);
9556
+ };
9557
+ var paths = (value2) => {
9558
+ if (!Array.isArray(value2)) return void 0;
9559
+ const items = value2.flatMap((item) => {
9560
+ const path = text3(item, 1024);
9561
+ return path ? [path] : [];
9562
+ }).slice(0, 12);
9563
+ return items.length ? items : void 0;
9564
+ };
9565
+ function patchStats(value2) {
9566
+ if (typeof value2 !== "string") return {};
9567
+ let additions = 0;
9568
+ let deletions = 0;
9569
+ for (const line2 of value2.slice(0, 262144).split("\n")) {
9570
+ if (line2.startsWith("+++") || line2.startsWith("---")) continue;
9571
+ if (line2.startsWith("+")) additions += 1;
9572
+ else if (line2.startsWith("-")) deletions += 1;
9573
+ }
9574
+ return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
9575
+ }
9576
+ function searchResults(value2) {
9577
+ if (typeof value2 !== "string") return void 0;
9578
+ const results = value2.split("\n").flatMap((line2) => {
9579
+ const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
9580
+ if (!match) return [];
9581
+ const lineNumber = Number(match[2]);
9582
+ const itemText = text3(match[3], 240);
9583
+ if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
9584
+ return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
9585
+ }).slice(0, 5);
9586
+ return results.length ? results : void 0;
9587
+ }
9588
+ function codeToolRequestPresentation(request3) {
9589
+ const input = request3.input;
9590
+ if (request3.tool === "sandbox.read") {
9591
+ const path = text3(input.path, 1024);
9592
+ if (!path) return void 0;
9593
+ const startLine = integer2(input.startLine);
9594
+ const endLine = integer2(input.endLine);
9595
+ return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
9596
+ }
9597
+ if (request3.tool === "sandbox.list") {
9598
+ const scope = text3(input.prefix, 1024);
9599
+ return { kind: "list", ...scope ? { scope } : {} };
9600
+ }
9601
+ if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
9602
+ const query = text3(input.query, 512);
9603
+ const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
9604
+ if (request3.tool === "sandbox.search" && !query) return void 0;
9605
+ return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
9606
+ }
9607
+ if (request3.tool === "sandbox.apply_patch") {
9608
+ return { kind: "patch", ...patchStats(input.patch) };
9609
+ }
9610
+ const recipeId = text3(input.recipeId, 120);
9611
+ return recipeId ? { kind: "recipe", recipeId } : void 0;
9612
+ }
9613
+ function codeToolResultPresentation(request3, response2) {
9614
+ const started = codeToolRequestPresentation(request3);
9615
+ if (!started || !response2.ok) return started;
9616
+ const details = record32(response2.details);
9617
+ if (started.kind === "read") {
9618
+ return {
9619
+ ...started,
9620
+ ...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
9621
+ ...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
9622
+ ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
9623
+ };
9624
+ }
9625
+ if (started.kind === "list") {
9626
+ const listed = response2.content.split("\n").filter((line2) => line2 && !line2.startsWith("\u2026") && !line2.startsWith("Workspace ")).map((line2) => text3(line2, 1024)).filter((line2) => Boolean(line2)).slice(0, 8);
9627
+ return {
9628
+ ...started,
9629
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
9630
+ ...listed.length ? { paths: listed } : {}
9631
+ };
9632
+ }
9633
+ if (started.kind === "query") {
9634
+ const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
9635
+ const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
9636
+ return {
9637
+ ...started,
9638
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
9639
+ ...results ? { results } : {},
9640
+ ...resultExcerpt ? { excerpt: resultExcerpt } : {}
9641
+ };
9642
+ }
9643
+ if (started.kind === "patch") {
9644
+ return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
9645
+ }
9646
+ const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
9647
+ return {
9648
+ ...started,
9649
+ ...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
9650
+ ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
9651
+ ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
9652
+ ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
9653
+ };
9654
+ }
9540
9655
  var TheseusRuntimeEngine = class {
9541
9656
  constructor(options) {
9542
9657
  this.options = options;
@@ -9770,18 +9885,29 @@ var TheseusRuntimeEngine = class {
9770
9885
  return {
9771
9886
  execute: async (context, request3) => {
9772
9887
  const startedAt = Date.now();
9888
+ const operationId = digestRuntimeValue(`${command.commandId}:${request3.requestId}`);
9889
+ const startedPresentation = codeToolRequestPresentation(request3);
9773
9890
  await this.#event(
9774
9891
  command,
9775
- { type: "tool", phase: "started", tool: request3.tool },
9892
+ {
9893
+ type: "tool",
9894
+ phase: "started",
9895
+ tool: request3.tool,
9896
+ operationId,
9897
+ ...startedPresentation ? { presentation: startedPresentation } : {}
9898
+ },
9776
9899
  active.conversationRefs
9777
9900
  ).catch(() => void 0);
9778
9901
  const response2 = await broker.execute(context, request3);
9902
+ const completedPresentation = codeToolResultPresentation(request3, response2);
9779
9903
  await this.#event(command, {
9780
9904
  type: "tool",
9781
9905
  phase: "completed",
9782
9906
  tool: request3.tool,
9783
9907
  ok: response2.ok,
9784
- durationMs: Date.now() - startedAt
9908
+ durationMs: Date.now() - startedAt,
9909
+ operationId,
9910
+ ...completedPresentation ? { presentation: completedPresentation } : {}
9785
9911
  }, active.conversationRefs).catch(() => void 0);
9786
9912
  return response2;
9787
9913
  }
@@ -10824,7 +10950,8 @@ Usage:
10824
10950
  odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
10825
10951
  odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
10826
10952
  odla-ai pm task add --app <id> --title <t> [--column <backlog|ready|doing|review|done>] [--goal <id>|--alignment-decision <id>] [--assignee <id>] [--description <text>|--body <text>] [--acceptance <text>] [--execution <human|agent|either>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
10827
- odla-ai pm next --app <id> [--project <id>] [--json]
10953
+ odla-ai pm next --app <id> [--project <id>] [--verbose] [--json]
10954
+ odla-ai pm start --app <id> [<task-id>] [--goal <id>] [--verbose] [--mutation-id <id>] [--json] [claims the top Ready task in one call]
10828
10955
  odla-ai pm watch --app <id> [--cursor <cursor>] [--entity goal|task|decision|bug] [--action created|updated|deleted|comment.created|comment.updated] [--state <state>] [--by <principalId>] [--self <principalId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
10829
10956
  odla-ai pm task ready <id> --expected-revision <n> [--goal <id>|--alignment-decision <id>] [--description <text>|--body <text>] [--acceptance <text>] [--execution <human|agent|either>] [--mutation-id <id>] [--json]
10830
10957
  odla-ai pm task claim <id> --expected-revision <n> [--mutation-id <id>] [--json]
@@ -10845,7 +10972,7 @@ Usage:
10845
10972
  odla-ai pm <goal|task|decision|bug> comments <id> [--json]
10846
10973
  odla-ai pm <goal|task|decision|bug> history <id> [--limit <n>] [--json]
10847
10974
  odla-ai pm <goal|task|decision|bug> rm <id>
10848
- odla-ai pm handoff --app <id> [--project <id>] [--json]
10975
+ odla-ai pm handoff --app <id> [--project <id>] [--verbose] [--json]
10849
10976
  odla-ai discuss groups [--json]
10850
10977
  odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--mentions] [--json]
10851
10978
  odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
@@ -11010,6 +11137,16 @@ Commands:
11010
11137
  execution contract and a reviewed revision. Any caller with PM
11011
11138
  mutation access to that app/project may do it; claims coordinate
11012
11139
  execution but never own or lock the PM record.
11140
+ To pick up work: "pm next" reads open goals, Ready candidates,
11141
+ and work in progress; "pm start" claims the top Ready task in
11142
+ one call, compare-and-swapped on the revision it just read, and
11143
+ returns the task, its goal, and the acceptance criteria to work
11144
+ against. Finish with "pm task done <id>", or hand it back with
11145
+ "pm task release <id> --expected-revision <n>". "pm start" never
11146
+ marks work Ready: that contract is reviewed separately so an
11147
+ agent cannot authorize its own work.
11148
+ Intake output is projected to the fields an executing caller
11149
+ acts on; --verbose returns whole records with their audit trail.
11013
11150
  bug Intent-first alias for PM bugs. "bug report" writes to
11014
11151
  odla PM; odla product defects do not belong in GitHub Issues.
11015
11152
  discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
@@ -11760,6 +11897,15 @@ var FIELD_MAP = {
11760
11897
  execution: { key: "executionMode" },
11761
11898
  "expected-revision": { key: "expectedRevision", num: true }
11762
11899
  };
11900
+ var PmRequestError = class extends Error {
11901
+ constructor(status, message2) {
11902
+ super(message2);
11903
+ this.status = status;
11904
+ this.name = "PmRequestError";
11905
+ }
11906
+ status;
11907
+ };
11908
+ var isRevisionConflict = (error) => error instanceof PmRequestError && error.status === 409;
11763
11909
  async function pmRequest(ctx, method, path, body) {
11764
11910
  const response2 = await ctx.doFetch(`${ctx.platformUrl}/registry/pm${path}`, {
11765
11911
  method,
@@ -11779,7 +11925,8 @@ async function pmRequest(ctx, method, path, body) {
11779
11925
  const message2 = error.message;
11780
11926
  if (typeof message2 === "string" && message2.length > 0) detail = message2;
11781
11927
  }
11782
- throw new Error(
11928
+ throw new PmRequestError(
11929
+ response2.status,
11783
11930
  `pm ${method} ${path} failed: ${detail ?? `registry returned ${response2.status}`} (${response2.status})`
11784
11931
  );
11785
11932
  }
@@ -11794,8 +11941,8 @@ function collectFields(parsed, allowClear) {
11794
11941
  if (allowClear) out[spec.key] = null;
11795
11942
  continue;
11796
11943
  }
11797
- const text3 = stringOpt(value2);
11798
- out[spec.key] = spec.num ? Number(text3) : text3;
11944
+ const text4 = stringOpt(value2);
11945
+ out[spec.key] = spec.num ? Number(text4) : text4;
11799
11946
  }
11800
11947
  return out;
11801
11948
  }
@@ -11968,103 +12115,196 @@ async function pmTaskLifecycle(ctx, id2, action2, parsed) {
11968
12115
  ctx.out.log(`task: ${label} \u2192 ${state2}`);
11969
12116
  });
11970
12117
  }
11971
- async function allRecords(ctx, entity, appId, projectId) {
12118
+ async function pmRemove(ctx, entity, id2) {
12119
+ await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
12120
+ ctx.out.log(`deleted ${entity} ${id2}`);
12121
+ }
12122
+
12123
+ // src/pm-lean.ts
12124
+ var COMMON = ["id", "appId", "projectId", "title", "revision"];
12125
+ var PER_ENTITY = {
12126
+ goal: ["status", "proof", "targetPct", "currentPct"],
12127
+ task: [
12128
+ "column",
12129
+ "goalId",
12130
+ "alignmentDecisionId",
12131
+ "description",
12132
+ "acceptanceCriteria",
12133
+ "executionMode",
12134
+ "assigneeId",
12135
+ "claimedByPrincipalId",
12136
+ "dueAt"
12137
+ ],
12138
+ decision: ["status", "body", "supersedesId"],
12139
+ bug: ["status", "severity", "description", "goalId", "assigneeId", "decisionId"]
12140
+ };
12141
+ function leanRecord(entity, record11) {
12142
+ const out = {};
12143
+ for (const key of [...COMMON, ...PER_ENTITY[entity]]) {
12144
+ const value2 = record11[key];
12145
+ if (value2 !== void 0 && value2 !== null) out[key] = value2;
12146
+ }
12147
+ return out;
12148
+ }
12149
+ function leanRecords(entity, records, verbose) {
12150
+ return verbose ? records : records.map((record11) => leanRecord(entity, record11));
12151
+ }
12152
+
12153
+ // src/pm-intake.ts
12154
+ var READY_COLUMN = "todo";
12155
+ var UNMET_GOAL_STATUS = "open";
12156
+ var ACTIVE_TASK_COLUMNS = "backlog,todo,doing,review";
12157
+ var OPEN_BUG_STATUSES = "open,triaged";
12158
+ var wantsVerbose = (parsed) => parsed.options.verbose === true;
12159
+ async function listFiltered(ctx, entity, appId, projectId, filters) {
11972
12160
  const records = [];
11973
12161
  for (; ; ) {
11974
- const q = new URLSearchParams({
11975
- app: appId,
11976
- limit: "100",
11977
- offset: String(records.length)
11978
- });
12162
+ const q = new URLSearchParams({ app: appId, limit: "100", offset: String(records.length) });
11979
12163
  if (projectId) q.set("project", projectId);
12164
+ for (const [key, value2] of Object.entries(filters)) q.set(key, value2);
11980
12165
  const page2 = await pmRequest(ctx, "GET", `/${entity}?${q}`);
11981
12166
  records.push(...page2.records);
11982
12167
  if (records.length >= page2.total || page2.records.length === 0) return records;
11983
12168
  }
11984
12169
  }
11985
- async function pmNext(ctx, parsed) {
12170
+ function requireApp(ctx, parsed, command) {
11986
12171
  const appId = stringOpt(parsed.options.app) ?? ctx.appId;
11987
- const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
11988
- if (!appId) throw new Error("pm next needs --app <appId>");
12172
+ if (!appId) throw new Error(`pm ${command} needs --app <appId>`);
12173
+ return { appId, projectId: stringOpt(parsed.options.project) ?? ctx.projectId };
12174
+ }
12175
+ async function pmNext(ctx, parsed) {
12176
+ const { appId, projectId } = requireApp(ctx, parsed, "next");
12177
+ const verbose = wantsVerbose(parsed);
11989
12178
  const [goals, tasks] = await Promise.all([
11990
- allRecords(ctx, "goal", appId, projectId),
11991
- allRecords(ctx, "task", appId, projectId)
12179
+ listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
12180
+ // One request for both live columns; the split below is over open work only.
12181
+ listFiltered(ctx, "task", appId, projectId, { column: `${READY_COLUMN},doing` })
11992
12182
  ]);
12183
+ const ready = tasks.filter((record11) => record11.column === READY_COLUMN);
12184
+ const doing = tasks.filter((record11) => record11.column === "doing");
12185
+ const guidance = !goals.length ? "discuss alignment with the user before creating or claiming project work" : !ready.length ? 'refine a linked Backlog task and mark it Ready with "pm task ready <id>"' : `claim the top Ready task in one call: odla-ai pm start --app ${appId}`;
11993
12186
  const result = {
11994
12187
  appId,
11995
12188
  projectId,
11996
- openGoals: goals.filter((record11) => record11.status === "open"),
11997
- doing: tasks.filter((record11) => record11.column === "doing"),
11998
- ready: tasks.filter((record11) => record11.column === "todo")
12189
+ openGoals: leanRecords("goal", goals, verbose),
12190
+ doing: leanRecords("task", doing, verbose),
12191
+ ready: leanRecords("task", ready, verbose),
12192
+ next: guidance
11999
12193
  };
12000
12194
  emit2(ctx, result, () => {
12001
12195
  ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
12002
- for (const [label, records] of [
12003
- ["doing", result.doing],
12004
- ["ready", result.ready],
12005
- ["open goals", result.openGoals]
12196
+ for (const [label, entity, records] of [
12197
+ ["doing", "task", doing],
12198
+ ["ready", "task", ready],
12199
+ ["open goals", "goal", goals]
12006
12200
  ]) {
12007
12201
  ctx.out.log(`${label}:`);
12008
12202
  if (!records.length) ctx.out.log("- (none)");
12009
- else for (const record11 of records) printRecord(
12010
- ctx,
12011
- label === "open goals" ? "goal" : "task",
12012
- record11
12013
- );
12014
- }
12015
- if (!result.openGoals.length) {
12016
- ctx.out.log("next: discuss alignment with the user before creating or claiming project work");
12017
- } else if (!result.ready.length) {
12018
- ctx.out.log("next: refine a linked Backlog task and mark it Ready");
12019
- } else {
12020
- ctx.out.log("next: review a Ready task, then claim it with its revision");
12203
+ else for (const record11 of records) printRecord(ctx, entity, record11);
12021
12204
  }
12205
+ ctx.out.log(`next: ${guidance}`);
12022
12206
  });
12023
12207
  }
12024
12208
  async function pmHandoff(ctx, parsed) {
12025
- const appId = stringOpt(parsed.options.app) ?? ctx.appId;
12026
- const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
12027
- if (!appId) throw new Error("pm handoff needs --app <appId>");
12209
+ const { appId, projectId } = requireApp(ctx, parsed, "handoff");
12210
+ const verbose = wantsVerbose(parsed);
12028
12211
  const [goals, tasks, bugs] = await Promise.all([
12029
- allRecords(ctx, "goal", appId, projectId),
12030
- allRecords(ctx, "task", appId, projectId),
12031
- allRecords(ctx, "bug", appId, projectId)
12212
+ listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
12213
+ listFiltered(ctx, "task", appId, projectId, { column: ACTIVE_TASK_COLUMNS }),
12214
+ listFiltered(ctx, "bug", appId, projectId, { status: OPEN_BUG_STATUSES })
12032
12215
  ]);
12033
- const handoff = {
12216
+ const clean4 = !goals.length && !tasks.length && !bugs.length;
12217
+ const result = {
12034
12218
  appId,
12035
12219
  projectId,
12036
- unmetGoals: goals.filter((record11) => record11.status !== "met"),
12037
- activeTasks: tasks.filter((record11) => record11.column !== "done"),
12038
- openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
12039
- };
12040
- const result = {
12041
- ...handoff,
12042
- clean: !handoff.unmetGoals.length && !handoff.activeTasks.length && !handoff.openBugs.length
12220
+ unmetGoals: leanRecords("goal", goals, verbose),
12221
+ activeTasks: leanRecords("task", tasks, verbose),
12222
+ openBugs: leanRecords("bug", bugs, verbose),
12223
+ clean: clean4
12043
12224
  };
12044
12225
  emit2(ctx, result, () => {
12045
- if (result.clean) {
12226
+ if (clean4) {
12046
12227
  ctx.out.log(`${appId}: no unresolved PM work`);
12047
12228
  return;
12048
12229
  }
12049
12230
  ctx.out.log(`${appId}: authoritative PM handoff`);
12050
- for (const [label, records] of [
12051
- ["unmet goals", result.unmetGoals],
12052
- ["active tasks", result.activeTasks],
12053
- ["open bugs", result.openBugs]
12231
+ for (const [label, entity, records] of [
12232
+ ["unmet goals", "goal", goals],
12233
+ ["active tasks", "task", tasks],
12234
+ ["open bugs", "bug", bugs]
12054
12235
  ]) {
12055
12236
  ctx.out.log(`${label}:`);
12056
12237
  if (!records.length) ctx.out.log("- (none)");
12057
- else for (const record11 of records) printRecord(
12058
- ctx,
12059
- label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
12060
- record11
12061
- );
12238
+ else for (const record11 of records) printRecord(ctx, entity, record11);
12062
12239
  }
12063
12240
  });
12064
12241
  }
12065
- async function pmRemove(ctx, entity, id2) {
12066
- await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
12067
- ctx.out.log(`deleted ${entity} ${id2}`);
12242
+
12243
+ // src/pm-start.ts
12244
+ var getTask = async (ctx, id2) => (await pmRequest(ctx, "GET", `/task/${encodeURIComponent(id2)}`)).record;
12245
+ async function claimAt(ctx, task, parsed) {
12246
+ const res = await pmRequest(
12247
+ ctx,
12248
+ "POST",
12249
+ `/task/${encodeURIComponent(task.id)}/claim`,
12250
+ { expectedRevision: task.revision, mutationId: writeMutationId2(parsed) }
12251
+ );
12252
+ return res.record;
12253
+ }
12254
+ async function selectTask(ctx, parsed, appId, projectId) {
12255
+ const named = parsed.positionals[2];
12256
+ if (named) return getTask(ctx, named);
12257
+ const goalId = stringOpt(parsed.options.goal);
12258
+ const ready = await listFiltered(ctx, "task", appId, projectId, {
12259
+ column: READY_COLUMN,
12260
+ ...goalId ? { goalId } : {}
12261
+ });
12262
+ const candidate = ready[0];
12263
+ if (!candidate) {
12264
+ throw new Error(
12265
+ `no Ready task to claim in ${appId}${goalId ? ` for goal ${goalId}` : ""}. Run "odla-ai pm next --app ${appId}" to see open goals and Backlog work, then "odla-ai pm task ready <id>" once a task has a complete contract.`
12266
+ );
12267
+ }
12268
+ return candidate;
12269
+ }
12270
+ async function pmStart(ctx, parsed) {
12271
+ const appId = stringOpt(parsed.options.app) ?? ctx.appId;
12272
+ if (!appId) throw new Error("pm start needs --app <appId>");
12273
+ const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
12274
+ const verbose = wantsVerbose(parsed);
12275
+ const selected = await selectTask(ctx, parsed, appId, projectId);
12276
+ if (selected.column !== READY_COLUMN) {
12277
+ throw new Error(
12278
+ `task ${selected.id} is ${selected.column === "doing" ? "already claimed" : `in ${selected.column}`}, not Ready. Only Ready work can be started; run "odla-ai pm task ready ${selected.id} --expected-revision ${selected.revision}" once its goal, description, and acceptance criteria are complete.`
12279
+ );
12280
+ }
12281
+ let claimed;
12282
+ try {
12283
+ claimed = await claimAt(ctx, selected, parsed);
12284
+ } catch (error) {
12285
+ if (!isRevisionConflict(error)) throw error;
12286
+ const reloaded = await getTask(ctx, selected.id);
12287
+ if (reloaded.column !== READY_COLUMN) {
12288
+ throw new Error(
12289
+ `task ${selected.id} was taken while starting it (now ${reloaded.column}). Run "odla-ai pm start --app ${appId}" again to take the next Ready task.`
12290
+ );
12291
+ }
12292
+ claimed = await claimAt(ctx, reloaded, parsed);
12293
+ }
12294
+ const task = claimed ?? await getTask(ctx, selected.id);
12295
+ const goalId = typeof task.goalId === "string" ? task.goalId : void 0;
12296
+ const goal = goalId ? await pmRequest(ctx, "GET", `/goal/${encodeURIComponent(goalId)}`).then((res) => res.record).catch(() => null) : null;
12297
+ const result = {
12298
+ claimed: verbose ? task : leanRecord("task", task),
12299
+ goal: goal ? verbose ? goal : leanRecord("goal", goal) : null,
12300
+ next: 'work the acceptance criteria, then "odla-ai pm task done <id>" (or "odla-ai pm task release <id> --expected-revision <n>" to hand it back)'
12301
+ };
12302
+ emit2(ctx, result, () => {
12303
+ ctx.out.log(`claimed: ${studioRecordLink(ctx, "task", task)}`);
12304
+ if (goal) ctx.out.log(`goal: ${studioRecordLink(ctx, "goal", goal)}`);
12305
+ if (typeof task.acceptanceCriteria === "string") ctx.out.log(`acceptance: ${task.acceptanceCriteria}`);
12306
+ ctx.out.log(`next: ${result.next}`);
12307
+ });
12068
12308
  }
12069
12309
 
12070
12310
  // src/pm-links.ts
@@ -12538,9 +12778,13 @@ async function pmCommand(parsed, deps = {}) {
12538
12778
  throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
12539
12779
  }
12540
12780
  if (word === "next") {
12541
- assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
12781
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
12542
12782
  return pmNext(await buildContext2(parsed, deps), parsed);
12543
12783
  }
12784
+ if (word === "start") {
12785
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "goal", "verbose", "mutation-id"], 3);
12786
+ return pmStart(await buildContext2(parsed, deps), parsed);
12787
+ }
12544
12788
  if (word === "watch") {
12545
12789
  assertArgs(parsed, [
12546
12790
  ...COMMON_OPTIONS,
@@ -12558,11 +12802,11 @@ async function pmCommand(parsed, deps = {}) {
12558
12802
  return pmWatch(await buildContext2(parsed, deps), parsed).then(() => void 0);
12559
12803
  }
12560
12804
  if (word === "handoff") {
12561
- assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
12805
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
12562
12806
  return pmHandoff(await buildContext2(parsed, deps), parsed);
12563
12807
  }
12564
12808
  const entity = ALIASES[word];
12565
- if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
12809
+ if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm start" to claim Ready work, or "odla-ai pm bug list" (goal|task|decision|bug).`);
12566
12810
  const requestedAction = parsed.positionals[2] ?? "list";
12567
12811
  const action2 = canonicalAction(requestedAction);
12568
12812
  if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|history|rm.`);
@@ -13110,14 +13354,14 @@ function statusMinutes(value2) {
13110
13354
  }
13111
13355
  async function read2(url, headers, doFetch) {
13112
13356
  const response2 = await doFetch(url, { headers });
13113
- const text3 = await response2.text();
13357
+ const text4 = await response2.text();
13114
13358
  let body = {};
13115
- if (text3) {
13359
+ if (text4) {
13116
13360
  try {
13117
- const value2 = JSON.parse(text3);
13361
+ const value2 = JSON.parse(text4);
13118
13362
  body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
13119
13363
  } catch {
13120
- body = { message: text3.slice(0, 300) };
13364
+ body = { message: text4.slice(0, 300) };
13121
13365
  }
13122
13366
  }
13123
13367
  return { httpStatus: response2.status, body };
@@ -13347,13 +13591,13 @@ async function monitorCommand(parsed, deps = {}) {
13347
13591
  }
13348
13592
  async function request2(url, init, doFetch) {
13349
13593
  const response2 = await doFetch(url, init);
13350
- const text3 = await response2.text();
13594
+ const text4 = await response2.text();
13351
13595
  let body = {};
13352
13596
  try {
13353
- const parsed = text3 ? JSON.parse(text3) : {};
13597
+ const parsed = text4 ? JSON.parse(text4) : {};
13354
13598
  body = record10(parsed) ? parsed : { value: parsed };
13355
13599
  } catch {
13356
- body = { message: text3.slice(0, 500) };
13600
+ body = { message: text4.slice(0, 500) };
13357
13601
  }
13358
13602
  if (!response2.ok) {
13359
13603
  const error = record10(body.error) ? body.error : body;
@@ -13556,8 +13800,8 @@ function runtimeUrl(cfg, suffix = "") {
13556
13800
  return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
13557
13801
  }
13558
13802
  async function safeError(response2) {
13559
- const text3 = await response2.text();
13560
- return redactSecrets(text3.slice(0, 1e3));
13803
+ const text4 = await response2.text();
13804
+ return redactSecrets(text4.slice(0, 1e3));
13561
13805
  }
13562
13806
  async function finish(doFetch, cfg, token, sessionId, method) {
13563
13807
  return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
@@ -13976,6 +14220,7 @@ var COMMAND_SURFACE = {
13976
14220
  project: { list: {}, add: {}, create: {}, use: {} },
13977
14221
  handoff: {},
13978
14222
  next: {},
14223
+ start: {},
13979
14224
  watch: {}
13980
14225
  },
13981
14226
  provision: {},
@@ -14050,13 +14295,13 @@ function invocationPath(words2) {
14050
14295
  return path;
14051
14296
  }
14052
14297
  function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
14053
- const paths = [];
14298
+ const paths2 = [];
14054
14299
  for (const [word, child] of Object.entries(node)) {
14055
14300
  const path = [...prefix, word];
14056
- paths.push(path);
14057
- paths.push(...surfacePaths(child, path));
14301
+ paths2.push(path);
14302
+ paths2.push(...surfacePaths(child, path));
14058
14303
  }
14059
- return paths;
14304
+ return paths2;
14060
14305
  }
14061
14306
 
14062
14307
  // src/record.ts
@@ -14114,10 +14359,10 @@ var DAY_MS = 24 * 60 * 60 * 1e3;
14114
14359
  var UNITS = { d: DAY_MS, w: 7 * DAY_MS, y: 365 * DAY_MS };
14115
14360
  function parseDeviceTtl(raw) {
14116
14361
  if (raw === void 0 || raw === true) return void 0;
14117
- const text3 = String(raw).trim().toLowerCase();
14118
- if (!text3) return void 0;
14119
- if (text3 === "forever" || text3 === "never") return 100 * 365 * DAY_MS;
14120
- const match = /^(\d+)\s*([dwy])$/.exec(text3);
14362
+ const text4 = String(raw).trim().toLowerCase();
14363
+ if (!text4) return void 0;
14364
+ if (text4 === "forever" || text4 === "never") return 100 * 365 * DAY_MS;
14365
+ const match = /^(\d+)\s*([dwy])$/.exec(text4);
14121
14366
  if (!match) {
14122
14367
  throw new Error(
14123
14368
  `--device-ttl expects a duration like 30d, 6w, 2y, or "forever" (got "${raw}")`
@@ -14499,12 +14744,12 @@ async function runbookRemove(ctx, slug) {
14499
14744
  // src/runbook-import.ts
14500
14745
  var import_node_fs21 = require("fs");
14501
14746
  var import_node_path19 = require("path");
14502
- function parseRunbook(text3, slug) {
14503
- let rest = text3;
14747
+ function parseRunbook(text4, slug) {
14748
+ let rest = text4;
14504
14749
  const meta = {};
14505
- const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text3);
14750
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text4);
14506
14751
  if (fm) {
14507
- rest = text3.slice(fm[0].length);
14752
+ rest = text4.slice(fm[0].length);
14508
14753
  for (const line2 of fm[1].split(/\r?\n/)) {
14509
14754
  const pair = /^(\w+)\s*:\s*(.+)$/.exec(line2.trim());
14510
14755
  if (!pair) continue;