@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 +185 -38
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-74DXL2MT.js → chunk-K6ATAKGR.js} +185 -38
- package/dist/chunk-K6ATAKGR.js.map +1 -0
- package/dist/{cli-EGZGMIKB.js → cli-4FCRBWED.js} +2 -2
- package/dist/index.cjs +184 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/skills/odla/SKILL.md +6 -6
- package/skills/odla/references/agent-identity.md +4 -3
- package/skills/odla/references/pm-work-intake.md +9 -10
- package/skills/odla/references/pm.md +9 -11
- package/dist/chunk-74DXL2MT.js.map +0 -1
- /package/dist/{cli-EGZGMIKB.js.map → cli-4FCRBWED.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runCli
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-K6ATAKGR.js";
|
|
5
5
|
import {
|
|
6
6
|
exitCodeFor
|
|
7
7
|
} from "./chunk-UKLSRQ5J.js";
|
|
@@ -9,4 +9,4 @@ export {
|
|
|
9
9
|
exitCodeFor,
|
|
10
10
|
runCli
|
|
11
11
|
};
|
|
12
|
-
//# sourceMappingURL=cli-
|
|
12
|
+
//# sourceMappingURL=cli-4FCRBWED.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -6275,7 +6275,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
|
|
|
6275
6275
|
}
|
|
6276
6276
|
}
|
|
6277
6277
|
|
|
6278
|
-
// ../harness/dist/chunk-
|
|
6278
|
+
// ../harness/dist/chunk-GYWQM76X.js
|
|
6279
6279
|
var import_crypto = require("crypto");
|
|
6280
6280
|
var import_promises5 = require("fs/promises");
|
|
6281
6281
|
var import_path5 = require("path");
|
|
@@ -6612,7 +6612,7 @@ function validateSnapshot(snapshot, limits) {
|
|
|
6612
6612
|
}
|
|
6613
6613
|
}
|
|
6614
6614
|
|
|
6615
|
-
// ../harness/dist/chunk-
|
|
6615
|
+
// ../harness/dist/chunk-GYWQM76X.js
|
|
6616
6616
|
var import_child_process4 = require("child_process");
|
|
6617
6617
|
var import_promises6 = require("fs/promises");
|
|
6618
6618
|
var import_path6 = require("path");
|
|
@@ -6628,6 +6628,7 @@ var import_os3 = require("os");
|
|
|
6628
6628
|
var import_path8 = require("path");
|
|
6629
6629
|
var import_ai4 = require("@odla-ai/ai");
|
|
6630
6630
|
var import_ai5 = require("@odla-ai/ai");
|
|
6631
|
+
var import_child_process6 = require("child_process");
|
|
6631
6632
|
var import_promises9 = require("fs/promises");
|
|
6632
6633
|
var import_path9 = require("path");
|
|
6633
6634
|
|
|
@@ -6915,7 +6916,7 @@ function looksLikeDestination(value2) {
|
|
|
6915
6916
|
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
|
|
6916
6917
|
}
|
|
6917
6918
|
|
|
6918
|
-
// ../harness/dist/chunk-
|
|
6919
|
+
// ../harness/dist/chunk-GYWQM76X.js
|
|
6919
6920
|
var import_promises10 = require("fs/promises");
|
|
6920
6921
|
var import_promises11 = require("fs/promises");
|
|
6921
6922
|
var import_path10 = require("path");
|
|
@@ -7183,7 +7184,7 @@ async function buildCodeGraph(input) {
|
|
|
7183
7184
|
return builder.build();
|
|
7184
7185
|
}
|
|
7185
7186
|
|
|
7186
|
-
// ../harness/dist/chunk-
|
|
7187
|
+
// ../harness/dist/chunk-GYWQM76X.js
|
|
7187
7188
|
var import_crypto4 = require("crypto");
|
|
7188
7189
|
async function digestStagedWorkspace(root, limits) {
|
|
7189
7190
|
const files = [];
|
|
@@ -8247,7 +8248,10 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
|
|
|
8247
8248
|
var V2_SYSTEM_PROMPT = `You are the coding agent inside an odla Code harness.
|
|
8248
8249
|
Start by orienting: odla_list shows the files in the workspace and odla_search
|
|
8249
8250
|
finds a literal string across them. Prefer those over guessing a path.
|
|
8250
|
-
Then odla_read a bounded range, and odla_apply_git_diff to mutate.
|
|
8251
|
+
Then odla_read a bounded range, and odla_apply_git_diff to mutate. When you
|
|
8252
|
+
need several independent searches or file ranges, issue those read-only calls
|
|
8253
|
+
together in one turn; their results stay ordered and the harness overlaps them.
|
|
8254
|
+
Never issue odla_apply_git_diff or odla_run_recipe alongside another tool call.
|
|
8251
8255
|
For mutations, call odla_apply_git_diff with raw git diff text. It must start
|
|
8252
8256
|
with "diff --git a/<path> b/<path>", include matching "---" and "+++" file
|
|
8253
8257
|
headers and numbered "@@" hunks, and never use "*** Begin Patch" wrappers.
|
|
@@ -8278,18 +8282,26 @@ var SYSTEM_PROMPT_FOR = {
|
|
|
8278
8282
|
};
|
|
8279
8283
|
function codeSkill(opts) {
|
|
8280
8284
|
let seq = 0;
|
|
8285
|
+
let nextCompletion = 1;
|
|
8286
|
+
const completed = /* @__PURE__ */ new Map();
|
|
8281
8287
|
const call4 = async (tool, input, signal) => {
|
|
8288
|
+
const sequence = ++seq;
|
|
8282
8289
|
const startedAt = Date.now();
|
|
8283
8290
|
const response2 = await opts.broker.execute(
|
|
8284
8291
|
{ lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
|
|
8285
|
-
{ requestId: `bench-${tool}-${
|
|
8292
|
+
{ requestId: `bench-${tool}-${sequence}`, tool, input }
|
|
8286
8293
|
);
|
|
8287
|
-
|
|
8294
|
+
completed.set(sequence, {
|
|
8288
8295
|
tool,
|
|
8289
8296
|
ok: response2.ok,
|
|
8290
8297
|
durationMs: Date.now() - startedAt,
|
|
8291
8298
|
...response2.ok ? {} : { error: String(response2.content).slice(0, 300) }
|
|
8292
8299
|
});
|
|
8300
|
+
while (completed.has(nextCompletion)) {
|
|
8301
|
+
const completion = completed.get(nextCompletion);
|
|
8302
|
+
completed.delete(nextCompletion++);
|
|
8303
|
+
opts.onToolCall?.(completion);
|
|
8304
|
+
}
|
|
8293
8305
|
return { content: response2.content, isError: !response2.ok };
|
|
8294
8306
|
};
|
|
8295
8307
|
const read22 = {
|
|
@@ -8305,6 +8317,7 @@ function codeSkill(opts) {
|
|
|
8305
8317
|
},
|
|
8306
8318
|
additionalProperties: false
|
|
8307
8319
|
},
|
|
8320
|
+
concurrency: "parallel",
|
|
8308
8321
|
handler: (input, ctx) => call4("sandbox.read", input, ctx.signal)
|
|
8309
8322
|
};
|
|
8310
8323
|
const applyPatch = {
|
|
@@ -8346,6 +8359,7 @@ function codeSkill(opts) {
|
|
|
8346
8359
|
},
|
|
8347
8360
|
additionalProperties: false
|
|
8348
8361
|
},
|
|
8362
|
+
concurrency: "parallel",
|
|
8349
8363
|
handler: (input, ctx) => call4("sandbox.list", input, ctx.signal)
|
|
8350
8364
|
};
|
|
8351
8365
|
const searchFiles = {
|
|
@@ -8362,11 +8376,13 @@ function codeSkill(opts) {
|
|
|
8362
8376
|
},
|
|
8363
8377
|
additionalProperties: false
|
|
8364
8378
|
},
|
|
8379
|
+
concurrency: "parallel",
|
|
8365
8380
|
handler: (input, ctx) => call4("sandbox.search", input, ctx.signal)
|
|
8366
8381
|
};
|
|
8367
8382
|
const graphTool = (name, tool, description, required) => ({
|
|
8368
8383
|
name,
|
|
8369
8384
|
description,
|
|
8385
|
+
concurrency: "parallel",
|
|
8370
8386
|
inputSchema: {
|
|
8371
8387
|
type: "object",
|
|
8372
8388
|
...required ? { required: ["query"] } : {},
|
|
@@ -8544,6 +8560,24 @@ function createCodeRuntimeInference(options) {
|
|
|
8544
8560
|
var DEFAULT_MAX_FILES = 2e4;
|
|
8545
8561
|
var DEFAULT_MAX_RESULTS = 100;
|
|
8546
8562
|
var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
8563
|
+
function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = registeredFiles) {
|
|
8564
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
8565
|
+
return {
|
|
8566
|
+
files(root) {
|
|
8567
|
+
const existing = cache2.get(root);
|
|
8568
|
+
if (existing) return existing;
|
|
8569
|
+
const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
|
|
8570
|
+
cache2.set(root, pending);
|
|
8571
|
+
void pending.catch(() => {
|
|
8572
|
+
if (cache2.get(root) === pending) cache2.delete(root);
|
|
8573
|
+
});
|
|
8574
|
+
return pending;
|
|
8575
|
+
},
|
|
8576
|
+
invalidate(root) {
|
|
8577
|
+
cache2.delete(root);
|
|
8578
|
+
}
|
|
8579
|
+
};
|
|
8580
|
+
}
|
|
8547
8581
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
8548
8582
|
const paths = [];
|
|
8549
8583
|
const walk = async (directory) => {
|
|
@@ -8574,28 +8608,122 @@ function listWorkspace(paths, options = {}) {
|
|
|
8574
8608
|
return scoped.slice(0, max);
|
|
8575
8609
|
}
|
|
8576
8610
|
async function searchWorkspace(root, paths, options) {
|
|
8577
|
-
|
|
8578
|
-
if (!query) throw new TypeError("search query must be a non-empty string");
|
|
8611
|
+
options.signal?.throwIfAborted();
|
|
8612
|
+
if (!options.query) throw new TypeError("search query must be a non-empty string");
|
|
8579
8613
|
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
8580
8614
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
8581
8615
|
const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
|
|
8616
|
+
if (scoped.length === 0) return [];
|
|
8617
|
+
try {
|
|
8618
|
+
return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
8619
|
+
} catch (error) {
|
|
8620
|
+
options.signal?.throwIfAborted();
|
|
8621
|
+
return fallbackSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
8622
|
+
}
|
|
8623
|
+
}
|
|
8624
|
+
var MAX_NATIVE_ARG_BYTES = 96 * 1024;
|
|
8625
|
+
async function nativeSearch(root, paths, options) {
|
|
8626
|
+
const batches = [];
|
|
8627
|
+
let batch = [];
|
|
8628
|
+
let bytes = 0;
|
|
8629
|
+
for (const path of paths) {
|
|
8630
|
+
const size = Buffer.byteLength(path) + 1;
|
|
8631
|
+
if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
|
|
8632
|
+
batches.push(batch);
|
|
8633
|
+
batch = [];
|
|
8634
|
+
bytes = 0;
|
|
8635
|
+
}
|
|
8636
|
+
batch.push(path);
|
|
8637
|
+
bytes += size;
|
|
8638
|
+
}
|
|
8639
|
+
if (batch.length > 0) batches.push(batch);
|
|
8640
|
+
const matches = [];
|
|
8641
|
+
for (const files of batches) {
|
|
8642
|
+
const remaining = options.maxResults - matches.length;
|
|
8643
|
+
if (remaining <= 0) break;
|
|
8644
|
+
matches.push(...await nativeSearchBatch(root, files, options, remaining));
|
|
8645
|
+
}
|
|
8646
|
+
return matches;
|
|
8647
|
+
}
|
|
8648
|
+
function nativeSearchBatch(root, paths, options, remaining) {
|
|
8649
|
+
return new Promise((resolveMatches, reject) => {
|
|
8650
|
+
const args = [
|
|
8651
|
+
"--fixed-strings",
|
|
8652
|
+
"--json",
|
|
8653
|
+
"--no-messages",
|
|
8654
|
+
"--sort=path",
|
|
8655
|
+
`--max-filesize=${options.maxFileBytes}`,
|
|
8656
|
+
"--max-columns=4096",
|
|
8657
|
+
"--max-columns-preview",
|
|
8658
|
+
options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
|
|
8659
|
+
"--",
|
|
8660
|
+
options.query,
|
|
8661
|
+
...paths
|
|
8662
|
+
];
|
|
8663
|
+
const child = (0, import_child_process6.spawn)("rg", args, {
|
|
8664
|
+
cwd: root,
|
|
8665
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
8666
|
+
...options.signal ? { signal: options.signal } : {}
|
|
8667
|
+
});
|
|
8668
|
+
const matches = [];
|
|
8669
|
+
let carry = "";
|
|
8670
|
+
let stopped = false;
|
|
8671
|
+
const consume = (line2) => {
|
|
8672
|
+
if (matches.length >= remaining) return;
|
|
8673
|
+
let event;
|
|
8674
|
+
try {
|
|
8675
|
+
event = JSON.parse(line2);
|
|
8676
|
+
} catch {
|
|
8677
|
+
return;
|
|
8678
|
+
}
|
|
8679
|
+
const path = event.data?.path?.text;
|
|
8680
|
+
const lineNumber = event.data?.line_number;
|
|
8681
|
+
const source = event.data?.lines?.text;
|
|
8682
|
+
if (event.type !== "match" || path === void 0 || lineNumber === void 0 || source === void 0) return;
|
|
8683
|
+
matches.push({ path, line: lineNumber, text: source.trim().slice(0, 240) });
|
|
8684
|
+
if (matches.length >= remaining) {
|
|
8685
|
+
stopped = true;
|
|
8686
|
+
child.kill();
|
|
8687
|
+
}
|
|
8688
|
+
};
|
|
8689
|
+
child.stdout.setEncoding("utf8");
|
|
8690
|
+
child.stdout.on("data", (chunk) => {
|
|
8691
|
+
carry += chunk;
|
|
8692
|
+
let newline = carry.indexOf("\n");
|
|
8693
|
+
while (newline >= 0) {
|
|
8694
|
+
consume(carry.slice(0, newline));
|
|
8695
|
+
carry = carry.slice(newline + 1);
|
|
8696
|
+
newline = carry.indexOf("\n");
|
|
8697
|
+
}
|
|
8698
|
+
});
|
|
8699
|
+
child.once("error", reject);
|
|
8700
|
+
child.once("close", (code) => {
|
|
8701
|
+
if (carry) consume(carry);
|
|
8702
|
+
if (stopped || code === 0 || code === 1) resolveMatches(matches);
|
|
8703
|
+
else reject(new Error(`native search exited with status ${code ?? "unknown"}`));
|
|
8704
|
+
});
|
|
8705
|
+
});
|
|
8706
|
+
}
|
|
8707
|
+
async function fallbackSearch(root, scoped, options) {
|
|
8708
|
+
const query = options.caseSensitive === false ? options.query.toLowerCase() : options.query;
|
|
8582
8709
|
const matches = [];
|
|
8583
8710
|
for (const path of scoped) {
|
|
8584
|
-
|
|
8711
|
+
options.signal?.throwIfAborted();
|
|
8712
|
+
if (matches.length >= options.maxResults) break;
|
|
8585
8713
|
let source;
|
|
8586
8714
|
try {
|
|
8587
8715
|
source = await (0, import_promises9.readFile)((0, import_path9.resolve)(root, path));
|
|
8588
8716
|
} catch {
|
|
8589
8717
|
continue;
|
|
8590
8718
|
}
|
|
8591
|
-
if (source.byteLength > maxFileBytes || source.includes(0)) continue;
|
|
8719
|
+
if (source.byteLength > options.maxFileBytes || source.includes(0)) continue;
|
|
8592
8720
|
const lines = source.toString("utf8").split("\n");
|
|
8593
8721
|
for (let index = 0; index < lines.length; index += 1) {
|
|
8594
8722
|
const raw = lines[index];
|
|
8595
8723
|
const haystack = options.caseSensitive === false ? raw.toLowerCase() : raw;
|
|
8596
8724
|
if (!haystack.includes(query)) continue;
|
|
8597
8725
|
matches.push({ path, line: index + 1, text: raw.trim().slice(0, 240) });
|
|
8598
|
-
if (matches.length >= maxResults) break;
|
|
8726
|
+
if (matches.length >= options.maxResults) break;
|
|
8599
8727
|
}
|
|
8600
8728
|
}
|
|
8601
8729
|
return matches;
|
|
@@ -8850,6 +8978,9 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
8850
8978
|
cache.set(workspaceDir, built);
|
|
8851
8979
|
return built;
|
|
8852
8980
|
}
|
|
8981
|
+
function forgetWorkspaceGraphs(workspaceDir) {
|
|
8982
|
+
cache.delete(workspaceDir);
|
|
8983
|
+
}
|
|
8853
8984
|
var shortId = (id2) => id2.slice(id2.indexOf(":") + 1);
|
|
8854
8985
|
function renderOverview(graphs, prefix) {
|
|
8855
8986
|
const rows = rollup(graphs.graph, FILE, prefix === void 0 ? {} : { prefix });
|
|
@@ -8894,7 +9025,7 @@ var GRAPH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
8894
9025
|
"sandbox.who_imports",
|
|
8895
9026
|
"sandbox.who_touches"
|
|
8896
9027
|
]);
|
|
8897
|
-
async function read(context, request3, options, policy) {
|
|
9028
|
+
async function read(context, request3, options, policy, registry) {
|
|
8898
9029
|
exactKeys(request3.input, ["path", "startLine", "endLine"]);
|
|
8899
9030
|
const path = stringField(request3.input, "path");
|
|
8900
9031
|
const startLine = optionalInteger(request3.input.startLine) ?? 1;
|
|
@@ -8902,7 +9033,7 @@ async function read(context, request3, options, policy) {
|
|
|
8902
9033
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
8903
9034
|
throw new TypeError("requested line range exceeds its bound");
|
|
8904
9035
|
}
|
|
8905
|
-
const paths = await
|
|
9036
|
+
const paths = await registry.files(context.workspaceDir);
|
|
8906
9037
|
if (!paths.includes(path)) {
|
|
8907
9038
|
throw new TypeError(`no such file in the staged workspace: "${path}". Use sandbox.overview, sandbox.where_is or sandbox.search to find the correct path.`);
|
|
8908
9039
|
}
|
|
@@ -8922,13 +9053,13 @@ async function read(context, request3, options, policy) {
|
|
|
8922
9053
|
}
|
|
8923
9054
|
return response(request3, true, content2, { path, startLine, endLine: Math.min(endLine, lines.length) });
|
|
8924
9055
|
}
|
|
8925
|
-
async function list(context, request3, options, policy) {
|
|
9056
|
+
async function list(context, request3, options, policy, registry) {
|
|
8926
9057
|
exactKeys(request3.input, ["prefix", "maxEntries"]);
|
|
8927
9058
|
const raw = request3.input.prefix;
|
|
8928
9059
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
8929
9060
|
const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
|
|
8930
9061
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
8931
|
-
const paths = await
|
|
9062
|
+
const paths = await registry.files(context.workspaceDir);
|
|
8932
9063
|
const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
|
|
8933
9064
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8934
9065
|
const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
|
|
@@ -8946,7 +9077,7 @@ async function list(context, request3, options, policy) {
|
|
|
8946
9077
|
{ count: entries.length, truncated }
|
|
8947
9078
|
);
|
|
8948
9079
|
}
|
|
8949
|
-
async function search(context, request3, options, policy) {
|
|
9080
|
+
async function search(context, request3, options, policy, registry) {
|
|
8950
9081
|
exactKeys(request3.input, ["query", "prefix", "maxResults", "caseSensitive"]);
|
|
8951
9082
|
const query = stringField(request3.input, "query");
|
|
8952
9083
|
if (query.length > 512) throw new TypeError("search query exceeds its bound");
|
|
@@ -8955,21 +9086,22 @@ async function search(context, request3, options, policy) {
|
|
|
8955
9086
|
const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
|
|
8956
9087
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
8957
9088
|
const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
|
|
8958
|
-
const paths = await
|
|
9089
|
+
const paths = await registry.files(context.workspaceDir);
|
|
8959
9090
|
const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
|
|
8960
9091
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8961
9092
|
const matches = await searchWorkspace(context.workspaceDir, paths, {
|
|
8962
9093
|
query,
|
|
8963
9094
|
maxResults,
|
|
8964
9095
|
caseSensitive,
|
|
8965
|
-
...prefix ? { prefix } : {}
|
|
9096
|
+
...prefix ? { prefix } : {},
|
|
9097
|
+
...context.signal ? { signal: context.signal } : {}
|
|
8966
9098
|
});
|
|
8967
9099
|
if (!matches.length) return response(request3, true, `No match for "${query}".`, { count: 0 });
|
|
8968
9100
|
return response(request3, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
|
|
8969
9101
|
count: matches.length
|
|
8970
9102
|
});
|
|
8971
9103
|
}
|
|
8972
|
-
async function graphQuery(context, request3, options, policy) {
|
|
9104
|
+
async function graphQuery(context, request3, options, policy, registry) {
|
|
8973
9105
|
exactKeys(request3.input, ["query"]);
|
|
8974
9106
|
const raw = request3.input.query;
|
|
8975
9107
|
const query = typeof raw === "string" ? raw : "";
|
|
@@ -8979,7 +9111,7 @@ async function graphQuery(context, request3, options, policy) {
|
|
|
8979
9111
|
selector: query
|
|
8980
9112
|
}));
|
|
8981
9113
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8982
|
-
const paths = await
|
|
9114
|
+
const paths = await registry.files(context.workspaceDir);
|
|
8983
9115
|
const graphs = await workspaceGraphs(context.workspaceDir, paths);
|
|
8984
9116
|
if (request3.tool === "sandbox.overview") {
|
|
8985
9117
|
return response(request3, true, renderOverview(graphs, query || void 0));
|
|
@@ -8993,23 +9125,38 @@ function createCodeToolBroker(options) {
|
|
|
8993
9125
|
validateOptions(options);
|
|
8994
9126
|
const recipes = new Map(options.recipes.map((recipe2) => [recipe2.id, recipe2]));
|
|
8995
9127
|
const policy = createCodePolicyGate(options);
|
|
8996
|
-
|
|
9128
|
+
const registry = createWorkspaceFileRegistry();
|
|
9129
|
+
let barrier = Promise.resolve();
|
|
9130
|
+
const activeReads = /* @__PURE__ */ new Set();
|
|
8997
9131
|
return {
|
|
8998
9132
|
execute(context, request3) {
|
|
8999
|
-
|
|
9000
|
-
|
|
9133
|
+
if (isReadTool(request3.tool)) {
|
|
9134
|
+
const result2 = barrier.then(() => route2(context, request3, options, recipes, policy, registry));
|
|
9135
|
+
const settled = result2.then(() => void 0, () => void 0);
|
|
9136
|
+
activeReads.add(settled);
|
|
9137
|
+
void settled.then(() => {
|
|
9138
|
+
activeReads.delete(settled);
|
|
9139
|
+
});
|
|
9140
|
+
return result2;
|
|
9141
|
+
}
|
|
9142
|
+
const earlierReads = [...activeReads];
|
|
9143
|
+
const result = barrier.then(() => Promise.all(earlierReads)).then(() => route2(context, request3, options, recipes, policy, registry));
|
|
9144
|
+
barrier = result.then(() => void 0, () => void 0);
|
|
9001
9145
|
return result;
|
|
9002
9146
|
}
|
|
9003
9147
|
};
|
|
9004
9148
|
}
|
|
9005
|
-
|
|
9149
|
+
function isReadTool(tool) {
|
|
9150
|
+
return tool === "sandbox.read" || tool === "sandbox.list" || tool === "sandbox.search" || GRAPH_TOOLS.has(tool);
|
|
9151
|
+
}
|
|
9152
|
+
async function route2(context, request3, options, recipes, policy, registry) {
|
|
9006
9153
|
try {
|
|
9007
9154
|
if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
|
|
9008
|
-
if (request3.tool === "sandbox.read") return await read(context, request3, options, policy);
|
|
9009
|
-
if (request3.tool === "sandbox.list") return await list(context, request3, options, policy);
|
|
9010
|
-
if (request3.tool === "sandbox.search") return await search(context, request3, options, policy);
|
|
9011
|
-
if (GRAPH_TOOLS.has(request3.tool)) return await graphQuery(context, request3, options, policy);
|
|
9012
|
-
if (request3.tool === "sandbox.apply_patch") return await patch(context, request3, options, policy);
|
|
9155
|
+
if (request3.tool === "sandbox.read") return await read(context, request3, options, policy, registry);
|
|
9156
|
+
if (request3.tool === "sandbox.list") return await list(context, request3, options, policy, registry);
|
|
9157
|
+
if (request3.tool === "sandbox.search") return await search(context, request3, options, policy, registry);
|
|
9158
|
+
if (GRAPH_TOOLS.has(request3.tool)) return await graphQuery(context, request3, options, policy, registry);
|
|
9159
|
+
if (request3.tool === "sandbox.apply_patch") return await patch(context, request3, options, policy, registry);
|
|
9013
9160
|
return await recipe(context, request3, options, recipes, policy);
|
|
9014
9161
|
} catch (reason) {
|
|
9015
9162
|
return response(request3, false, toolFailureMessage(reason));
|
|
@@ -9024,7 +9171,7 @@ function toolFailureMessage(reason) {
|
|
|
9024
9171
|
if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
|
|
9025
9172
|
return "tool failed closed";
|
|
9026
9173
|
}
|
|
9027
|
-
async function patch(context, request3, options, policy) {
|
|
9174
|
+
async function patch(context, request3, options, policy, registry) {
|
|
9028
9175
|
exactKeys(request3.input, ["patch"]);
|
|
9029
9176
|
const value2 = stringField(request3.input, "patch");
|
|
9030
9177
|
const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
|
|
@@ -9034,6 +9181,8 @@ async function patch(context, request3, options, policy) {
|
|
|
9034
9181
|
const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
|
|
9035
9182
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
9036
9183
|
await applyCodePatch(context.workspaceDir, value2, paths);
|
|
9184
|
+
registry.invalidate(context.workspaceDir);
|
|
9185
|
+
forgetWorkspaceGraphs(context.workspaceDir);
|
|
9037
9186
|
return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
|
|
9038
9187
|
}
|
|
9039
9188
|
async function recipe(context, request3, options, recipes, policy) {
|
|
@@ -10857,12 +11006,10 @@ Commands:
|
|
|
10857
11006
|
explicitly. Same device-grant auth as "app".
|
|
10858
11007
|
Status changes and comments post to each item's @odla-ai/chat
|
|
10859
11008
|
discussion thread.
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
pm.plan agent promotes. This is deliberate, not a permission
|
|
10865
|
-
gap \u2014 it was reported as one.
|
|
11009
|
+
Creating or promoting a task in Ready requires a complete
|
|
11010
|
+
execution contract and a reviewed revision. Any caller with PM
|
|
11011
|
+
mutation access to that app/project may do it; claims coordinate
|
|
11012
|
+
execution but never own or lock the PM record.
|
|
10866
11013
|
bug Intent-first alias for PM bugs. "bug report" writes to
|
|
10867
11014
|
odla PM; odla product defects do not belong in GitHub Issues.
|
|
10868
11015
|
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|