@odla-ai/cli 0.42.0 → 0.42.2

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. */
@@ -11684,12 +11833,10 @@ Commands:
11684
11833
  explicitly. Same device-grant auth as "app".
11685
11834
  Status changes and comments post to each item's @odla-ai/chat
11686
11835
  discussion thread.
11687
- NOTE: "--column ready" is OWNER-ONLY. Creating a task in Ready
11688
- approves its complete execution contract, so it needs pm.plan,
11689
- which no device enrollment or handshake approval can grant. An
11690
- agent proposes in Backlog (the default); a human owner or a
11691
- pm.plan agent promotes. This is deliberate, not a permission
11692
- gap \u2014 it was reported as one.
11836
+ Creating or promoting a task in Ready requires a complete
11837
+ execution contract and a reviewed revision. Any caller with PM
11838
+ mutation access to that app/project may do it; claims coordinate
11839
+ execution but never own or lock the PM record.
11693
11840
  bug Intent-first alias for PM bugs. "bug report" writes to
11694
11841
  odla PM; odla product defects do not belong in GitHub Issues.
11695
11842
  discuss Group discussions (via @odla-ai/chat) for the apps you co-own: