@odla-ai/harness 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/dist/{chunk-ZYNGL5SC.js → chunk-GYWQM76X.js} +197 -39
- package/dist/chunk-GYWQM76X.js.map +1 -0
- package/dist/code-runtime-cli.cjs +197 -39
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +4 -4
- package/dist/code-runtime-cli.js.map +1 -1
- package/dist/node.cjs +197 -39
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +19 -13
- package/dist/node.d.ts +19 -13
- package/dist/node.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-ZYNGL5SC.js.map +0 -1
|
@@ -1149,7 +1149,7 @@ var CodeRuntimeCheckpointManager = class {
|
|
|
1149
1149
|
await this.options.event(command, { type: "message", actor: "system", body: prepared.note }, active.conversationRefs).catch(() => void 0);
|
|
1150
1150
|
await active.workspace.cleanup();
|
|
1151
1151
|
await this.options.event(command, { type: "status", status: "checkpointed" }, active.conversationRefs).catch(() => void 0);
|
|
1152
|
-
return { status: "checkpointed", checkpoint: prepared.checkpoint, message: "
|
|
1152
|
+
return { status: "checkpointed", checkpoint: prepared.checkpoint, message: "Theseus stopped at a portable checkpoint" };
|
|
1153
1153
|
}
|
|
1154
1154
|
async acknowledged(command, result) {
|
|
1155
1155
|
if (command.kind !== "checkpoint_stop" || result.status !== "checkpointed") return false;
|
|
@@ -1391,7 +1391,7 @@ var import_ai2 = require("@odla-ai/ai");
|
|
|
1391
1391
|
var import_ai = require("@odla-ai/ai");
|
|
1392
1392
|
|
|
1393
1393
|
// src/code-agent-skill.ts
|
|
1394
|
-
var V1_SYSTEM_PROMPT = `You are
|
|
1394
|
+
var V1_SYSTEM_PROMPT = `You are Theseus, the coding agent inside an odla Code harness.
|
|
1395
1395
|
Use only the odla_read, odla_apply_git_diff, and odla_run_recipe tools.
|
|
1396
1396
|
For mutations, call odla_apply_git_diff with raw git diff text. It must start
|
|
1397
1397
|
with "diff --git a/<path> b/<path>", include matching "---" and "+++" file
|
|
@@ -1401,7 +1401,10 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
|
|
|
1401
1401
|
var V2_SYSTEM_PROMPT = `You are the coding agent inside an odla Code harness.
|
|
1402
1402
|
Start by orienting: odla_list shows the files in the workspace and odla_search
|
|
1403
1403
|
finds a literal string across them. Prefer those over guessing a path.
|
|
1404
|
-
Then odla_read a bounded range, and odla_apply_git_diff to mutate.
|
|
1404
|
+
Then odla_read a bounded range, and odla_apply_git_diff to mutate. When you
|
|
1405
|
+
need several independent searches or file ranges, issue those read-only calls
|
|
1406
|
+
together in one turn; their results stay ordered and the harness overlaps them.
|
|
1407
|
+
Never issue odla_apply_git_diff or odla_run_recipe alongside another tool call.
|
|
1405
1408
|
For mutations, call odla_apply_git_diff with raw git diff text. It must start
|
|
1406
1409
|
with "diff --git a/<path> b/<path>", include matching "---" and "+++" file
|
|
1407
1410
|
headers and numbered "@@" hunks, and never use "*** Begin Patch" wrappers.
|
|
@@ -1432,18 +1435,26 @@ var SYSTEM_PROMPT_FOR = {
|
|
|
1432
1435
|
};
|
|
1433
1436
|
function codeSkill(opts) {
|
|
1434
1437
|
let seq = 0;
|
|
1438
|
+
let nextCompletion = 1;
|
|
1439
|
+
const completed = /* @__PURE__ */ new Map();
|
|
1435
1440
|
const call = async (tool, input, signal) => {
|
|
1441
|
+
const sequence = ++seq;
|
|
1436
1442
|
const startedAt = Date.now();
|
|
1437
1443
|
const response2 = await opts.broker.execute(
|
|
1438
1444
|
{ lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
|
|
1439
|
-
{ requestId: `bench-${tool}-${
|
|
1445
|
+
{ requestId: `bench-${tool}-${sequence}`, tool, input }
|
|
1440
1446
|
);
|
|
1441
|
-
|
|
1447
|
+
completed.set(sequence, {
|
|
1442
1448
|
tool,
|
|
1443
1449
|
ok: response2.ok,
|
|
1444
1450
|
durationMs: Date.now() - startedAt,
|
|
1445
1451
|
...response2.ok ? {} : { error: String(response2.content).slice(0, 300) }
|
|
1446
1452
|
});
|
|
1453
|
+
while (completed.has(nextCompletion)) {
|
|
1454
|
+
const completion = completed.get(nextCompletion);
|
|
1455
|
+
completed.delete(nextCompletion++);
|
|
1456
|
+
opts.onToolCall?.(completion);
|
|
1457
|
+
}
|
|
1447
1458
|
return { content: response2.content, isError: !response2.ok };
|
|
1448
1459
|
};
|
|
1449
1460
|
const read2 = {
|
|
@@ -1459,6 +1470,7 @@ function codeSkill(opts) {
|
|
|
1459
1470
|
},
|
|
1460
1471
|
additionalProperties: false
|
|
1461
1472
|
},
|
|
1473
|
+
concurrency: "parallel",
|
|
1462
1474
|
handler: (input, ctx) => call("sandbox.read", input, ctx.signal)
|
|
1463
1475
|
};
|
|
1464
1476
|
const applyPatch = {
|
|
@@ -1474,11 +1486,17 @@ function codeSkill(opts) {
|
|
|
1474
1486
|
};
|
|
1475
1487
|
const runRecipe = {
|
|
1476
1488
|
name: "odla_run_recipe",
|
|
1477
|
-
description:
|
|
1489
|
+
description: `Run one app-registered build or test recipe through CaMeL policy.${opts.recipeIds?.length ? ` Available recipes: ${opts.recipeIds.join(", ")}.` : ""}`,
|
|
1478
1490
|
inputSchema: {
|
|
1479
1491
|
type: "object",
|
|
1480
1492
|
required: ["recipeId"],
|
|
1481
|
-
properties: { recipeId: {
|
|
1493
|
+
properties: { recipeId: {
|
|
1494
|
+
type: "string",
|
|
1495
|
+
minLength: 1,
|
|
1496
|
+
maxLength: 120,
|
|
1497
|
+
pattern: "^[a-zA-Z0-9._:-]+$",
|
|
1498
|
+
...opts.recipeIds?.length ? { enum: [...opts.recipeIds] } : {}
|
|
1499
|
+
} },
|
|
1482
1500
|
additionalProperties: false
|
|
1483
1501
|
},
|
|
1484
1502
|
handler: (input, ctx) => call("sandbox.run_recipe", input, ctx.signal)
|
|
@@ -1494,6 +1512,7 @@ function codeSkill(opts) {
|
|
|
1494
1512
|
},
|
|
1495
1513
|
additionalProperties: false
|
|
1496
1514
|
},
|
|
1515
|
+
concurrency: "parallel",
|
|
1497
1516
|
handler: (input, ctx) => call("sandbox.list", input, ctx.signal)
|
|
1498
1517
|
};
|
|
1499
1518
|
const searchFiles = {
|
|
@@ -1510,11 +1529,13 @@ function codeSkill(opts) {
|
|
|
1510
1529
|
},
|
|
1511
1530
|
additionalProperties: false
|
|
1512
1531
|
},
|
|
1532
|
+
concurrency: "parallel",
|
|
1513
1533
|
handler: (input, ctx) => call("sandbox.search", input, ctx.signal)
|
|
1514
1534
|
};
|
|
1515
1535
|
const graphTool = (name, tool, description, required) => ({
|
|
1516
1536
|
name,
|
|
1517
1537
|
description,
|
|
1538
|
+
concurrency: "parallel",
|
|
1518
1539
|
inputSchema: {
|
|
1519
1540
|
type: "object",
|
|
1520
1541
|
...required ? { required: ["query"] } : {},
|
|
@@ -1562,6 +1583,7 @@ async function runCodeAgent(options) {
|
|
|
1562
1583
|
lease: options.lease,
|
|
1563
1584
|
workspaceDir: options.workspaceDir,
|
|
1564
1585
|
surface,
|
|
1586
|
+
...options.recipeIds ? { recipeIds: options.recipeIds } : {},
|
|
1565
1587
|
onToolCall: (call) => {
|
|
1566
1588
|
toolCalls.push(call);
|
|
1567
1589
|
options.onToolCall?.(call);
|
|
@@ -1592,7 +1614,7 @@ async function runCodeAgent(options) {
|
|
|
1592
1614
|
// src/code-runtime-attempt.ts
|
|
1593
1615
|
async function runCodeAgentAttempt(options) {
|
|
1594
1616
|
try {
|
|
1595
|
-
const surface = options.surface ?? "
|
|
1617
|
+
const surface = options.surface ?? "v3";
|
|
1596
1618
|
const { run } = await runCodeAgent({
|
|
1597
1619
|
inference: options.inference,
|
|
1598
1620
|
broker: options.broker,
|
|
@@ -1603,6 +1625,7 @@ async function runCodeAgentAttempt(options) {
|
|
|
1603
1625
|
// id only labels the request the control plane is about to rewrite.
|
|
1604
1626
|
model: "brokered",
|
|
1605
1627
|
surface,
|
|
1628
|
+
...options.recipeIds ? { recipeIds: options.recipeIds } : {},
|
|
1606
1629
|
...options.maxSteps === void 0 ? {} : { maxSteps: options.maxSteps },
|
|
1607
1630
|
...options.budget ? { budget: options.budget } : {},
|
|
1608
1631
|
...options.signal ? { signal: options.signal } : {},
|
|
@@ -1942,11 +1965,30 @@ function response(request, ok, content, details) {
|
|
|
1942
1965
|
var import_promises9 = require("fs/promises");
|
|
1943
1966
|
|
|
1944
1967
|
// src/code-tool-discovery.ts
|
|
1968
|
+
var import_node_child_process5 = require("child_process");
|
|
1945
1969
|
var import_promises7 = require("fs/promises");
|
|
1946
1970
|
var import_node_path8 = require("path");
|
|
1947
1971
|
var DEFAULT_MAX_FILES = 2e4;
|
|
1948
1972
|
var DEFAULT_MAX_RESULTS = 100;
|
|
1949
1973
|
var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
1974
|
+
function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = registeredFiles) {
|
|
1975
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
1976
|
+
return {
|
|
1977
|
+
files(root) {
|
|
1978
|
+
const existing = cache2.get(root);
|
|
1979
|
+
if (existing) return existing;
|
|
1980
|
+
const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
|
|
1981
|
+
cache2.set(root, pending);
|
|
1982
|
+
void pending.catch(() => {
|
|
1983
|
+
if (cache2.get(root) === pending) cache2.delete(root);
|
|
1984
|
+
});
|
|
1985
|
+
return pending;
|
|
1986
|
+
},
|
|
1987
|
+
invalidate(root) {
|
|
1988
|
+
cache2.delete(root);
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1950
1992
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
1951
1993
|
const paths = [];
|
|
1952
1994
|
const walk = async (directory) => {
|
|
@@ -1977,28 +2019,122 @@ function listWorkspace(paths, options = {}) {
|
|
|
1977
2019
|
return scoped.slice(0, max);
|
|
1978
2020
|
}
|
|
1979
2021
|
async function searchWorkspace(root, paths, options) {
|
|
1980
|
-
|
|
1981
|
-
if (!query) throw new TypeError("search query must be a non-empty string");
|
|
2022
|
+
options.signal?.throwIfAborted();
|
|
2023
|
+
if (!options.query) throw new TypeError("search query must be a non-empty string");
|
|
1982
2024
|
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
1983
2025
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
1984
2026
|
const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
|
|
2027
|
+
if (scoped.length === 0) return [];
|
|
2028
|
+
try {
|
|
2029
|
+
return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
2030
|
+
} catch (error) {
|
|
2031
|
+
options.signal?.throwIfAborted();
|
|
2032
|
+
return fallbackSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
var MAX_NATIVE_ARG_BYTES = 96 * 1024;
|
|
2036
|
+
async function nativeSearch(root, paths, options) {
|
|
2037
|
+
const batches = [];
|
|
2038
|
+
let batch = [];
|
|
2039
|
+
let bytes = 0;
|
|
2040
|
+
for (const path of paths) {
|
|
2041
|
+
const size = Buffer.byteLength(path) + 1;
|
|
2042
|
+
if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
|
|
2043
|
+
batches.push(batch);
|
|
2044
|
+
batch = [];
|
|
2045
|
+
bytes = 0;
|
|
2046
|
+
}
|
|
2047
|
+
batch.push(path);
|
|
2048
|
+
bytes += size;
|
|
2049
|
+
}
|
|
2050
|
+
if (batch.length > 0) batches.push(batch);
|
|
2051
|
+
const matches = [];
|
|
2052
|
+
for (const files of batches) {
|
|
2053
|
+
const remaining = options.maxResults - matches.length;
|
|
2054
|
+
if (remaining <= 0) break;
|
|
2055
|
+
matches.push(...await nativeSearchBatch(root, files, options, remaining));
|
|
2056
|
+
}
|
|
2057
|
+
return matches;
|
|
2058
|
+
}
|
|
2059
|
+
function nativeSearchBatch(root, paths, options, remaining) {
|
|
2060
|
+
return new Promise((resolveMatches, reject) => {
|
|
2061
|
+
const args = [
|
|
2062
|
+
"--fixed-strings",
|
|
2063
|
+
"--json",
|
|
2064
|
+
"--no-messages",
|
|
2065
|
+
"--sort=path",
|
|
2066
|
+
`--max-filesize=${options.maxFileBytes}`,
|
|
2067
|
+
"--max-columns=4096",
|
|
2068
|
+
"--max-columns-preview",
|
|
2069
|
+
options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
|
|
2070
|
+
"--",
|
|
2071
|
+
options.query,
|
|
2072
|
+
...paths
|
|
2073
|
+
];
|
|
2074
|
+
const child = (0, import_node_child_process5.spawn)("rg", args, {
|
|
2075
|
+
cwd: root,
|
|
2076
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2077
|
+
...options.signal ? { signal: options.signal } : {}
|
|
2078
|
+
});
|
|
2079
|
+
const matches = [];
|
|
2080
|
+
let carry = "";
|
|
2081
|
+
let stopped = false;
|
|
2082
|
+
const consume = (line) => {
|
|
2083
|
+
if (matches.length >= remaining) return;
|
|
2084
|
+
let event;
|
|
2085
|
+
try {
|
|
2086
|
+
event = JSON.parse(line);
|
|
2087
|
+
} catch {
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
const path = event.data?.path?.text;
|
|
2091
|
+
const lineNumber = event.data?.line_number;
|
|
2092
|
+
const source = event.data?.lines?.text;
|
|
2093
|
+
if (event.type !== "match" || path === void 0 || lineNumber === void 0 || source === void 0) return;
|
|
2094
|
+
matches.push({ path, line: lineNumber, text: source.trim().slice(0, 240) });
|
|
2095
|
+
if (matches.length >= remaining) {
|
|
2096
|
+
stopped = true;
|
|
2097
|
+
child.kill();
|
|
2098
|
+
}
|
|
2099
|
+
};
|
|
2100
|
+
child.stdout.setEncoding("utf8");
|
|
2101
|
+
child.stdout.on("data", (chunk) => {
|
|
2102
|
+
carry += chunk;
|
|
2103
|
+
let newline = carry.indexOf("\n");
|
|
2104
|
+
while (newline >= 0) {
|
|
2105
|
+
consume(carry.slice(0, newline));
|
|
2106
|
+
carry = carry.slice(newline + 1);
|
|
2107
|
+
newline = carry.indexOf("\n");
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
child.once("error", reject);
|
|
2111
|
+
child.once("close", (code) => {
|
|
2112
|
+
if (carry) consume(carry);
|
|
2113
|
+
if (stopped || code === 0 || code === 1) resolveMatches(matches);
|
|
2114
|
+
else reject(new Error(`native search exited with status ${code ?? "unknown"}`));
|
|
2115
|
+
});
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
async function fallbackSearch(root, scoped, options) {
|
|
2119
|
+
const query = options.caseSensitive === false ? options.query.toLowerCase() : options.query;
|
|
1985
2120
|
const matches = [];
|
|
1986
2121
|
for (const path of scoped) {
|
|
1987
|
-
|
|
2122
|
+
options.signal?.throwIfAborted();
|
|
2123
|
+
if (matches.length >= options.maxResults) break;
|
|
1988
2124
|
let source;
|
|
1989
2125
|
try {
|
|
1990
2126
|
source = await (0, import_promises7.readFile)((0, import_node_path8.resolve)(root, path));
|
|
1991
2127
|
} catch {
|
|
1992
2128
|
continue;
|
|
1993
2129
|
}
|
|
1994
|
-
if (source.byteLength > maxFileBytes || source.includes(0)) continue;
|
|
2130
|
+
if (source.byteLength > options.maxFileBytes || source.includes(0)) continue;
|
|
1995
2131
|
const lines = source.toString("utf8").split("\n");
|
|
1996
2132
|
for (let index = 0; index < lines.length; index += 1) {
|
|
1997
2133
|
const raw = lines[index];
|
|
1998
2134
|
const haystack = options.caseSensitive === false ? raw.toLowerCase() : raw;
|
|
1999
2135
|
if (!haystack.includes(query)) continue;
|
|
2000
2136
|
matches.push({ path, line: index + 1, text: raw.trim().slice(0, 240) });
|
|
2001
|
-
if (matches.length >= maxResults) break;
|
|
2137
|
+
if (matches.length >= options.maxResults) break;
|
|
2002
2138
|
}
|
|
2003
2139
|
}
|
|
2004
2140
|
return matches;
|
|
@@ -2023,6 +2159,9 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
2023
2159
|
cache.set(workspaceDir, built);
|
|
2024
2160
|
return built;
|
|
2025
2161
|
}
|
|
2162
|
+
function forgetWorkspaceGraphs(workspaceDir) {
|
|
2163
|
+
cache.delete(workspaceDir);
|
|
2164
|
+
}
|
|
2026
2165
|
var shortId = (id) => id.slice(id.indexOf(":") + 1);
|
|
2027
2166
|
function renderOverview(graphs, prefix) {
|
|
2028
2167
|
const rows = (0, import_graph.rollup)(graphs.graph, import_code4.FILE, prefix === void 0 ? {} : { prefix });
|
|
@@ -2069,7 +2208,7 @@ var GRAPH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
2069
2208
|
"sandbox.who_imports",
|
|
2070
2209
|
"sandbox.who_touches"
|
|
2071
2210
|
]);
|
|
2072
|
-
async function read(context, request, options, policy) {
|
|
2211
|
+
async function read(context, request, options, policy, registry) {
|
|
2073
2212
|
exactKeys(request.input, ["path", "startLine", "endLine"]);
|
|
2074
2213
|
const path = stringField(request.input, "path");
|
|
2075
2214
|
const startLine = optionalInteger(request.input.startLine) ?? 1;
|
|
@@ -2077,7 +2216,7 @@ async function read(context, request, options, policy) {
|
|
|
2077
2216
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
2078
2217
|
throw new TypeError("requested line range exceeds its bound");
|
|
2079
2218
|
}
|
|
2080
|
-
const paths = await
|
|
2219
|
+
const paths = await registry.files(context.workspaceDir);
|
|
2081
2220
|
if (!paths.includes(path)) {
|
|
2082
2221
|
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.`);
|
|
2083
2222
|
}
|
|
@@ -2097,13 +2236,13 @@ async function read(context, request, options, policy) {
|
|
|
2097
2236
|
}
|
|
2098
2237
|
return response(request, true, content, { path, startLine, endLine: Math.min(endLine, lines.length) });
|
|
2099
2238
|
}
|
|
2100
|
-
async function list(context, request, options, policy) {
|
|
2239
|
+
async function list(context, request, options, policy, registry) {
|
|
2101
2240
|
exactKeys(request.input, ["prefix", "maxEntries"]);
|
|
2102
2241
|
const raw = request.input.prefix;
|
|
2103
2242
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
2104
2243
|
const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
|
|
2105
2244
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
2106
|
-
const paths = await
|
|
2245
|
+
const paths = await registry.files(context.workspaceDir);
|
|
2107
2246
|
const allowed = await policy.list(policyContext(context, request, options, { paths, ...prefix ? { prefix } : {} }));
|
|
2108
2247
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2109
2248
|
const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
|
|
@@ -2121,7 +2260,7 @@ async function list(context, request, options, policy) {
|
|
|
2121
2260
|
{ count: entries.length, truncated }
|
|
2122
2261
|
);
|
|
2123
2262
|
}
|
|
2124
|
-
async function search(context, request, options, policy) {
|
|
2263
|
+
async function search(context, request, options, policy, registry) {
|
|
2125
2264
|
exactKeys(request.input, ["query", "prefix", "maxResults", "caseSensitive"]);
|
|
2126
2265
|
const query = stringField(request.input, "query");
|
|
2127
2266
|
if (query.length > 512) throw new TypeError("search query exceeds its bound");
|
|
@@ -2130,21 +2269,22 @@ async function search(context, request, options, policy) {
|
|
|
2130
2269
|
const maxResults = optionalInteger(request.input.maxResults) ?? 100;
|
|
2131
2270
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
2132
2271
|
const caseSensitive = request.input.caseSensitive === void 0 ? true : request.input.caseSensitive === true;
|
|
2133
|
-
const paths = await
|
|
2272
|
+
const paths = await registry.files(context.workspaceDir);
|
|
2134
2273
|
const allowed = await policy.search(policyContext(context, request, options, { paths, query, ...prefix ? { prefix } : {} }));
|
|
2135
2274
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2136
2275
|
const matches = await searchWorkspace(context.workspaceDir, paths, {
|
|
2137
2276
|
query,
|
|
2138
2277
|
maxResults,
|
|
2139
2278
|
caseSensitive,
|
|
2140
|
-
...prefix ? { prefix } : {}
|
|
2279
|
+
...prefix ? { prefix } : {},
|
|
2280
|
+
...context.signal ? { signal: context.signal } : {}
|
|
2141
2281
|
});
|
|
2142
2282
|
if (!matches.length) return response(request, true, `No match for "${query}".`, { count: 0 });
|
|
2143
2283
|
return response(request, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
|
|
2144
2284
|
count: matches.length
|
|
2145
2285
|
});
|
|
2146
2286
|
}
|
|
2147
|
-
async function graphQuery(context, request, options, policy) {
|
|
2287
|
+
async function graphQuery(context, request, options, policy, registry) {
|
|
2148
2288
|
exactKeys(request.input, ["query"]);
|
|
2149
2289
|
const raw = request.input.query;
|
|
2150
2290
|
const query = typeof raw === "string" ? raw : "";
|
|
@@ -2154,7 +2294,7 @@ async function graphQuery(context, request, options, policy) {
|
|
|
2154
2294
|
selector: query
|
|
2155
2295
|
}));
|
|
2156
2296
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2157
|
-
const paths = await
|
|
2297
|
+
const paths = await registry.files(context.workspaceDir);
|
|
2158
2298
|
const graphs = await workspaceGraphs(context.workspaceDir, paths);
|
|
2159
2299
|
if (request.tool === "sandbox.overview") {
|
|
2160
2300
|
return response(request, true, renderOverview(graphs, query || void 0));
|
|
@@ -2170,23 +2310,38 @@ function createCodeToolBroker(options) {
|
|
|
2170
2310
|
validateOptions(options);
|
|
2171
2311
|
const recipes = new Map(options.recipes.map((recipe2) => [recipe2.id, recipe2]));
|
|
2172
2312
|
const policy = createCodePolicyGate(options);
|
|
2173
|
-
|
|
2313
|
+
const registry = createWorkspaceFileRegistry();
|
|
2314
|
+
let barrier = Promise.resolve();
|
|
2315
|
+
const activeReads = /* @__PURE__ */ new Set();
|
|
2174
2316
|
return {
|
|
2175
2317
|
execute(context, request) {
|
|
2176
|
-
|
|
2177
|
-
|
|
2318
|
+
if (isReadTool(request.tool)) {
|
|
2319
|
+
const result2 = barrier.then(() => route(context, request, options, recipes, policy, registry));
|
|
2320
|
+
const settled = result2.then(() => void 0, () => void 0);
|
|
2321
|
+
activeReads.add(settled);
|
|
2322
|
+
void settled.then(() => {
|
|
2323
|
+
activeReads.delete(settled);
|
|
2324
|
+
});
|
|
2325
|
+
return result2;
|
|
2326
|
+
}
|
|
2327
|
+
const earlierReads = [...activeReads];
|
|
2328
|
+
const result = barrier.then(() => Promise.all(earlierReads)).then(() => route(context, request, options, recipes, policy, registry));
|
|
2329
|
+
barrier = result.then(() => void 0, () => void 0);
|
|
2178
2330
|
return result;
|
|
2179
2331
|
}
|
|
2180
2332
|
};
|
|
2181
2333
|
}
|
|
2182
|
-
|
|
2334
|
+
function isReadTool(tool) {
|
|
2335
|
+
return tool === "sandbox.read" || tool === "sandbox.list" || tool === "sandbox.search" || GRAPH_TOOLS.has(tool);
|
|
2336
|
+
}
|
|
2337
|
+
async function route(context, request, options, recipes, policy, registry) {
|
|
2183
2338
|
try {
|
|
2184
2339
|
if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
|
|
2185
|
-
if (request.tool === "sandbox.read") return await read(context, request, options, policy);
|
|
2186
|
-
if (request.tool === "sandbox.list") return await list(context, request, options, policy);
|
|
2187
|
-
if (request.tool === "sandbox.search") return await search(context, request, options, policy);
|
|
2188
|
-
if (GRAPH_TOOLS.has(request.tool)) return await graphQuery(context, request, options, policy);
|
|
2189
|
-
if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy);
|
|
2340
|
+
if (request.tool === "sandbox.read") return await read(context, request, options, policy, registry);
|
|
2341
|
+
if (request.tool === "sandbox.list") return await list(context, request, options, policy, registry);
|
|
2342
|
+
if (request.tool === "sandbox.search") return await search(context, request, options, policy, registry);
|
|
2343
|
+
if (GRAPH_TOOLS.has(request.tool)) return await graphQuery(context, request, options, policy, registry);
|
|
2344
|
+
if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy, registry);
|
|
2190
2345
|
return await recipe(context, request, options, recipes, policy);
|
|
2191
2346
|
} catch (reason) {
|
|
2192
2347
|
return response(request, false, toolFailureMessage(reason));
|
|
@@ -2201,7 +2356,7 @@ function toolFailureMessage(reason) {
|
|
|
2201
2356
|
if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
|
|
2202
2357
|
return "tool failed closed";
|
|
2203
2358
|
}
|
|
2204
|
-
async function patch(context, request, options, policy) {
|
|
2359
|
+
async function patch(context, request, options, policy, registry) {
|
|
2205
2360
|
exactKeys(request.input, ["patch"]);
|
|
2206
2361
|
const value = stringField(request.input, "patch");
|
|
2207
2362
|
const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
|
|
@@ -2211,6 +2366,8 @@ async function patch(context, request, options, policy) {
|
|
|
2211
2366
|
const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
|
|
2212
2367
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2213
2368
|
await applyCodePatch(context.workspaceDir, value, paths);
|
|
2369
|
+
registry.invalidate(context.workspaceDir);
|
|
2370
|
+
forgetWorkspaceGraphs(context.workspaceDir);
|
|
2214
2371
|
return response(request, true, `Applied patch to ${paths.length} file(s).`, { paths });
|
|
2215
2372
|
}
|
|
2216
2373
|
async function recipe(context, request, options, recipes, policy) {
|
|
@@ -2579,7 +2736,7 @@ var digestRuntimeValue = (value) => `sha256:${(0, import_node_crypto4.createHash
|
|
|
2579
2736
|
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
2580
2737
|
|
|
2581
2738
|
// src/code-runtime-engine.ts
|
|
2582
|
-
var
|
|
2739
|
+
var TheseusRuntimeEngine = class {
|
|
2583
2740
|
constructor(options) {
|
|
2584
2741
|
this.options = options;
|
|
2585
2742
|
this.#attempt = options.runAgentAttempt ?? runCodeAgentAttempt;
|
|
@@ -2665,7 +2822,7 @@ var CodePiRuntimeEngine = class {
|
|
|
2665
2822
|
await this.#failure(command, active, detail);
|
|
2666
2823
|
return null;
|
|
2667
2824
|
});
|
|
2668
|
-
return { status: "running", message: resume ? "
|
|
2825
|
+
return { status: "running", message: resume ? "Theseus resumed from a portable checkpoint" : "Theseus started" };
|
|
2669
2826
|
}
|
|
2670
2827
|
/**
|
|
2671
2828
|
* Pursue a goal: attempt, judge with the clean verifier, re-prompt from what
|
|
@@ -2753,7 +2910,7 @@ var CodePiRuntimeEngine = class {
|
|
|
2753
2910
|
await this.#failure(command, active, detail);
|
|
2754
2911
|
return null;
|
|
2755
2912
|
});
|
|
2756
|
-
return { status: "running", message: "
|
|
2913
|
+
return { status: "running", message: "Theseus accepted the owner prompt" };
|
|
2757
2914
|
}
|
|
2758
2915
|
async #runAttempt(command, metadata, active) {
|
|
2759
2916
|
const lease = fakeCodeLease(command, metadata);
|
|
@@ -2778,7 +2935,8 @@ var CodePiRuntimeEngine = class {
|
|
|
2778
2935
|
lease,
|
|
2779
2936
|
workspaceDir: active.workspace.workspaceDir,
|
|
2780
2937
|
prompt: metadata.prompt,
|
|
2781
|
-
signal: active.abort.signal
|
|
2938
|
+
signal: active.abort.signal,
|
|
2939
|
+
recipeIds: this.options.recipes.map((recipe2) => recipe2.id)
|
|
2782
2940
|
});
|
|
2783
2941
|
const closing = result.finalText.trim();
|
|
2784
2942
|
const completed = result.status === "completed" && Boolean(closing);
|
|
@@ -2842,7 +3000,7 @@ var CodePiRuntimeEngine = class {
|
|
|
2842
3000
|
}
|
|
2843
3001
|
}
|
|
2844
3002
|
async #diagnostic(command, active, value) {
|
|
2845
|
-
const detail = value.trim().slice(0, 2e3) || "
|
|
3003
|
+
const detail = value.trim().slice(0, 2e3) || "Theseus runtime failed";
|
|
2846
3004
|
this.options.onDiagnostic?.(detail);
|
|
2847
3005
|
await this.#event(
|
|
2848
3006
|
command,
|
|
@@ -2924,8 +3082,8 @@ async function main() {
|
|
|
2924
3082
|
};
|
|
2925
3083
|
const controller = new AbortController();
|
|
2926
3084
|
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => controller.abort(signal));
|
|
2927
|
-
const control = createCodeRuntimeControlClient({ endpoint: options.endpoint, token
|
|
2928
|
-
const commandEngine = new
|
|
3085
|
+
const control = createCodeRuntimeControlClient({ endpoint: options.endpoint, token });
|
|
3086
|
+
const commandEngine = new TheseusRuntimeEngine({
|
|
2929
3087
|
control,
|
|
2930
3088
|
engine,
|
|
2931
3089
|
recipes: policy.recipes,
|