@odla-ai/harness 0.10.4 → 0.11.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 +24 -0
- package/dist/{chunk-IV3VMKDV.js → chunk-DP6LOGBF.js} +193 -67
- package/dist/chunk-DP6LOGBF.js.map +1 -0
- package/dist/code-runtime-cli.cjs +173 -55
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +1 -1
- package/dist/node.cjs +192 -62
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +90 -14
- package/dist/node.d.ts +90 -14
- 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
|
@@ -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");
|
|
@@ -422,40 +422,40 @@ async function readBoundedResponse(body, maximum) {
|
|
|
422
422
|
return result;
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
-
// src/code-runtime.ts
|
|
426
|
-
var
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (!
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
425
|
+
// src/code-runtime-overload.ts
|
|
426
|
+
var OVERLOAD_RETRY_DELAYS_MS = [2e3, 4e3, 8e3, 16e3];
|
|
427
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set(["control_plane_overloaded", "registry_overloaded", "transport_unavailable"]);
|
|
428
|
+
function overloadedControlFailure(cause) {
|
|
429
|
+
if (!cause || typeof cause !== "object") return false;
|
|
430
|
+
const failure = cause;
|
|
431
|
+
return failure.status === 503 && typeof failure.code === "string" && RETRYABLE_CODES.has(failure.code);
|
|
432
|
+
}
|
|
433
|
+
async function withOverloadRetry(call, wait2, onRetry = () => void 0) {
|
|
434
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
435
435
|
try {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
if (
|
|
440
|
-
await
|
|
441
|
-
|
|
442
|
-
if (options.signal?.aborted) return;
|
|
443
|
-
if (options.once || !retryableControlFailure(error)) throw error;
|
|
444
|
-
await options.onRetry?.(error, retryMs);
|
|
445
|
-
await wait(retryMs, options.signal);
|
|
446
|
-
retryMs = Math.min(retryMs * 2, 3e4);
|
|
436
|
+
return await call();
|
|
437
|
+
} catch (cause) {
|
|
438
|
+
const delayMs = OVERLOAD_RETRY_DELAYS_MS[attempt];
|
|
439
|
+
if (delayMs === void 0 || !overloadedControlFailure(cause)) throw cause;
|
|
440
|
+
await onRetry(cause, delayMs);
|
|
441
|
+
await wait2(delayMs);
|
|
447
442
|
}
|
|
448
|
-
}
|
|
443
|
+
}
|
|
449
444
|
}
|
|
445
|
+
|
|
446
|
+
// src/code-runtime-reconciler.ts
|
|
447
|
+
var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
450
448
|
var CodeRuntimeReconciler = class {
|
|
451
|
-
constructor(control, engine, onDiagnostic) {
|
|
449
|
+
constructor(control, engine, onDiagnostic, options = {}) {
|
|
452
450
|
this.control = control;
|
|
453
451
|
this.engine = engine;
|
|
454
452
|
this.onDiagnostic = onDiagnostic;
|
|
453
|
+
this.options = options;
|
|
455
454
|
}
|
|
456
455
|
control;
|
|
457
456
|
engine;
|
|
458
457
|
onDiagnostic;
|
|
458
|
+
options;
|
|
459
459
|
results = /* @__PURE__ */ new Map();
|
|
460
460
|
async reconcile(snapshot) {
|
|
461
461
|
for (const command of snapshot.commands) {
|
|
@@ -474,7 +474,15 @@ var CodeRuntimeReconciler = class {
|
|
|
474
474
|
await this.control.acknowledge(command.commandId, completed.result);
|
|
475
475
|
if (!completed.notified) {
|
|
476
476
|
try {
|
|
477
|
-
await
|
|
477
|
+
await withOverloadRetry(
|
|
478
|
+
async () => {
|
|
479
|
+
await this.engine.acknowledged?.(command, completed.result);
|
|
480
|
+
},
|
|
481
|
+
(ms) => (this.options.wait ?? sleep)(ms),
|
|
482
|
+
(cause, delayMs) => this.onDiagnostic?.(
|
|
483
|
+
`command ${command.commandId} acknowledged handling waits ${delayMs}ms for an overloaded control plane \xB7 ${cause instanceof Error ? cause.message : String(cause)}`
|
|
484
|
+
)
|
|
485
|
+
);
|
|
478
486
|
} catch (error) {
|
|
479
487
|
this.onDiagnostic?.(
|
|
480
488
|
`command ${command.commandId} acknowledged handling failed \xB7 ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -485,6 +493,32 @@ var CodeRuntimeReconciler = class {
|
|
|
485
493
|
}
|
|
486
494
|
}
|
|
487
495
|
};
|
|
496
|
+
|
|
497
|
+
// src/code-runtime.ts
|
|
498
|
+
var CODE_RUNTIME_PROTOCOL_VERSION = 3;
|
|
499
|
+
async function runCodeRuntimeHeartbeatLoop(options) {
|
|
500
|
+
const heartbeatMs = options.heartbeatMs ?? 15e3;
|
|
501
|
+
if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3 || heartbeatMs > 3e5) {
|
|
502
|
+
throw new TypeError("heartbeatMs must be an integer from 1000 to 300000");
|
|
503
|
+
}
|
|
504
|
+
let retryMs = 1e3;
|
|
505
|
+
do {
|
|
506
|
+
if (options.signal?.aborted) return;
|
|
507
|
+
try {
|
|
508
|
+
const snapshot = await options.control.heartbeat(options.runtimeVersion, options.capabilities);
|
|
509
|
+
await options.onSnapshot?.(snapshot);
|
|
510
|
+
retryMs = 1e3;
|
|
511
|
+
if (options.once) return;
|
|
512
|
+
await wait(heartbeatMs, options.signal);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
if (options.signal?.aborted) return;
|
|
515
|
+
if (options.once || !retryableControlFailure(error)) throw error;
|
|
516
|
+
await options.onRetry?.(error, retryMs);
|
|
517
|
+
await wait(retryMs, options.signal);
|
|
518
|
+
retryMs = Math.min(retryMs * 2, 3e4);
|
|
519
|
+
}
|
|
520
|
+
} while (!options.signal?.aborted);
|
|
521
|
+
}
|
|
488
522
|
function retryableControlFailure(value) {
|
|
489
523
|
if (!value || typeof value !== "object") return false;
|
|
490
524
|
const failure = value;
|
|
@@ -1412,8 +1446,8 @@ var CodeRuntimeCheckpointManager = class {
|
|
|
1412
1446
|
trustedBaseDigest: active.trustedBaseDigest,
|
|
1413
1447
|
planningInputDigest: active.planningInputDigest,
|
|
1414
1448
|
conversationRefs: active.conversationRefs,
|
|
1415
|
-
fallbackPolicyDigest: this.options.fallbackPolicyDigest,
|
|
1416
|
-
recipes: this.options.recipes,
|
|
1449
|
+
fallbackPolicyDigest: active.buildPolicyDigest ?? this.options.fallbackPolicyDigest,
|
|
1450
|
+
recipes: active.recipes ?? this.options.recipes,
|
|
1417
1451
|
recipeExecutor: this.options.recipeExecutor,
|
|
1418
1452
|
review: (patch2, verification) => this.options.control.review(command.sessionId, { patch: patch2, verification })
|
|
1419
1453
|
});
|
|
@@ -2354,13 +2388,19 @@ function createCodeRuntimeSessionSkillLoader(control) {
|
|
|
2354
2388
|
...tool.acceptsTaint === void 0 ? {} : { acceptsTaint: tool.acceptsTaint },
|
|
2355
2389
|
handler: async (input, context) => {
|
|
2356
2390
|
if (!context.toolCallId) throw new TypeError("collaboration tool call identity is required");
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2391
|
+
try {
|
|
2392
|
+
return await execute2(command.sessionId, {
|
|
2393
|
+
commandId: command.commandId,
|
|
2394
|
+
toolCallId: context.toolCallId,
|
|
2395
|
+
skill: manifest.name,
|
|
2396
|
+
tool: tool.name,
|
|
2397
|
+
input
|
|
2398
|
+
}, context.signal);
|
|
2399
|
+
} catch (cause) {
|
|
2400
|
+
if (context.signal?.aborted) throw cause;
|
|
2401
|
+
const detail = (cause instanceof Error ? cause.message : String(cause)).slice(0, 500);
|
|
2402
|
+
return { content: `Tool "${tool.name}" failed: ${detail}`, isError: true };
|
|
2403
|
+
}
|
|
2364
2404
|
}
|
|
2365
2405
|
}))
|
|
2366
2406
|
}));
|
|
@@ -2378,23 +2418,7 @@ async function sessionSkillsFor(options, command) {
|
|
|
2378
2418
|
}
|
|
2379
2419
|
|
|
2380
2420
|
// src/code-runtime-inference.ts
|
|
2381
|
-
var
|
|
2382
|
-
var RETRYABLE_CODES = /* @__PURE__ */ new Set(["control_plane_overloaded", "registry_overloaded", "transport_unavailable"]);
|
|
2383
|
-
function overloadedControlFailure(cause) {
|
|
2384
|
-
return cause instanceof CodeRuntimeControlError && cause.status === 503 && RETRYABLE_CODES.has(cause.code);
|
|
2385
|
-
}
|
|
2386
|
-
async function inferWithBackoff(infer, wait2, onRetry) {
|
|
2387
|
-
for (let attempt = 0; ; attempt += 1) {
|
|
2388
|
-
try {
|
|
2389
|
-
return await infer();
|
|
2390
|
-
} catch (cause) {
|
|
2391
|
-
const delayMs = OVERLOAD_RETRY_DELAYS_MS[attempt];
|
|
2392
|
-
if (delayMs === void 0 || !overloadedControlFailure(cause)) throw cause;
|
|
2393
|
-
await onRetry(cause, delayMs);
|
|
2394
|
-
await wait2(delayMs);
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2421
|
+
var inferWithBackoff = (infer, wait2, onRetry) => withOverloadRetry(infer, wait2, (cause, delayMs) => onRetry(cause, delayMs));
|
|
2398
2422
|
async function handleCodeRuntimeInference(input) {
|
|
2399
2423
|
const { command, request, state } = input;
|
|
2400
2424
|
const startedAt = Date.now();
|
|
@@ -3806,6 +3830,96 @@ function observeCodeRuntimeSessionSkills(command, skills, emit) {
|
|
|
3806
3830
|
}));
|
|
3807
3831
|
}
|
|
3808
3832
|
|
|
3833
|
+
// src/code-repository-recipes.ts
|
|
3834
|
+
var import_promises13 = require("fs/promises");
|
|
3835
|
+
var import_node_path12 = require("path");
|
|
3836
|
+
var REPOSITORY_RECIPES_FILE = "odla.recipes.json";
|
|
3837
|
+
var MAX_RECIPES = 16;
|
|
3838
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
3839
|
+
var MAX_TIMEOUT_MS = 15 * 6e4;
|
|
3840
|
+
var ID2 = /^[A-Za-z0-9._:-]{1,120}$/;
|
|
3841
|
+
var RECIPE_FIELDS = /* @__PURE__ */ new Set(["id", "command", "timeoutMs"]);
|
|
3842
|
+
var fault = (detail) => new TypeError(`${REPOSITORY_RECIPES_FILE} is malformed: ${detail}`);
|
|
3843
|
+
function parseRepositoryRecipes(text2, envelope) {
|
|
3844
|
+
let parsed;
|
|
3845
|
+
try {
|
|
3846
|
+
parsed = JSON.parse(text2);
|
|
3847
|
+
} catch {
|
|
3848
|
+
throw fault("not valid JSON");
|
|
3849
|
+
}
|
|
3850
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw fault("the document must be an object");
|
|
3851
|
+
const document = parsed;
|
|
3852
|
+
if (document.version !== 1) throw fault("version must be 1");
|
|
3853
|
+
const entries = document.recipes;
|
|
3854
|
+
if (!Array.isArray(entries) || entries.length < 1) throw fault("recipes must be a non-empty array");
|
|
3855
|
+
if (entries.length > MAX_RECIPES) throw fault(`at most ${MAX_RECIPES} recipes may be declared`);
|
|
3856
|
+
const recipes = [];
|
|
3857
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3858
|
+
entries.forEach((entry, index) => {
|
|
3859
|
+
const label = `recipe ${index + 1}`;
|
|
3860
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw fault(`${label} must be an object`);
|
|
3861
|
+
const row = entry;
|
|
3862
|
+
const unknown = Object.keys(row).filter((key) => !RECIPE_FIELDS.has(key));
|
|
3863
|
+
if (unknown.length) throw fault(`${label} has unsupported field ${unknown[0]}; the host owns image and resource limits`);
|
|
3864
|
+
if (typeof row.id !== "string" || !ID2.test(row.id)) throw fault(`${label} needs an id of 1 to 120 letters, digits, . _ : or -`);
|
|
3865
|
+
if (seen.has(row.id)) throw fault(`${label} repeats id ${row.id}`);
|
|
3866
|
+
seen.add(row.id);
|
|
3867
|
+
if (!Array.isArray(row.command) || row.command.length < 1 || row.command.some((part) => typeof part !== "string" || !part)) {
|
|
3868
|
+
throw fault(`${label} (${row.id}) needs command as a non-empty array of non-empty strings`);
|
|
3869
|
+
}
|
|
3870
|
+
const timeoutMs = row.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
3871
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_TIMEOUT_MS) {
|
|
3872
|
+
throw fault(`${label} (${row.id}) timeoutMs must be an integer from 1 to ${MAX_TIMEOUT_MS}`);
|
|
3873
|
+
}
|
|
3874
|
+
const recipe2 = {
|
|
3875
|
+
id: row.id,
|
|
3876
|
+
command: [...row.command],
|
|
3877
|
+
timeoutMs,
|
|
3878
|
+
image: envelope.image,
|
|
3879
|
+
maxOutputBytes: envelope.maxOutputBytes,
|
|
3880
|
+
cpus: envelope.cpus,
|
|
3881
|
+
memory: envelope.memory,
|
|
3882
|
+
pids: envelope.pids
|
|
3883
|
+
};
|
|
3884
|
+
try {
|
|
3885
|
+
assertCodeBuildRecipe(recipe2);
|
|
3886
|
+
} catch (cause) {
|
|
3887
|
+
throw fault(`${label} (${row.id}) is not a runnable recipe: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
3888
|
+
}
|
|
3889
|
+
recipes.push(recipe2);
|
|
3890
|
+
});
|
|
3891
|
+
return recipes;
|
|
3892
|
+
}
|
|
3893
|
+
async function readRepositoryRecipes(baselineDir, envelope) {
|
|
3894
|
+
let text2;
|
|
3895
|
+
try {
|
|
3896
|
+
text2 = await (0, import_promises13.readFile)((0, import_node_path12.join)(baselineDir, REPOSITORY_RECIPES_FILE), "utf8");
|
|
3897
|
+
} catch (cause) {
|
|
3898
|
+
if (cause.code === "ENOENT") return null;
|
|
3899
|
+
throw cause;
|
|
3900
|
+
}
|
|
3901
|
+
return parseRepositoryRecipes(text2, envelope);
|
|
3902
|
+
}
|
|
3903
|
+
async function resolveCodeRecipes(baselineDir, release, envelope) {
|
|
3904
|
+
const declared = envelope ? await readRepositoryRecipes(baselineDir, envelope) : null;
|
|
3905
|
+
return declared ? { recipes: declared, source: "repository" } : { recipes: release, source: "release" };
|
|
3906
|
+
}
|
|
3907
|
+
function describeCodeRecipes(resolved) {
|
|
3908
|
+
const ids = resolved.recipes.map((recipe2) => recipe2.id).join(", ");
|
|
3909
|
+
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}`;
|
|
3910
|
+
}
|
|
3911
|
+
async function sessionRecipesFor(workspace, options, resume) {
|
|
3912
|
+
const resolved = await resolveCodeRecipes(workspace.baselineDir, options.recipes, options.repositoryRecipes ?? null).catch(async (cause) => {
|
|
3913
|
+
await workspace.cleanup();
|
|
3914
|
+
throw cause;
|
|
3915
|
+
});
|
|
3916
|
+
return {
|
|
3917
|
+
...resolved,
|
|
3918
|
+
buildPolicyDigest: digestRuntimeValue(JSON.stringify(resolved.recipes)),
|
|
3919
|
+
note: !resume && resolved.source === "repository" ? describeCodeRecipes(resolved) : null
|
|
3920
|
+
};
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3809
3923
|
// src/code-runtime-engine.ts
|
|
3810
3924
|
var TheseusRuntimeEngine = class {
|
|
3811
3925
|
constructor(options) {
|
|
@@ -3857,6 +3971,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3857
3971
|
control: this.options.control,
|
|
3858
3972
|
...this.options.localSource ? { localSource: this.options.localSource } : {}
|
|
3859
3973
|
});
|
|
3974
|
+
const resolvedRecipes = await sessionRecipesFor(workspace, this.options, resume);
|
|
3860
3975
|
const abort = new AbortController(), startGate = codeRuntimeAcknowledgementGate(abort.signal);
|
|
3861
3976
|
const conversationRefs = [];
|
|
3862
3977
|
const active = {
|
|
@@ -3865,6 +3980,8 @@ var TheseusRuntimeEngine = class {
|
|
|
3865
3980
|
conversationRefs,
|
|
3866
3981
|
acknowledged: false,
|
|
3867
3982
|
startGate,
|
|
3983
|
+
recipes: resolvedRecipes.recipes,
|
|
3984
|
+
buildPolicyDigest: resolvedRecipes.buildPolicyDigest,
|
|
3868
3985
|
role: metadata.role,
|
|
3869
3986
|
readOnly: metadata.readOnly,
|
|
3870
3987
|
title: metadata.title,
|
|
@@ -3882,6 +3999,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3882
3999
|
done: Promise.resolve(null)
|
|
3883
4000
|
};
|
|
3884
4001
|
this.#active.set(command.sessionId, active);
|
|
4002
|
+
if (resolvedRecipes.note) await this.#event(command, { type: "message", actor: "system", body: resolvedRecipes.note }, conversationRefs);
|
|
3885
4003
|
if (requestedLocal) {
|
|
3886
4004
|
await this.#event(command, {
|
|
3887
4005
|
type: "message",
|
|
@@ -3917,7 +4035,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3917
4035
|
const active = await this.#takeOver(command, "pursue requires an active Code session");
|
|
3918
4036
|
active.done = startGoalPursuit({
|
|
3919
4037
|
spec,
|
|
3920
|
-
recipes:
|
|
4038
|
+
recipes: active.recipes,
|
|
3921
4039
|
recipeExecutor: this.options.recipeExecutor ?? createContainerRecipeExecutor(this.options.engine),
|
|
3922
4040
|
workspace: active.workspace,
|
|
3923
4041
|
baseCommitSha: active.baseCommitSha,
|
|
@@ -3996,7 +4114,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3996
4114
|
async #runAttempt(command, metadata, active) {
|
|
3997
4115
|
const lease = fakeCodeLease(command, metadata);
|
|
3998
4116
|
const broker = this.#observed(command, active, createCodeRuntimeToolBroker({
|
|
3999
|
-
recipes:
|
|
4117
|
+
recipes: active.recipes,
|
|
4000
4118
|
engine: this.options.engine,
|
|
4001
4119
|
recipeAuthorization: this.options.recipeAuthorization
|
|
4002
4120
|
}, lease, metadata.role));
|
|
@@ -4022,7 +4140,7 @@ var TheseusRuntimeEngine = class {
|
|
|
4022
4140
|
// A review-role session is read-only and still runs the proof; a planner
|
|
4023
4141
|
// marked read-only by its payload runs nothing.
|
|
4024
4142
|
recipes: metadata.role === "review" || !metadata.readOnly,
|
|
4025
|
-
recipeIds:
|
|
4143
|
+
recipeIds: active.recipes.map((recipe2) => recipe2.id),
|
|
4026
4144
|
...extraSkills.length ? { extraSkills } : {}
|
|
4027
4145
|
});
|
|
4028
4146
|
const closing = result.finalText.trim();
|
|
@@ -4131,7 +4249,7 @@ function parse(argv) {
|
|
|
4131
4249
|
};
|
|
4132
4250
|
}
|
|
4133
4251
|
async function readPolicy(path) {
|
|
4134
|
-
const value = JSON.parse(await (0,
|
|
4252
|
+
const value = JSON.parse(await (0, import_promises14.readFile)(path, "utf8"));
|
|
4135
4253
|
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");
|
|
4136
4254
|
const recipes = value.recipes;
|
|
4137
4255
|
for (const recipe2 of recipes) assertCodeBuildRecipe(recipe2);
|