@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/bin.cjs CHANGED
@@ -556,10 +556,10 @@ function isManagedDevVar(line2) {
556
556
  const match = line2.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
557
557
  return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
558
558
  }
559
- function writePrivateText(path, text3) {
559
+ function writePrivateText(path, text4) {
560
560
  (0, import_node_fs7.mkdirSync)((0, import_node_path5.dirname)(path), { recursive: true });
561
561
  const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
562
- (0, import_node_fs7.writeFileSync)(temporary, text3, { mode: 384 });
562
+ (0, import_node_fs7.writeFileSync)(temporary, text4, { mode: 384 });
563
563
  (0, import_node_fs7.chmodSync)(temporary, 384);
564
564
  (0, import_node_fs7.renameSync)(temporary, path);
565
565
  }
@@ -1105,12 +1105,12 @@ async function readAdminAiAudit(request3) {
1105
1105
  }
1106
1106
  }
1107
1107
  async function responseBody(response2) {
1108
- const text3 = await response2.text();
1109
- if (!text3) return {};
1108
+ const text4 = await response2.text();
1109
+ if (!text4) return {};
1110
1110
  try {
1111
- return JSON.parse(text3);
1111
+ return JSON.parse(text4);
1112
1112
  } catch {
1113
- return { message: text3.slice(0, 300) };
1113
+ return { message: text4.slice(0, 300) };
1114
1114
  }
1115
1115
  }
1116
1116
  function apiError(status, body) {
@@ -1226,12 +1226,12 @@ function timestamp2(value2) {
1226
1226
  return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
1227
1227
  }
1228
1228
  async function responseBody2(res) {
1229
- const text3 = await res.text();
1230
- if (!text3) return {};
1229
+ const text4 = await res.text();
1230
+ if (!text4) return {};
1231
1231
  try {
1232
- return JSON.parse(text3);
1232
+ return JSON.parse(text4);
1233
1233
  } catch {
1234
- return { message: text3.slice(0, 300) };
1234
+ return { message: text4.slice(0, 300) };
1235
1235
  }
1236
1236
  }
1237
1237
  function apiError2(action2, status, body) {
@@ -1414,12 +1414,12 @@ function catalogModels(body) {
1414
1414
  return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
1415
1415
  }
1416
1416
  async function responseBody3(res) {
1417
- const text3 = await res.text();
1418
- if (!text3) return {};
1417
+ const text4 = await res.text();
1418
+ if (!text4) return {};
1419
1419
  try {
1420
- return JSON.parse(text3);
1420
+ return JSON.parse(text4);
1421
1421
  } catch {
1422
- return { message: text3.slice(0, 300) };
1422
+ return { message: text4.slice(0, 300) };
1423
1423
  }
1424
1424
  }
1425
1425
  function apiError3(action2, status, body) {
@@ -2936,8 +2936,8 @@ async function appImport(options) {
2936
2936
  const out = options.stdout ?? console;
2937
2937
  const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
2938
2938
  const { tenant } = resolveTenant(cfg, options.env);
2939
- const text3 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs12.readFileSync)(0, "utf8")))() : (0, import_node_fs12.readFileSync)(options.file, "utf8");
2940
- const { format, sources } = (0, import_import.parseImport)(text3, options.ns);
2939
+ const text4 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs12.readFileSync)(0, "utf8")))() : (0, import_node_fs12.readFileSync)(options.file, "utf8");
2940
+ const { format, sources } = (0, import_import.parseImport)(text4, options.ns);
2941
2941
  if (format === "namespace-map" && options.ns) {
2942
2942
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
2943
2943
  }
@@ -3274,7 +3274,7 @@ var init_brand_design_unpack = __esm({
3274
3274
  "text/html": "html",
3275
3275
  "application/json": "json"
3276
3276
  };
3277
- encode = (text3) => new TextEncoder().encode(text3);
3277
+ encode = (text4) => new TextEncoder().encode(text4);
3278
3278
  }
3279
3279
  });
3280
3280
 
@@ -4130,9 +4130,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
4130
4130
  }
4131
4131
  throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
4132
4132
  }
4133
- function errorCode(text3) {
4133
+ function errorCode(text4) {
4134
4134
  try {
4135
- const body = JSON.parse(text3);
4135
+ const body = JSON.parse(text4);
4136
4136
  return typeof body.error?.code === "string" ? body.error.code : null;
4137
4137
  } catch {
4138
4138
  return null;
@@ -4918,15 +4918,15 @@ function readWranglerConfig(path) {
4918
4918
  return null;
4919
4919
  }
4920
4920
  }
4921
- function stripJsonComments(text3) {
4921
+ function stripJsonComments(text4) {
4922
4922
  let result = "";
4923
4923
  let inString = false;
4924
- for (let i = 0; i < text3.length; i++) {
4925
- const ch = text3[i];
4924
+ for (let i = 0; i < text4.length; i++) {
4925
+ const ch = text4[i];
4926
4926
  if (inString) {
4927
4927
  result += ch;
4928
4928
  if (ch === "\\") {
4929
- result += text3[i + 1] ?? "";
4929
+ result += text4[i + 1] ?? "";
4930
4930
  i++;
4931
4931
  } else if (ch === '"') {
4932
4932
  inString = false;
@@ -4938,14 +4938,14 @@ function stripJsonComments(text3) {
4938
4938
  result += ch;
4939
4939
  continue;
4940
4940
  }
4941
- if (ch === "/" && text3[i + 1] === "/") {
4942
- while (i < text3.length && text3[i] !== "\n") i++;
4941
+ if (ch === "/" && text4[i + 1] === "/") {
4942
+ while (i < text4.length && text4[i] !== "\n") i++;
4943
4943
  result += "\n";
4944
4944
  continue;
4945
4945
  }
4946
- if (ch === "/" && text3[i + 1] === "*") {
4946
+ if (ch === "/" && text4[i + 1] === "*") {
4947
4947
  i += 2;
4948
- while (i < text3.length && !(text3[i] === "*" && text3[i + 1] === "/")) i++;
4948
+ while (i < text4.length && !(text4[i] === "*" && text4[i + 1] === "/")) i++;
4949
4949
  i++;
4950
4950
  continue;
4951
4951
  }
@@ -5529,9 +5529,9 @@ function initProject(options) {
5529
5529
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
5530
5530
  out.log("updated .gitignore for local odla credentials");
5531
5531
  }
5532
- function writeIfMissing(path, text3) {
5532
+ function writeIfMissing(path, text4) {
5533
5533
  if ((0, import_node_fs16.existsSync)(path)) return;
5534
- (0, import_node_fs16.writeFileSync)(path, text3);
5534
+ (0, import_node_fs16.writeFileSync)(path, text4);
5535
5535
  }
5536
5536
  function configTemplate(input) {
5537
5537
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -5763,13 +5763,13 @@ async function secretsSetClerkKey(options) {
5763
5763
  body: JSON.stringify({ value: value2 })
5764
5764
  });
5765
5765
  if (!res.ok) {
5766
- const text3 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
5767
- throw new Error(`store Clerk secret key failed (${res.status}): ${text3 || "request failed"}`);
5766
+ const text4 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
5767
+ throw new Error(`store Clerk secret key failed (${res.status}): ${text4 || "request failed"}`);
5768
5768
  }
5769
5769
  out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
5770
5770
  }
5771
- function scrubValue(text3, value2) {
5772
- return redactSecrets(text3).split(value2).join("[value redacted]");
5771
+ function scrubValue(text4, value2) {
5772
+ return redactSecrets(text4).split(value2).join("[value redacted]");
5773
5773
  }
5774
5774
  async function resolveVaultWrite(options) {
5775
5775
  const out = options.stdout ?? console;
@@ -6086,8 +6086,8 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
6086
6086
  harnesses: installations
6087
6087
  };
6088
6088
  }
6089
- function pathsUnder(root, paths) {
6090
- return [...paths].map((path) => (0, import_node_path16.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path16.sep}`) && !(0, import_node_path16.isAbsolute)(path)).sort();
6089
+ function pathsUnder(root, paths2) {
6090
+ return [...paths2].map((path) => (0, import_node_path16.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path16.sep}`) && !(0, import_node_path16.isAbsolute)(path)).sort();
6091
6091
  }
6092
6092
  function normalizeHarnesses(values, global) {
6093
6093
  const requested = values?.length ? values : ["claude"];
@@ -6557,17 +6557,17 @@ var init_cli_project = __esm({
6557
6557
  }
6558
6558
  });
6559
6559
 
6560
- // ../harness/dist/chunk-LNQNFGQC.js
6560
+ // ../harness/dist/chunk-RXNHCGWE.js
6561
6561
  var HARNESS_PROTOCOL_VERSION;
6562
- var init_chunk_LNQNFGQC = __esm({
6563
- "../harness/dist/chunk-LNQNFGQC.js"() {
6562
+ var init_chunk_RXNHCGWE = __esm({
6563
+ "../harness/dist/chunk-RXNHCGWE.js"() {
6564
6564
  "use strict";
6565
6565
  init_cjs_shims();
6566
6566
  HARNESS_PROTOCOL_VERSION = 1;
6567
6567
  }
6568
6568
  });
6569
6569
 
6570
- // ../harness/dist/chunk-K76I2TCQ.js
6570
+ // ../harness/dist/chunk-CR6RE3A2.js
6571
6571
  function assertPinnedImage(image) {
6572
6572
  if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
6573
6573
  }
@@ -6795,12 +6795,12 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
6795
6795
  });
6796
6796
  if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
6797
6797
  if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
6798
- const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
6799
- if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
6798
+ const paths2 = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
6799
+ if (paths2.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
6800
6800
  const root = (0, import_path4.resolve)(sourceDir);
6801
6801
  const files = [];
6802
6802
  let bytes = 0;
6803
- for (const relativePath of paths) {
6803
+ for (const relativePath of paths2) {
6804
6804
  if (!allowedWorkspacePath(relativePath)) continue;
6805
6805
  const source = (0, import_path4.resolve)(root, relativePath);
6806
6806
  if (!source.startsWith(`${root}${import_path4.sep}`)) throw new TypeError("git file path escapes workspace");
@@ -6916,8 +6916,8 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
6916
6916
  }
6917
6917
  }
6918
6918
  var import_child_process, import_fs, import_promises2, import_path, import_process, import_promises3, import_os, import_path2, import_child_process2, import_path3, import_promises4, import_os2, import_path4, import_child_process3, DIGEST_IMAGE, SKIP_WORKSPACE_DIRS, SECRET_WORKSPACE_FILE;
6919
- var init_chunk_K76I2TCQ = __esm({
6920
- "../harness/dist/chunk-K76I2TCQ.js"() {
6919
+ var init_chunk_CR6RE3A2 = __esm({
6920
+ "../harness/dist/chunk-CR6RE3A2.js"() {
6921
6921
  "use strict";
6922
6922
  init_cjs_shims();
6923
6923
  import_child_process = require("child_process");
@@ -7276,13 +7276,13 @@ function validateSnapshot(snapshot, limits) {
7276
7276
  if (!Number.isSafeInteger(limits.maximumFiles) || limits.maximumFiles < 1 || !Number.isSafeInteger(limits.maximumBytes) || limits.maximumBytes < 1 || snapshot.files.length > limits.maximumFiles) {
7277
7277
  throw new CamelError("limit_exceeded", "Code snapshot exceeds its registered limits.");
7278
7278
  }
7279
- const paths = /* @__PURE__ */ new Set();
7279
+ const paths2 = /* @__PURE__ */ new Set();
7280
7280
  let bytes = 0;
7281
7281
  for (const file of snapshot.files) {
7282
- 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") {
7282
+ 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") {
7283
7283
  throw new CamelError("state_conflict", "Code snapshot contains an invalid or duplicate path.");
7284
7284
  }
7285
- paths.add(file.path);
7285
+ paths2.add(file.path);
7286
7286
  bytes += utf8Length(file.path) + utf8Length(file.content);
7287
7287
  }
7288
7288
  if (bytes > limits.maximumBytes) {
@@ -7433,8 +7433,8 @@ function boundedInteger(value2, spec) {
7433
7433
  }
7434
7434
  function boundedNumber(value2, spec) {
7435
7435
  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.");
7436
- const text3 = String(value2);
7437
- 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.");
7436
+ const text4 = String(value2);
7437
+ 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.");
7438
7438
  return value2;
7439
7439
  }
7440
7440
  function enumMember(value2, spec) {
@@ -7617,8 +7617,8 @@ function validateUnsafeSelector(path, value2, tool) {
7617
7617
  return void 0;
7618
7618
  }
7619
7619
  function looksLikeDestination(value2) {
7620
- const text3 = value2.trim();
7621
- return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
7620
+ const text4 = value2.trim();
7621
+ return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text4) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text4);
7622
7622
  }
7623
7623
  var init_policy = __esm({
7624
7624
  "../camel/dist/policy.js"() {
@@ -7792,9 +7792,9 @@ async function extractImports(builder, input) {
7792
7792
  const sources = input.paths.filter(isSourcePath);
7793
7793
  const known = new Set(sources);
7794
7794
  for (const path of sources) {
7795
- let text3;
7795
+ let text4;
7796
7796
  try {
7797
- text3 = await input.read(path);
7797
+ text4 = await input.read(path);
7798
7798
  } catch {
7799
7799
  continue;
7800
7800
  }
@@ -7802,13 +7802,13 @@ async function extractImports(builder, input) {
7802
7802
  const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
7803
7803
  if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
7804
7804
  const specifiers = /* @__PURE__ */ new Set();
7805
- for (const match of text3.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
7806
- for (const match of text3.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
7805
+ for (const match of text4.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
7806
+ for (const match of text4.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
7807
7807
  for (const specifier of specifiers) {
7808
7808
  const resolved = resolveImport(path, specifier, known);
7809
7809
  if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
7810
7810
  }
7811
- for (const name of exportedNames(text3)) {
7811
+ for (const name of exportedNames(text4)) {
7812
7812
  builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
7813
7813
  }
7814
7814
  }
@@ -7821,16 +7821,16 @@ async function extractData(builder, input) {
7821
7821
  };
7822
7822
  for (const path of input.paths) {
7823
7823
  if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
7824
- let text3;
7824
+ let text4;
7825
7825
  try {
7826
- text3 = await input.read(path);
7826
+ text4 = await input.read(path);
7827
7827
  } catch {
7828
7828
  continue;
7829
7829
  }
7830
- for (const statement of text3.matchAll(STATEMENT)) {
7830
+ for (const statement of text4.matchAll(STATEMENT)) {
7831
7831
  const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
7832
7832
  const start = statement.index ?? 0;
7833
- const rest = text3.slice(start + statement[0].length, start + STATEMENT_WINDOW);
7833
+ const rest = text4.slice(start + statement[0].length, start + STATEMENT_WINDOW);
7834
7834
  if (verb === "SELECT") {
7835
7835
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
7836
7836
  continue;
@@ -7846,16 +7846,16 @@ async function extractData(builder, input) {
7846
7846
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
7847
7847
  }
7848
7848
  }
7849
- for (const match of text3.matchAll(NS_CONST)) {
7850
- touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text3, match.index ?? 0));
7849
+ for (const match of text4.matchAll(NS_CONST)) {
7850
+ touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text4, match.index ?? 0));
7851
7851
  }
7852
- for (const match of text3.matchAll(NS_LITERAL)) {
7853
- touch(path, match[1], NAMESPACE, accessFor(text3, match.index ?? 0));
7852
+ for (const match of text4.matchAll(NS_LITERAL)) {
7853
+ touch(path, match[1], NAMESPACE, accessFor(text4, match.index ?? 0));
7854
7854
  }
7855
7855
  }
7856
7856
  }
7857
- function accessFor(text3, index) {
7858
- const window = text3.slice(Math.max(0, index - 160), index + 40);
7857
+ function accessFor(text4, index) {
7858
+ const window = text4.slice(Math.max(0, index - 160), index + 40);
7859
7859
  return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
7860
7860
  }
7861
7861
  async function buildCodeGraph(input) {
@@ -7915,7 +7915,7 @@ var init_code2 = __esm({
7915
7915
  }
7916
7916
  });
7917
7917
 
7918
- // ../harness/dist/chunk-GYWQM76X.js
7918
+ // ../harness/dist/chunk-ISR434K7.js
7919
7919
  async function digestStagedWorkspace(root, limits) {
7920
7920
  const files = [];
7921
7921
  const walk = async (directory) => {
@@ -8195,7 +8195,7 @@ function validateCodePatch(rawPatch, maxBytes) {
8195
8195
  if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
8196
8196
  throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
8197
8197
  }
8198
- const paths = [];
8198
+ const paths2 = [];
8199
8199
  const lines = patch2.split("\n");
8200
8200
  for (let index = 0; index < lines.length; index += 1) {
8201
8201
  const line2 = lines[index];
@@ -8211,10 +8211,10 @@ function validateCodePatch(rawPatch, maxBytes) {
8211
8211
  if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
8212
8212
  throw new TypeError("patch file headers do not match the declared path");
8213
8213
  }
8214
- paths.push(path);
8214
+ paths2.push(path);
8215
8215
  }
8216
- if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
8217
- return paths;
8216
+ if (!paths2.length || new Set(paths2).size !== paths2.length) throw new TypeError("patch has no diffs or repeats a path");
8217
+ return paths2;
8218
8218
  }
8219
8219
  function validHeaderPath(value2, path, prefix) {
8220
8220
  return value2 === "/dev/null" || value2 === `${prefix}/${path}`;
@@ -8239,11 +8239,11 @@ function describePatchFailure(patch2, detail) {
8239
8239
  const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
8240
8240
  return `patch did not apply: ${detail}${hint}`;
8241
8241
  }
8242
- async function applyCodePatch(workspaceDir, rawPatch, paths) {
8242
+ async function applyCodePatch(workspaceDir, rawPatch, paths2) {
8243
8243
  const patch2 = stripPatchEnvelope(rawPatch);
8244
8244
  await gitApply(workspaceDir, patch2, true);
8245
8245
  await gitApply(workspaceDir, patch2, false);
8246
- for (const path of paths) {
8246
+ for (const path of paths2) {
8247
8247
  try {
8248
8248
  const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
8249
8249
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
@@ -8265,8 +8265,8 @@ function gitApply(cwd, patch2, check) {
8265
8265
  });
8266
8266
  let stderr2 = "";
8267
8267
  child.stderr.setEncoding("utf8");
8268
- child.stderr.on("data", (text3) => {
8269
- if (stderr2.length < 4e3) stderr2 += text3.slice(0, 4e3);
8268
+ child.stderr.on("data", (text22) => {
8269
+ if (stderr2.length < 4e3) stderr2 += text22.slice(0, 4e3);
8270
8270
  });
8271
8271
  child.once("error", reject);
8272
8272
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
@@ -8290,8 +8290,8 @@ async function restoreCodeWorkspaceCheckpoint(input) {
8290
8290
  const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
8291
8291
  try {
8292
8292
  if (checkpoint.patch) {
8293
- const paths = validateCodePatch(checkpoint.patch, 256 * 1024);
8294
- await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths);
8293
+ const paths2 = validateCodePatch(checkpoint.patch, 256 * 1024);
8294
+ await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths2);
8295
8295
  }
8296
8296
  return { workspace, checkpoint };
8297
8297
  } catch (error) {
@@ -8432,8 +8432,8 @@ async function verifyCodeCandidate(input) {
8432
8432
  try {
8433
8433
  const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
8434
8434
  if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
8435
- const paths = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
8436
- await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths);
8435
+ const paths2 = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
8436
+ await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths2);
8437
8437
  const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
8438
8438
  const policyDigest = digestPolicy(policy);
8439
8439
  const patchDigest = digestBytes(input.candidatePatch);
@@ -8442,7 +8442,7 @@ async function verifyCodeCandidate(input) {
8442
8442
  trustedBaseDigest: input.trustedBaseDigest,
8443
8443
  patchDigest
8444
8444
  });
8445
- const changedTests = changedTestPaths(paths, policy);
8445
+ const changedTests = changedTestPaths(paths2, policy);
8446
8446
  if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
8447
8447
  const recipes = [];
8448
8448
  const logs = [];
@@ -8509,8 +8509,8 @@ function validate2(input) {
8509
8509
  }
8510
8510
  return result;
8511
8511
  }
8512
- function changedTestPaths(paths, policy) {
8513
- return paths.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
8512
+ function changedTestPaths(paths2, policy) {
8513
+ return paths2.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
8514
8514
  }
8515
8515
  function recipeReceipt(recipe2, result, artifacts) {
8516
8516
  const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
@@ -9143,7 +9143,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
9143
9143
  files(root) {
9144
9144
  const existing = cache2.get(root);
9145
9145
  if (existing) return existing;
9146
- const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
9146
+ const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
9147
9147
  cache2.set(root, pending);
9148
9148
  void pending.catch(() => {
9149
9149
  if (cache2.get(root) === pending) cache2.delete(root);
@@ -9156,7 +9156,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
9156
9156
  };
9157
9157
  }
9158
9158
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
9159
- const paths = [];
9159
+ const paths2 = [];
9160
9160
  const walk = async (directory) => {
9161
9161
  for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
9162
9162
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
@@ -9170,26 +9170,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
9170
9170
  } catch {
9171
9171
  continue;
9172
9172
  }
9173
- paths.push(path);
9174
- if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
9173
+ paths2.push(path);
9174
+ if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
9175
9175
  }
9176
9176
  }
9177
9177
  };
9178
9178
  await walk((0, import_path9.resolve)(root));
9179
- return paths.sort();
9179
+ return paths2.sort();
9180
9180
  }
9181
- function listWorkspace(paths, options = {}) {
9181
+ function listWorkspace(paths2, options = {}) {
9182
9182
  const max = options.maxEntries ?? 1e3;
9183
9183
  const prefix = options.prefix?.replace(/\/+$/, "");
9184
- const scoped = prefix ? paths.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths];
9184
+ const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
9185
9185
  return scoped.slice(0, max);
9186
9186
  }
9187
- async function searchWorkspace(root, paths, options) {
9187
+ async function searchWorkspace(root, paths2, options) {
9188
9188
  options.signal?.throwIfAborted();
9189
9189
  if (!options.query) throw new TypeError("search query must be a non-empty string");
9190
9190
  const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
9191
9191
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
9192
- const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
9192
+ const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
9193
9193
  if (scoped.length === 0) return [];
9194
9194
  try {
9195
9195
  return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
@@ -9198,11 +9198,11 @@ async function searchWorkspace(root, paths, options) {
9198
9198
  return fallbackSearch(root, scoped, { ...options, maxResults, maxFileBytes });
9199
9199
  }
9200
9200
  }
9201
- async function nativeSearch(root, paths, options) {
9201
+ async function nativeSearch(root, paths2, options) {
9202
9202
  const batches = [];
9203
9203
  let batch = [];
9204
9204
  let bytes = 0;
9205
- for (const path of paths) {
9205
+ for (const path of paths2) {
9206
9206
  const size = Buffer.byteLength(path) + 1;
9207
9207
  if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
9208
9208
  batches.push(batch);
@@ -9221,7 +9221,7 @@ async function nativeSearch(root, paths, options) {
9221
9221
  }
9222
9222
  return matches;
9223
9223
  }
9224
- function nativeSearchBatch(root, paths, options, remaining) {
9224
+ function nativeSearchBatch(root, paths2, options, remaining) {
9225
9225
  return new Promise((resolveMatches, reject) => {
9226
9226
  const args = [
9227
9227
  "--fixed-strings",
@@ -9234,7 +9234,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
9234
9234
  options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
9235
9235
  "--",
9236
9236
  options.query,
9237
- ...paths
9237
+ ...paths2
9238
9238
  ];
9239
9239
  const child = (0, import_child_process6.spawn)("rg", args, {
9240
9240
  cwd: root,
@@ -9380,16 +9380,16 @@ function createCodePolicyGate(options) {
9380
9380
  }
9381
9381
  };
9382
9382
  }
9383
- function directoryPrefixes(paths) {
9383
+ function directoryPrefixes(paths2) {
9384
9384
  const prefixes = /* @__PURE__ */ new Set(["."]);
9385
- for (const path of paths) {
9385
+ for (const path of paths2) {
9386
9386
  const parts = path.split("/");
9387
9387
  for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
9388
9388
  }
9389
9389
  return [...prefixes].sort();
9390
9390
  }
9391
- async function safePrefix(base, paths, prefix) {
9392
- const prefixes = directoryPrefixes(paths);
9391
+ async function safePrefix(base, paths2, prefix) {
9392
+ const prefixes = directoryPrefixes(paths2);
9393
9393
  const conversions = await conversionRegistry(
9394
9394
  [await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
9395
9395
  { "code.prefixes.v1": prefixes }
@@ -9500,7 +9500,7 @@ function optionalInteger(value2) {
9500
9500
  function response(request3, ok, content2, details) {
9501
9501
  return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
9502
9502
  }
9503
- function workspaceGraphs(workspaceDir, paths) {
9503
+ function workspaceGraphs(workspaceDir, paths2) {
9504
9504
  const existing = cache.get(workspaceDir);
9505
9505
  if (existing) return existing;
9506
9506
  const read22 = (path) => (0, import_promises11.readFile)((0, import_path10.join)(workspaceDir, path), "utf8");
@@ -9508,7 +9508,7 @@ function workspaceGraphs(workspaceDir, paths) {
9508
9508
  // No knownTables: a staged workspace may not carry migrations, and a filter
9509
9509
  // that silently drops every table is worse than an unfiltered one. Callers
9510
9510
  // with ground truth should build the graph themselves.
9511
- graph: await buildCodeGraph({ paths, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
9511
+ graph: await buildCodeGraph({ paths: paths2, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
9512
9512
  }))();
9513
9513
  cache.set(workspaceDir, built);
9514
9514
  return built;
@@ -9561,11 +9561,11 @@ async function read(context, request3, options, policy, registry) {
9561
9561
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
9562
9562
  throw new TypeError("requested line range exceeds its bound");
9563
9563
  }
9564
- const paths = await registry.files(context.workspaceDir);
9565
- if (!paths.includes(path)) {
9564
+ const paths2 = await registry.files(context.workspaceDir);
9565
+ if (!paths2.includes(path)) {
9566
9566
  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.`);
9567
9567
  }
9568
- const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
9568
+ const allowed = await policy.read(policyContext(context, request3, options, { paths: paths2, path, startLine, endLine }));
9569
9569
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9570
9570
  const target = resolveCodePath(context.workspaceDir, path);
9571
9571
  const info = await (0, import_promises10.stat)(target);
@@ -9587,16 +9587,16 @@ async function list(context, request3, options, policy, registry) {
9587
9587
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
9588
9588
  const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
9589
9589
  if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
9590
- const paths = await registry.files(context.workspaceDir);
9591
- const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
9590
+ const paths2 = await registry.files(context.workspaceDir);
9591
+ const allowed = await policy.list(policyContext(context, request3, options, { paths: paths2, ...prefix ? { prefix } : {} }));
9592
9592
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9593
- const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
9593
+ const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
9594
9594
  if (!entries.length) {
9595
9595
  return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
9596
9596
  }
9597
- const truncated = entries.length < paths.length && entries.length === maxEntries;
9598
- const hint = !prefix && paths.length > 500 ? `
9599
- \u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
9597
+ const truncated = entries.length < paths2.length && entries.length === maxEntries;
9598
+ const hint = !prefix && paths2.length > 500 ? `
9599
+ \u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
9600
9600
  return response(
9601
9601
  request3,
9602
9602
  true,
@@ -9614,10 +9614,10 @@ async function search(context, request3, options, policy, registry) {
9614
9614
  const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
9615
9615
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
9616
9616
  const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
9617
- const paths = await registry.files(context.workspaceDir);
9618
- const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
9617
+ const paths2 = await registry.files(context.workspaceDir);
9618
+ const allowed = await policy.search(policyContext(context, request3, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
9619
9619
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9620
- const matches = await searchWorkspace(context.workspaceDir, paths, {
9620
+ const matches = await searchWorkspace(context.workspaceDir, paths2, {
9621
9621
  query,
9622
9622
  maxResults,
9623
9623
  caseSensitive,
@@ -9639,8 +9639,8 @@ async function graphQuery(context, request3, options, policy, registry) {
9639
9639
  selector: query
9640
9640
  }));
9641
9641
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9642
- const paths = await registry.files(context.workspaceDir);
9643
- const graphs = await workspaceGraphs(context.workspaceDir, paths);
9642
+ const paths2 = await registry.files(context.workspaceDir);
9643
+ const graphs = await workspaceGraphs(context.workspaceDir, paths2);
9644
9644
  if (request3.tool === "sandbox.overview") {
9645
9645
  return response(request3, true, renderOverview(graphs, query || void 0));
9646
9646
  }
@@ -9702,16 +9702,16 @@ function toolFailureMessage(reason) {
9702
9702
  async function patch(context, request3, options, policy, registry) {
9703
9703
  exactKeys(request3.input, ["patch"]);
9704
9704
  const value2 = stringField(request3.input, "patch");
9705
- const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
9706
- if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
9705
+ const paths2 = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
9706
+ if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
9707
9707
  throw new TypeError("patch targets a read-only reference source");
9708
9708
  }
9709
9709
  const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
9710
9710
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9711
- await applyCodePatch(context.workspaceDir, value2, paths);
9711
+ await applyCodePatch(context.workspaceDir, value2, paths2);
9712
9712
  registry.invalidate(context.workspaceDir);
9713
9713
  forgetWorkspaceGraphs(context.workspaceDir);
9714
- return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
9714
+ return response(request3, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
9715
9715
  }
9716
9716
  async function recipe(context, request3, options, recipes, policy) {
9717
9717
  exactKeys(request3.input, ["recipeId"]);
@@ -10058,16 +10058,107 @@ async function startGoalPursuit(input) {
10058
10058
  async function appendCodeRuntimeEvent(control, command, event, refs) {
10059
10059
  const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
10060
10060
  refs.push(eventId);
10061
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
10061
+ const attributed = { ...event, interactionId: command.commandId };
10062
+ const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
10062
10063
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
10063
10064
  }
10064
- var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_ai5, import_child_process6, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, SOURCE_MAX_FILES, SOURCE_MAX_BYTES, SOURCE_SET_MAX_BYTES, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, MAX_NATIVE_ARG_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, TheseusRuntimeEngine;
10065
- var init_chunk_GYWQM76X = __esm({
10066
- "../harness/dist/chunk-GYWQM76X.js"() {
10065
+ function patchStats(value2) {
10066
+ if (typeof value2 !== "string") return {};
10067
+ let additions = 0;
10068
+ let deletions = 0;
10069
+ for (const line2 of value2.slice(0, 262144).split("\n")) {
10070
+ if (line2.startsWith("+++") || line2.startsWith("---")) continue;
10071
+ if (line2.startsWith("+")) additions += 1;
10072
+ else if (line2.startsWith("-")) deletions += 1;
10073
+ }
10074
+ return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
10075
+ }
10076
+ function searchResults(value2) {
10077
+ if (typeof value2 !== "string") return void 0;
10078
+ const results = value2.split("\n").flatMap((line2) => {
10079
+ const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
10080
+ if (!match) return [];
10081
+ const lineNumber = Number(match[2]);
10082
+ const itemText = text3(match[3], 240);
10083
+ if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
10084
+ return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
10085
+ }).slice(0, 5);
10086
+ return results.length ? results : void 0;
10087
+ }
10088
+ function codeToolRequestPresentation(request3) {
10089
+ const input = request3.input;
10090
+ if (request3.tool === "sandbox.read") {
10091
+ const path = text3(input.path, 1024);
10092
+ if (!path) return void 0;
10093
+ const startLine = integer2(input.startLine);
10094
+ const endLine = integer2(input.endLine);
10095
+ return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
10096
+ }
10097
+ if (request3.tool === "sandbox.list") {
10098
+ const scope = text3(input.prefix, 1024);
10099
+ return { kind: "list", ...scope ? { scope } : {} };
10100
+ }
10101
+ if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
10102
+ const query = text3(input.query, 512);
10103
+ const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
10104
+ if (request3.tool === "sandbox.search" && !query) return void 0;
10105
+ return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
10106
+ }
10107
+ if (request3.tool === "sandbox.apply_patch") {
10108
+ return { kind: "patch", ...patchStats(input.patch) };
10109
+ }
10110
+ const recipeId = text3(input.recipeId, 120);
10111
+ return recipeId ? { kind: "recipe", recipeId } : void 0;
10112
+ }
10113
+ function codeToolResultPresentation(request3, response2) {
10114
+ const started = codeToolRequestPresentation(request3);
10115
+ if (!started || !response2.ok) return started;
10116
+ const details = record32(response2.details);
10117
+ if (started.kind === "read") {
10118
+ return {
10119
+ ...started,
10120
+ ...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
10121
+ ...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
10122
+ ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
10123
+ };
10124
+ }
10125
+ if (started.kind === "list") {
10126
+ 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);
10127
+ return {
10128
+ ...started,
10129
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
10130
+ ...listed.length ? { paths: listed } : {}
10131
+ };
10132
+ }
10133
+ if (started.kind === "query") {
10134
+ const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
10135
+ const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
10136
+ return {
10137
+ ...started,
10138
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
10139
+ ...results ? { results } : {},
10140
+ ...resultExcerpt ? { excerpt: resultExcerpt } : {}
10141
+ };
10142
+ }
10143
+ if (started.kind === "patch") {
10144
+ return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
10145
+ }
10146
+ const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
10147
+ return {
10148
+ ...started,
10149
+ ...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
10150
+ ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
10151
+ ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
10152
+ ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
10153
+ };
10154
+ }
10155
+ var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_ai5, import_child_process6, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, SOURCE_MAX_FILES, SOURCE_MAX_BYTES, SOURCE_SET_MAX_BYTES, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, MAX_NATIVE_ARG_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, text3, integer2, record32, excerpt, paths, TheseusRuntimeEngine;
10156
+ var init_chunk_ISR434K7 = __esm({
10157
+ "../harness/dist/chunk-ISR434K7.js"() {
10067
10158
  "use strict";
10068
10159
  init_cjs_shims();
10069
- init_chunk_K76I2TCQ();
10070
- init_chunk_LNQNFGQC();
10160
+ init_chunk_CR6RE3A2();
10161
+ init_chunk_RXNHCGWE();
10071
10162
  import_crypto = require("crypto");
10072
10163
  import_promises5 = require("fs/promises");
10073
10164
  import_path5 = require("path");
@@ -10306,6 +10397,30 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
10306
10397
  POSITIVE = (value2) => Number.isFinite(value2) && Number(value2) > 0 ? Number(value2) : void 0;
10307
10398
  digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
10308
10399
  runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
10400
+ text3 = (value2, maximum) => {
10401
+ if (typeof value2 !== "string") return void 0;
10402
+ const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
10403
+ return bounded ? bounded.slice(0, maximum) : void 0;
10404
+ };
10405
+ integer2 = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
10406
+ record32 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
10407
+ excerpt = (value2, tail = false) => {
10408
+ if (typeof value2 !== "string") return void 0;
10409
+ const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
10410
+ const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
10411
+ if (!source) return void 0;
10412
+ const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
10413
+ const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
10414
+ return text3(selected.join("\n"), 2400);
10415
+ };
10416
+ paths = (value2) => {
10417
+ if (!Array.isArray(value2)) return void 0;
10418
+ const items = value2.flatMap((item) => {
10419
+ const path = text3(item, 1024);
10420
+ return path ? [path] : [];
10421
+ }).slice(0, 12);
10422
+ return items.length ? items : void 0;
10423
+ };
10309
10424
  TheseusRuntimeEngine = class {
10310
10425
  constructor(options) {
10311
10426
  this.options = options;
@@ -10539,18 +10654,29 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
10539
10654
  return {
10540
10655
  execute: async (context, request3) => {
10541
10656
  const startedAt = Date.now();
10657
+ const operationId = digestRuntimeValue(`${command.commandId}:${request3.requestId}`);
10658
+ const startedPresentation = codeToolRequestPresentation(request3);
10542
10659
  await this.#event(
10543
10660
  command,
10544
- { type: "tool", phase: "started", tool: request3.tool },
10661
+ {
10662
+ type: "tool",
10663
+ phase: "started",
10664
+ tool: request3.tool,
10665
+ operationId,
10666
+ ...startedPresentation ? { presentation: startedPresentation } : {}
10667
+ },
10545
10668
  active.conversationRefs
10546
10669
  ).catch(() => void 0);
10547
10670
  const response2 = await broker.execute(context, request3);
10671
+ const completedPresentation = codeToolResultPresentation(request3, response2);
10548
10672
  await this.#event(command, {
10549
10673
  type: "tool",
10550
10674
  phase: "completed",
10551
10675
  tool: request3.tool,
10552
10676
  ok: response2.ok,
10553
- durationMs: Date.now() - startedAt
10677
+ durationMs: Date.now() - startedAt,
10678
+ operationId,
10679
+ ...completedPresentation ? { presentation: completedPresentation } : {}
10554
10680
  }, active.conversationRefs).catch(() => void 0);
10555
10681
  return response2;
10556
10682
  }
@@ -10591,8 +10717,8 @@ var init_node = __esm({
10591
10717
  "../harness/dist/node.js"() {
10592
10718
  "use strict";
10593
10719
  init_cjs_shims();
10594
- init_chunk_GYWQM76X();
10595
- init_chunk_K76I2TCQ();
10720
+ init_chunk_ISR434K7();
10721
+ init_chunk_CR6RE3A2();
10596
10722
  MEASURED_PREMIUM = Object.freeze({
10597
10723
  /** 3 racers vs pure depth at equal budget: 21,044 / 7,936. */
10598
10724
  racePerRacer: 0.55,
@@ -11649,7 +11775,8 @@ Usage:
11649
11775
  odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
11650
11776
  odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
11651
11777
  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]
11652
- odla-ai pm next --app <id> [--project <id>] [--json]
11778
+ odla-ai pm next --app <id> [--project <id>] [--verbose] [--json]
11779
+ odla-ai pm start --app <id> [<task-id>] [--goal <id>] [--verbose] [--mutation-id <id>] [--json] [claims the top Ready task in one call]
11653
11780
  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]
11654
11781
  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]
11655
11782
  odla-ai pm task claim <id> --expected-revision <n> [--mutation-id <id>] [--json]
@@ -11670,7 +11797,7 @@ Usage:
11670
11797
  odla-ai pm <goal|task|decision|bug> comments <id> [--json]
11671
11798
  odla-ai pm <goal|task|decision|bug> history <id> [--limit <n>] [--json]
11672
11799
  odla-ai pm <goal|task|decision|bug> rm <id>
11673
- odla-ai pm handoff --app <id> [--project <id>] [--json]
11800
+ odla-ai pm handoff --app <id> [--project <id>] [--verbose] [--json]
11674
11801
  odla-ai discuss groups [--json]
11675
11802
  odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--mentions] [--json]
11676
11803
  odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
@@ -11837,6 +11964,16 @@ Commands:
11837
11964
  execution contract and a reviewed revision. Any caller with PM
11838
11965
  mutation access to that app/project may do it; claims coordinate
11839
11966
  execution but never own or lock the PM record.
11967
+ To pick up work: "pm next" reads open goals, Ready candidates,
11968
+ and work in progress; "pm start" claims the top Ready task in
11969
+ one call, compare-and-swapped on the revision it just read, and
11970
+ returns the task, its goal, and the acceptance criteria to work
11971
+ against. Finish with "pm task done <id>", or hand it back with
11972
+ "pm task release <id> --expected-revision <n>". "pm start" never
11973
+ marks work Ready: that contract is reviewed separately so an
11974
+ agent cannot authorize its own work.
11975
+ Intake output is projected to the fields an executing caller
11976
+ acts on; --verbose returns whole records with their audit trail.
11840
11977
  bug Intent-first alias for PM bugs. "bug report" writes to
11841
11978
  odla PM; odla product defects do not belong in GitHub Issues.
11842
11979
  discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
@@ -12648,7 +12785,8 @@ async function pmRequest(ctx, method, path, body) {
12648
12785
  const message2 = error.message;
12649
12786
  if (typeof message2 === "string" && message2.length > 0) detail = message2;
12650
12787
  }
12651
- throw new Error(
12788
+ throw new PmRequestError(
12789
+ response2.status,
12652
12790
  `pm ${method} ${path} failed: ${detail ?? `registry returned ${response2.status}`} (${response2.status})`
12653
12791
  );
12654
12792
  }
@@ -12663,8 +12801,8 @@ function collectFields(parsed, allowClear) {
12663
12801
  if (allowClear) out[spec.key] = null;
12664
12802
  continue;
12665
12803
  }
12666
- const text3 = stringOpt(value2);
12667
- out[spec.key] = spec.num ? Number(text3) : text3;
12804
+ const text4 = stringOpt(value2);
12805
+ out[spec.key] = spec.num ? Number(text4) : text4;
12668
12806
  }
12669
12807
  return out;
12670
12808
  }
@@ -12708,7 +12846,7 @@ function emit2(ctx, value2, human) {
12708
12846
  if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
12709
12847
  else human();
12710
12848
  }
12711
- var DONE, writeMutationId2, FIELD_MAP, STUDIO_SECTION;
12849
+ var DONE, writeMutationId2, FIELD_MAP, PmRequestError, isRevisionConflict, STUDIO_SECTION;
12712
12850
  var init_pm_action_core = __esm({
12713
12851
  "src/pm-action-core.ts"() {
12714
12852
  "use strict";
@@ -12741,6 +12879,15 @@ var init_pm_action_core = __esm({
12741
12879
  execution: { key: "executionMode" },
12742
12880
  "expected-revision": { key: "expectedRevision", num: true }
12743
12881
  };
12882
+ PmRequestError = class extends Error {
12883
+ constructor(status, message2) {
12884
+ super(message2);
12885
+ this.status = status;
12886
+ this.name = "PmRequestError";
12887
+ }
12888
+ status;
12889
+ };
12890
+ isRevisionConflict = (error) => error instanceof PmRequestError && error.status === 409;
12744
12891
  STUDIO_SECTION = {
12745
12892
  goal: "goals",
12746
12893
  task: "board",
@@ -12872,110 +13019,231 @@ async function pmTaskLifecycle(ctx, id2, action2, parsed) {
12872
13019
  ctx.out.log(`task: ${label} \u2192 ${state2}`);
12873
13020
  });
12874
13021
  }
12875
- async function allRecords(ctx, entity, appId, projectId) {
13022
+ async function pmRemove(ctx, entity, id2) {
13023
+ await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
13024
+ ctx.out.log(`deleted ${entity} ${id2}`);
13025
+ }
13026
+ var init_pm_actions = __esm({
13027
+ "src/pm-actions.ts"() {
13028
+ "use strict";
13029
+ init_cjs_shims();
13030
+ init_argv();
13031
+ init_pm_action_core();
13032
+ }
13033
+ });
13034
+
13035
+ // src/pm-lean.ts
13036
+ function leanRecord(entity, record11) {
13037
+ const out = {};
13038
+ for (const key of [...COMMON, ...PER_ENTITY[entity]]) {
13039
+ const value2 = record11[key];
13040
+ if (value2 !== void 0 && value2 !== null) out[key] = value2;
13041
+ }
13042
+ return out;
13043
+ }
13044
+ function leanRecords(entity, records, verbose) {
13045
+ return verbose ? records : records.map((record11) => leanRecord(entity, record11));
13046
+ }
13047
+ var COMMON, PER_ENTITY;
13048
+ var init_pm_lean = __esm({
13049
+ "src/pm-lean.ts"() {
13050
+ "use strict";
13051
+ init_cjs_shims();
13052
+ COMMON = ["id", "appId", "projectId", "title", "revision"];
13053
+ PER_ENTITY = {
13054
+ goal: ["status", "proof", "targetPct", "currentPct"],
13055
+ task: [
13056
+ "column",
13057
+ "goalId",
13058
+ "alignmentDecisionId",
13059
+ "description",
13060
+ "acceptanceCriteria",
13061
+ "executionMode",
13062
+ "assigneeId",
13063
+ "claimedByPrincipalId",
13064
+ "dueAt"
13065
+ ],
13066
+ decision: ["status", "body", "supersedesId"],
13067
+ bug: ["status", "severity", "description", "goalId", "assigneeId", "decisionId"]
13068
+ };
13069
+ }
13070
+ });
13071
+
13072
+ // src/pm-intake.ts
13073
+ async function listFiltered(ctx, entity, appId, projectId, filters) {
12876
13074
  const records = [];
12877
13075
  for (; ; ) {
12878
- const q = new URLSearchParams({
12879
- app: appId,
12880
- limit: "100",
12881
- offset: String(records.length)
12882
- });
13076
+ const q = new URLSearchParams({ app: appId, limit: "100", offset: String(records.length) });
12883
13077
  if (projectId) q.set("project", projectId);
13078
+ for (const [key, value2] of Object.entries(filters)) q.set(key, value2);
12884
13079
  const page2 = await pmRequest(ctx, "GET", `/${entity}?${q}`);
12885
13080
  records.push(...page2.records);
12886
13081
  if (records.length >= page2.total || page2.records.length === 0) return records;
12887
13082
  }
12888
13083
  }
12889
- async function pmNext(ctx, parsed) {
13084
+ function requireApp(ctx, parsed, command) {
12890
13085
  const appId = stringOpt(parsed.options.app) ?? ctx.appId;
12891
- const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
12892
- if (!appId) throw new Error("pm next needs --app <appId>");
13086
+ if (!appId) throw new Error(`pm ${command} needs --app <appId>`);
13087
+ return { appId, projectId: stringOpt(parsed.options.project) ?? ctx.projectId };
13088
+ }
13089
+ async function pmNext(ctx, parsed) {
13090
+ const { appId, projectId } = requireApp(ctx, parsed, "next");
13091
+ const verbose = wantsVerbose(parsed);
12893
13092
  const [goals, tasks] = await Promise.all([
12894
- allRecords(ctx, "goal", appId, projectId),
12895
- allRecords(ctx, "task", appId, projectId)
13093
+ listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
13094
+ // One request for both live columns; the split below is over open work only.
13095
+ listFiltered(ctx, "task", appId, projectId, { column: `${READY_COLUMN},doing` })
12896
13096
  ]);
13097
+ const ready = tasks.filter((record11) => record11.column === READY_COLUMN);
13098
+ const doing = tasks.filter((record11) => record11.column === "doing");
13099
+ 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}`;
12897
13100
  const result = {
12898
13101
  appId,
12899
13102
  projectId,
12900
- openGoals: goals.filter((record11) => record11.status === "open"),
12901
- doing: tasks.filter((record11) => record11.column === "doing"),
12902
- ready: tasks.filter((record11) => record11.column === "todo")
13103
+ openGoals: leanRecords("goal", goals, verbose),
13104
+ doing: leanRecords("task", doing, verbose),
13105
+ ready: leanRecords("task", ready, verbose),
13106
+ next: guidance
12903
13107
  };
12904
13108
  emit2(ctx, result, () => {
12905
13109
  ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
12906
- for (const [label, records] of [
12907
- ["doing", result.doing],
12908
- ["ready", result.ready],
12909
- ["open goals", result.openGoals]
13110
+ for (const [label, entity, records] of [
13111
+ ["doing", "task", doing],
13112
+ ["ready", "task", ready],
13113
+ ["open goals", "goal", goals]
12910
13114
  ]) {
12911
13115
  ctx.out.log(`${label}:`);
12912
13116
  if (!records.length) ctx.out.log("- (none)");
12913
- else for (const record11 of records) printRecord(
12914
- ctx,
12915
- label === "open goals" ? "goal" : "task",
12916
- record11
12917
- );
12918
- }
12919
- if (!result.openGoals.length) {
12920
- ctx.out.log("next: discuss alignment with the user before creating or claiming project work");
12921
- } else if (!result.ready.length) {
12922
- ctx.out.log("next: refine a linked Backlog task and mark it Ready");
12923
- } else {
12924
- ctx.out.log("next: review a Ready task, then claim it with its revision");
13117
+ else for (const record11 of records) printRecord(ctx, entity, record11);
12925
13118
  }
13119
+ ctx.out.log(`next: ${guidance}`);
12926
13120
  });
12927
13121
  }
12928
13122
  async function pmHandoff(ctx, parsed) {
12929
- const appId = stringOpt(parsed.options.app) ?? ctx.appId;
12930
- const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
12931
- if (!appId) throw new Error("pm handoff needs --app <appId>");
13123
+ const { appId, projectId } = requireApp(ctx, parsed, "handoff");
13124
+ const verbose = wantsVerbose(parsed);
12932
13125
  const [goals, tasks, bugs] = await Promise.all([
12933
- allRecords(ctx, "goal", appId, projectId),
12934
- allRecords(ctx, "task", appId, projectId),
12935
- allRecords(ctx, "bug", appId, projectId)
13126
+ listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
13127
+ listFiltered(ctx, "task", appId, projectId, { column: ACTIVE_TASK_COLUMNS }),
13128
+ listFiltered(ctx, "bug", appId, projectId, { status: OPEN_BUG_STATUSES })
12936
13129
  ]);
12937
- const handoff = {
13130
+ const clean4 = !goals.length && !tasks.length && !bugs.length;
13131
+ const result = {
12938
13132
  appId,
12939
13133
  projectId,
12940
- unmetGoals: goals.filter((record11) => record11.status !== "met"),
12941
- activeTasks: tasks.filter((record11) => record11.column !== "done"),
12942
- openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
12943
- };
12944
- const result = {
12945
- ...handoff,
12946
- clean: !handoff.unmetGoals.length && !handoff.activeTasks.length && !handoff.openBugs.length
13134
+ unmetGoals: leanRecords("goal", goals, verbose),
13135
+ activeTasks: leanRecords("task", tasks, verbose),
13136
+ openBugs: leanRecords("bug", bugs, verbose),
13137
+ clean: clean4
12947
13138
  };
12948
13139
  emit2(ctx, result, () => {
12949
- if (result.clean) {
13140
+ if (clean4) {
12950
13141
  ctx.out.log(`${appId}: no unresolved PM work`);
12951
13142
  return;
12952
13143
  }
12953
13144
  ctx.out.log(`${appId}: authoritative PM handoff`);
12954
- for (const [label, records] of [
12955
- ["unmet goals", result.unmetGoals],
12956
- ["active tasks", result.activeTasks],
12957
- ["open bugs", result.openBugs]
13145
+ for (const [label, entity, records] of [
13146
+ ["unmet goals", "goal", goals],
13147
+ ["active tasks", "task", tasks],
13148
+ ["open bugs", "bug", bugs]
12958
13149
  ]) {
12959
13150
  ctx.out.log(`${label}:`);
12960
13151
  if (!records.length) ctx.out.log("- (none)");
12961
- else for (const record11 of records) printRecord(
12962
- ctx,
12963
- label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
12964
- record11
12965
- );
13152
+ else for (const record11 of records) printRecord(ctx, entity, record11);
12966
13153
  }
12967
13154
  });
12968
13155
  }
12969
- async function pmRemove(ctx, entity, id2) {
12970
- await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
12971
- ctx.out.log(`deleted ${entity} ${id2}`);
13156
+ var READY_COLUMN, UNMET_GOAL_STATUS, ACTIVE_TASK_COLUMNS, OPEN_BUG_STATUSES, wantsVerbose;
13157
+ var init_pm_intake = __esm({
13158
+ "src/pm-intake.ts"() {
13159
+ "use strict";
13160
+ init_cjs_shims();
13161
+ init_argv();
13162
+ init_pm_action_core();
13163
+ init_pm_lean();
13164
+ READY_COLUMN = "todo";
13165
+ UNMET_GOAL_STATUS = "open";
13166
+ ACTIVE_TASK_COLUMNS = "backlog,todo,doing,review";
13167
+ OPEN_BUG_STATUSES = "open,triaged";
13168
+ wantsVerbose = (parsed) => parsed.options.verbose === true;
13169
+ }
13170
+ });
13171
+
13172
+ // src/pm-start.ts
13173
+ async function claimAt(ctx, task, parsed) {
13174
+ const res = await pmRequest(
13175
+ ctx,
13176
+ "POST",
13177
+ `/task/${encodeURIComponent(task.id)}/claim`,
13178
+ { expectedRevision: task.revision, mutationId: writeMutationId2(parsed) }
13179
+ );
13180
+ return res.record;
13181
+ }
13182
+ async function selectTask(ctx, parsed, appId, projectId) {
13183
+ const named = parsed.positionals[2];
13184
+ if (named) return getTask(ctx, named);
13185
+ const goalId = stringOpt(parsed.options.goal);
13186
+ const ready = await listFiltered(ctx, "task", appId, projectId, {
13187
+ column: READY_COLUMN,
13188
+ ...goalId ? { goalId } : {}
13189
+ });
13190
+ const candidate = ready[0];
13191
+ if (!candidate) {
13192
+ throw new Error(
13193
+ `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.`
13194
+ );
13195
+ }
13196
+ return candidate;
12972
13197
  }
12973
- var init_pm_actions = __esm({
12974
- "src/pm-actions.ts"() {
13198
+ async function pmStart(ctx, parsed) {
13199
+ const appId = stringOpt(parsed.options.app) ?? ctx.appId;
13200
+ if (!appId) throw new Error("pm start needs --app <appId>");
13201
+ const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
13202
+ const verbose = wantsVerbose(parsed);
13203
+ const selected = await selectTask(ctx, parsed, appId, projectId);
13204
+ if (selected.column !== READY_COLUMN) {
13205
+ throw new Error(
13206
+ `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.`
13207
+ );
13208
+ }
13209
+ let claimed;
13210
+ try {
13211
+ claimed = await claimAt(ctx, selected, parsed);
13212
+ } catch (error) {
13213
+ if (!isRevisionConflict(error)) throw error;
13214
+ const reloaded = await getTask(ctx, selected.id);
13215
+ if (reloaded.column !== READY_COLUMN) {
13216
+ throw new Error(
13217
+ `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.`
13218
+ );
13219
+ }
13220
+ claimed = await claimAt(ctx, reloaded, parsed);
13221
+ }
13222
+ const task = claimed ?? await getTask(ctx, selected.id);
13223
+ const goalId = typeof task.goalId === "string" ? task.goalId : void 0;
13224
+ const goal = goalId ? await pmRequest(ctx, "GET", `/goal/${encodeURIComponent(goalId)}`).then((res) => res.record).catch(() => null) : null;
13225
+ const result = {
13226
+ claimed: verbose ? task : leanRecord("task", task),
13227
+ goal: goal ? verbose ? goal : leanRecord("goal", goal) : null,
13228
+ 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)'
13229
+ };
13230
+ emit2(ctx, result, () => {
13231
+ ctx.out.log(`claimed: ${studioRecordLink(ctx, "task", task)}`);
13232
+ if (goal) ctx.out.log(`goal: ${studioRecordLink(ctx, "goal", goal)}`);
13233
+ if (typeof task.acceptanceCriteria === "string") ctx.out.log(`acceptance: ${task.acceptanceCriteria}`);
13234
+ ctx.out.log(`next: ${result.next}`);
13235
+ });
13236
+ }
13237
+ var getTask;
13238
+ var init_pm_start = __esm({
13239
+ "src/pm-start.ts"() {
12975
13240
  "use strict";
12976
13241
  init_cjs_shims();
12977
13242
  init_argv();
12978
13243
  init_pm_action_core();
13244
+ init_pm_lean();
13245
+ init_pm_intake();
13246
+ getTask = async (ctx, id2) => (await pmRequest(ctx, "GET", `/task/${encodeURIComponent(id2)}`)).record;
12979
13247
  }
12980
13248
  });
12981
13249
 
@@ -13457,9 +13725,13 @@ async function pmCommand(parsed, deps = {}) {
13457
13725
  throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
13458
13726
  }
13459
13727
  if (word === "next") {
13460
- assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
13728
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
13461
13729
  return pmNext(await buildContext2(parsed, deps), parsed);
13462
13730
  }
13731
+ if (word === "start") {
13732
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "goal", "verbose", "mutation-id"], 3);
13733
+ return pmStart(await buildContext2(parsed, deps), parsed);
13734
+ }
13463
13735
  if (word === "watch") {
13464
13736
  assertArgs(parsed, [
13465
13737
  ...COMMON_OPTIONS,
@@ -13477,11 +13749,11 @@ async function pmCommand(parsed, deps = {}) {
13477
13749
  return pmWatch(await buildContext2(parsed, deps), parsed).then(() => void 0);
13478
13750
  }
13479
13751
  if (word === "handoff") {
13480
- assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
13752
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
13481
13753
  return pmHandoff(await buildContext2(parsed, deps), parsed);
13482
13754
  }
13483
13755
  const entity = ALIASES[word];
13484
- if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
13756
+ 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).`);
13485
13757
  const requestedAction = parsed.positionals[2] ?? "list";
13486
13758
  const action2 = canonicalAction(requestedAction);
13487
13759
  if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|history|rm.`);
@@ -13528,6 +13800,8 @@ var init_pm_command = __esm({
13528
13800
  init_argv();
13529
13801
  init_operator_context();
13530
13802
  init_pm_actions();
13803
+ init_pm_intake();
13804
+ init_pm_start();
13531
13805
  init_pm_links();
13532
13806
  init_pm_comments();
13533
13807
  init_pm_history();
@@ -14125,14 +14399,14 @@ function statusMinutes(value2) {
14125
14399
  }
14126
14400
  async function read2(url, headers, doFetch) {
14127
14401
  const response2 = await doFetch(url, { headers });
14128
- const text3 = await response2.text();
14402
+ const text4 = await response2.text();
14129
14403
  let body = {};
14130
- if (text3) {
14404
+ if (text4) {
14131
14405
  try {
14132
- const value2 = JSON.parse(text3);
14406
+ const value2 = JSON.parse(text4);
14133
14407
  body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
14134
14408
  } catch {
14135
- body = { message: text3.slice(0, 300) };
14409
+ body = { message: text4.slice(0, 300) };
14136
14410
  }
14137
14411
  }
14138
14412
  return { httpStatus: response2.status, body };
@@ -14365,13 +14639,13 @@ async function monitorCommand(parsed, deps = {}) {
14365
14639
  }
14366
14640
  async function request2(url, init, doFetch) {
14367
14641
  const response2 = await doFetch(url, init);
14368
- const text3 = await response2.text();
14642
+ const text4 = await response2.text();
14369
14643
  let body = {};
14370
14644
  try {
14371
- const parsed = text3 ? JSON.parse(text3) : {};
14645
+ const parsed = text4 ? JSON.parse(text4) : {};
14372
14646
  body = record10(parsed) ? parsed : { value: parsed };
14373
14647
  } catch {
14374
- body = { message: text3.slice(0, 500) };
14648
+ body = { message: text4.slice(0, 500) };
14375
14649
  }
14376
14650
  if (!response2.ok) {
14377
14651
  const error = record10(body.error) ? body.error : body;
@@ -14611,8 +14885,8 @@ function runtimeUrl(cfg, suffix = "") {
14611
14885
  return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
14612
14886
  }
14613
14887
  async function safeError(response2) {
14614
- const text3 = await response2.text();
14615
- return redactSecrets(text3.slice(0, 1e3));
14888
+ const text4 = await response2.text();
14889
+ return redactSecrets(text4.slice(0, 1e3));
14616
14890
  }
14617
14891
  async function finish(doFetch, cfg, token, sessionId, method) {
14618
14892
  return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
@@ -15107,6 +15381,7 @@ var init_surface = __esm({
15107
15381
  project: { list: {}, add: {}, create: {}, use: {} },
15108
15382
  handoff: {},
15109
15383
  next: {},
15384
+ start: {},
15110
15385
  watch: {}
15111
15386
  },
15112
15387
  provision: {},
@@ -15213,10 +15488,10 @@ var init_advisory_output = __esm({
15213
15488
  // src/device-ttl.ts
15214
15489
  function parseDeviceTtl(raw) {
15215
15490
  if (raw === void 0 || raw === true) return void 0;
15216
- const text3 = String(raw).trim().toLowerCase();
15217
- if (!text3) return void 0;
15218
- if (text3 === "forever" || text3 === "never") return 100 * 365 * DAY_MS;
15219
- const match = /^(\d+)\s*([dwy])$/.exec(text3);
15491
+ const text4 = String(raw).trim().toLowerCase();
15492
+ if (!text4) return void 0;
15493
+ if (text4 === "forever" || text4 === "never") return 100 * 365 * DAY_MS;
15494
+ const match = /^(\d+)\s*([dwy])$/.exec(text4);
15220
15495
  if (!match) {
15221
15496
  throw new Error(
15222
15497
  `--device-ttl expects a duration like 30d, 6w, 2y, or "forever" (got "${raw}")`
@@ -15583,12 +15858,12 @@ var init_runbook_actions = __esm({
15583
15858
  });
15584
15859
 
15585
15860
  // src/runbook-import.ts
15586
- function parseRunbook(text3, slug) {
15587
- let rest = text3;
15861
+ function parseRunbook(text4, slug) {
15862
+ let rest = text4;
15588
15863
  const meta = {};
15589
- const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text3);
15864
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text4);
15590
15865
  if (fm) {
15591
- rest = text3.slice(fm[0].length);
15866
+ rest = text4.slice(fm[0].length);
15592
15867
  for (const line2 of fm[1].split(/\r?\n/)) {
15593
15868
  const pair = /^(\w+)\s*:\s*(.+)$/.exec(line2.trim());
15594
15869
  if (!pair) continue;