@odla-ai/harness 0.10.3 → 0.11.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/README.md +24 -0
- package/dist/{chunk-TMBYA5JX.js → chunk-IXYKIPRA.js} +144 -29
- package/dist/chunk-IXYKIPRA.js.map +1 -0
- package/dist/code-runtime-cli.cjs +124 -18
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +1 -1
- package/dist/node.cjs +145 -25
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +82 -1
- package/dist/node.d.ts +82 -1
- package/dist/node.js +15 -1
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-TMBYA5JX.js.map +0 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// src/code-runtime-cli.ts
|
|
5
5
|
var import_node_os4 = require("os");
|
|
6
|
-
var
|
|
6
|
+
var import_promises14 = require("fs/promises");
|
|
7
7
|
|
|
8
8
|
// src/code-runtime-client-validation.ts
|
|
9
9
|
var import_code = require("@odla-ai/camel/code");
|
|
@@ -1412,8 +1412,8 @@ var CodeRuntimeCheckpointManager = class {
|
|
|
1412
1412
|
trustedBaseDigest: active.trustedBaseDigest,
|
|
1413
1413
|
planningInputDigest: active.planningInputDigest,
|
|
1414
1414
|
conversationRefs: active.conversationRefs,
|
|
1415
|
-
fallbackPolicyDigest: this.options.fallbackPolicyDigest,
|
|
1416
|
-
recipes: this.options.recipes,
|
|
1415
|
+
fallbackPolicyDigest: active.buildPolicyDigest ?? this.options.fallbackPolicyDigest,
|
|
1416
|
+
recipes: active.recipes ?? this.options.recipes,
|
|
1417
1417
|
recipeExecutor: this.options.recipeExecutor,
|
|
1418
1418
|
review: (patch2, verification) => this.options.control.review(command.sessionId, { patch: patch2, verification })
|
|
1419
1419
|
});
|
|
@@ -2244,6 +2244,10 @@ function codeSkill(opts) {
|
|
|
2244
2244
|
}
|
|
2245
2245
|
|
|
2246
2246
|
// src/code-agent.ts
|
|
2247
|
+
function readOnlyNotice(readOnly, recipes) {
|
|
2248
|
+
if (!readOnly) return "";
|
|
2249
|
+
return recipes ? "\n\nThis session is read-only: inspect and report; repository mutation is intentionally unavailable. The registered proof recipes are available through odla_run_recipe and must be run before any verdict." : "\n\nThis session is read-only. Inspect and report; repository mutation and recipe execution are intentionally unavailable.";
|
|
2250
|
+
}
|
|
2247
2251
|
async function runCodeAgent(options) {
|
|
2248
2252
|
const toolCalls = [];
|
|
2249
2253
|
const surface = options.surface ?? "v1";
|
|
@@ -2253,6 +2257,7 @@ async function runCodeAgent(options) {
|
|
|
2253
2257
|
workspaceDir: options.workspaceDir,
|
|
2254
2258
|
surface,
|
|
2255
2259
|
...options.readOnly === void 0 ? {} : { readOnly: options.readOnly },
|
|
2260
|
+
...options.recipes === void 0 ? {} : { recipes: options.recipes },
|
|
2256
2261
|
...options.recipeIds ? { recipeIds: options.recipeIds } : {},
|
|
2257
2262
|
onToolCall: (call) => {
|
|
2258
2263
|
toolCalls.push(call);
|
|
@@ -2265,7 +2270,7 @@ async function runCodeAgent(options) {
|
|
|
2265
2270
|
{
|
|
2266
2271
|
name: "odla-code",
|
|
2267
2272
|
model: options.model,
|
|
2268
|
-
system: options.system ?? `${SYSTEM_PROMPT_FOR[surface]}${options.readOnly
|
|
2273
|
+
system: options.system ?? `${SYSTEM_PROMPT_FOR[surface]}${readOnlyNotice(options.readOnly, options.recipes)}`,
|
|
2269
2274
|
skills: [skill, ...options.extraSkills ?? []],
|
|
2270
2275
|
maxSteps: options.maxSteps ?? 24,
|
|
2271
2276
|
maxTokens: options.maxTokens ?? 16384
|
|
@@ -2349,13 +2354,19 @@ function createCodeRuntimeSessionSkillLoader(control) {
|
|
|
2349
2354
|
...tool.acceptsTaint === void 0 ? {} : { acceptsTaint: tool.acceptsTaint },
|
|
2350
2355
|
handler: async (input, context) => {
|
|
2351
2356
|
if (!context.toolCallId) throw new TypeError("collaboration tool call identity is required");
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2357
|
+
try {
|
|
2358
|
+
return await execute2(command.sessionId, {
|
|
2359
|
+
commandId: command.commandId,
|
|
2360
|
+
toolCallId: context.toolCallId,
|
|
2361
|
+
skill: manifest.name,
|
|
2362
|
+
tool: tool.name,
|
|
2363
|
+
input
|
|
2364
|
+
}, context.signal);
|
|
2365
|
+
} catch (cause) {
|
|
2366
|
+
if (context.signal?.aborted) throw cause;
|
|
2367
|
+
const detail = (cause instanceof Error ? cause.message : String(cause)).slice(0, 500);
|
|
2368
|
+
return { content: `Tool "${tool.name}" failed: ${detail}`, isError: true };
|
|
2369
|
+
}
|
|
2359
2370
|
}
|
|
2360
2371
|
}))
|
|
2361
2372
|
}));
|
|
@@ -3418,16 +3429,17 @@ function createCodeRuntimeToolBroker(input, lease, role) {
|
|
|
3418
3429
|
readerId: `code-session:${lease.task.taskId}`,
|
|
3419
3430
|
readOnlyPrefixes: [".odla-references"]
|
|
3420
3431
|
});
|
|
3421
|
-
const
|
|
3432
|
+
const reviewTools = /* @__PURE__ */ new Set([
|
|
3422
3433
|
"sandbox.read",
|
|
3423
3434
|
"sandbox.list",
|
|
3424
3435
|
"sandbox.search",
|
|
3425
3436
|
"sandbox.overview",
|
|
3426
3437
|
"sandbox.where_is",
|
|
3427
3438
|
"sandbox.who_imports",
|
|
3428
|
-
"sandbox.who_touches"
|
|
3439
|
+
"sandbox.who_touches",
|
|
3440
|
+
"sandbox.run_recipe"
|
|
3429
3441
|
]);
|
|
3430
|
-
return role === "coding" ? broker : { execute: (context, request) =>
|
|
3442
|
+
return role === "coding" ? broker : { execute: (context, request) => reviewTools.has(request.tool) ? broker.execute(context, request) : Promise.resolve({ requestId: request.requestId, ok: false, content: "review sessions are read-only" }) };
|
|
3431
3443
|
}
|
|
3432
3444
|
|
|
3433
3445
|
// src/code-memory.ts
|
|
@@ -3800,6 +3812,96 @@ function observeCodeRuntimeSessionSkills(command, skills, emit) {
|
|
|
3800
3812
|
}));
|
|
3801
3813
|
}
|
|
3802
3814
|
|
|
3815
|
+
// src/code-repository-recipes.ts
|
|
3816
|
+
var import_promises13 = require("fs/promises");
|
|
3817
|
+
var import_node_path12 = require("path");
|
|
3818
|
+
var REPOSITORY_RECIPES_FILE = "odla.recipes.json";
|
|
3819
|
+
var MAX_RECIPES = 16;
|
|
3820
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
3821
|
+
var MAX_TIMEOUT_MS = 15 * 6e4;
|
|
3822
|
+
var ID2 = /^[A-Za-z0-9._:-]{1,120}$/;
|
|
3823
|
+
var RECIPE_FIELDS = /* @__PURE__ */ new Set(["id", "command", "timeoutMs"]);
|
|
3824
|
+
var fault = (detail) => new TypeError(`${REPOSITORY_RECIPES_FILE} is malformed: ${detail}`);
|
|
3825
|
+
function parseRepositoryRecipes(text2, envelope) {
|
|
3826
|
+
let parsed;
|
|
3827
|
+
try {
|
|
3828
|
+
parsed = JSON.parse(text2);
|
|
3829
|
+
} catch {
|
|
3830
|
+
throw fault("not valid JSON");
|
|
3831
|
+
}
|
|
3832
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw fault("the document must be an object");
|
|
3833
|
+
const document = parsed;
|
|
3834
|
+
if (document.version !== 1) throw fault("version must be 1");
|
|
3835
|
+
const entries = document.recipes;
|
|
3836
|
+
if (!Array.isArray(entries) || entries.length < 1) throw fault("recipes must be a non-empty array");
|
|
3837
|
+
if (entries.length > MAX_RECIPES) throw fault(`at most ${MAX_RECIPES} recipes may be declared`);
|
|
3838
|
+
const recipes = [];
|
|
3839
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3840
|
+
entries.forEach((entry, index) => {
|
|
3841
|
+
const label = `recipe ${index + 1}`;
|
|
3842
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw fault(`${label} must be an object`);
|
|
3843
|
+
const row = entry;
|
|
3844
|
+
const unknown = Object.keys(row).filter((key) => !RECIPE_FIELDS.has(key));
|
|
3845
|
+
if (unknown.length) throw fault(`${label} has unsupported field ${unknown[0]}; the host owns image and resource limits`);
|
|
3846
|
+
if (typeof row.id !== "string" || !ID2.test(row.id)) throw fault(`${label} needs an id of 1 to 120 letters, digits, . _ : or -`);
|
|
3847
|
+
if (seen.has(row.id)) throw fault(`${label} repeats id ${row.id}`);
|
|
3848
|
+
seen.add(row.id);
|
|
3849
|
+
if (!Array.isArray(row.command) || row.command.length < 1 || row.command.some((part) => typeof part !== "string" || !part)) {
|
|
3850
|
+
throw fault(`${label} (${row.id}) needs command as a non-empty array of non-empty strings`);
|
|
3851
|
+
}
|
|
3852
|
+
const timeoutMs = row.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
3853
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_TIMEOUT_MS) {
|
|
3854
|
+
throw fault(`${label} (${row.id}) timeoutMs must be an integer from 1 to ${MAX_TIMEOUT_MS}`);
|
|
3855
|
+
}
|
|
3856
|
+
const recipe2 = {
|
|
3857
|
+
id: row.id,
|
|
3858
|
+
command: [...row.command],
|
|
3859
|
+
timeoutMs,
|
|
3860
|
+
image: envelope.image,
|
|
3861
|
+
maxOutputBytes: envelope.maxOutputBytes,
|
|
3862
|
+
cpus: envelope.cpus,
|
|
3863
|
+
memory: envelope.memory,
|
|
3864
|
+
pids: envelope.pids
|
|
3865
|
+
};
|
|
3866
|
+
try {
|
|
3867
|
+
assertCodeBuildRecipe(recipe2);
|
|
3868
|
+
} catch (cause) {
|
|
3869
|
+
throw fault(`${label} (${row.id}) is not a runnable recipe: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
3870
|
+
}
|
|
3871
|
+
recipes.push(recipe2);
|
|
3872
|
+
});
|
|
3873
|
+
return recipes;
|
|
3874
|
+
}
|
|
3875
|
+
async function readRepositoryRecipes(baselineDir, envelope) {
|
|
3876
|
+
let text2;
|
|
3877
|
+
try {
|
|
3878
|
+
text2 = await (0, import_promises13.readFile)((0, import_node_path12.join)(baselineDir, REPOSITORY_RECIPES_FILE), "utf8");
|
|
3879
|
+
} catch (cause) {
|
|
3880
|
+
if (cause.code === "ENOENT") return null;
|
|
3881
|
+
throw cause;
|
|
3882
|
+
}
|
|
3883
|
+
return parseRepositoryRecipes(text2, envelope);
|
|
3884
|
+
}
|
|
3885
|
+
async function resolveCodeRecipes(baselineDir, release, envelope) {
|
|
3886
|
+
const declared = envelope ? await readRepositoryRecipes(baselineDir, envelope) : null;
|
|
3887
|
+
return declared ? { recipes: declared, source: "repository" } : { recipes: release, source: "release" };
|
|
3888
|
+
}
|
|
3889
|
+
function describeCodeRecipes(resolved) {
|
|
3890
|
+
const ids = resolved.recipes.map((recipe2) => recipe2.id).join(", ");
|
|
3891
|
+
return resolved.source === "repository" ? `Verification recipes: declared by the repository in ${REPOSITORY_RECIPES_FILE} \xB7 ${ids}` : `Verification recipes: release-owned (the repository declares none in ${REPOSITORY_RECIPES_FILE}) \xB7 ${ids}`;
|
|
3892
|
+
}
|
|
3893
|
+
async function sessionRecipesFor(workspace, options, resume) {
|
|
3894
|
+
const resolved = await resolveCodeRecipes(workspace.baselineDir, options.recipes, options.repositoryRecipes ?? null).catch(async (cause) => {
|
|
3895
|
+
await workspace.cleanup();
|
|
3896
|
+
throw cause;
|
|
3897
|
+
});
|
|
3898
|
+
return {
|
|
3899
|
+
...resolved,
|
|
3900
|
+
buildPolicyDigest: digestRuntimeValue(JSON.stringify(resolved.recipes)),
|
|
3901
|
+
note: !resume && resolved.source === "repository" ? describeCodeRecipes(resolved) : null
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3803
3905
|
// src/code-runtime-engine.ts
|
|
3804
3906
|
var TheseusRuntimeEngine = class {
|
|
3805
3907
|
constructor(options) {
|
|
@@ -3851,6 +3953,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3851
3953
|
control: this.options.control,
|
|
3852
3954
|
...this.options.localSource ? { localSource: this.options.localSource } : {}
|
|
3853
3955
|
});
|
|
3956
|
+
const resolvedRecipes = await sessionRecipesFor(workspace, this.options, resume);
|
|
3854
3957
|
const abort = new AbortController(), startGate = codeRuntimeAcknowledgementGate(abort.signal);
|
|
3855
3958
|
const conversationRefs = [];
|
|
3856
3959
|
const active = {
|
|
@@ -3859,6 +3962,8 @@ var TheseusRuntimeEngine = class {
|
|
|
3859
3962
|
conversationRefs,
|
|
3860
3963
|
acknowledged: false,
|
|
3861
3964
|
startGate,
|
|
3965
|
+
recipes: resolvedRecipes.recipes,
|
|
3966
|
+
buildPolicyDigest: resolvedRecipes.buildPolicyDigest,
|
|
3862
3967
|
role: metadata.role,
|
|
3863
3968
|
readOnly: metadata.readOnly,
|
|
3864
3969
|
title: metadata.title,
|
|
@@ -3876,6 +3981,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3876
3981
|
done: Promise.resolve(null)
|
|
3877
3982
|
};
|
|
3878
3983
|
this.#active.set(command.sessionId, active);
|
|
3984
|
+
if (resolvedRecipes.note) await this.#event(command, { type: "message", actor: "system", body: resolvedRecipes.note }, conversationRefs);
|
|
3879
3985
|
if (requestedLocal) {
|
|
3880
3986
|
await this.#event(command, {
|
|
3881
3987
|
type: "message",
|
|
@@ -3911,7 +4017,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3911
4017
|
const active = await this.#takeOver(command, "pursue requires an active Code session");
|
|
3912
4018
|
active.done = startGoalPursuit({
|
|
3913
4019
|
spec,
|
|
3914
|
-
recipes:
|
|
4020
|
+
recipes: active.recipes,
|
|
3915
4021
|
recipeExecutor: this.options.recipeExecutor ?? createContainerRecipeExecutor(this.options.engine),
|
|
3916
4022
|
workspace: active.workspace,
|
|
3917
4023
|
baseCommitSha: active.baseCommitSha,
|
|
@@ -3990,7 +4096,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3990
4096
|
async #runAttempt(command, metadata, active) {
|
|
3991
4097
|
const lease = fakeCodeLease(command, metadata);
|
|
3992
4098
|
const broker = this.#observed(command, active, createCodeRuntimeToolBroker({
|
|
3993
|
-
recipes:
|
|
4099
|
+
recipes: active.recipes,
|
|
3994
4100
|
engine: this.options.engine,
|
|
3995
4101
|
recipeAuthorization: this.options.recipeAuthorization
|
|
3996
4102
|
}, lease, metadata.role));
|
|
@@ -4016,7 +4122,7 @@ var TheseusRuntimeEngine = class {
|
|
|
4016
4122
|
// A review-role session is read-only and still runs the proof; a planner
|
|
4017
4123
|
// marked read-only by its payload runs nothing.
|
|
4018
4124
|
recipes: metadata.role === "review" || !metadata.readOnly,
|
|
4019
|
-
recipeIds:
|
|
4125
|
+
recipeIds: active.recipes.map((recipe2) => recipe2.id),
|
|
4020
4126
|
...extraSkills.length ? { extraSkills } : {}
|
|
4021
4127
|
});
|
|
4022
4128
|
const closing = result.finalText.trim();
|
|
@@ -4125,7 +4231,7 @@ function parse(argv) {
|
|
|
4125
4231
|
};
|
|
4126
4232
|
}
|
|
4127
4233
|
async function readPolicy(path) {
|
|
4128
|
-
const value = JSON.parse(await (0,
|
|
4234
|
+
const value = JSON.parse(await (0, import_promises14.readFile)(path, "utf8"));
|
|
4129
4235
|
if (!value || Object.keys(value).some((key) => !["recipes", "recipeAuthorization"].includes(key)) || !Array.isArray(value.recipes) || !value.recipes.length || value.recipeAuthorization !== void 0 && value.recipeAuthorization !== "registered_recipe" && value.recipeAuthorization !== "exact_approval") throw new TypeError("invalid Code build policy file");
|
|
4130
4236
|
const recipes = value.recipes;
|
|
4131
4237
|
for (const recipe2 of recipes) assertCodeBuildRecipe(recipe2);
|