@odla-ai/harness 0.10.4 → 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-IV3VMKDV.js → chunk-IXYKIPRA.js} +133 -25
- package/dist/chunk-IXYKIPRA.js.map +1 -0
- package/dist/code-runtime-cli.cjs +114 -14
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +1 -1
- package/dist/node.cjs +133 -21
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +70 -1
- package/dist/node.d.ts +70 -1
- package/dist/node.js +13 -1
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-IV3VMKDV.js.map +0 -1
package/README.md
CHANGED
|
@@ -109,6 +109,30 @@ before approval:
|
|
|
109
109
|
}
|
|
110
110
|
~~~
|
|
111
111
|
|
|
112
|
+
A repository can replace that release-owned list with its own. Code reads
|
|
113
|
+
`odla.recipes.json` from the root of the trusted base (the default-branch
|
|
114
|
+
snapshot it staged, never the candidate's working tree) when a session starts:
|
|
115
|
+
|
|
116
|
+
~~~json
|
|
117
|
+
{
|
|
118
|
+
"version": 1,
|
|
119
|
+
"recipes": [
|
|
120
|
+
{ "id": "engine-tests", "command": ["node", "--test", "test/"] },
|
|
121
|
+
{ "id": "gates", "command": ["node", "scripts/gates.mjs"], "timeoutMs": 60000 }
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
~~~
|
|
125
|
+
|
|
126
|
+
The repository chooses each recipe's `id`, argv `command`, and optional
|
|
127
|
+
`timeoutMs` (default 120000, at most 900000, up to 16 recipes); the host
|
|
128
|
+
supplies the digest-pinned image and the resource limits, and a declaration that
|
|
129
|
+
names an `image` or any other field is refused. A malformed file fails the
|
|
130
|
+
session start with the fault named rather than falling back, and the session
|
|
131
|
+
thread's first system message says which list gates it and where that list came
|
|
132
|
+
from. Every declared recipe runs in the same fresh, networkless container as the
|
|
133
|
+
release list, so a repository whose tests need `node_modules` still cannot run
|
|
134
|
+
them here.
|
|
135
|
+
|
|
112
136
|
The runtime validates HTTPS, never places its credential in a request body,
|
|
113
137
|
never opens a listener, and rejects malformed or cross-host binding responses.
|
|
114
138
|
Theseus reads, patches, and runs only registered build recipes through the typed
|
|
@@ -1148,8 +1148,8 @@ var CodeRuntimeCheckpointManager = class {
|
|
|
1148
1148
|
trustedBaseDigest: active.trustedBaseDigest,
|
|
1149
1149
|
planningInputDigest: active.planningInputDigest,
|
|
1150
1150
|
conversationRefs: active.conversationRefs,
|
|
1151
|
-
fallbackPolicyDigest: this.options.fallbackPolicyDigest,
|
|
1152
|
-
recipes: this.options.recipes,
|
|
1151
|
+
fallbackPolicyDigest: active.buildPolicyDigest ?? this.options.fallbackPolicyDigest,
|
|
1152
|
+
recipes: active.recipes ?? this.options.recipes,
|
|
1153
1153
|
recipeExecutor: this.options.recipeExecutor,
|
|
1154
1154
|
review: (patch2, verification) => this.options.control.review(command.sessionId, { patch: patch2, verification })
|
|
1155
1155
|
});
|
|
@@ -2089,13 +2089,19 @@ function createCodeRuntimeSessionSkillLoader(control) {
|
|
|
2089
2089
|
...tool.acceptsTaint === void 0 ? {} : { acceptsTaint: tool.acceptsTaint },
|
|
2090
2090
|
handler: async (input, context) => {
|
|
2091
2091
|
if (!context.toolCallId) throw new TypeError("collaboration tool call identity is required");
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2092
|
+
try {
|
|
2093
|
+
return await execute2(command.sessionId, {
|
|
2094
|
+
commandId: command.commandId,
|
|
2095
|
+
toolCallId: context.toolCallId,
|
|
2096
|
+
skill: manifest.name,
|
|
2097
|
+
tool: tool.name,
|
|
2098
|
+
input
|
|
2099
|
+
}, context.signal);
|
|
2100
|
+
} catch (cause) {
|
|
2101
|
+
if (context.signal?.aborted) throw cause;
|
|
2102
|
+
const detail = (cause instanceof Error ? cause.message : String(cause)).slice(0, 500);
|
|
2103
|
+
return { content: `Tool "${tool.name}" failed: ${detail}`, isError: true };
|
|
2104
|
+
}
|
|
2099
2105
|
}
|
|
2100
2106
|
}))
|
|
2101
2107
|
}));
|
|
@@ -3150,6 +3156,110 @@ function assertBudget(budget) {
|
|
|
3150
3156
|
}
|
|
3151
3157
|
}
|
|
3152
3158
|
|
|
3159
|
+
// src/code-repository-recipes.ts
|
|
3160
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
3161
|
+
import { join as join6 } from "path";
|
|
3162
|
+
|
|
3163
|
+
// src/code-runtime-events.ts
|
|
3164
|
+
import { createHash as createHash3 } from "crypto";
|
|
3165
|
+
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
3166
|
+
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
3167
|
+
refs.push(eventId);
|
|
3168
|
+
const attributed = { ...event, interactionId: command.commandId };
|
|
3169
|
+
const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
|
|
3170
|
+
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
3171
|
+
}
|
|
3172
|
+
var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
|
|
3173
|
+
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
3174
|
+
|
|
3175
|
+
// src/code-repository-recipes.ts
|
|
3176
|
+
var REPOSITORY_RECIPES_FILE = "odla.recipes.json";
|
|
3177
|
+
var MAX_RECIPES = 16;
|
|
3178
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
3179
|
+
var MAX_TIMEOUT_MS = 15 * 6e4;
|
|
3180
|
+
var ID2 = /^[A-Za-z0-9._:-]{1,120}$/;
|
|
3181
|
+
var RECIPE_FIELDS = /* @__PURE__ */ new Set(["id", "command", "timeoutMs"]);
|
|
3182
|
+
var fault = (detail) => new TypeError(`${REPOSITORY_RECIPES_FILE} is malformed: ${detail}`);
|
|
3183
|
+
function parseRepositoryRecipes(text, envelope) {
|
|
3184
|
+
let parsed;
|
|
3185
|
+
try {
|
|
3186
|
+
parsed = JSON.parse(text);
|
|
3187
|
+
} catch {
|
|
3188
|
+
throw fault("not valid JSON");
|
|
3189
|
+
}
|
|
3190
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw fault("the document must be an object");
|
|
3191
|
+
const document = parsed;
|
|
3192
|
+
if (document.version !== 1) throw fault("version must be 1");
|
|
3193
|
+
const entries = document.recipes;
|
|
3194
|
+
if (!Array.isArray(entries) || entries.length < 1) throw fault("recipes must be a non-empty array");
|
|
3195
|
+
if (entries.length > MAX_RECIPES) throw fault(`at most ${MAX_RECIPES} recipes may be declared`);
|
|
3196
|
+
const recipes = [];
|
|
3197
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3198
|
+
entries.forEach((entry, index) => {
|
|
3199
|
+
const label = `recipe ${index + 1}`;
|
|
3200
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw fault(`${label} must be an object`);
|
|
3201
|
+
const row = entry;
|
|
3202
|
+
const unknown = Object.keys(row).filter((key) => !RECIPE_FIELDS.has(key));
|
|
3203
|
+
if (unknown.length) throw fault(`${label} has unsupported field ${unknown[0]}; the host owns image and resource limits`);
|
|
3204
|
+
if (typeof row.id !== "string" || !ID2.test(row.id)) throw fault(`${label} needs an id of 1 to 120 letters, digits, . _ : or -`);
|
|
3205
|
+
if (seen.has(row.id)) throw fault(`${label} repeats id ${row.id}`);
|
|
3206
|
+
seen.add(row.id);
|
|
3207
|
+
if (!Array.isArray(row.command) || row.command.length < 1 || row.command.some((part) => typeof part !== "string" || !part)) {
|
|
3208
|
+
throw fault(`${label} (${row.id}) needs command as a non-empty array of non-empty strings`);
|
|
3209
|
+
}
|
|
3210
|
+
const timeoutMs = row.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
3211
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_TIMEOUT_MS) {
|
|
3212
|
+
throw fault(`${label} (${row.id}) timeoutMs must be an integer from 1 to ${MAX_TIMEOUT_MS}`);
|
|
3213
|
+
}
|
|
3214
|
+
const recipe2 = {
|
|
3215
|
+
id: row.id,
|
|
3216
|
+
command: [...row.command],
|
|
3217
|
+
timeoutMs,
|
|
3218
|
+
image: envelope.image,
|
|
3219
|
+
maxOutputBytes: envelope.maxOutputBytes,
|
|
3220
|
+
cpus: envelope.cpus,
|
|
3221
|
+
memory: envelope.memory,
|
|
3222
|
+
pids: envelope.pids
|
|
3223
|
+
};
|
|
3224
|
+
try {
|
|
3225
|
+
assertCodeBuildRecipe(recipe2);
|
|
3226
|
+
} catch (cause) {
|
|
3227
|
+
throw fault(`${label} (${row.id}) is not a runnable recipe: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
3228
|
+
}
|
|
3229
|
+
recipes.push(recipe2);
|
|
3230
|
+
});
|
|
3231
|
+
return recipes;
|
|
3232
|
+
}
|
|
3233
|
+
async function readRepositoryRecipes(baselineDir, envelope) {
|
|
3234
|
+
let text;
|
|
3235
|
+
try {
|
|
3236
|
+
text = await readFile6(join6(baselineDir, REPOSITORY_RECIPES_FILE), "utf8");
|
|
3237
|
+
} catch (cause) {
|
|
3238
|
+
if (cause.code === "ENOENT") return null;
|
|
3239
|
+
throw cause;
|
|
3240
|
+
}
|
|
3241
|
+
return parseRepositoryRecipes(text, envelope);
|
|
3242
|
+
}
|
|
3243
|
+
async function resolveCodeRecipes(baselineDir, release, envelope) {
|
|
3244
|
+
const declared = envelope ? await readRepositoryRecipes(baselineDir, envelope) : null;
|
|
3245
|
+
return declared ? { recipes: declared, source: "repository" } : { recipes: release, source: "release" };
|
|
3246
|
+
}
|
|
3247
|
+
function describeCodeRecipes(resolved) {
|
|
3248
|
+
const ids = resolved.recipes.map((recipe2) => recipe2.id).join(", ");
|
|
3249
|
+
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}`;
|
|
3250
|
+
}
|
|
3251
|
+
async function sessionRecipesFor(workspace, options, resume) {
|
|
3252
|
+
const resolved = await resolveCodeRecipes(workspace.baselineDir, options.recipes, options.repositoryRecipes ?? null).catch(async (cause) => {
|
|
3253
|
+
await workspace.cleanup();
|
|
3254
|
+
throw cause;
|
|
3255
|
+
});
|
|
3256
|
+
return {
|
|
3257
|
+
...resolved,
|
|
3258
|
+
buildPolicyDigest: digestRuntimeValue(JSON.stringify(resolved.recipes)),
|
|
3259
|
+
note: !resume && resolved.source === "repository" ? describeCodeRecipes(resolved) : null
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3153
3263
|
// src/code-runtime-broker.ts
|
|
3154
3264
|
function createCodeRuntimeToolBroker(input, lease, role) {
|
|
3155
3265
|
const broker = createCodeToolBroker({
|
|
@@ -3331,18 +3441,6 @@ async function startGoalPursuit(input) {
|
|
|
3331
3441
|
return { status: run.met ? "completed" : "failed", finalText: "" };
|
|
3332
3442
|
}
|
|
3333
3443
|
|
|
3334
|
-
// src/code-runtime-events.ts
|
|
3335
|
-
import { createHash as createHash3 } from "crypto";
|
|
3336
|
-
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
3337
|
-
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
3338
|
-
refs.push(eventId);
|
|
3339
|
-
const attributed = { ...event, interactionId: command.commandId };
|
|
3340
|
-
const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
|
|
3341
|
-
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
3342
|
-
}
|
|
3343
|
-
var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
|
|
3344
|
-
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
3345
|
-
|
|
3346
3444
|
// src/code-runtime-acknowledgement-gate.ts
|
|
3347
3445
|
function codeRuntimeAcknowledgementGate(signal) {
|
|
3348
3446
|
let settle;
|
|
@@ -3464,6 +3562,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3464
3562
|
control: this.options.control,
|
|
3465
3563
|
...this.options.localSource ? { localSource: this.options.localSource } : {}
|
|
3466
3564
|
});
|
|
3565
|
+
const resolvedRecipes = await sessionRecipesFor(workspace, this.options, resume);
|
|
3467
3566
|
const abort = new AbortController(), startGate = codeRuntimeAcknowledgementGate(abort.signal);
|
|
3468
3567
|
const conversationRefs = [];
|
|
3469
3568
|
const active = {
|
|
@@ -3472,6 +3571,8 @@ var TheseusRuntimeEngine = class {
|
|
|
3472
3571
|
conversationRefs,
|
|
3473
3572
|
acknowledged: false,
|
|
3474
3573
|
startGate,
|
|
3574
|
+
recipes: resolvedRecipes.recipes,
|
|
3575
|
+
buildPolicyDigest: resolvedRecipes.buildPolicyDigest,
|
|
3475
3576
|
role: metadata.role,
|
|
3476
3577
|
readOnly: metadata.readOnly,
|
|
3477
3578
|
title: metadata.title,
|
|
@@ -3489,6 +3590,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3489
3590
|
done: Promise.resolve(null)
|
|
3490
3591
|
};
|
|
3491
3592
|
this.#active.set(command.sessionId, active);
|
|
3593
|
+
if (resolvedRecipes.note) await this.#event(command, { type: "message", actor: "system", body: resolvedRecipes.note }, conversationRefs);
|
|
3492
3594
|
if (requestedLocal) {
|
|
3493
3595
|
await this.#event(command, {
|
|
3494
3596
|
type: "message",
|
|
@@ -3524,7 +3626,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3524
3626
|
const active = await this.#takeOver(command, "pursue requires an active Code session");
|
|
3525
3627
|
active.done = startGoalPursuit({
|
|
3526
3628
|
spec,
|
|
3527
|
-
recipes:
|
|
3629
|
+
recipes: active.recipes,
|
|
3528
3630
|
recipeExecutor: this.options.recipeExecutor ?? createContainerRecipeExecutor(this.options.engine),
|
|
3529
3631
|
workspace: active.workspace,
|
|
3530
3632
|
baseCommitSha: active.baseCommitSha,
|
|
@@ -3603,7 +3705,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3603
3705
|
async #runAttempt(command, metadata, active) {
|
|
3604
3706
|
const lease = fakeCodeLease(command, metadata);
|
|
3605
3707
|
const broker = this.#observed(command, active, createCodeRuntimeToolBroker({
|
|
3606
|
-
recipes:
|
|
3708
|
+
recipes: active.recipes,
|
|
3607
3709
|
engine: this.options.engine,
|
|
3608
3710
|
recipeAuthorization: this.options.recipeAuthorization
|
|
3609
3711
|
}, lease, metadata.role));
|
|
@@ -3629,7 +3731,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3629
3731
|
// A review-role session is read-only and still runs the proof; a planner
|
|
3630
3732
|
// marked read-only by its payload runs nothing.
|
|
3631
3733
|
recipes: metadata.role === "review" || !metadata.readOnly,
|
|
3632
|
-
recipeIds:
|
|
3734
|
+
recipeIds: active.recipes.map((recipe2) => recipe2.id),
|
|
3633
3735
|
...extraSkills.length ? { extraSkills } : {}
|
|
3634
3736
|
});
|
|
3635
3737
|
const closing = result.finalText.trim();
|
|
@@ -3740,6 +3842,12 @@ export {
|
|
|
3740
3842
|
renderMemories,
|
|
3741
3843
|
hazardFromAttempt,
|
|
3742
3844
|
runGoal,
|
|
3845
|
+
REPOSITORY_RECIPES_FILE,
|
|
3846
|
+
parseRepositoryRecipes,
|
|
3847
|
+
readRepositoryRecipes,
|
|
3848
|
+
resolveCodeRecipes,
|
|
3849
|
+
describeCodeRecipes,
|
|
3850
|
+
sessionRecipesFor,
|
|
3743
3851
|
TheseusRuntimeEngine
|
|
3744
3852
|
};
|
|
3745
|
-
//# sourceMappingURL=chunk-
|
|
3853
|
+
//# sourceMappingURL=chunk-IXYKIPRA.js.map
|