@odla-ai/cli 0.42.1 → 0.43.0

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
@@ -7915,7 +7915,7 @@ var init_code2 = __esm({
7915
7915
  }
7916
7916
  });
7917
7917
 
7918
- // ../harness/dist/chunk-QZXCQSPZ.js
7918
+ // ../harness/dist/chunk-GYWQM76X.js
7919
7919
  async function digestStagedWorkspace(root, limits) {
7920
7920
  const files = [];
7921
7921
  const walk = async (directory) => {
@@ -8862,18 +8862,26 @@ async function materializeCommandWorkspace(input) {
8862
8862
  }
8863
8863
  function codeSkill(opts) {
8864
8864
  let seq = 0;
8865
+ let nextCompletion = 1;
8866
+ const completed = /* @__PURE__ */ new Map();
8865
8867
  const call4 = async (tool, input, signal) => {
8868
+ const sequence = ++seq;
8866
8869
  const startedAt = Date.now();
8867
8870
  const response2 = await opts.broker.execute(
8868
8871
  { lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
8869
- { requestId: `bench-${tool}-${++seq}`, tool, input }
8872
+ { requestId: `bench-${tool}-${sequence}`, tool, input }
8870
8873
  );
8871
- opts.onToolCall?.({
8874
+ completed.set(sequence, {
8872
8875
  tool,
8873
8876
  ok: response2.ok,
8874
8877
  durationMs: Date.now() - startedAt,
8875
8878
  ...response2.ok ? {} : { error: String(response2.content).slice(0, 300) }
8876
8879
  });
8880
+ while (completed.has(nextCompletion)) {
8881
+ const completion = completed.get(nextCompletion);
8882
+ completed.delete(nextCompletion++);
8883
+ opts.onToolCall?.(completion);
8884
+ }
8877
8885
  return { content: response2.content, isError: !response2.ok };
8878
8886
  };
8879
8887
  const read22 = {
@@ -8889,6 +8897,7 @@ function codeSkill(opts) {
8889
8897
  },
8890
8898
  additionalProperties: false
8891
8899
  },
8900
+ concurrency: "parallel",
8892
8901
  handler: (input, ctx) => call4("sandbox.read", input, ctx.signal)
8893
8902
  };
8894
8903
  const applyPatch = {
@@ -8930,6 +8939,7 @@ function codeSkill(opts) {
8930
8939
  },
8931
8940
  additionalProperties: false
8932
8941
  },
8942
+ concurrency: "parallel",
8933
8943
  handler: (input, ctx) => call4("sandbox.list", input, ctx.signal)
8934
8944
  };
8935
8945
  const searchFiles = {
@@ -8946,11 +8956,13 @@ function codeSkill(opts) {
8946
8956
  },
8947
8957
  additionalProperties: false
8948
8958
  },
8959
+ concurrency: "parallel",
8949
8960
  handler: (input, ctx) => call4("sandbox.search", input, ctx.signal)
8950
8961
  };
8951
8962
  const graphTool = (name, tool, description, required) => ({
8952
8963
  name,
8953
8964
  description,
8965
+ concurrency: "parallel",
8954
8966
  inputSchema: {
8955
8967
  type: "object",
8956
8968
  ...required ? { required: ["query"] } : {},
@@ -9125,6 +9137,24 @@ function createCodeRuntimeInference(options) {
9125
9137
  catalog: {}
9126
9138
  };
9127
9139
  }
9140
+ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = registeredFiles) {
9141
+ const cache2 = /* @__PURE__ */ new Map();
9142
+ return {
9143
+ files(root) {
9144
+ const existing = cache2.get(root);
9145
+ if (existing) return existing;
9146
+ const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
9147
+ cache2.set(root, pending);
9148
+ void pending.catch(() => {
9149
+ if (cache2.get(root) === pending) cache2.delete(root);
9150
+ });
9151
+ return pending;
9152
+ },
9153
+ invalidate(root) {
9154
+ cache2.delete(root);
9155
+ }
9156
+ };
9157
+ }
9128
9158
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
9129
9159
  const paths = [];
9130
9160
  const walk = async (directory) => {
@@ -9155,28 +9185,121 @@ function listWorkspace(paths, options = {}) {
9155
9185
  return scoped.slice(0, max);
9156
9186
  }
9157
9187
  async function searchWorkspace(root, paths, options) {
9158
- const query = options.caseSensitive === false ? options.query.toLowerCase() : options.query;
9159
- if (!query) throw new TypeError("search query must be a non-empty string");
9188
+ options.signal?.throwIfAborted();
9189
+ if (!options.query) throw new TypeError("search query must be a non-empty string");
9160
9190
  const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
9161
9191
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
9162
9192
  const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
9193
+ if (scoped.length === 0) return [];
9194
+ try {
9195
+ return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
9196
+ } catch (error) {
9197
+ options.signal?.throwIfAborted();
9198
+ return fallbackSearch(root, scoped, { ...options, maxResults, maxFileBytes });
9199
+ }
9200
+ }
9201
+ async function nativeSearch(root, paths, options) {
9202
+ const batches = [];
9203
+ let batch = [];
9204
+ let bytes = 0;
9205
+ for (const path of paths) {
9206
+ const size = Buffer.byteLength(path) + 1;
9207
+ if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
9208
+ batches.push(batch);
9209
+ batch = [];
9210
+ bytes = 0;
9211
+ }
9212
+ batch.push(path);
9213
+ bytes += size;
9214
+ }
9215
+ if (batch.length > 0) batches.push(batch);
9216
+ const matches = [];
9217
+ for (const files of batches) {
9218
+ const remaining = options.maxResults - matches.length;
9219
+ if (remaining <= 0) break;
9220
+ matches.push(...await nativeSearchBatch(root, files, options, remaining));
9221
+ }
9222
+ return matches;
9223
+ }
9224
+ function nativeSearchBatch(root, paths, options, remaining) {
9225
+ return new Promise((resolveMatches, reject) => {
9226
+ const args = [
9227
+ "--fixed-strings",
9228
+ "--json",
9229
+ "--no-messages",
9230
+ "--sort=path",
9231
+ `--max-filesize=${options.maxFileBytes}`,
9232
+ "--max-columns=4096",
9233
+ "--max-columns-preview",
9234
+ options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
9235
+ "--",
9236
+ options.query,
9237
+ ...paths
9238
+ ];
9239
+ const child = (0, import_child_process6.spawn)("rg", args, {
9240
+ cwd: root,
9241
+ stdio: ["ignore", "pipe", "ignore"],
9242
+ ...options.signal ? { signal: options.signal } : {}
9243
+ });
9244
+ const matches = [];
9245
+ let carry = "";
9246
+ let stopped = false;
9247
+ const consume = (line2) => {
9248
+ if (matches.length >= remaining) return;
9249
+ let event;
9250
+ try {
9251
+ event = JSON.parse(line2);
9252
+ } catch {
9253
+ return;
9254
+ }
9255
+ const path = event.data?.path?.text;
9256
+ const lineNumber = event.data?.line_number;
9257
+ const source = event.data?.lines?.text;
9258
+ if (event.type !== "match" || path === void 0 || lineNumber === void 0 || source === void 0) return;
9259
+ matches.push({ path, line: lineNumber, text: source.trim().slice(0, 240) });
9260
+ if (matches.length >= remaining) {
9261
+ stopped = true;
9262
+ child.kill();
9263
+ }
9264
+ };
9265
+ child.stdout.setEncoding("utf8");
9266
+ child.stdout.on("data", (chunk) => {
9267
+ carry += chunk;
9268
+ let newline = carry.indexOf("\n");
9269
+ while (newline >= 0) {
9270
+ consume(carry.slice(0, newline));
9271
+ carry = carry.slice(newline + 1);
9272
+ newline = carry.indexOf("\n");
9273
+ }
9274
+ });
9275
+ child.once("error", reject);
9276
+ child.once("close", (code) => {
9277
+ if (carry) consume(carry);
9278
+ if (stopped || code === 0 || code === 1) resolveMatches(matches);
9279
+ else reject(new Error(`native search exited with status ${code ?? "unknown"}`));
9280
+ });
9281
+ });
9282
+ }
9283
+ async function fallbackSearch(root, scoped, options) {
9284
+ const query = options.caseSensitive === false ? options.query.toLowerCase() : options.query;
9163
9285
  const matches = [];
9164
9286
  for (const path of scoped) {
9165
- if (matches.length >= maxResults) break;
9287
+ options.signal?.throwIfAborted();
9288
+ if (matches.length >= options.maxResults) break;
9166
9289
  let source;
9167
9290
  try {
9168
9291
  source = await (0, import_promises9.readFile)((0, import_path9.resolve)(root, path));
9169
9292
  } catch {
9170
9293
  continue;
9171
9294
  }
9172
- if (source.byteLength > maxFileBytes || source.includes(0)) continue;
9295
+ if (source.byteLength > options.maxFileBytes || source.includes(0)) continue;
9173
9296
  const lines = source.toString("utf8").split("\n");
9174
9297
  for (let index = 0; index < lines.length; index += 1) {
9175
9298
  const raw = lines[index];
9176
9299
  const haystack = options.caseSensitive === false ? raw.toLowerCase() : raw;
9177
9300
  if (!haystack.includes(query)) continue;
9178
9301
  matches.push({ path, line: index + 1, text: raw.trim().slice(0, 240) });
9179
- if (matches.length >= maxResults) break;
9302
+ if (matches.length >= options.maxResults) break;
9180
9303
  }
9181
9304
  }
9182
9305
  return matches;
@@ -9390,6 +9513,9 @@ function workspaceGraphs(workspaceDir, paths) {
9390
9513
  cache.set(workspaceDir, built);
9391
9514
  return built;
9392
9515
  }
9516
+ function forgetWorkspaceGraphs(workspaceDir) {
9517
+ cache.delete(workspaceDir);
9518
+ }
9393
9519
  function renderOverview(graphs, prefix) {
9394
9520
  const rows = rollup(graphs.graph, FILE, prefix === void 0 ? {} : { prefix });
9395
9521
  if (rows.length === 0) return prefix ? `No source under "${prefix}".` : "No source files.";
@@ -9427,7 +9553,7 @@ function renderWhoTouches(graphs, query) {
9427
9553
  ].join("\n");
9428
9554
  }).join("\n\n");
9429
9555
  }
9430
- async function read(context, request3, options, policy) {
9556
+ async function read(context, request3, options, policy, registry) {
9431
9557
  exactKeys(request3.input, ["path", "startLine", "endLine"]);
9432
9558
  const path = stringField(request3.input, "path");
9433
9559
  const startLine = optionalInteger(request3.input.startLine) ?? 1;
@@ -9435,7 +9561,7 @@ async function read(context, request3, options, policy) {
9435
9561
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
9436
9562
  throw new TypeError("requested line range exceeds its bound");
9437
9563
  }
9438
- const paths = await registeredFiles(context.workspaceDir, 2e4);
9564
+ const paths = await registry.files(context.workspaceDir);
9439
9565
  if (!paths.includes(path)) {
9440
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.`);
9441
9567
  }
@@ -9455,13 +9581,13 @@ async function read(context, request3, options, policy) {
9455
9581
  }
9456
9582
  return response(request3, true, content2, { path, startLine, endLine: Math.min(endLine, lines.length) });
9457
9583
  }
9458
- async function list(context, request3, options, policy) {
9584
+ async function list(context, request3, options, policy, registry) {
9459
9585
  exactKeys(request3.input, ["prefix", "maxEntries"]);
9460
9586
  const raw = request3.input.prefix;
9461
9587
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
9462
9588
  const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
9463
9589
  if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
9464
- const paths = await registeredFiles(context.workspaceDir, 2e4);
9590
+ const paths = await registry.files(context.workspaceDir);
9465
9591
  const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
9466
9592
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9467
9593
  const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
@@ -9479,7 +9605,7 @@ async function list(context, request3, options, policy) {
9479
9605
  { count: entries.length, truncated }
9480
9606
  );
9481
9607
  }
9482
- async function search(context, request3, options, policy) {
9608
+ async function search(context, request3, options, policy, registry) {
9483
9609
  exactKeys(request3.input, ["query", "prefix", "maxResults", "caseSensitive"]);
9484
9610
  const query = stringField(request3.input, "query");
9485
9611
  if (query.length > 512) throw new TypeError("search query exceeds its bound");
@@ -9488,21 +9614,22 @@ async function search(context, request3, options, policy) {
9488
9614
  const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
9489
9615
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
9490
9616
  const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
9491
- const paths = await registeredFiles(context.workspaceDir, 2e4);
9617
+ const paths = await registry.files(context.workspaceDir);
9492
9618
  const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
9493
9619
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9494
9620
  const matches = await searchWorkspace(context.workspaceDir, paths, {
9495
9621
  query,
9496
9622
  maxResults,
9497
9623
  caseSensitive,
9498
- ...prefix ? { prefix } : {}
9624
+ ...prefix ? { prefix } : {},
9625
+ ...context.signal ? { signal: context.signal } : {}
9499
9626
  });
9500
9627
  if (!matches.length) return response(request3, true, `No match for "${query}".`, { count: 0 });
9501
9628
  return response(request3, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
9502
9629
  count: matches.length
9503
9630
  });
9504
9631
  }
9505
- async function graphQuery(context, request3, options, policy) {
9632
+ async function graphQuery(context, request3, options, policy, registry) {
9506
9633
  exactKeys(request3.input, ["query"]);
9507
9634
  const raw = request3.input.query;
9508
9635
  const query = typeof raw === "string" ? raw : "";
@@ -9512,7 +9639,7 @@ async function graphQuery(context, request3, options, policy) {
9512
9639
  selector: query
9513
9640
  }));
9514
9641
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9515
- const paths = await registeredFiles(context.workspaceDir, 2e4);
9642
+ const paths = await registry.files(context.workspaceDir);
9516
9643
  const graphs = await workspaceGraphs(context.workspaceDir, paths);
9517
9644
  if (request3.tool === "sandbox.overview") {
9518
9645
  return response(request3, true, renderOverview(graphs, query || void 0));
@@ -9526,23 +9653,38 @@ function createCodeToolBroker(options) {
9526
9653
  validateOptions(options);
9527
9654
  const recipes = new Map(options.recipes.map((recipe2) => [recipe2.id, recipe2]));
9528
9655
  const policy = createCodePolicyGate(options);
9529
- let tail = Promise.resolve();
9656
+ const registry = createWorkspaceFileRegistry();
9657
+ let barrier = Promise.resolve();
9658
+ const activeReads = /* @__PURE__ */ new Set();
9530
9659
  return {
9531
9660
  execute(context, request3) {
9532
- const result = tail.then(() => route2(context, request3, options, recipes, policy));
9533
- tail = result.then(() => void 0, () => void 0);
9661
+ if (isReadTool(request3.tool)) {
9662
+ const result2 = barrier.then(() => route2(context, request3, options, recipes, policy, registry));
9663
+ const settled = result2.then(() => void 0, () => void 0);
9664
+ activeReads.add(settled);
9665
+ void settled.then(() => {
9666
+ activeReads.delete(settled);
9667
+ });
9668
+ return result2;
9669
+ }
9670
+ const earlierReads = [...activeReads];
9671
+ const result = barrier.then(() => Promise.all(earlierReads)).then(() => route2(context, request3, options, recipes, policy, registry));
9672
+ barrier = result.then(() => void 0, () => void 0);
9534
9673
  return result;
9535
9674
  }
9536
9675
  };
9537
9676
  }
9538
- async function route2(context, request3, options, recipes, policy) {
9677
+ function isReadTool(tool) {
9678
+ return tool === "sandbox.read" || tool === "sandbox.list" || tool === "sandbox.search" || GRAPH_TOOLS.has(tool);
9679
+ }
9680
+ async function route2(context, request3, options, recipes, policy, registry) {
9539
9681
  try {
9540
9682
  if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
9541
- if (request3.tool === "sandbox.read") return await read(context, request3, options, policy);
9542
- if (request3.tool === "sandbox.list") return await list(context, request3, options, policy);
9543
- if (request3.tool === "sandbox.search") return await search(context, request3, options, policy);
9544
- if (GRAPH_TOOLS.has(request3.tool)) return await graphQuery(context, request3, options, policy);
9545
- if (request3.tool === "sandbox.apply_patch") return await patch(context, request3, options, policy);
9683
+ if (request3.tool === "sandbox.read") return await read(context, request3, options, policy, registry);
9684
+ if (request3.tool === "sandbox.list") return await list(context, request3, options, policy, registry);
9685
+ if (request3.tool === "sandbox.search") return await search(context, request3, options, policy, registry);
9686
+ if (GRAPH_TOOLS.has(request3.tool)) return await graphQuery(context, request3, options, policy, registry);
9687
+ if (request3.tool === "sandbox.apply_patch") return await patch(context, request3, options, policy, registry);
9546
9688
  return await recipe(context, request3, options, recipes, policy);
9547
9689
  } catch (reason) {
9548
9690
  return response(request3, false, toolFailureMessage(reason));
@@ -9557,7 +9699,7 @@ function toolFailureMessage(reason) {
9557
9699
  if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
9558
9700
  return "tool failed closed";
9559
9701
  }
9560
- async function patch(context, request3, options, policy) {
9702
+ async function patch(context, request3, options, policy, registry) {
9561
9703
  exactKeys(request3.input, ["patch"]);
9562
9704
  const value2 = stringField(request3.input, "patch");
9563
9705
  const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
@@ -9567,6 +9709,8 @@ async function patch(context, request3, options, policy) {
9567
9709
  const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
9568
9710
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
9569
9711
  await applyCodePatch(context.workspaceDir, value2, paths);
9712
+ registry.invalidate(context.workspaceDir);
9713
+ forgetWorkspaceGraphs(context.workspaceDir);
9570
9714
  return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
9571
9715
  }
9572
9716
  async function recipe(context, request3, options, recipes, policy) {
@@ -9917,9 +10061,9 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
9917
10061
  const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
9918
10062
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
9919
10063
  }
9920
- 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_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, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, TheseusRuntimeEngine;
9921
- var init_chunk_QZXCQSPZ = __esm({
9922
- "../harness/dist/chunk-QZXCQSPZ.js"() {
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"() {
9923
10067
  "use strict";
9924
10068
  init_cjs_shims();
9925
10069
  init_chunk_K76I2TCQ();
@@ -9945,6 +10089,7 @@ var init_chunk_QZXCQSPZ = __esm({
9945
10089
  import_path8 = require("path");
9946
10090
  import_ai4 = require("@odla-ai/ai");
9947
10091
  import_ai5 = require("@odla-ai/ai");
10092
+ import_child_process6 = require("child_process");
9948
10093
  import_promises9 = require("fs/promises");
9949
10094
  import_path9 = require("path");
9950
10095
  init_dist();
@@ -10073,7 +10218,10 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
10073
10218
  V2_SYSTEM_PROMPT = `You are the coding agent inside an odla Code harness.
10074
10219
  Start by orienting: odla_list shows the files in the workspace and odla_search
10075
10220
  finds a literal string across them. Prefer those over guessing a path.
10076
- Then odla_read a bounded range, and odla_apply_git_diff to mutate.
10221
+ Then odla_read a bounded range, and odla_apply_git_diff to mutate. When you
10222
+ need several independent searches or file ranges, issue those read-only calls
10223
+ together in one turn; their results stay ordered and the harness overlaps them.
10224
+ Never issue odla_apply_git_diff or odla_run_recipe alongside another tool call.
10077
10225
  For mutations, call odla_apply_git_diff with raw git diff text. It must start
10078
10226
  with "diff --git a/<path> b/<path>", include matching "---" and "+++" file
10079
10227
  headers and numbered "@@" hunks, and never use "*** Begin Patch" wrappers.
@@ -10105,6 +10253,7 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
10105
10253
  DEFAULT_MAX_FILES = 2e4;
10106
10254
  DEFAULT_MAX_RESULTS = 100;
10107
10255
  DEFAULT_MAX_FILE_BYTES = 512 * 1024;
10256
+ MAX_NATIVE_ARG_BYTES = 96 * 1024;
10108
10257
  DESTINATIONS = "code-workspaces.v1";
10109
10258
  READ = descriptor("sandbox.read", "scoped_data_read", {
10110
10259
  workspace: "destination",
@@ -10442,7 +10591,7 @@ var init_node = __esm({
10442
10591
  "../harness/dist/node.js"() {
10443
10592
  "use strict";
10444
10593
  init_cjs_shims();
10445
- init_chunk_QZXCQSPZ();
10594
+ init_chunk_GYWQM76X();
10446
10595
  init_chunk_K76I2TCQ();
10447
10596
  MEASURED_PREMIUM = Object.freeze({
10448
10597
  /** 3 racers vs pure depth at equal budget: 21,044 / 7,936. */
@@ -11500,7 +11649,8 @@ Usage:
11500
11649
  odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
11501
11650
  odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
11502
11651
  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]
11503
- odla-ai pm next --app <id> [--project <id>] [--json]
11652
+ odla-ai pm next --app <id> [--project <id>] [--verbose] [--json]
11653
+ odla-ai pm start --app <id> [<task-id>] [--goal <id>] [--verbose] [--mutation-id <id>] [--json] [claims the top Ready task in one call]
11504
11654
  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]
11505
11655
  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]
11506
11656
  odla-ai pm task claim <id> --expected-revision <n> [--mutation-id <id>] [--json]
@@ -11521,7 +11671,7 @@ Usage:
11521
11671
  odla-ai pm <goal|task|decision|bug> comments <id> [--json]
11522
11672
  odla-ai pm <goal|task|decision|bug> history <id> [--limit <n>] [--json]
11523
11673
  odla-ai pm <goal|task|decision|bug> rm <id>
11524
- odla-ai pm handoff --app <id> [--project <id>] [--json]
11674
+ odla-ai pm handoff --app <id> [--project <id>] [--verbose] [--json]
11525
11675
  odla-ai discuss groups [--json]
11526
11676
  odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--mentions] [--json]
11527
11677
  odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
@@ -11688,6 +11838,16 @@ Commands:
11688
11838
  execution contract and a reviewed revision. Any caller with PM
11689
11839
  mutation access to that app/project may do it; claims coordinate
11690
11840
  execution but never own or lock the PM record.
11841
+ To pick up work: "pm next" reads open goals, Ready candidates,
11842
+ and work in progress; "pm start" claims the top Ready task in
11843
+ one call, compare-and-swapped on the revision it just read, and
11844
+ returns the task, its goal, and the acceptance criteria to work
11845
+ against. Finish with "pm task done <id>", or hand it back with
11846
+ "pm task release <id> --expected-revision <n>". "pm start" never
11847
+ marks work Ready: that contract is reviewed separately so an
11848
+ agent cannot authorize its own work.
11849
+ Intake output is projected to the fields an executing caller
11850
+ acts on; --verbose returns whole records with their audit trail.
11691
11851
  bug Intent-first alias for PM bugs. "bug report" writes to
11692
11852
  odla PM; odla product defects do not belong in GitHub Issues.
11693
11853
  discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
@@ -12499,7 +12659,8 @@ async function pmRequest(ctx, method, path, body) {
12499
12659
  const message2 = error.message;
12500
12660
  if (typeof message2 === "string" && message2.length > 0) detail = message2;
12501
12661
  }
12502
- throw new Error(
12662
+ throw new PmRequestError(
12663
+ response2.status,
12503
12664
  `pm ${method} ${path} failed: ${detail ?? `registry returned ${response2.status}`} (${response2.status})`
12504
12665
  );
12505
12666
  }
@@ -12559,7 +12720,7 @@ function emit2(ctx, value2, human) {
12559
12720
  if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
12560
12721
  else human();
12561
12722
  }
12562
- var DONE, writeMutationId2, FIELD_MAP, STUDIO_SECTION;
12723
+ var DONE, writeMutationId2, FIELD_MAP, PmRequestError, isRevisionConflict, STUDIO_SECTION;
12563
12724
  var init_pm_action_core = __esm({
12564
12725
  "src/pm-action-core.ts"() {
12565
12726
  "use strict";
@@ -12592,6 +12753,15 @@ var init_pm_action_core = __esm({
12592
12753
  execution: { key: "executionMode" },
12593
12754
  "expected-revision": { key: "expectedRevision", num: true }
12594
12755
  };
12756
+ PmRequestError = class extends Error {
12757
+ constructor(status, message2) {
12758
+ super(message2);
12759
+ this.status = status;
12760
+ this.name = "PmRequestError";
12761
+ }
12762
+ status;
12763
+ };
12764
+ isRevisionConflict = (error) => error instanceof PmRequestError && error.status === 409;
12595
12765
  STUDIO_SECTION = {
12596
12766
  goal: "goals",
12597
12767
  task: "board",
@@ -12723,110 +12893,231 @@ async function pmTaskLifecycle(ctx, id2, action2, parsed) {
12723
12893
  ctx.out.log(`task: ${label} \u2192 ${state2}`);
12724
12894
  });
12725
12895
  }
12726
- async function allRecords(ctx, entity, appId, projectId) {
12896
+ async function pmRemove(ctx, entity, id2) {
12897
+ await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
12898
+ ctx.out.log(`deleted ${entity} ${id2}`);
12899
+ }
12900
+ var init_pm_actions = __esm({
12901
+ "src/pm-actions.ts"() {
12902
+ "use strict";
12903
+ init_cjs_shims();
12904
+ init_argv();
12905
+ init_pm_action_core();
12906
+ }
12907
+ });
12908
+
12909
+ // src/pm-lean.ts
12910
+ function leanRecord(entity, record11) {
12911
+ const out = {};
12912
+ for (const key of [...COMMON, ...PER_ENTITY[entity]]) {
12913
+ const value2 = record11[key];
12914
+ if (value2 !== void 0 && value2 !== null) out[key] = value2;
12915
+ }
12916
+ return out;
12917
+ }
12918
+ function leanRecords(entity, records, verbose) {
12919
+ return verbose ? records : records.map((record11) => leanRecord(entity, record11));
12920
+ }
12921
+ var COMMON, PER_ENTITY;
12922
+ var init_pm_lean = __esm({
12923
+ "src/pm-lean.ts"() {
12924
+ "use strict";
12925
+ init_cjs_shims();
12926
+ COMMON = ["id", "appId", "projectId", "title", "revision"];
12927
+ PER_ENTITY = {
12928
+ goal: ["status", "proof", "targetPct", "currentPct"],
12929
+ task: [
12930
+ "column",
12931
+ "goalId",
12932
+ "alignmentDecisionId",
12933
+ "description",
12934
+ "acceptanceCriteria",
12935
+ "executionMode",
12936
+ "assigneeId",
12937
+ "claimedByPrincipalId",
12938
+ "dueAt"
12939
+ ],
12940
+ decision: ["status", "body", "supersedesId"],
12941
+ bug: ["status", "severity", "description", "goalId", "assigneeId", "decisionId"]
12942
+ };
12943
+ }
12944
+ });
12945
+
12946
+ // src/pm-intake.ts
12947
+ async function listFiltered(ctx, entity, appId, projectId, filters) {
12727
12948
  const records = [];
12728
12949
  for (; ; ) {
12729
- const q = new URLSearchParams({
12730
- app: appId,
12731
- limit: "100",
12732
- offset: String(records.length)
12733
- });
12950
+ const q = new URLSearchParams({ app: appId, limit: "100", offset: String(records.length) });
12734
12951
  if (projectId) q.set("project", projectId);
12952
+ for (const [key, value2] of Object.entries(filters)) q.set(key, value2);
12735
12953
  const page2 = await pmRequest(ctx, "GET", `/${entity}?${q}`);
12736
12954
  records.push(...page2.records);
12737
12955
  if (records.length >= page2.total || page2.records.length === 0) return records;
12738
12956
  }
12739
12957
  }
12740
- async function pmNext(ctx, parsed) {
12958
+ function requireApp(ctx, parsed, command) {
12741
12959
  const appId = stringOpt(parsed.options.app) ?? ctx.appId;
12742
- const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
12743
- if (!appId) throw new Error("pm next needs --app <appId>");
12960
+ if (!appId) throw new Error(`pm ${command} needs --app <appId>`);
12961
+ return { appId, projectId: stringOpt(parsed.options.project) ?? ctx.projectId };
12962
+ }
12963
+ async function pmNext(ctx, parsed) {
12964
+ const { appId, projectId } = requireApp(ctx, parsed, "next");
12965
+ const verbose = wantsVerbose(parsed);
12744
12966
  const [goals, tasks] = await Promise.all([
12745
- allRecords(ctx, "goal", appId, projectId),
12746
- allRecords(ctx, "task", appId, projectId)
12967
+ listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
12968
+ // One request for both live columns; the split below is over open work only.
12969
+ listFiltered(ctx, "task", appId, projectId, { column: `${READY_COLUMN},doing` })
12747
12970
  ]);
12971
+ const ready = tasks.filter((record11) => record11.column === READY_COLUMN);
12972
+ const doing = tasks.filter((record11) => record11.column === "doing");
12973
+ 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}`;
12748
12974
  const result = {
12749
12975
  appId,
12750
12976
  projectId,
12751
- openGoals: goals.filter((record11) => record11.status === "open"),
12752
- doing: tasks.filter((record11) => record11.column === "doing"),
12753
- ready: tasks.filter((record11) => record11.column === "todo")
12977
+ openGoals: leanRecords("goal", goals, verbose),
12978
+ doing: leanRecords("task", doing, verbose),
12979
+ ready: leanRecords("task", ready, verbose),
12980
+ next: guidance
12754
12981
  };
12755
12982
  emit2(ctx, result, () => {
12756
12983
  ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
12757
- for (const [label, records] of [
12758
- ["doing", result.doing],
12759
- ["ready", result.ready],
12760
- ["open goals", result.openGoals]
12984
+ for (const [label, entity, records] of [
12985
+ ["doing", "task", doing],
12986
+ ["ready", "task", ready],
12987
+ ["open goals", "goal", goals]
12761
12988
  ]) {
12762
12989
  ctx.out.log(`${label}:`);
12763
12990
  if (!records.length) ctx.out.log("- (none)");
12764
- else for (const record11 of records) printRecord(
12765
- ctx,
12766
- label === "open goals" ? "goal" : "task",
12767
- record11
12768
- );
12769
- }
12770
- if (!result.openGoals.length) {
12771
- ctx.out.log("next: discuss alignment with the user before creating or claiming project work");
12772
- } else if (!result.ready.length) {
12773
- ctx.out.log("next: refine a linked Backlog task and mark it Ready");
12774
- } else {
12775
- ctx.out.log("next: review a Ready task, then claim it with its revision");
12991
+ else for (const record11 of records) printRecord(ctx, entity, record11);
12776
12992
  }
12993
+ ctx.out.log(`next: ${guidance}`);
12777
12994
  });
12778
12995
  }
12779
12996
  async function pmHandoff(ctx, parsed) {
12780
- const appId = stringOpt(parsed.options.app) ?? ctx.appId;
12781
- const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
12782
- if (!appId) throw new Error("pm handoff needs --app <appId>");
12997
+ const { appId, projectId } = requireApp(ctx, parsed, "handoff");
12998
+ const verbose = wantsVerbose(parsed);
12783
12999
  const [goals, tasks, bugs] = await Promise.all([
12784
- allRecords(ctx, "goal", appId, projectId),
12785
- allRecords(ctx, "task", appId, projectId),
12786
- allRecords(ctx, "bug", appId, projectId)
13000
+ listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
13001
+ listFiltered(ctx, "task", appId, projectId, { column: ACTIVE_TASK_COLUMNS }),
13002
+ listFiltered(ctx, "bug", appId, projectId, { status: OPEN_BUG_STATUSES })
12787
13003
  ]);
12788
- const handoff = {
13004
+ const clean4 = !goals.length && !tasks.length && !bugs.length;
13005
+ const result = {
12789
13006
  appId,
12790
13007
  projectId,
12791
- unmetGoals: goals.filter((record11) => record11.status !== "met"),
12792
- activeTasks: tasks.filter((record11) => record11.column !== "done"),
12793
- openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
12794
- };
12795
- const result = {
12796
- ...handoff,
12797
- clean: !handoff.unmetGoals.length && !handoff.activeTasks.length && !handoff.openBugs.length
13008
+ unmetGoals: leanRecords("goal", goals, verbose),
13009
+ activeTasks: leanRecords("task", tasks, verbose),
13010
+ openBugs: leanRecords("bug", bugs, verbose),
13011
+ clean: clean4
12798
13012
  };
12799
13013
  emit2(ctx, result, () => {
12800
- if (result.clean) {
13014
+ if (clean4) {
12801
13015
  ctx.out.log(`${appId}: no unresolved PM work`);
12802
13016
  return;
12803
13017
  }
12804
13018
  ctx.out.log(`${appId}: authoritative PM handoff`);
12805
- for (const [label, records] of [
12806
- ["unmet goals", result.unmetGoals],
12807
- ["active tasks", result.activeTasks],
12808
- ["open bugs", result.openBugs]
13019
+ for (const [label, entity, records] of [
13020
+ ["unmet goals", "goal", goals],
13021
+ ["active tasks", "task", tasks],
13022
+ ["open bugs", "bug", bugs]
12809
13023
  ]) {
12810
13024
  ctx.out.log(`${label}:`);
12811
13025
  if (!records.length) ctx.out.log("- (none)");
12812
- else for (const record11 of records) printRecord(
12813
- ctx,
12814
- label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
12815
- record11
12816
- );
13026
+ else for (const record11 of records) printRecord(ctx, entity, record11);
12817
13027
  }
12818
13028
  });
12819
13029
  }
12820
- async function pmRemove(ctx, entity, id2) {
12821
- await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
12822
- ctx.out.log(`deleted ${entity} ${id2}`);
13030
+ var READY_COLUMN, UNMET_GOAL_STATUS, ACTIVE_TASK_COLUMNS, OPEN_BUG_STATUSES, wantsVerbose;
13031
+ var init_pm_intake = __esm({
13032
+ "src/pm-intake.ts"() {
13033
+ "use strict";
13034
+ init_cjs_shims();
13035
+ init_argv();
13036
+ init_pm_action_core();
13037
+ init_pm_lean();
13038
+ READY_COLUMN = "todo";
13039
+ UNMET_GOAL_STATUS = "open";
13040
+ ACTIVE_TASK_COLUMNS = "backlog,todo,doing,review";
13041
+ OPEN_BUG_STATUSES = "open,triaged";
13042
+ wantsVerbose = (parsed) => parsed.options.verbose === true;
13043
+ }
13044
+ });
13045
+
13046
+ // src/pm-start.ts
13047
+ async function claimAt(ctx, task, parsed) {
13048
+ const res = await pmRequest(
13049
+ ctx,
13050
+ "POST",
13051
+ `/task/${encodeURIComponent(task.id)}/claim`,
13052
+ { expectedRevision: task.revision, mutationId: writeMutationId2(parsed) }
13053
+ );
13054
+ return res.record;
13055
+ }
13056
+ async function selectTask(ctx, parsed, appId, projectId) {
13057
+ const named = parsed.positionals[2];
13058
+ if (named) return getTask(ctx, named);
13059
+ const goalId = stringOpt(parsed.options.goal);
13060
+ const ready = await listFiltered(ctx, "task", appId, projectId, {
13061
+ column: READY_COLUMN,
13062
+ ...goalId ? { goalId } : {}
13063
+ });
13064
+ const candidate = ready[0];
13065
+ if (!candidate) {
13066
+ throw new Error(
13067
+ `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.`
13068
+ );
13069
+ }
13070
+ return candidate;
12823
13071
  }
12824
- var init_pm_actions = __esm({
12825
- "src/pm-actions.ts"() {
13072
+ async function pmStart(ctx, parsed) {
13073
+ const appId = stringOpt(parsed.options.app) ?? ctx.appId;
13074
+ if (!appId) throw new Error("pm start needs --app <appId>");
13075
+ const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
13076
+ const verbose = wantsVerbose(parsed);
13077
+ const selected = await selectTask(ctx, parsed, appId, projectId);
13078
+ if (selected.column !== READY_COLUMN) {
13079
+ throw new Error(
13080
+ `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.`
13081
+ );
13082
+ }
13083
+ let claimed;
13084
+ try {
13085
+ claimed = await claimAt(ctx, selected, parsed);
13086
+ } catch (error) {
13087
+ if (!isRevisionConflict(error)) throw error;
13088
+ const reloaded = await getTask(ctx, selected.id);
13089
+ if (reloaded.column !== READY_COLUMN) {
13090
+ throw new Error(
13091
+ `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.`
13092
+ );
13093
+ }
13094
+ claimed = await claimAt(ctx, reloaded, parsed);
13095
+ }
13096
+ const task = claimed ?? await getTask(ctx, selected.id);
13097
+ const goalId = typeof task.goalId === "string" ? task.goalId : void 0;
13098
+ const goal = goalId ? await pmRequest(ctx, "GET", `/goal/${encodeURIComponent(goalId)}`).then((res) => res.record).catch(() => null) : null;
13099
+ const result = {
13100
+ claimed: verbose ? task : leanRecord("task", task),
13101
+ goal: goal ? verbose ? goal : leanRecord("goal", goal) : null,
13102
+ 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)'
13103
+ };
13104
+ emit2(ctx, result, () => {
13105
+ ctx.out.log(`claimed: ${studioRecordLink(ctx, "task", task)}`);
13106
+ if (goal) ctx.out.log(`goal: ${studioRecordLink(ctx, "goal", goal)}`);
13107
+ if (typeof task.acceptanceCriteria === "string") ctx.out.log(`acceptance: ${task.acceptanceCriteria}`);
13108
+ ctx.out.log(`next: ${result.next}`);
13109
+ });
13110
+ }
13111
+ var getTask;
13112
+ var init_pm_start = __esm({
13113
+ "src/pm-start.ts"() {
12826
13114
  "use strict";
12827
13115
  init_cjs_shims();
12828
13116
  init_argv();
12829
13117
  init_pm_action_core();
13118
+ init_pm_lean();
13119
+ init_pm_intake();
13120
+ getTask = async (ctx, id2) => (await pmRequest(ctx, "GET", `/task/${encodeURIComponent(id2)}`)).record;
12830
13121
  }
12831
13122
  });
12832
13123
 
@@ -13308,9 +13599,13 @@ async function pmCommand(parsed, deps = {}) {
13308
13599
  throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
13309
13600
  }
13310
13601
  if (word === "next") {
13311
- assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
13602
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
13312
13603
  return pmNext(await buildContext2(parsed, deps), parsed);
13313
13604
  }
13605
+ if (word === "start") {
13606
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "goal", "verbose", "mutation-id"], 3);
13607
+ return pmStart(await buildContext2(parsed, deps), parsed);
13608
+ }
13314
13609
  if (word === "watch") {
13315
13610
  assertArgs(parsed, [
13316
13611
  ...COMMON_OPTIONS,
@@ -13328,11 +13623,11 @@ async function pmCommand(parsed, deps = {}) {
13328
13623
  return pmWatch(await buildContext2(parsed, deps), parsed).then(() => void 0);
13329
13624
  }
13330
13625
  if (word === "handoff") {
13331
- assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
13626
+ assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
13332
13627
  return pmHandoff(await buildContext2(parsed, deps), parsed);
13333
13628
  }
13334
13629
  const entity = ALIASES[word];
13335
- if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
13630
+ 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).`);
13336
13631
  const requestedAction = parsed.positionals[2] ?? "list";
13337
13632
  const action2 = canonicalAction(requestedAction);
13338
13633
  if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|history|rm.`);
@@ -13379,6 +13674,8 @@ var init_pm_command = __esm({
13379
13674
  init_argv();
13380
13675
  init_operator_context();
13381
13676
  init_pm_actions();
13677
+ init_pm_intake();
13678
+ init_pm_start();
13382
13679
  init_pm_links();
13383
13680
  init_pm_comments();
13384
13681
  init_pm_history();