@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.
@@ -365,10 +365,10 @@ function isManagedDevVar(line2) {
365
365
  const match = line2.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
366
366
  return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
367
367
  }
368
- function writePrivateText(path, text3) {
368
+ function writePrivateText(path, text4) {
369
369
  mkdirSync2(dirname3(path), { recursive: true });
370
370
  const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
371
- writeFileSync(temporary, text3, { mode: 384 });
371
+ writeFileSync(temporary, text4, { mode: 384 });
372
372
  chmodSync2(temporary, 384);
373
373
  renameSync(temporary, path);
374
374
  }
@@ -808,12 +808,12 @@ async function readAdminAiAudit(request3) {
808
808
  }
809
809
  }
810
810
  async function responseBody(response2) {
811
- const text3 = await response2.text();
812
- if (!text3) return {};
811
+ const text4 = await response2.text();
812
+ if (!text4) return {};
813
813
  try {
814
- return JSON.parse(text3);
814
+ return JSON.parse(text4);
815
815
  } catch {
816
- return { message: text3.slice(0, 300) };
816
+ return { message: text4.slice(0, 300) };
817
817
  }
818
818
  }
819
819
  function apiError(status, body) {
@@ -915,12 +915,12 @@ function timestamp2(value2) {
915
915
  return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
916
916
  }
917
917
  async function responseBody2(res) {
918
- const text3 = await res.text();
919
- if (!text3) return {};
918
+ const text4 = await res.text();
919
+ if (!text4) return {};
920
920
  try {
921
- return JSON.parse(text3);
921
+ return JSON.parse(text4);
922
922
  } catch {
923
- return { message: text3.slice(0, 300) };
923
+ return { message: text4.slice(0, 300) };
924
924
  }
925
925
  }
926
926
  function apiError2(action2, status, body) {
@@ -1096,12 +1096,12 @@ function catalogModels(body) {
1096
1096
  return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
1097
1097
  }
1098
1098
  async function responseBody3(res) {
1099
- const text3 = await res.text();
1100
- if (!text3) return {};
1099
+ const text4 = await res.text();
1100
+ if (!text4) return {};
1101
1101
  try {
1102
- return JSON.parse(text3);
1102
+ return JSON.parse(text4);
1103
1103
  } catch {
1104
- return { message: text3.slice(0, 300) };
1104
+ return { message: text4.slice(0, 300) };
1105
1105
  }
1106
1106
  }
1107
1107
  function apiError3(action2, status, body) {
@@ -2474,8 +2474,8 @@ async function appImport(options) {
2474
2474
  const out = options.stdout ?? console;
2475
2475
  const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
2476
2476
  const { tenant } = resolveTenant(cfg, options.env);
2477
- const text3 = options.file === "-" ? (options.readStdin ?? (() => readFileSync5(0, "utf8")))() : readFileSync5(options.file, "utf8");
2478
- const { format, sources } = parseImport(text3, options.ns);
2477
+ const text4 = options.file === "-" ? (options.readStdin ?? (() => readFileSync5(0, "utf8")))() : readFileSync5(options.file, "utf8");
2478
+ const { format, sources } = parseImport(text4, options.ns);
2479
2479
  if (format === "namespace-map" && options.ns) {
2480
2480
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
2481
2481
  }
@@ -2691,7 +2691,7 @@ var EXTENSIONS = {
2691
2691
  "text/html": "html",
2692
2692
  "application/json": "json"
2693
2693
  };
2694
- var encode = (text3) => new TextEncoder().encode(text3);
2694
+ var encode = (text4) => new TextEncoder().encode(text4);
2695
2695
  function assetFileName(uuid, mime) {
2696
2696
  const ext = EXTENSIONS[mime.split(";")[0].trim().toLowerCase()] ?? "bin";
2697
2697
  return `${uuid.replace(/[^a-zA-Z0-9._-]/g, "_")}.${ext}`;
@@ -3549,9 +3549,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
3549
3549
  }
3550
3550
  throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
3551
3551
  }
3552
- function errorCode(text3) {
3552
+ function errorCode(text4) {
3553
3553
  try {
3554
- const body = JSON.parse(text3);
3554
+ const body = JSON.parse(text4);
3555
3555
  return typeof body.error?.code === "string" ? body.error.code : null;
3556
3556
  } catch {
3557
3557
  return null;
@@ -4286,15 +4286,15 @@ function readWranglerConfig(path) {
4286
4286
  return null;
4287
4287
  }
4288
4288
  }
4289
- function stripJsonComments(text3) {
4289
+ function stripJsonComments(text4) {
4290
4290
  let result = "";
4291
4291
  let inString = false;
4292
- for (let i = 0; i < text3.length; i++) {
4293
- const ch = text3[i];
4292
+ for (let i = 0; i < text4.length; i++) {
4293
+ const ch = text4[i];
4294
4294
  if (inString) {
4295
4295
  result += ch;
4296
4296
  if (ch === "\\") {
4297
- result += text3[i + 1] ?? "";
4297
+ result += text4[i + 1] ?? "";
4298
4298
  i++;
4299
4299
  } else if (ch === '"') {
4300
4300
  inString = false;
@@ -4306,14 +4306,14 @@ function stripJsonComments(text3) {
4306
4306
  result += ch;
4307
4307
  continue;
4308
4308
  }
4309
- if (ch === "/" && text3[i + 1] === "/") {
4310
- while (i < text3.length && text3[i] !== "\n") i++;
4309
+ if (ch === "/" && text4[i + 1] === "/") {
4310
+ while (i < text4.length && text4[i] !== "\n") i++;
4311
4311
  result += "\n";
4312
4312
  continue;
4313
4313
  }
4314
- if (ch === "/" && text3[i + 1] === "*") {
4314
+ if (ch === "/" && text4[i + 1] === "*") {
4315
4315
  i += 2;
4316
- while (i < text3.length && !(text3[i] === "*" && text3[i + 1] === "/")) i++;
4316
+ while (i < text4.length && !(text4[i] === "*" && text4[i + 1] === "/")) i++;
4317
4317
  i++;
4318
4318
  continue;
4319
4319
  }
@@ -4827,9 +4827,9 @@ function initProject(options) {
4827
4827
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
4828
4828
  out.log("updated .gitignore for local odla credentials");
4829
4829
  }
4830
- function writeIfMissing(path, text3) {
4830
+ function writeIfMissing(path, text4) {
4831
4831
  if (existsSync9(path)) return;
4832
- writeFileSync2(path, text3);
4832
+ writeFileSync2(path, text4);
4833
4833
  }
4834
4834
  function configTemplate(input) {
4835
4835
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -5041,13 +5041,13 @@ async function secretsSetClerkKey(options) {
5041
5041
  body: JSON.stringify({ value: value2 })
5042
5042
  });
5043
5043
  if (!res.ok) {
5044
- const text3 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
5045
- throw new Error(`store Clerk secret key failed (${res.status}): ${text3 || "request failed"}`);
5044
+ const text4 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
5045
+ throw new Error(`store Clerk secret key failed (${res.status}): ${text4 || "request failed"}`);
5046
5046
  }
5047
5047
  out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
5048
5048
  }
5049
- function scrubValue(text3, value2) {
5050
- return redactSecrets(text3).split(value2).join("[value redacted]");
5049
+ function scrubValue(text4, value2) {
5050
+ return redactSecrets(text4).split(value2).join("[value redacted]");
5051
5051
  }
5052
5052
  async function resolveVaultWrite(options) {
5053
5053
  const out = options.stdout ?? console;
@@ -5339,8 +5339,8 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
5339
5339
  harnesses: installations
5340
5340
  };
5341
5341
  }
5342
- function pathsUnder(root, paths) {
5343
- return [...paths].map((path) => relative2(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute3(path)).sort();
5342
+ function pathsUnder(root, paths2) {
5343
+ return [...paths2].map((path) => relative2(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute3(path)).sort();
5344
5344
  }
5345
5345
  function normalizeHarnesses(values, global) {
5346
5346
  const requested = values?.length ? values : ["claude"];
@@ -5769,10 +5769,10 @@ import { existsSync as existsSync11 } from "fs";
5769
5769
  import { cpus, hostname, totalmem } from "os";
5770
5770
  import { resolve as resolve11 } from "path";
5771
5771
 
5772
- // ../harness/dist/chunk-LNQNFGQC.js
5772
+ // ../harness/dist/chunk-RXNHCGWE.js
5773
5773
  var HARNESS_PROTOCOL_VERSION = 1;
5774
5774
 
5775
- // ../harness/dist/chunk-K76I2TCQ.js
5775
+ // ../harness/dist/chunk-CR6RE3A2.js
5776
5776
  import { execFile, spawn as spawn3 } from "child_process";
5777
5777
  import { constants } from "fs";
5778
5778
  import { access } from "fs/promises";
@@ -6024,12 +6024,12 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
6024
6024
  });
6025
6025
  if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
6026
6026
  if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
6027
- const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
6028
- if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
6027
+ const paths2 = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
6028
+ if (paths2.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
6029
6029
  const root = resolve22(sourceDir);
6030
6030
  const files = [];
6031
6031
  let bytes = 0;
6032
- for (const relativePath of paths) {
6032
+ for (const relativePath of paths2) {
6033
6033
  if (!allowedWorkspacePath(relativePath)) continue;
6034
6034
  const source = resolve22(root, relativePath);
6035
6035
  if (!source.startsWith(`${root}${sep22}`)) throw new TypeError("git file path escapes workspace");
@@ -6145,7 +6145,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
6145
6145
  }
6146
6146
  }
6147
6147
 
6148
- // ../harness/dist/chunk-GYWQM76X.js
6148
+ // ../harness/dist/chunk-ISR434K7.js
6149
6149
  import { createHash as createHash3 } from "crypto";
6150
6150
  import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
6151
6151
  import { relative as relative4, resolve as resolve10 } from "path";
@@ -6468,13 +6468,13 @@ function validateSnapshot(snapshot, limits) {
6468
6468
  if (!Number.isSafeInteger(limits.maximumFiles) || limits.maximumFiles < 1 || !Number.isSafeInteger(limits.maximumBytes) || limits.maximumBytes < 1 || snapshot.files.length > limits.maximumFiles) {
6469
6469
  throw new CamelError("limit_exceeded", "Code snapshot exceeds its registered limits.");
6470
6470
  }
6471
- const paths = /* @__PURE__ */ new Set();
6471
+ const paths2 = /* @__PURE__ */ new Set();
6472
6472
  let bytes = 0;
6473
6473
  for (const file of snapshot.files) {
6474
- 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") {
6474
+ 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") {
6475
6475
  throw new CamelError("state_conflict", "Code snapshot contains an invalid or duplicate path.");
6476
6476
  }
6477
- paths.add(file.path);
6477
+ paths2.add(file.path);
6478
6478
  bytes += utf8Length(file.path) + utf8Length(file.content);
6479
6479
  }
6480
6480
  if (bytes > limits.maximumBytes) {
@@ -6482,7 +6482,7 @@ function validateSnapshot(snapshot, limits) {
6482
6482
  }
6483
6483
  }
6484
6484
 
6485
- // ../harness/dist/chunk-GYWQM76X.js
6485
+ // ../harness/dist/chunk-ISR434K7.js
6486
6486
  import { spawn as spawn4 } from "child_process";
6487
6487
  import { lstat as lstat2 } from "fs/promises";
6488
6488
  import { resolve as resolve23, sep as sep3 } from "path";
@@ -6628,8 +6628,8 @@ function boundedInteger(value2, spec) {
6628
6628
  }
6629
6629
  function boundedNumber(value2, spec) {
6630
6630
  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.");
6631
- const text3 = String(value2);
6632
- 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.");
6631
+ const text4 = String(value2);
6632
+ 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.");
6633
6633
  return value2;
6634
6634
  }
6635
6635
  function enumMember(value2, spec) {
@@ -6785,11 +6785,11 @@ function validateUnsafeSelector(path, value2, tool) {
6785
6785
  return void 0;
6786
6786
  }
6787
6787
  function looksLikeDestination(value2) {
6788
- const text3 = value2.trim();
6789
- return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
6788
+ const text4 = value2.trim();
6789
+ return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text4) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text4);
6790
6790
  }
6791
6791
 
6792
- // ../harness/dist/chunk-GYWQM76X.js
6792
+ // ../harness/dist/chunk-ISR434K7.js
6793
6793
  import { readFile as readFile4, stat as stat2 } from "fs/promises";
6794
6794
  import { readFile as readFile3 } from "fs/promises";
6795
6795
  import { join as join33 } from "path";
@@ -6954,9 +6954,9 @@ async function extractImports(builder, input) {
6954
6954
  const sources = input.paths.filter(isSourcePath);
6955
6955
  const known = new Set(sources);
6956
6956
  for (const path of sources) {
6957
- let text3;
6957
+ let text4;
6958
6958
  try {
6959
- text3 = await input.read(path);
6959
+ text4 = await input.read(path);
6960
6960
  } catch {
6961
6961
  continue;
6962
6962
  }
@@ -6964,13 +6964,13 @@ async function extractImports(builder, input) {
6964
6964
  const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
6965
6965
  if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
6966
6966
  const specifiers = /* @__PURE__ */ new Set();
6967
- for (const match of text3.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
6968
- for (const match of text3.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
6967
+ for (const match of text4.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
6968
+ for (const match of text4.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
6969
6969
  for (const specifier of specifiers) {
6970
6970
  const resolved = resolveImport(path, specifier, known);
6971
6971
  if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
6972
6972
  }
6973
- for (const name of exportedNames(text3)) {
6973
+ for (const name of exportedNames(text4)) {
6974
6974
  builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
6975
6975
  }
6976
6976
  }
@@ -7011,16 +7011,16 @@ async function extractData(builder, input) {
7011
7011
  };
7012
7012
  for (const path of input.paths) {
7013
7013
  if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
7014
- let text3;
7014
+ let text4;
7015
7015
  try {
7016
- text3 = await input.read(path);
7016
+ text4 = await input.read(path);
7017
7017
  } catch {
7018
7018
  continue;
7019
7019
  }
7020
- for (const statement of text3.matchAll(STATEMENT)) {
7020
+ for (const statement of text4.matchAll(STATEMENT)) {
7021
7021
  const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
7022
7022
  const start = statement.index ?? 0;
7023
- const rest = text3.slice(start + statement[0].length, start + STATEMENT_WINDOW);
7023
+ const rest = text4.slice(start + statement[0].length, start + STATEMENT_WINDOW);
7024
7024
  if (verb === "SELECT") {
7025
7025
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
7026
7026
  continue;
@@ -7036,16 +7036,16 @@ async function extractData(builder, input) {
7036
7036
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
7037
7037
  }
7038
7038
  }
7039
- for (const match of text3.matchAll(NS_CONST)) {
7040
- touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text3, match.index ?? 0));
7039
+ for (const match of text4.matchAll(NS_CONST)) {
7040
+ touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text4, match.index ?? 0));
7041
7041
  }
7042
- for (const match of text3.matchAll(NS_LITERAL)) {
7043
- touch(path, match[1], NAMESPACE, accessFor(text3, match.index ?? 0));
7042
+ for (const match of text4.matchAll(NS_LITERAL)) {
7043
+ touch(path, match[1], NAMESPACE, accessFor(text4, match.index ?? 0));
7044
7044
  }
7045
7045
  }
7046
7046
  }
7047
- function accessFor(text3, index) {
7048
- const window = text3.slice(Math.max(0, index - 160), index + 40);
7047
+ function accessFor(text4, index) {
7048
+ const window = text4.slice(Math.max(0, index - 160), index + 40);
7049
7049
  return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
7050
7050
  }
7051
7051
  async function buildCodeGraph(input) {
@@ -7057,7 +7057,7 @@ async function buildCodeGraph(input) {
7057
7057
  return builder.build();
7058
7058
  }
7059
7059
 
7060
- // ../harness/dist/chunk-GYWQM76X.js
7060
+ // ../harness/dist/chunk-ISR434K7.js
7061
7061
  import { createHash as createHash32 } from "crypto";
7062
7062
  async function digestStagedWorkspace(root, limits) {
7063
7063
  const files = [];
@@ -7385,7 +7385,7 @@ function validateCodePatch(rawPatch, maxBytes) {
7385
7385
  if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
7386
7386
  throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
7387
7387
  }
7388
- const paths = [];
7388
+ const paths2 = [];
7389
7389
  const lines = patch2.split("\n");
7390
7390
  for (let index = 0; index < lines.length; index += 1) {
7391
7391
  const line2 = lines[index];
@@ -7401,10 +7401,10 @@ function validateCodePatch(rawPatch, maxBytes) {
7401
7401
  if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
7402
7402
  throw new TypeError("patch file headers do not match the declared path");
7403
7403
  }
7404
- paths.push(path);
7404
+ paths2.push(path);
7405
7405
  }
7406
- if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
7407
- return paths;
7406
+ if (!paths2.length || new Set(paths2).size !== paths2.length) throw new TypeError("patch has no diffs or repeats a path");
7407
+ return paths2;
7408
7408
  }
7409
7409
  function validHeaderPath(value2, path, prefix) {
7410
7410
  return value2 === "/dev/null" || value2 === `${prefix}/${path}`;
@@ -7429,11 +7429,11 @@ function describePatchFailure(patch2, detail) {
7429
7429
  const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
7430
7430
  return `patch did not apply: ${detail}${hint}`;
7431
7431
  }
7432
- async function applyCodePatch(workspaceDir, rawPatch, paths) {
7432
+ async function applyCodePatch(workspaceDir, rawPatch, paths2) {
7433
7433
  const patch2 = stripPatchEnvelope(rawPatch);
7434
7434
  await gitApply(workspaceDir, patch2, true);
7435
7435
  await gitApply(workspaceDir, patch2, false);
7436
- for (const path of paths) {
7436
+ for (const path of paths2) {
7437
7437
  try {
7438
7438
  const info = await lstat2(resolveCodePath(workspaceDir, path));
7439
7439
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
@@ -7455,8 +7455,8 @@ function gitApply(cwd, patch2, check) {
7455
7455
  });
7456
7456
  let stderr2 = "";
7457
7457
  child.stderr.setEncoding("utf8");
7458
- child.stderr.on("data", (text3) => {
7459
- if (stderr2.length < 4e3) stderr2 += text3.slice(0, 4e3);
7458
+ child.stderr.on("data", (text22) => {
7459
+ if (stderr2.length < 4e3) stderr2 += text22.slice(0, 4e3);
7460
7460
  });
7461
7461
  child.once("error", reject);
7462
7462
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
@@ -7480,8 +7480,8 @@ async function restoreCodeWorkspaceCheckpoint(input) {
7480
7480
  const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
7481
7481
  try {
7482
7482
  if (checkpoint.patch) {
7483
- const paths = validateCodePatch(checkpoint.patch, 256 * 1024);
7484
- await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths);
7483
+ const paths2 = validateCodePatch(checkpoint.patch, 256 * 1024);
7484
+ await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths2);
7485
7485
  }
7486
7486
  return { workspace, checkpoint };
7487
7487
  } catch (error) {
@@ -7630,8 +7630,8 @@ async function verifyCodeCandidate(input) {
7630
7630
  try {
7631
7631
  const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
7632
7632
  if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
7633
- const paths = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
7634
- await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths);
7633
+ const paths2 = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
7634
+ await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths2);
7635
7635
  const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
7636
7636
  const policyDigest = digestPolicy(policy);
7637
7637
  const patchDigest = digestBytes(input.candidatePatch);
@@ -7640,7 +7640,7 @@ async function verifyCodeCandidate(input) {
7640
7640
  trustedBaseDigest: input.trustedBaseDigest,
7641
7641
  patchDigest
7642
7642
  });
7643
- const changedTests = changedTestPaths(paths, policy);
7643
+ const changedTests = changedTestPaths(paths2, policy);
7644
7644
  if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
7645
7645
  const recipes = [];
7646
7646
  const logs = [];
@@ -7707,8 +7707,8 @@ function validate2(input) {
7707
7707
  }
7708
7708
  return result;
7709
7709
  }
7710
- function changedTestPaths(paths, policy) {
7711
- return paths.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
7710
+ function changedTestPaths(paths2, policy) {
7711
+ return paths2.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
7712
7712
  }
7713
7713
  function recipeReceipt(recipe2, result, artifacts) {
7714
7714
  const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
@@ -8439,7 +8439,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
8439
8439
  files(root) {
8440
8440
  const existing = cache2.get(root);
8441
8441
  if (existing) return existing;
8442
- const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
8442
+ const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
8443
8443
  cache2.set(root, pending);
8444
8444
  void pending.catch(() => {
8445
8445
  if (cache2.get(root) === pending) cache2.delete(root);
@@ -8452,7 +8452,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
8452
8452
  };
8453
8453
  }
8454
8454
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
8455
- const paths = [];
8455
+ const paths2 = [];
8456
8456
  const walk = async (directory) => {
8457
8457
  for (const entry of await readdir22(directory, { withFileTypes: true })) {
8458
8458
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
@@ -8466,26 +8466,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
8466
8466
  } catch {
8467
8467
  continue;
8468
8468
  }
8469
- paths.push(path);
8470
- if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
8469
+ paths2.push(path);
8470
+ if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
8471
8471
  }
8472
8472
  }
8473
8473
  };
8474
8474
  await walk(resolve42(root));
8475
- return paths.sort();
8475
+ return paths2.sort();
8476
8476
  }
8477
- function listWorkspace(paths, options = {}) {
8477
+ function listWorkspace(paths2, options = {}) {
8478
8478
  const max = options.maxEntries ?? 1e3;
8479
8479
  const prefix = options.prefix?.replace(/\/+$/, "");
8480
- const scoped = prefix ? paths.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths];
8480
+ const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
8481
8481
  return scoped.slice(0, max);
8482
8482
  }
8483
- async function searchWorkspace(root, paths, options) {
8483
+ async function searchWorkspace(root, paths2, options) {
8484
8484
  options.signal?.throwIfAborted();
8485
8485
  if (!options.query) throw new TypeError("search query must be a non-empty string");
8486
8486
  const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
8487
8487
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
8488
- const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
8488
+ const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
8489
8489
  if (scoped.length === 0) return [];
8490
8490
  try {
8491
8491
  return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
@@ -8495,11 +8495,11 @@ async function searchWorkspace(root, paths, options) {
8495
8495
  }
8496
8496
  }
8497
8497
  var MAX_NATIVE_ARG_BYTES = 96 * 1024;
8498
- async function nativeSearch(root, paths, options) {
8498
+ async function nativeSearch(root, paths2, options) {
8499
8499
  const batches = [];
8500
8500
  let batch = [];
8501
8501
  let bytes = 0;
8502
- for (const path of paths) {
8502
+ for (const path of paths2) {
8503
8503
  const size = Buffer.byteLength(path) + 1;
8504
8504
  if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
8505
8505
  batches.push(batch);
@@ -8518,7 +8518,7 @@ async function nativeSearch(root, paths, options) {
8518
8518
  }
8519
8519
  return matches;
8520
8520
  }
8521
- function nativeSearchBatch(root, paths, options, remaining) {
8521
+ function nativeSearchBatch(root, paths2, options, remaining) {
8522
8522
  return new Promise((resolveMatches, reject) => {
8523
8523
  const args = [
8524
8524
  "--fixed-strings",
@@ -8531,7 +8531,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
8531
8531
  options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
8532
8532
  "--",
8533
8533
  options.query,
8534
- ...paths
8534
+ ...paths2
8535
8535
  ];
8536
8536
  const child = spawn33("rg", args, {
8537
8537
  cwd: root,
@@ -8717,16 +8717,16 @@ function createCodePolicyGate(options) {
8717
8717
  }
8718
8718
  };
8719
8719
  }
8720
- function directoryPrefixes(paths) {
8720
+ function directoryPrefixes(paths2) {
8721
8721
  const prefixes = /* @__PURE__ */ new Set(["."]);
8722
- for (const path of paths) {
8722
+ for (const path of paths2) {
8723
8723
  const parts = path.split("/");
8724
8724
  for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
8725
8725
  }
8726
8726
  return [...prefixes].sort();
8727
8727
  }
8728
- async function safePrefix(base, paths, prefix) {
8729
- const prefixes = directoryPrefixes(paths);
8728
+ async function safePrefix(base, paths2, prefix) {
8729
+ const prefixes = directoryPrefixes(paths2);
8730
8730
  const conversions = await conversionRegistry(
8731
8731
  [await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
8732
8732
  { "code.prefixes.v1": prefixes }
@@ -8838,7 +8838,7 @@ function response(request3, ok, content2, details) {
8838
8838
  return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
8839
8839
  }
8840
8840
  var cache = /* @__PURE__ */ new Map();
8841
- function workspaceGraphs(workspaceDir, paths) {
8841
+ function workspaceGraphs(workspaceDir, paths2) {
8842
8842
  const existing = cache.get(workspaceDir);
8843
8843
  if (existing) return existing;
8844
8844
  const read22 = (path) => readFile3(join33(workspaceDir, path), "utf8");
@@ -8846,7 +8846,7 @@ function workspaceGraphs(workspaceDir, paths) {
8846
8846
  // No knownTables: a staged workspace may not carry migrations, and a filter
8847
8847
  // that silently drops every table is worse than an unfiltered one. Callers
8848
8848
  // with ground truth should build the graph themselves.
8849
- graph: await buildCodeGraph({ paths, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
8849
+ graph: await buildCodeGraph({ paths: paths2, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
8850
8850
  }))();
8851
8851
  cache.set(workspaceDir, built);
8852
8852
  return built;
@@ -8906,11 +8906,11 @@ async function read(context, request3, options, policy, registry) {
8906
8906
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
8907
8907
  throw new TypeError("requested line range exceeds its bound");
8908
8908
  }
8909
- const paths = await registry.files(context.workspaceDir);
8910
- if (!paths.includes(path)) {
8909
+ const paths2 = await registry.files(context.workspaceDir);
8910
+ if (!paths2.includes(path)) {
8911
8911
  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.`);
8912
8912
  }
8913
- const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
8913
+ const allowed = await policy.read(policyContext(context, request3, options, { paths: paths2, path, startLine, endLine }));
8914
8914
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8915
8915
  const target = resolveCodePath(context.workspaceDir, path);
8916
8916
  const info = await stat2(target);
@@ -8932,16 +8932,16 @@ async function list(context, request3, options, policy, registry) {
8932
8932
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
8933
8933
  const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
8934
8934
  if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
8935
- const paths = await registry.files(context.workspaceDir);
8936
- const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
8935
+ const paths2 = await registry.files(context.workspaceDir);
8936
+ const allowed = await policy.list(policyContext(context, request3, options, { paths: paths2, ...prefix ? { prefix } : {} }));
8937
8937
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8938
- const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
8938
+ const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
8939
8939
  if (!entries.length) {
8940
8940
  return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
8941
8941
  }
8942
- const truncated = entries.length < paths.length && entries.length === maxEntries;
8943
- const hint = !prefix && paths.length > 500 ? `
8944
- \u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
8942
+ const truncated = entries.length < paths2.length && entries.length === maxEntries;
8943
+ const hint = !prefix && paths2.length > 500 ? `
8944
+ \u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
8945
8945
  return response(
8946
8946
  request3,
8947
8947
  true,
@@ -8959,10 +8959,10 @@ async function search(context, request3, options, policy, registry) {
8959
8959
  const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
8960
8960
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
8961
8961
  const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
8962
- const paths = await registry.files(context.workspaceDir);
8963
- const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
8962
+ const paths2 = await registry.files(context.workspaceDir);
8963
+ const allowed = await policy.search(policyContext(context, request3, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
8964
8964
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8965
- const matches = await searchWorkspace(context.workspaceDir, paths, {
8965
+ const matches = await searchWorkspace(context.workspaceDir, paths2, {
8966
8966
  query,
8967
8967
  maxResults,
8968
8968
  caseSensitive,
@@ -8984,8 +8984,8 @@ async function graphQuery(context, request3, options, policy, registry) {
8984
8984
  selector: query
8985
8985
  }));
8986
8986
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8987
- const paths = await registry.files(context.workspaceDir);
8988
- const graphs = await workspaceGraphs(context.workspaceDir, paths);
8987
+ const paths2 = await registry.files(context.workspaceDir);
8988
+ const graphs = await workspaceGraphs(context.workspaceDir, paths2);
8989
8989
  if (request3.tool === "sandbox.overview") {
8990
8990
  return response(request3, true, renderOverview(graphs, query || void 0));
8991
8991
  }
@@ -9047,16 +9047,16 @@ function toolFailureMessage(reason) {
9047
9047
  async function patch(context, request3, options, policy, registry) {
9048
9048
  exactKeys(request3.input, ["patch"]);
9049
9049
  const value2 = stringField(request3.input, "patch");
9050
- const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
9051
- if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
9050
+ const paths2 = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
9051
+ if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
9052
9052
  throw new TypeError("patch targets a read-only reference source");
9053
9053
  }
9054
9054
  const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
9055
9055
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9056
- await applyCodePatch(context.workspaceDir, value2, paths);
9056
+ await applyCodePatch(context.workspaceDir, value2, paths2);
9057
9057
  registry.invalidate(context.workspaceDir);
9058
9058
  forgetWorkspaceGraphs(context.workspaceDir);
9059
- return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
9059
+ return response(request3, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
9060
9060
  }
9061
9061
  async function recipe(context, request3, options, recipes, policy) {
9062
9062
  exactKeys(request3.input, ["recipeId"]);
@@ -9405,11 +9405,126 @@ async function startGoalPursuit(input) {
9405
9405
  async function appendCodeRuntimeEvent(control, command, event, refs) {
9406
9406
  const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
9407
9407
  refs.push(eventId);
9408
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
9408
+ const attributed = { ...event, interactionId: command.commandId };
9409
+ const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
9409
9410
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
9410
9411
  }
9411
9412
  var digestRuntimeValue = (value2) => `sha256:${createHash32("sha256").update(value2).digest("hex")}`;
9412
9413
  var runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
9414
+ var text3 = (value2, maximum) => {
9415
+ if (typeof value2 !== "string") return void 0;
9416
+ const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
9417
+ return bounded ? bounded.slice(0, maximum) : void 0;
9418
+ };
9419
+ var integer2 = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
9420
+ var record32 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
9421
+ var excerpt = (value2, tail = false) => {
9422
+ if (typeof value2 !== "string") return void 0;
9423
+ const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
9424
+ const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
9425
+ if (!source) return void 0;
9426
+ const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
9427
+ const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
9428
+ return text3(selected.join("\n"), 2400);
9429
+ };
9430
+ var paths = (value2) => {
9431
+ if (!Array.isArray(value2)) return void 0;
9432
+ const items = value2.flatMap((item) => {
9433
+ const path = text3(item, 1024);
9434
+ return path ? [path] : [];
9435
+ }).slice(0, 12);
9436
+ return items.length ? items : void 0;
9437
+ };
9438
+ function patchStats(value2) {
9439
+ if (typeof value2 !== "string") return {};
9440
+ let additions = 0;
9441
+ let deletions = 0;
9442
+ for (const line2 of value2.slice(0, 262144).split("\n")) {
9443
+ if (line2.startsWith("+++") || line2.startsWith("---")) continue;
9444
+ if (line2.startsWith("+")) additions += 1;
9445
+ else if (line2.startsWith("-")) deletions += 1;
9446
+ }
9447
+ return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
9448
+ }
9449
+ function searchResults(value2) {
9450
+ if (typeof value2 !== "string") return void 0;
9451
+ const results = value2.split("\n").flatMap((line2) => {
9452
+ const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
9453
+ if (!match) return [];
9454
+ const lineNumber = Number(match[2]);
9455
+ const itemText = text3(match[3], 240);
9456
+ if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
9457
+ return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
9458
+ }).slice(0, 5);
9459
+ return results.length ? results : void 0;
9460
+ }
9461
+ function codeToolRequestPresentation(request3) {
9462
+ const input = request3.input;
9463
+ if (request3.tool === "sandbox.read") {
9464
+ const path = text3(input.path, 1024);
9465
+ if (!path) return void 0;
9466
+ const startLine = integer2(input.startLine);
9467
+ const endLine = integer2(input.endLine);
9468
+ return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
9469
+ }
9470
+ if (request3.tool === "sandbox.list") {
9471
+ const scope = text3(input.prefix, 1024);
9472
+ return { kind: "list", ...scope ? { scope } : {} };
9473
+ }
9474
+ if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
9475
+ const query = text3(input.query, 512);
9476
+ const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
9477
+ if (request3.tool === "sandbox.search" && !query) return void 0;
9478
+ return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
9479
+ }
9480
+ if (request3.tool === "sandbox.apply_patch") {
9481
+ return { kind: "patch", ...patchStats(input.patch) };
9482
+ }
9483
+ const recipeId = text3(input.recipeId, 120);
9484
+ return recipeId ? { kind: "recipe", recipeId } : void 0;
9485
+ }
9486
+ function codeToolResultPresentation(request3, response2) {
9487
+ const started = codeToolRequestPresentation(request3);
9488
+ if (!started || !response2.ok) return started;
9489
+ const details = record32(response2.details);
9490
+ if (started.kind === "read") {
9491
+ return {
9492
+ ...started,
9493
+ ...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
9494
+ ...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
9495
+ ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
9496
+ };
9497
+ }
9498
+ if (started.kind === "list") {
9499
+ 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);
9500
+ return {
9501
+ ...started,
9502
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
9503
+ ...listed.length ? { paths: listed } : {}
9504
+ };
9505
+ }
9506
+ if (started.kind === "query") {
9507
+ const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
9508
+ const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
9509
+ return {
9510
+ ...started,
9511
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
9512
+ ...results ? { results } : {},
9513
+ ...resultExcerpt ? { excerpt: resultExcerpt } : {}
9514
+ };
9515
+ }
9516
+ if (started.kind === "patch") {
9517
+ return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
9518
+ }
9519
+ const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
9520
+ return {
9521
+ ...started,
9522
+ ...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
9523
+ ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
9524
+ ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
9525
+ ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
9526
+ };
9527
+ }
9413
9528
  var TheseusRuntimeEngine = class {
9414
9529
  constructor(options) {
9415
9530
  this.options = options;
@@ -9643,18 +9758,29 @@ var TheseusRuntimeEngine = class {
9643
9758
  return {
9644
9759
  execute: async (context, request3) => {
9645
9760
  const startedAt = Date.now();
9761
+ const operationId = digestRuntimeValue(`${command.commandId}:${request3.requestId}`);
9762
+ const startedPresentation = codeToolRequestPresentation(request3);
9646
9763
  await this.#event(
9647
9764
  command,
9648
- { type: "tool", phase: "started", tool: request3.tool },
9765
+ {
9766
+ type: "tool",
9767
+ phase: "started",
9768
+ tool: request3.tool,
9769
+ operationId,
9770
+ ...startedPresentation ? { presentation: startedPresentation } : {}
9771
+ },
9649
9772
  active.conversationRefs
9650
9773
  ).catch(() => void 0);
9651
9774
  const response2 = await broker.execute(context, request3);
9775
+ const completedPresentation = codeToolResultPresentation(request3, response2);
9652
9776
  await this.#event(command, {
9653
9777
  type: "tool",
9654
9778
  phase: "completed",
9655
9779
  tool: request3.tool,
9656
9780
  ok: response2.ok,
9657
- durationMs: Date.now() - startedAt
9781
+ durationMs: Date.now() - startedAt,
9782
+ operationId,
9783
+ ...completedPresentation ? { presentation: completedPresentation } : {}
9658
9784
  }, active.conversationRefs).catch(() => void 0);
9659
9785
  return response2;
9660
9786
  }
@@ -10697,7 +10823,8 @@ Usage:
10697
10823
  odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
10698
10824
  odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
10699
10825
  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]
10700
- odla-ai pm next --app <id> [--project <id>] [--json]
10826
+ odla-ai pm next --app <id> [--project <id>] [--verbose] [--json]
10827
+ odla-ai pm start --app <id> [<task-id>] [--goal <id>] [--verbose] [--mutation-id <id>] [--json] [claims the top Ready task in one call]
10701
10828
  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]
10702
10829
  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]
10703
10830
  odla-ai pm task claim <id> --expected-revision <n> [--mutation-id <id>] [--json]
@@ -10718,7 +10845,7 @@ Usage:
10718
10845
  odla-ai pm <goal|task|decision|bug> comments <id> [--json]
10719
10846
  odla-ai pm <goal|task|decision|bug> history <id> [--limit <n>] [--json]
10720
10847
  odla-ai pm <goal|task|decision|bug> rm <id>
10721
- odla-ai pm handoff --app <id> [--project <id>] [--json]
10848
+ odla-ai pm handoff --app <id> [--project <id>] [--verbose] [--json]
10722
10849
  odla-ai discuss groups [--json]
10723
10850
  odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--mentions] [--json]
10724
10851
  odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
@@ -10883,6 +11010,16 @@ Commands:
10883
11010
  execution contract and a reviewed revision. Any caller with PM
10884
11011
  mutation access to that app/project may do it; claims coordinate
10885
11012
  execution but never own or lock the PM record.
11013
+ To pick up work: "pm next" reads open goals, Ready candidates,
11014
+ and work in progress; "pm start" claims the top Ready task in
11015
+ one call, compare-and-swapped on the revision it just read, and
11016
+ returns the task, its goal, and the acceptance criteria to work
11017
+ against. Finish with "pm task done <id>", or hand it back with
11018
+ "pm task release <id> --expected-revision <n>". "pm start" never
11019
+ marks work Ready: that contract is reviewed separately so an
11020
+ agent cannot authorize its own work.
11021
+ Intake output is projected to the fields an executing caller
11022
+ acts on; --verbose returns whole records with their audit trail.
10886
11023
  bug Intent-first alias for PM bugs. "bug report" writes to
10887
11024
  odla PM; odla product defects do not belong in GitHub Issues.
10888
11025
  discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
@@ -11633,6 +11770,15 @@ var FIELD_MAP = {
11633
11770
  execution: { key: "executionMode" },
11634
11771
  "expected-revision": { key: "expectedRevision", num: true }
11635
11772
  };
11773
+ var PmRequestError = class extends Error {
11774
+ constructor(status, message2) {
11775
+ super(message2);
11776
+ this.status = status;
11777
+ this.name = "PmRequestError";
11778
+ }
11779
+ status;
11780
+ };
11781
+ var isRevisionConflict = (error) => error instanceof PmRequestError && error.status === 409;
11636
11782
  async function pmRequest(ctx, method, path, body) {
11637
11783
  const response2 = await ctx.doFetch(`${ctx.platformUrl}/registry/pm${path}`, {
11638
11784
  method,
@@ -11652,7 +11798,8 @@ async function pmRequest(ctx, method, path, body) {
11652
11798
  const message2 = error.message;
11653
11799
  if (typeof message2 === "string" && message2.length > 0) detail = message2;
11654
11800
  }
11655
- throw new Error(
11801
+ throw new PmRequestError(
11802
+ response2.status,
11656
11803
  `pm ${method} ${path} failed: ${detail ?? `registry returned ${response2.status}`} (${response2.status})`
11657
11804
  );
11658
11805
  }
@@ -11667,8 +11814,8 @@ function collectFields(parsed, allowClear) {
11667
11814
  if (allowClear) out[spec.key] = null;
11668
11815
  continue;
11669
11816
  }
11670
- const text3 = stringOpt(value2);
11671
- out[spec.key] = spec.num ? Number(text3) : text3;
11817
+ const text4 = stringOpt(value2);
11818
+ out[spec.key] = spec.num ? Number(text4) : text4;
11672
11819
  }
11673
11820
  return out;
11674
11821
  }
@@ -11841,103 +11988,196 @@ async function pmTaskLifecycle(ctx, id2, action2, parsed) {
11841
11988
  ctx.out.log(`task: ${label} \u2192 ${state2}`);
11842
11989
  });
11843
11990
  }
11844
- async function allRecords(ctx, entity, appId, projectId) {
11991
+ async function pmRemove(ctx, entity, id2) {
11992
+ await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
11993
+ ctx.out.log(`deleted ${entity} ${id2}`);
11994
+ }
11995
+
11996
+ // src/pm-lean.ts
11997
+ var COMMON = ["id", "appId", "projectId", "title", "revision"];
11998
+ var PER_ENTITY = {
11999
+ goal: ["status", "proof", "targetPct", "currentPct"],
12000
+ task: [
12001
+ "column",
12002
+ "goalId",
12003
+ "alignmentDecisionId",
12004
+ "description",
12005
+ "acceptanceCriteria",
12006
+ "executionMode",
12007
+ "assigneeId",
12008
+ "claimedByPrincipalId",
12009
+ "dueAt"
12010
+ ],
12011
+ decision: ["status", "body", "supersedesId"],
12012
+ bug: ["status", "severity", "description", "goalId", "assigneeId", "decisionId"]
12013
+ };
12014
+ function leanRecord(entity, record11) {
12015
+ const out = {};
12016
+ for (const key of [...COMMON, ...PER_ENTITY[entity]]) {
12017
+ const value2 = record11[key];
12018
+ if (value2 !== void 0 && value2 !== null) out[key] = value2;
12019
+ }
12020
+ return out;
12021
+ }
12022
+ function leanRecords(entity, records, verbose) {
12023
+ return verbose ? records : records.map((record11) => leanRecord(entity, record11));
12024
+ }
12025
+
12026
+ // src/pm-intake.ts
12027
+ var READY_COLUMN = "todo";
12028
+ var UNMET_GOAL_STATUS = "open";
12029
+ var ACTIVE_TASK_COLUMNS = "backlog,todo,doing,review";
12030
+ var OPEN_BUG_STATUSES = "open,triaged";
12031
+ var wantsVerbose = (parsed) => parsed.options.verbose === true;
12032
+ async function listFiltered(ctx, entity, appId, projectId, filters) {
11845
12033
  const records = [];
11846
12034
  for (; ; ) {
11847
- const q = new URLSearchParams({
11848
- app: appId,
11849
- limit: "100",
11850
- offset: String(records.length)
11851
- });
12035
+ const q = new URLSearchParams({ app: appId, limit: "100", offset: String(records.length) });
11852
12036
  if (projectId) q.set("project", projectId);
12037
+ for (const [key, value2] of Object.entries(filters)) q.set(key, value2);
11853
12038
  const page2 = await pmRequest(ctx, "GET", `/${entity}?${q}`);
11854
12039
  records.push(...page2.records);
11855
12040
  if (records.length >= page2.total || page2.records.length === 0) return records;
11856
12041
  }
11857
12042
  }
11858
- async function pmNext(ctx, parsed) {
12043
+ function requireApp(ctx, parsed, command) {
11859
12044
  const appId = stringOpt(parsed.options.app) ?? ctx.appId;
11860
- const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
11861
- if (!appId) throw new Error("pm next needs --app <appId>");
12045
+ if (!appId) throw new Error(`pm ${command} needs --app <appId>`);
12046
+ return { appId, projectId: stringOpt(parsed.options.project) ?? ctx.projectId };
12047
+ }
12048
+ async function pmNext(ctx, parsed) {
12049
+ const { appId, projectId } = requireApp(ctx, parsed, "next");
12050
+ const verbose = wantsVerbose(parsed);
11862
12051
  const [goals, tasks] = await Promise.all([
11863
- allRecords(ctx, "goal", appId, projectId),
11864
- allRecords(ctx, "task", appId, projectId)
12052
+ listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
12053
+ // One request for both live columns; the split below is over open work only.
12054
+ listFiltered(ctx, "task", appId, projectId, { column: `${READY_COLUMN},doing` })
11865
12055
  ]);
12056
+ const ready = tasks.filter((record11) => record11.column === READY_COLUMN);
12057
+ const doing = tasks.filter((record11) => record11.column === "doing");
12058
+ 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}`;
11866
12059
  const result = {
11867
12060
  appId,
11868
12061
  projectId,
11869
- openGoals: goals.filter((record11) => record11.status === "open"),
11870
- doing: tasks.filter((record11) => record11.column === "doing"),
11871
- ready: tasks.filter((record11) => record11.column === "todo")
12062
+ openGoals: leanRecords("goal", goals, verbose),
12063
+ doing: leanRecords("task", doing, verbose),
12064
+ ready: leanRecords("task", ready, verbose),
12065
+ next: guidance
11872
12066
  };
11873
12067
  emit2(ctx, result, () => {
11874
12068
  ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
11875
- for (const [label, records] of [
11876
- ["doing", result.doing],
11877
- ["ready", result.ready],
11878
- ["open goals", result.openGoals]
12069
+ for (const [label, entity, records] of [
12070
+ ["doing", "task", doing],
12071
+ ["ready", "task", ready],
12072
+ ["open goals", "goal", goals]
11879
12073
  ]) {
11880
12074
  ctx.out.log(`${label}:`);
11881
12075
  if (!records.length) ctx.out.log("- (none)");
11882
- else for (const record11 of records) printRecord(
11883
- ctx,
11884
- label === "open goals" ? "goal" : "task",
11885
- record11
11886
- );
11887
- }
11888
- if (!result.openGoals.length) {
11889
- ctx.out.log("next: discuss alignment with the user before creating or claiming project work");
11890
- } else if (!result.ready.length) {
11891
- ctx.out.log("next: refine a linked Backlog task and mark it Ready");
11892
- } else {
11893
- ctx.out.log("next: review a Ready task, then claim it with its revision");
12076
+ else for (const record11 of records) printRecord(ctx, entity, record11);
11894
12077
  }
12078
+ ctx.out.log(`next: ${guidance}`);
11895
12079
  });
11896
12080
  }
11897
12081
  async function pmHandoff(ctx, parsed) {
11898
- const appId = stringOpt(parsed.options.app) ?? ctx.appId;
11899
- const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
11900
- if (!appId) throw new Error("pm handoff needs --app <appId>");
12082
+ const { appId, projectId } = requireApp(ctx, parsed, "handoff");
12083
+ const verbose = wantsVerbose(parsed);
11901
12084
  const [goals, tasks, bugs] = await Promise.all([
11902
- allRecords(ctx, "goal", appId, projectId),
11903
- allRecords(ctx, "task", appId, projectId),
11904
- allRecords(ctx, "bug", appId, projectId)
12085
+ listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
12086
+ listFiltered(ctx, "task", appId, projectId, { column: ACTIVE_TASK_COLUMNS }),
12087
+ listFiltered(ctx, "bug", appId, projectId, { status: OPEN_BUG_STATUSES })
11905
12088
  ]);
11906
- const handoff = {
12089
+ const clean4 = !goals.length && !tasks.length && !bugs.length;
12090
+ const result = {
11907
12091
  appId,
11908
12092
  projectId,
11909
- unmetGoals: goals.filter((record11) => record11.status !== "met"),
11910
- activeTasks: tasks.filter((record11) => record11.column !== "done"),
11911
- openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
11912
- };
11913
- const result = {
11914
- ...handoff,
11915
- clean: !handoff.unmetGoals.length && !handoff.activeTasks.length && !handoff.openBugs.length
12093
+ unmetGoals: leanRecords("goal", goals, verbose),
12094
+ activeTasks: leanRecords("task", tasks, verbose),
12095
+ openBugs: leanRecords("bug", bugs, verbose),
12096
+ clean: clean4
11916
12097
  };
11917
12098
  emit2(ctx, result, () => {
11918
- if (result.clean) {
12099
+ if (clean4) {
11919
12100
  ctx.out.log(`${appId}: no unresolved PM work`);
11920
12101
  return;
11921
12102
  }
11922
12103
  ctx.out.log(`${appId}: authoritative PM handoff`);
11923
- for (const [label, records] of [
11924
- ["unmet goals", result.unmetGoals],
11925
- ["active tasks", result.activeTasks],
11926
- ["open bugs", result.openBugs]
12104
+ for (const [label, entity, records] of [
12105
+ ["unmet goals", "goal", goals],
12106
+ ["active tasks", "task", tasks],
12107
+ ["open bugs", "bug", bugs]
11927
12108
  ]) {
11928
12109
  ctx.out.log(`${label}:`);
11929
12110
  if (!records.length) ctx.out.log("- (none)");
11930
- else for (const record11 of records) printRecord(
11931
- ctx,
11932
- label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
11933
- record11
11934
- );
12111
+ else for (const record11 of records) printRecord(ctx, entity, record11);
11935
12112
  }
11936
12113
  });
11937
12114
  }
11938
- async function pmRemove(ctx, entity, id2) {
11939
- await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
11940
- ctx.out.log(`deleted ${entity} ${id2}`);
12115
+
12116
+ // src/pm-start.ts
12117
+ var getTask = async (ctx, id2) => (await pmRequest(ctx, "GET", `/task/${encodeURIComponent(id2)}`)).record;
12118
+ async function claimAt(ctx, task, parsed) {
12119
+ const res = await pmRequest(
12120
+ ctx,
12121
+ "POST",
12122
+ `/task/${encodeURIComponent(task.id)}/claim`,
12123
+ { expectedRevision: task.revision, mutationId: writeMutationId2(parsed) }
12124
+ );
12125
+ return res.record;
12126
+ }
12127
+ async function selectTask(ctx, parsed, appId, projectId) {
12128
+ const named = parsed.positionals[2];
12129
+ if (named) return getTask(ctx, named);
12130
+ const goalId = stringOpt(parsed.options.goal);
12131
+ const ready = await listFiltered(ctx, "task", appId, projectId, {
12132
+ column: READY_COLUMN,
12133
+ ...goalId ? { goalId } : {}
12134
+ });
12135
+ const candidate = ready[0];
12136
+ if (!candidate) {
12137
+ throw new Error(
12138
+ `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.`
12139
+ );
12140
+ }
12141
+ return candidate;
12142
+ }
12143
+ async function pmStart(ctx, parsed) {
12144
+ const appId = stringOpt(parsed.options.app) ?? ctx.appId;
12145
+ if (!appId) throw new Error("pm start needs --app <appId>");
12146
+ const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
12147
+ const verbose = wantsVerbose(parsed);
12148
+ const selected = await selectTask(ctx, parsed, appId, projectId);
12149
+ if (selected.column !== READY_COLUMN) {
12150
+ throw new Error(
12151
+ `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.`
12152
+ );
12153
+ }
12154
+ let claimed;
12155
+ try {
12156
+ claimed = await claimAt(ctx, selected, parsed);
12157
+ } catch (error) {
12158
+ if (!isRevisionConflict(error)) throw error;
12159
+ const reloaded = await getTask(ctx, selected.id);
12160
+ if (reloaded.column !== READY_COLUMN) {
12161
+ throw new Error(
12162
+ `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.`
12163
+ );
12164
+ }
12165
+ claimed = await claimAt(ctx, reloaded, parsed);
12166
+ }
12167
+ const task = claimed ?? await getTask(ctx, selected.id);
12168
+ const goalId = typeof task.goalId === "string" ? task.goalId : void 0;
12169
+ const goal = goalId ? await pmRequest(ctx, "GET", `/goal/${encodeURIComponent(goalId)}`).then((res) => res.record).catch(() => null) : null;
12170
+ const result = {
12171
+ claimed: verbose ? task : leanRecord("task", task),
12172
+ goal: goal ? verbose ? goal : leanRecord("goal", goal) : null,
12173
+ 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)'
12174
+ };
12175
+ emit2(ctx, result, () => {
12176
+ ctx.out.log(`claimed: ${studioRecordLink(ctx, "task", task)}`);
12177
+ if (goal) ctx.out.log(`goal: ${studioRecordLink(ctx, "goal", goal)}`);
12178
+ if (typeof task.acceptanceCriteria === "string") ctx.out.log(`acceptance: ${task.acceptanceCriteria}`);
12179
+ ctx.out.log(`next: ${result.next}`);
12180
+ });
11941
12181
  }
11942
12182
 
11943
12183
  // src/pm-links.ts
@@ -12411,9 +12651,13 @@ async function pmCommand(parsed, deps = {}) {
12411
12651
  throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
12412
12652
  }
12413
12653
  if (word === "next") {
12414
- assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
12654
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
12415
12655
  return pmNext(await buildContext2(parsed, deps), parsed);
12416
12656
  }
12657
+ if (word === "start") {
12658
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "goal", "verbose", "mutation-id"], 3);
12659
+ return pmStart(await buildContext2(parsed, deps), parsed);
12660
+ }
12417
12661
  if (word === "watch") {
12418
12662
  assertArgs(parsed, [
12419
12663
  ...COMMON_OPTIONS,
@@ -12431,11 +12675,11 @@ async function pmCommand(parsed, deps = {}) {
12431
12675
  return pmWatch(await buildContext2(parsed, deps), parsed).then(() => void 0);
12432
12676
  }
12433
12677
  if (word === "handoff") {
12434
- assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
12678
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
12435
12679
  return pmHandoff(await buildContext2(parsed, deps), parsed);
12436
12680
  }
12437
12681
  const entity = ALIASES[word];
12438
- if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
12682
+ 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).`);
12439
12683
  const requestedAction = parsed.positionals[2] ?? "list";
12440
12684
  const action2 = canonicalAction(requestedAction);
12441
12685
  if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|history|rm.`);
@@ -12983,14 +13227,14 @@ function statusMinutes(value2) {
12983
13227
  }
12984
13228
  async function read2(url, headers, doFetch) {
12985
13229
  const response2 = await doFetch(url, { headers });
12986
- const text3 = await response2.text();
13230
+ const text4 = await response2.text();
12987
13231
  let body = {};
12988
- if (text3) {
13232
+ if (text4) {
12989
13233
  try {
12990
- const value2 = JSON.parse(text3);
13234
+ const value2 = JSON.parse(text4);
12991
13235
  body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
12992
13236
  } catch {
12993
- body = { message: text3.slice(0, 300) };
13237
+ body = { message: text4.slice(0, 300) };
12994
13238
  }
12995
13239
  }
12996
13240
  return { httpStatus: response2.status, body };
@@ -13220,13 +13464,13 @@ async function monitorCommand(parsed, deps = {}) {
13220
13464
  }
13221
13465
  async function request2(url, init, doFetch) {
13222
13466
  const response2 = await doFetch(url, init);
13223
- const text3 = await response2.text();
13467
+ const text4 = await response2.text();
13224
13468
  let body = {};
13225
13469
  try {
13226
- const parsed = text3 ? JSON.parse(text3) : {};
13470
+ const parsed = text4 ? JSON.parse(text4) : {};
13227
13471
  body = record10(parsed) ? parsed : { value: parsed };
13228
13472
  } catch {
13229
- body = { message: text3.slice(0, 500) };
13473
+ body = { message: text4.slice(0, 500) };
13230
13474
  }
13231
13475
  if (!response2.ok) {
13232
13476
  const error = record10(body.error) ? body.error : body;
@@ -13429,8 +13673,8 @@ function runtimeUrl(cfg, suffix = "") {
13429
13673
  return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
13430
13674
  }
13431
13675
  async function safeError(response2) {
13432
- const text3 = await response2.text();
13433
- return redactSecrets(text3.slice(0, 1e3));
13676
+ const text4 = await response2.text();
13677
+ return redactSecrets(text4.slice(0, 1e3));
13434
13678
  }
13435
13679
  async function finish(doFetch, cfg, token, sessionId, method) {
13436
13680
  return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
@@ -13849,6 +14093,7 @@ var COMMAND_SURFACE = {
13849
14093
  project: { list: {}, add: {}, create: {}, use: {} },
13850
14094
  handoff: {},
13851
14095
  next: {},
14096
+ start: {},
13852
14097
  watch: {}
13853
14098
  },
13854
14099
  provision: {},
@@ -13923,13 +14168,13 @@ function invocationPath(words2) {
13923
14168
  return path;
13924
14169
  }
13925
14170
  function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
13926
- const paths = [];
14171
+ const paths2 = [];
13927
14172
  for (const [word, child] of Object.entries(node)) {
13928
14173
  const path = [...prefix, word];
13929
- paths.push(path);
13930
- paths.push(...surfacePaths(child, path));
14174
+ paths2.push(path);
14175
+ paths2.push(...surfacePaths(child, path));
13931
14176
  }
13932
- return paths;
14177
+ return paths2;
13933
14178
  }
13934
14179
 
13935
14180
  // src/record.ts
@@ -13992,10 +14237,10 @@ var DAY_MS = 24 * 60 * 60 * 1e3;
13992
14237
  var UNITS = { d: DAY_MS, w: 7 * DAY_MS, y: 365 * DAY_MS };
13993
14238
  function parseDeviceTtl(raw) {
13994
14239
  if (raw === void 0 || raw === true) return void 0;
13995
- const text3 = String(raw).trim().toLowerCase();
13996
- if (!text3) return void 0;
13997
- if (text3 === "forever" || text3 === "never") return 100 * 365 * DAY_MS;
13998
- const match = /^(\d+)\s*([dwy])$/.exec(text3);
14240
+ const text4 = String(raw).trim().toLowerCase();
14241
+ if (!text4) return void 0;
14242
+ if (text4 === "forever" || text4 === "never") return 100 * 365 * DAY_MS;
14243
+ const match = /^(\d+)\s*([dwy])$/.exec(text4);
13999
14244
  if (!match) {
14000
14245
  throw new Error(
14001
14246
  `--device-ttl expects a duration like 30d, 6w, 2y, or "forever" (got "${raw}")`
@@ -14328,12 +14573,12 @@ async function runbookRemove(ctx, slug) {
14328
14573
  // src/runbook-import.ts
14329
14574
  import { readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
14330
14575
  import { basename as basename2, join as join15 } from "path";
14331
- function parseRunbook(text3, slug) {
14332
- let rest = text3;
14576
+ function parseRunbook(text4, slug) {
14577
+ let rest = text4;
14333
14578
  const meta = {};
14334
- const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text3);
14579
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text4);
14335
14580
  if (fm) {
14336
- rest = text3.slice(fm[0].length);
14581
+ rest = text4.slice(fm[0].length);
14337
14582
  for (const line2 of fm[1].split(/\r?\n/)) {
14338
14583
  const pair = /^(\w+)\s*:\s*(.+)$/.exec(line2.trim());
14339
14584
  if (!pair) continue;
@@ -15958,4 +16203,4 @@ export {
15958
16203
  isTerminalHostedSecurityStatus,
15959
16204
  runCli
15960
16205
  };
15961
- //# sourceMappingURL=chunk-K6ATAKGR.js.map
16206
+ //# sourceMappingURL=chunk-T4OPSRME.js.map