@tangle-network/agent-runtime 0.259.0 → 0.261.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/dist/{activation-Cus2oZaT.d.ts → activation-BpJ2CmXx.d.ts} +2 -2
- package/dist/{activation-Btw2npV2.js → activation-BwrMmguq.js} +3 -3
- package/dist/{activation-Btw2npV2.js.map → activation-BwrMmguq.js.map} +1 -1
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +2 -2
- package/dist/{bundle-DXrBPEFw.js → bundle-CvLGRVO-.js} +2 -2
- package/dist/{bundle-DXrBPEFw.js.map → bundle-CvLGRVO-.js.map} +1 -1
- package/dist/candidate-execution/index.js +5 -4
- package/dist/candidate-execution/index.js.map +1 -1
- package/dist/{coordination-driver-BmtrkNPu.js → coordination-driver-LmDxHvAO.js} +77 -11
- package/dist/coordination-driver-LmDxHvAO.js.map +1 -0
- package/dist/{delegate-DJgmZZKH.js → delegate-rkhJf5w7.js} +2 -2
- package/dist/{delegate-DJgmZZKH.js.map → delegate-rkhJf5w7.js.map} +1 -1
- package/dist/durable.d.ts +285 -57
- package/dist/durable.js +637 -108
- package/dist/durable.js.map +1 -1
- package/dist/{graph-BKRvWzHx.js → graph-Bn3wSH8-.js} +4 -3
- package/dist/graph-Bn3wSH8-.js.map +1 -0
- package/dist/{improvement-cycle-D0Os0Hg_.js → improvement-cycle-DYiwfjWY.js} +8 -7
- package/dist/improvement-cycle-DYiwfjWY.js.map +1 -0
- package/dist/{index-sGOO254Q.d.ts → index-tubViIQJ.d.ts} +122 -12
- package/dist/index.d.ts +6 -6
- package/dist/index.js +9 -9
- package/dist/intelligence.d.ts +3 -3
- package/dist/intelligence.js +7 -6
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +4 -4
- package/dist/kernel.js +8 -8
- package/dist/{loop-runner-bin-DAU4rNIL.d.ts → loop-runner-bin-CL4W2pu1.d.ts} +3 -3
- package/dist/{loop-runner-bin-pRlDgm1i.js → loop-runner-bin-Dxqxf91b.js} +3 -3
- package/dist/{loop-runner-bin-pRlDgm1i.js.map → loop-runner-bin-Dxqxf91b.js.map} +1 -1
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +5 -5
- package/dist/mcp/index.js +4 -4
- package/dist/{openai-tools-BsqD1_Zr.d.ts → openai-tools-BlZHd0Nz.d.ts} +2 -2
- package/dist/{prepare-RuaAclli.js → prepare-oHUQdC5-.js} +22 -452
- package/dist/prepare-oHUQdC5-.js.map +1 -0
- package/dist/profile-D3eXNBQV.js +438 -0
- package/dist/profile-D3eXNBQV.js.map +1 -0
- package/dist/profiles.d.ts +2 -2
- package/dist/{protected-model-port-BFhSgMAy.js → protected-model-port-DqF0qDU6.js} +4 -3
- package/dist/{protected-model-port-BFhSgMAy.js.map → protected-model-port-DqF0qDU6.js.map} +1 -1
- package/dist/{provision-supervisor-B9tf4soF.js → provision-supervisor-By5oGk3t.js} +3 -3
- package/dist/{provision-supervisor-B9tf4soF.js.map → provision-supervisor-By5oGk3t.js.map} +1 -1
- package/dist/{runtime-DKiucqGX.js → runtime-BbQmK7qe.js} +218 -20
- package/dist/runtime-BbQmK7qe.js.map +1 -0
- package/dist/{server-DIsiQ6mu.js → server-CHlpLE22.js} +3 -3
- package/dist/{server-DIsiQ6mu.js.map → server-CHlpLE22.js.map} +1 -1
- package/dist/{stream-agent-turn-DRTFLphI.d.ts → stream-agent-turn-DTgxWXbR.d.ts} +2 -2
- package/dist/{structural-rollout-BIBxgW64.js → structural-rollout-Dby60zHe.js} +2 -2
- package/dist/{structural-rollout-BIBxgW64.js.map → structural-rollout-Dby60zHe.js.map} +1 -1
- package/dist/{substrate-BToFBpfh.d.ts → substrate-CxwdhZri.d.ts} +2 -2
- package/dist/{supervise-DsQjhLN-.js → supervise-1QzYAyS2.js} +78 -7
- package/dist/supervise-1QzYAyS2.js.map +1 -0
- package/dist/{supervisor-CJyzUjc7.js → supervisor-yavsoQQo.js} +84 -9
- package/dist/{supervisor-CJyzUjc7.js.map → supervisor-yavsoQQo.js.map} +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +14 -14
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/{types-ChewVlpE.d.ts → types-DNCNRu0E.d.ts} +24 -2
- package/dist/{workspace-archive-DQGFwvM6.js → workspace-archive-C9lgf77y.js} +11 -11
- package/dist/workspace-archive-C9lgf77y.js.map +1 -0
- package/package.json +5 -5
- package/dist/coordination-driver-BmtrkNPu.js.map +0 -1
- package/dist/graph-BKRvWzHx.js.map +0 -1
- package/dist/improvement-cycle-D0Os0Hg_.js.map +0 -1
- package/dist/prepare-RuaAclli.js.map +0 -1
- package/dist/runtime-DKiucqGX.js.map +0 -1
- package/dist/supervise-DsQjhLN-.js.map +0 -1
- package/dist/workspace-archive-DQGFwvM6.js.map +0 -1
package/dist/durable.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { $
|
|
1
|
+
import { $i as zeroSpend, $r as parseCommittedJsonLines, Qr as isNoEntError, St as loadSpawnForest, Vi as cloneSpend, ai as authoredProfileDigest, ei as prepareJsonlAppend, ni as writeAllBytes, ti as readCommittedJsonLines, tr as contentAddress, vt as FileSpawnJournal, wi as detachedSnapshot, zi as addSpend } from "./supervisor-yavsoQQo.js";
|
|
2
|
+
import { f as RuntimeRunStateError, i as ConfigError, m as ValidationError } from "./errors-DodWX-cb.js";
|
|
2
3
|
import { a as withPursuitContext, t as composeRuntimeHooks } from "./runtime-hooks-tXpAarhW.js";
|
|
3
4
|
import { a as writeAtomicDurableFile, r as publishExclusiveDurableFile } from "./durable-file-DWQA4ooo.js";
|
|
4
|
-
import {
|
|
5
|
+
import { i as applyExactAgentProfileDiff } from "./profile-D3eXNBQV.js";
|
|
6
|
+
import { _ as readRootStream, g as ROOT_STREAM_FILE, i as superviseRootProfile, r as supervise, v as readRootStreamReceipt } from "./supervise-1QzYAyS2.js";
|
|
5
7
|
import { h as supervisorRunDir } from "./run-layout-vwd4SwX_.js";
|
|
6
|
-
import { canonicalCandidateJson } from "@tangle-network/agent-interface";
|
|
8
|
+
import { canonicalCandidateDigest, canonicalCandidateJson, sha256Bytes } from "@tangle-network/agent-interface";
|
|
7
9
|
import { createHash } from "node:crypto";
|
|
8
10
|
import { execFile } from "node:child_process";
|
|
9
|
-
import { resolve } from "node:path";
|
|
10
|
-
import { mkdir, readFile, rmdir, unlink } from "node:fs/promises";
|
|
11
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
12
|
+
import { mkdir, open, readFile, realpath, rmdir, unlink } from "node:fs/promises";
|
|
11
13
|
import { promisify } from "node:util";
|
|
12
|
-
import { setTimeout } from "node:timers/promises";
|
|
14
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
13
15
|
//#region src/durable/chat-engine.ts
|
|
14
16
|
const encoder = new TextEncoder();
|
|
15
17
|
function encodeLine(event) {
|
|
@@ -487,6 +489,7 @@ function projectSpawnNode(nodes, record, attemptIndex) {
|
|
|
487
489
|
const runtime = stringField(payload, "runtime");
|
|
488
490
|
const depth = numberField(payload, "depth");
|
|
489
491
|
const assignmentId = stringField(payload, "assignmentId");
|
|
492
|
+
const successorOf = stringField(payload, "successorOf");
|
|
490
493
|
const attemptId = stringField(payload, "attemptId");
|
|
491
494
|
const startedAt = numberField(payload, "startedAt");
|
|
492
495
|
nodes.set(key, {
|
|
@@ -498,6 +501,7 @@ function projectSpawnNode(nodes, record, attemptIndex) {
|
|
|
498
501
|
...runtime ? { runtime } : {},
|
|
499
502
|
...depth !== void 0 ? { depth } : {},
|
|
500
503
|
...assignmentId ? { assignmentId } : {},
|
|
504
|
+
...successorOf ? { successorOf } : {},
|
|
501
505
|
...attemptId ? { attemptId } : {},
|
|
502
506
|
startedAt: startedAt ?? record.observedAt,
|
|
503
507
|
...payload && Object.hasOwn(payload, "identity") ? { identity: payload.identity } : {},
|
|
@@ -836,6 +840,227 @@ function booleanField(value, key) {
|
|
|
836
840
|
return typeof field === "boolean" ? field : void 0;
|
|
837
841
|
}
|
|
838
842
|
//#endregion
|
|
843
|
+
//#region src/durable/settle-record.ts
|
|
844
|
+
/** The settle record: the returned `SupervisedResult` as canonical JSON, written once. */
|
|
845
|
+
const SETTLE_RECORD_FILE = "result.json";
|
|
846
|
+
/** The failure record: the most recent throw, replaced by a later throw. */
|
|
847
|
+
const FAILURE_RECORD_FILE = "failure.json";
|
|
848
|
+
/** The directory already holds a settle record, so the run it records must not be re-entered. */
|
|
849
|
+
var SettledRunDirectoryError = class extends Error {
|
|
850
|
+
path;
|
|
851
|
+
/** The root of the recorded run, which is its `runId`. */
|
|
852
|
+
recordedRunId;
|
|
853
|
+
constructor(path, recordedRunId, requestedRunId) {
|
|
854
|
+
super(recordedRunId === requestedRunId ? `supervisePursuit: ${path} records that run '${recordedRunId}' already settled; a settled run is not re-entered` : `supervisePursuit: ${path} records that run '${recordedRunId}' already settled in this directory; a directory holds one settle record, so run '${requestedRunId}' needs its own runDir`);
|
|
855
|
+
this.name = "SettledRunDirectoryError";
|
|
856
|
+
this.path = path;
|
|
857
|
+
this.recordedRunId = recordedRunId;
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
/** A result member whose JSON form would misstate it. */
|
|
861
|
+
var UnrecordableSettleValueError = class extends TypeError {
|
|
862
|
+
path;
|
|
863
|
+
constructor(path, kind) {
|
|
864
|
+
super(`supervisePursuit: result${path} is a ${kind}, which JSON cannot record faithfully; a settle record holds plain data only`);
|
|
865
|
+
this.name = "UnrecordableSettleValueError";
|
|
866
|
+
this.path = path;
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
const PLAIN_PROTOTYPES = /* @__PURE__ */ new Set([
|
|
870
|
+
Object.prototype,
|
|
871
|
+
Array.prototype,
|
|
872
|
+
null
|
|
873
|
+
]);
|
|
874
|
+
/**
|
|
875
|
+
* The JSON value of a result, or a throw naming the first member JSON would misstate. An
|
|
876
|
+
* `undefined` member and a function are dropped, exactly as any JSON consumer drops them; a Map,
|
|
877
|
+
* a Set, a Date, a typed array, or a class instance would be rewritten to `{}` or a string and
|
|
878
|
+
* so is refused.
|
|
879
|
+
*/
|
|
880
|
+
function jsonValue(result) {
|
|
881
|
+
const seen = [];
|
|
882
|
+
const visit = (value, path) => {
|
|
883
|
+
if (value === null || typeof value !== "object") return;
|
|
884
|
+
if (value instanceof Map) throw new UnrecordableSettleValueError(path, "Map");
|
|
885
|
+
if (value instanceof Set) throw new UnrecordableSettleValueError(path, "Set");
|
|
886
|
+
if (value instanceof Date) throw new UnrecordableSettleValueError(path, "Date");
|
|
887
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) throw new UnrecordableSettleValueError(path, "binary buffer");
|
|
888
|
+
if (!PLAIN_PROTOTYPES.has(Object.getPrototypeOf(value))) throw new UnrecordableSettleValueError(path, "class instance");
|
|
889
|
+
if (seen.includes(value)) throw new UnrecordableSettleValueError(path, "cycle");
|
|
890
|
+
seen.push(value);
|
|
891
|
+
if (Array.isArray(value)) for (const [index, item] of value.entries()) visit(item, `${path}[${index}]`);
|
|
892
|
+
else for (const [key, item] of Object.entries(value)) visit(item, `${path}.${key}`);
|
|
893
|
+
seen.pop();
|
|
894
|
+
};
|
|
895
|
+
visit(result, "");
|
|
896
|
+
return JSON.parse(JSON.stringify(result));
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* The exact bytes `result.json` holds for a result: its JSON value serialized as RFC 8785
|
|
900
|
+
* canonical JSON. Throws `UnrecordableSettleValueError` before any byte is written when the
|
|
901
|
+
* result carries a value JSON would misstate.
|
|
902
|
+
*/
|
|
903
|
+
function settleRecordJson(result) {
|
|
904
|
+
return canonicalCandidateJson(jsonValue(result));
|
|
905
|
+
}
|
|
906
|
+
/** Publish `runDir/result.json` once. Throws when a record is already there. */
|
|
907
|
+
async function writeSettleRecord(runDir, result) {
|
|
908
|
+
const path = resolve(runDir, SETTLE_RECORD_FILE);
|
|
909
|
+
if (!publishExclusiveDurableFile(path, settleRecordJson(result))) throw new Error(`supervisePursuit: ${path} already exists; a settle record is written once`);
|
|
910
|
+
return path;
|
|
911
|
+
}
|
|
912
|
+
/** Replace `runDir/failure.json` with the most recent throw. */
|
|
913
|
+
async function writeFailureRecord(runDir, record) {
|
|
914
|
+
const path = resolve(runDir, FAILURE_RECORD_FILE);
|
|
915
|
+
writeAtomicDurableFile(path, `${JSON.stringify(record, null, 2)}\n`);
|
|
916
|
+
return path;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Read the settle record a run directory holds, or `undefined` when it holds none. A file that
|
|
920
|
+
* is present but is not a settle record is corruption and fails loud.
|
|
921
|
+
*/
|
|
922
|
+
async function readSettleRecord(runDir) {
|
|
923
|
+
const path = resolve(runDir, SETTLE_RECORD_FILE);
|
|
924
|
+
let text;
|
|
925
|
+
try {
|
|
926
|
+
text = await readFile(path, "utf8");
|
|
927
|
+
} catch (error) {
|
|
928
|
+
if (isNoEntError(error)) return void 0;
|
|
929
|
+
throw error;
|
|
930
|
+
}
|
|
931
|
+
let parsed;
|
|
932
|
+
try {
|
|
933
|
+
parsed = JSON.parse(text);
|
|
934
|
+
} catch (cause) {
|
|
935
|
+
throw new Error(`supervisePursuit: ${path} is not valid JSON`, { cause });
|
|
936
|
+
}
|
|
937
|
+
const record = parsed;
|
|
938
|
+
if (typeof record !== "object" || record === null || typeof record.kind !== "string" || typeof record.tree !== "object" || record.tree === null || typeof record.tree.root !== "string") throw new Error(`supervisePursuit: ${path} is not a settle record (no kind or tree.root)`);
|
|
939
|
+
return parsed;
|
|
940
|
+
}
|
|
941
|
+
/** Read the most recent failure record, or `undefined` when the directory holds none. */
|
|
942
|
+
async function readFailureRecord(runDir) {
|
|
943
|
+
const path = resolve(runDir, FAILURE_RECORD_FILE);
|
|
944
|
+
let text;
|
|
945
|
+
try {
|
|
946
|
+
text = await readFile(path, "utf8");
|
|
947
|
+
} catch (error) {
|
|
948
|
+
if (isNoEntError(error)) return void 0;
|
|
949
|
+
throw error;
|
|
950
|
+
}
|
|
951
|
+
let parsed;
|
|
952
|
+
try {
|
|
953
|
+
parsed = JSON.parse(text);
|
|
954
|
+
} catch (cause) {
|
|
955
|
+
throw new Error(`supervisePursuit: ${path} is not valid JSON`, { cause });
|
|
956
|
+
}
|
|
957
|
+
const record = parsed;
|
|
958
|
+
if (typeof record !== "object" || record === null || typeof record.runId !== "string" || typeof record.pursuitId !== "string" || typeof record.at !== "string" || typeof record.error?.name !== "string" || typeof record.error.message !== "string" || record.rootStream !== void 0 && (typeof record.rootStream.ref !== "string" || typeof record.rootStream.events !== "number")) throw new Error(`supervisePursuit: ${path} is not a failure record`);
|
|
959
|
+
return parsed;
|
|
960
|
+
}
|
|
961
|
+
//#endregion
|
|
962
|
+
//#region src/durable/run-fork.ts
|
|
963
|
+
/**
|
|
964
|
+
* The `execution.correlation` keys a fork records on its root. Runtime writes them; a caller that
|
|
965
|
+
* supplies one is refused. `lineageRootRunId` is the first run of the chain of parents, so the
|
|
966
|
+
* spend of every version of one lineage groups under one id.
|
|
967
|
+
*/
|
|
968
|
+
const RUN_FORK_CORRELATION_KEYS = Object.freeze([
|
|
969
|
+
"forkParentRunId",
|
|
970
|
+
"forkParentSettleDigest",
|
|
971
|
+
"forkProfileDiffId",
|
|
972
|
+
"lineageRootRunId"
|
|
973
|
+
]);
|
|
974
|
+
/** The correlation key an accepted uncertain parent adds: its uncertain node ids, comma-joined. */
|
|
975
|
+
const FORK_PARENT_UNCERTAIN_NODES_KEY = "forkParentUncertainNodes";
|
|
976
|
+
/**
|
|
977
|
+
* Verify the parent and derive the fork's profile and attribution. It reads only the parent, so
|
|
978
|
+
* a refused fork spends nothing and writes nothing.
|
|
979
|
+
*/
|
|
980
|
+
async function prepareRunFork(profile, task, opts, fork) {
|
|
981
|
+
const change = fork.change;
|
|
982
|
+
if (typeof change?.id !== "string" || change.id.trim().length === 0) throw new ValidationError("supervisePursuit fork: change.id must be a non-empty string");
|
|
983
|
+
const parentDir = await realDirectory(fork.runDir, "fork.runDir");
|
|
984
|
+
const forkDir = await realDirectory(opts.runDir, "runDir", true);
|
|
985
|
+
if (contains(parentDir, forkDir) || contains(forkDir, parentDir)) throw new ValidationError(`supervisePursuit fork: runDir ${forkDir} overlaps the parent's runDir ${parentDir}; a fork needs its own directory outside the parent's`);
|
|
986
|
+
let bytes;
|
|
987
|
+
try {
|
|
988
|
+
bytes = await readFile(resolve(parentDir, SETTLE_RECORD_FILE));
|
|
989
|
+
} catch (error) {
|
|
990
|
+
if (!isNoEntError(error)) throw error;
|
|
991
|
+
throw new RuntimeRunStateError(`supervisePursuit fork: ${parentDir} holds no ${SETTLE_RECORD_FILE}; only a settled run is forked`);
|
|
992
|
+
}
|
|
993
|
+
const observed = sha256Bytes(bytes);
|
|
994
|
+
if (observed !== fork.settleDigest) throw new RuntimeRunStateError(`supervisePursuit fork: ${SETTLE_RECORD_FILE} hashes to ${observed}, not the requested ${fork.settleDigest}`);
|
|
995
|
+
const settled = await readSettleRecord(parentDir);
|
|
996
|
+
if (settled === void 0) throw new RuntimeRunStateError(`supervisePursuit fork: ${parentDir} lost its settle record`);
|
|
997
|
+
const parentRunId = settled.tree.root;
|
|
998
|
+
if ((opts.runId ?? "supervise") === parentRunId) throw new ValidationError(`supervisePursuit fork: runId '${parentRunId}' is the parent's; a fork needs its own runId`);
|
|
999
|
+
const forest = await loadSpawnForest(new FileSpawnJournal(resolve(parentDir, "spawn-journal.jsonl")), parentRunId);
|
|
1000
|
+
const uncertain = [
|
|
1001
|
+
...forest.inDoubt.map((node) => `${node.nodeId} (no terminal record)`),
|
|
1002
|
+
...forest.missingTrees.map((tree) => `${tree.ownerNodeId} (tree ${tree.root} never begun)`),
|
|
1003
|
+
...(settled.teardownUnconfirmed ?? []).map((node) => `${node.id} (teardown unconfirmed)`)
|
|
1004
|
+
];
|
|
1005
|
+
if (settled.tree.inFlight > 0 && uncertain.length === 0) uncertain.push(`${settled.tree.inFlight} in flight`);
|
|
1006
|
+
if (uncertain.length > 0 && fork.acceptUncertain !== true) throw new RuntimeRunStateError(`supervisePursuit fork: run '${parentRunId}' has uncertain nodes: ${uncertain.join(", ")}; set fork.acceptUncertain to fork it anyway`);
|
|
1007
|
+
const root = forest.trees[0]?.events.find((event) => event.kind === "spawned" && event.parent === void 0);
|
|
1008
|
+
const recorded = root?.identity;
|
|
1009
|
+
if (recorded?.profileDigest === void 0 || recorded.taskDigest === void 0) throw new RuntimeRunStateError(`supervisePursuit fork: run '${parentRunId}' records no root profile and task digest`);
|
|
1010
|
+
const mismatches = [
|
|
1011
|
+
authoredProfileDigest(superviseRootProfile(profile, opts.profileGuidance)) !== recorded.profileDigest ? `profile is not the parent's ${recorded.profileDigest}` : void 0,
|
|
1012
|
+
digestOrUndefined(task) !== recorded.taskDigest ? `task is not the parent's ${recorded.taskDigest}` : void 0,
|
|
1013
|
+
contentAddress(root?.budget) !== contentAddress(opts.budget) ? "budget is not the parent's" : void 0
|
|
1014
|
+
].filter((mismatch) => mismatch !== void 0);
|
|
1015
|
+
if (mismatches.length > 0) throw new ValidationError(`supervisePursuit fork: ${mismatches.join("; ")}`);
|
|
1016
|
+
const forked = applyExactAgentProfileDiff(profile, change, "supervisePursuit fork");
|
|
1017
|
+
if (authoredProfileDigest(superviseRootProfile(forked, opts.profileGuidance)) === recorded.profileDigest) throw new ValidationError(`supervisePursuit fork: change '${change.id}' leaves the parent's profile unchanged`);
|
|
1018
|
+
const supplied = opts.execution?.correlation ?? {};
|
|
1019
|
+
const owned = [...RUN_FORK_CORRELATION_KEYS, FORK_PARENT_UNCERTAIN_NODES_KEY].filter((key) => Object.hasOwn(supplied, key));
|
|
1020
|
+
if (owned.length > 0) throw new ValidationError(`supervisePursuit fork: execution.correlation cannot set ${owned.join(", ")}; Runtime records them`);
|
|
1021
|
+
return Object.freeze({
|
|
1022
|
+
profile: forked,
|
|
1023
|
+
execution: Object.freeze({
|
|
1024
|
+
...opts.execution,
|
|
1025
|
+
correlation: Object.freeze({
|
|
1026
|
+
...supplied,
|
|
1027
|
+
forkParentRunId: parentRunId,
|
|
1028
|
+
forkParentSettleDigest: fork.settleDigest,
|
|
1029
|
+
forkProfileDiffId: change.id,
|
|
1030
|
+
lineageRootRunId: recorded.correlation?.lineageRootRunId ?? parentRunId,
|
|
1031
|
+
...uncertain.length === 0 ? {} : { [FORK_PARENT_UNCERTAIN_NODES_KEY]: uncertain.join(", ") }
|
|
1032
|
+
})
|
|
1033
|
+
})
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
function digestOrUndefined(value) {
|
|
1037
|
+
try {
|
|
1038
|
+
return canonicalCandidateDigest(value);
|
|
1039
|
+
} catch {
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Resolve a directory through symlinks. The fork's own directory may not exist yet; it resolves
|
|
1045
|
+
* through its nearest existing ancestor, so a symlinked ancestor cannot hide an overlap.
|
|
1046
|
+
*/
|
|
1047
|
+
async function realDirectory(path, name, mayBeAbsent = false) {
|
|
1048
|
+
if (typeof path !== "string" || path.trim().length === 0) throw new ValidationError(`supervisePursuit fork: ${name} must be a non-empty string`);
|
|
1049
|
+
const absolute = resolve(path.trim());
|
|
1050
|
+
try {
|
|
1051
|
+
return await realpath(absolute);
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
const ancestor = dirname(absolute);
|
|
1054
|
+
if (!mayBeAbsent || !isNoEntError(error) || ancestor === absolute) throw error;
|
|
1055
|
+
return join(await realDirectory(ancestor, name, true), basename(absolute));
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
/** Whether `inner` is `outer` or lies beneath it. */
|
|
1059
|
+
function contains(outer, inner) {
|
|
1060
|
+
const path = relative(outer, inner);
|
|
1061
|
+
return path === "" || !isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`);
|
|
1062
|
+
}
|
|
1063
|
+
//#endregion
|
|
839
1064
|
//#region src/durable/run-lock.ts
|
|
840
1065
|
/** The lock file `supervisePursuit` holds inside a run directory for the life of one call. */
|
|
841
1066
|
const RUN_DIRECTORY_LOCK_FILE = "supervise.lock";
|
|
@@ -1010,7 +1235,7 @@ async function withMutationGuard(path, mutate, waitForRelease = false) {
|
|
|
1010
1235
|
break;
|
|
1011
1236
|
} catch (cause) {
|
|
1012
1237
|
if (waitForRelease && cause.code === "EEXIST" && performance.now() < deadline) {
|
|
1013
|
-
await setTimeout(10);
|
|
1238
|
+
await setTimeout$1(10);
|
|
1014
1239
|
continue;
|
|
1015
1240
|
}
|
|
1016
1241
|
throw new Error(`supervisePursuit: cannot mutate ${path}; inspect ${guard} and remove it only after confirming no lock mutation is active`, { cause });
|
|
@@ -1029,123 +1254,424 @@ async function removeIfPresent(path) {
|
|
|
1029
1254
|
}
|
|
1030
1255
|
}
|
|
1031
1256
|
//#endregion
|
|
1032
|
-
//#region src/durable/
|
|
1033
|
-
/** The
|
|
1034
|
-
const
|
|
1035
|
-
/** The
|
|
1036
|
-
const
|
|
1037
|
-
/** The directory
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
this.path = path;
|
|
1046
|
-
this.recordedRunId = recordedRunId;
|
|
1047
|
-
}
|
|
1048
|
-
};
|
|
1049
|
-
/** A result member whose JSON form would misstate it. */
|
|
1050
|
-
var UnrecordableSettleValueError = class extends TypeError {
|
|
1051
|
-
path;
|
|
1052
|
-
constructor(path, kind) {
|
|
1053
|
-
super(`supervisePursuit: result${path} is a ${kind}, which JSON cannot record faithfully; a settle record holds plain data only`);
|
|
1054
|
-
this.name = "UnrecordableSettleValueError";
|
|
1055
|
-
this.path = path;
|
|
1056
|
-
}
|
|
1057
|
-
};
|
|
1058
|
-
const PLAIN_PROTOTYPES = /* @__PURE__ */ new Set([
|
|
1059
|
-
Object.prototype,
|
|
1060
|
-
Array.prototype,
|
|
1061
|
-
null
|
|
1062
|
-
]);
|
|
1063
|
-
/**
|
|
1064
|
-
* The JSON value of a result, or a throw naming the first member JSON would misstate. An
|
|
1065
|
-
* `undefined` member and a function are dropped, exactly as any JSON consumer drops them; a Map,
|
|
1066
|
-
* a Set, a Date, a typed array, or a class instance would be rewritten to `{}` or a string and
|
|
1067
|
-
* so is refused.
|
|
1068
|
-
*/
|
|
1069
|
-
function jsonValue(result) {
|
|
1070
|
-
const seen = [];
|
|
1071
|
-
const visit = (value, path) => {
|
|
1072
|
-
if (value === null || typeof value !== "object") return;
|
|
1073
|
-
if (value instanceof Map) throw new UnrecordableSettleValueError(path, "Map");
|
|
1074
|
-
if (value instanceof Set) throw new UnrecordableSettleValueError(path, "Set");
|
|
1075
|
-
if (value instanceof Date) throw new UnrecordableSettleValueError(path, "Date");
|
|
1076
|
-
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) throw new UnrecordableSettleValueError(path, "binary buffer");
|
|
1077
|
-
if (!PLAIN_PROTOTYPES.has(Object.getPrototypeOf(value))) throw new UnrecordableSettleValueError(path, "class instance");
|
|
1078
|
-
if (seen.includes(value)) throw new UnrecordableSettleValueError(path, "cycle");
|
|
1079
|
-
seen.push(value);
|
|
1080
|
-
if (Array.isArray(value)) for (const [index, item] of value.entries()) visit(item, `${path}[${index}]`);
|
|
1081
|
-
else for (const [key, item] of Object.entries(value)) visit(item, `${path}.${key}`);
|
|
1082
|
-
seen.pop();
|
|
1257
|
+
//#region src/durable/pursuit-versions.ts
|
|
1258
|
+
/** The longest delay Node's setTimeout honors. */
|
|
1259
|
+
const MAX_TIMER_MS = 2147483647;
|
|
1260
|
+
/** The ledger file inside the lineage directory. */
|
|
1261
|
+
const PURSUIT_VERSIONS_FILE = "versions.jsonl";
|
|
1262
|
+
/** The `<runDir>.v<n>` directory and `<runId>.v<n>` id of version `n`, beside the first. */
|
|
1263
|
+
function pursuitVersionRun(runDir, runId, version) {
|
|
1264
|
+
return version === 1 ? {
|
|
1265
|
+
runDir,
|
|
1266
|
+
runId
|
|
1267
|
+
} : {
|
|
1268
|
+
runDir: `${runDir}.v${version}`,
|
|
1269
|
+
runId: `${runId}.v${version}`
|
|
1083
1270
|
};
|
|
1084
|
-
visit(result, "");
|
|
1085
|
-
return JSON.parse(JSON.stringify(result));
|
|
1086
1271
|
}
|
|
1087
1272
|
/**
|
|
1088
|
-
*
|
|
1089
|
-
*
|
|
1090
|
-
* result carries a value JSON would misstate.
|
|
1273
|
+
* Validate a `versions` option before any compute. The same check runs inside `supervisePursuit`;
|
|
1274
|
+
* a caller that records the option as data can run it at preflight.
|
|
1091
1275
|
*/
|
|
1092
|
-
function
|
|
1093
|
-
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
const
|
|
1098
|
-
if (
|
|
1099
|
-
|
|
1276
|
+
function assertPursuitVersions(versions) {
|
|
1277
|
+
const fail = (message) => {
|
|
1278
|
+
throw new ValidationError(`supervisePursuit versions: ${message}`);
|
|
1279
|
+
};
|
|
1280
|
+
if (typeof versions !== "object" || versions === null) fail("must be an object");
|
|
1281
|
+
const { judge, next, stop, run } = versions;
|
|
1282
|
+
if (typeof judge === "string") {
|
|
1283
|
+
if (judge.trim().length === 0) fail("judge must name a registry entry");
|
|
1284
|
+
} else if (typeof judge !== "object" || judge === null || typeof judge.judge !== "function") fail("judge must be a VersionJudge or a registry name");
|
|
1285
|
+
else if (!isSha256(judge.digest)) fail("judge.digest must be a sha256 digest (sha256:<64 hex>)");
|
|
1286
|
+
if (typeof next === "string" ? next.trim().length === 0 : typeof next !== "function") fail("next must be a function or a registry name");
|
|
1287
|
+
if (run !== void 0 && typeof run !== "function") fail("run must be a function when set");
|
|
1288
|
+
const { usd } = versions;
|
|
1289
|
+
if (usd !== void 0 && typeof usd !== "function") fail("usd must be a function when set");
|
|
1290
|
+
if (typeof stop !== "object" || stop === null) fail("stop must be an object");
|
|
1291
|
+
const rule = stop;
|
|
1292
|
+
const known = /* @__PURE__ */ new Set([
|
|
1293
|
+
"patience",
|
|
1294
|
+
"maxVersions",
|
|
1295
|
+
"maxUsd",
|
|
1296
|
+
"deadlineMs",
|
|
1297
|
+
"minImprovement"
|
|
1298
|
+
]);
|
|
1299
|
+
const unknown = Object.keys(rule).filter((key) => !known.has(key));
|
|
1300
|
+
if (unknown.length > 0) fail(`stop has unknown fields: ${unknown.join(", ")}`);
|
|
1301
|
+
for (const key of ["patience", "maxVersions"]) if (!Number.isInteger(rule[key]) || rule[key] < 1) fail(`stop.${key} must be an integer of at least 1`);
|
|
1302
|
+
for (const key of ["maxUsd", "deadlineMs"]) if (typeof rule[key] !== "number" || !Number.isFinite(rule[key]) || rule[key] <= 0) fail(`stop.${key} must be a finite number above 0`);
|
|
1303
|
+
if (rule.minImprovement !== void 0 && (typeof rule.minImprovement !== "number" || !Number.isFinite(rule.minImprovement) || rule.minImprovement < 0)) fail("stop.minImprovement must be a finite number of at least 0");
|
|
1100
1304
|
}
|
|
1101
|
-
/**
|
|
1102
|
-
async function
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1305
|
+
/** Run a version chain. `supervisePursuit` calls this when `versions` is set. */
|
|
1306
|
+
async function runPursuitVersions(profile, task, opts, runOne) {
|
|
1307
|
+
const { versions, ...rest } = opts;
|
|
1308
|
+
assertPursuitVersions(versions);
|
|
1309
|
+
for (const key of [
|
|
1310
|
+
"journal",
|
|
1311
|
+
"blobs",
|
|
1312
|
+
"rootHandle",
|
|
1313
|
+
"steerDir"
|
|
1314
|
+
]) if (rest[key] !== void 0) throw new ValidationError(`supervisePursuit versions: ${key} names one run, and a version chain has one per version; omit it`);
|
|
1315
|
+
const registry = rest.registry;
|
|
1316
|
+
const judge = resolveNamed("judge", "versionJudges", versions.judge, registry?.versionJudges);
|
|
1317
|
+
if (!isSha256(judge.digest)) throw new ValidationError("supervisePursuit versions: judge.digest must be a sha256 digest");
|
|
1318
|
+
const next = resolveNamed("next", "nextVersions", versions.next, registry?.nextVersions);
|
|
1319
|
+
const stop = versions.stop;
|
|
1320
|
+
const minImprovement = stop.minImprovement ?? 0;
|
|
1321
|
+
const now = rest.now ?? Date.now;
|
|
1322
|
+
const pursuitId = rest.pursuitId.trim();
|
|
1323
|
+
const runDir = resolve(rest.runDir.trim());
|
|
1324
|
+
const runId = rest.runId ?? "supervise";
|
|
1325
|
+
const { fork: firstFork, signal: callerSignal, ...base } = rest;
|
|
1326
|
+
const lineageDir = `${runDir}.versions`;
|
|
1327
|
+
const ledgerPath = resolve(lineageDir, PURSUIT_VERSIONS_FILE);
|
|
1328
|
+
const firstProfile = firstFork === void 0 ? profile : applyExactAgentProfileDiff(profile, firstFork.change, "supervisePursuit versions");
|
|
1329
|
+
await mkdir(lineageDir, { recursive: true });
|
|
1330
|
+
const lock = await acquireRunDirectoryLock(lineageDir, `${runId}:versions`, now);
|
|
1331
|
+
try {
|
|
1332
|
+
const ledger = await readLedger(ledgerPath);
|
|
1333
|
+
let chain = ledger.find((line) => line.kind === "chain");
|
|
1334
|
+
if (chain === void 0) {
|
|
1335
|
+
const elapsed = (await readSettleRecord(runDir))?.spentTotal?.ms;
|
|
1336
|
+
const startedAt = new Date(now() - (typeof elapsed === "number" && Number.isFinite(elapsed) ? elapsed : 0)).toISOString();
|
|
1337
|
+
chain = {
|
|
1338
|
+
kind: "chain",
|
|
1339
|
+
pursuitId,
|
|
1340
|
+
runId,
|
|
1341
|
+
runDir,
|
|
1342
|
+
judgeDigest: judge.digest,
|
|
1343
|
+
stop: canonicalStop(stop),
|
|
1344
|
+
startedAt
|
|
1345
|
+
};
|
|
1346
|
+
await appendLedger(ledgerPath, chain);
|
|
1347
|
+
} else {
|
|
1348
|
+
const mismatch = [
|
|
1349
|
+
chain.pursuitId !== pursuitId ? `pursuitId '${chain.pursuitId}'` : void 0,
|
|
1350
|
+
chain.runId !== runId ? `runId '${chain.runId}'` : void 0,
|
|
1351
|
+
chain.runDir !== runDir ? `runDir '${chain.runDir}'` : void 0,
|
|
1352
|
+
chain.judgeDigest !== judge.digest ? `judge digest ${chain.judgeDigest}` : void 0,
|
|
1353
|
+
contentAddress(chain.stop) !== contentAddress(canonicalStop(stop)) ? "another stop rule" : void 0
|
|
1354
|
+
].filter((item) => item !== void 0);
|
|
1355
|
+
if (mismatch.length > 0) throw new ValidationError(`supervisePursuit versions: ${ledgerPath} records a chain with ${mismatch.join(", ")}; a chain keeps its identity, judge and stop rule`);
|
|
1356
|
+
}
|
|
1357
|
+
const startedAtMs = Date.parse(chain.startedAt);
|
|
1358
|
+
const deadline = new AbortController();
|
|
1359
|
+
let timer;
|
|
1360
|
+
const arm = () => {
|
|
1361
|
+
const remaining = startedAtMs + stop.deadlineMs - now();
|
|
1362
|
+
if (remaining <= 0) {
|
|
1363
|
+
deadline.abort(/* @__PURE__ */ new Error("the version chain reached its deadline"));
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
timer = setTimeout(arm, Math.min(remaining, MAX_TIMER_MS));
|
|
1367
|
+
timer.unref?.();
|
|
1368
|
+
};
|
|
1369
|
+
arm();
|
|
1370
|
+
const signal = callerSignal === void 0 ? deadline.signal : AbortSignal.any([callerSignal, deadline.signal]);
|
|
1371
|
+
try {
|
|
1372
|
+
const judged = [];
|
|
1373
|
+
const profiles = /* @__PURE__ */ new Map([[1, firstProfile]]);
|
|
1374
|
+
let pending = {
|
|
1375
|
+
version: 1,
|
|
1376
|
+
runId,
|
|
1377
|
+
runDir
|
|
1378
|
+
};
|
|
1379
|
+
let stopped;
|
|
1380
|
+
for (const line of ledger) if (line.kind === "start") {
|
|
1381
|
+
const parentProfile = profiles.get(line.parent.version);
|
|
1382
|
+
if (parentProfile === void 0) throw new RuntimeRunStateError(`supervisePursuit versions: ${ledgerPath} starts version ${line.version} from unknown version ${line.parent.version}`);
|
|
1383
|
+
profiles.set(line.version, applyExactAgentProfileDiff(parentProfile, line.change, "supervisePursuit versions"));
|
|
1384
|
+
pending = {
|
|
1385
|
+
version: line.version,
|
|
1386
|
+
runId: line.runId,
|
|
1387
|
+
runDir: line.runDir,
|
|
1388
|
+
parent: line.parent,
|
|
1389
|
+
change: line.change
|
|
1390
|
+
};
|
|
1391
|
+
} else if (line.kind === "judged") {
|
|
1392
|
+
if (line.version !== judged.length + 1 || line.version !== pending?.version) throw new RuntimeRunStateError(`supervisePursuit versions: ${ledgerPath} judges version ${line.version} out of order`);
|
|
1393
|
+
if (line.verdict.judgeDigest !== judge.digest) throw new ValidationError(`supervisePursuit versions: version ${line.version} was judged under ${line.verdict.judgeDigest}, not ${judge.digest}`);
|
|
1394
|
+
const settled = await readSettled(pending, profiles);
|
|
1395
|
+
if (settled.settleDigest !== line.settleDigest) throw new RuntimeRunStateError(`supervisePursuit versions: version ${line.version}'s result.json changed after it was judged`);
|
|
1396
|
+
judged.push(Object.freeze({
|
|
1397
|
+
...settled,
|
|
1398
|
+
verdict: line.verdict,
|
|
1399
|
+
improved: line.improved,
|
|
1400
|
+
usd: line.usd,
|
|
1401
|
+
usdKnown: line.usdKnown,
|
|
1402
|
+
usdSource: line.usdSource,
|
|
1403
|
+
...line.lineage === void 0 ? {} : { lineage: line.lineage }
|
|
1404
|
+
}));
|
|
1405
|
+
pending = void 0;
|
|
1406
|
+
} else if (line.kind === "stopped") stopped = line;
|
|
1407
|
+
while (stopped === void 0) {
|
|
1408
|
+
if (pending !== void 0) {
|
|
1409
|
+
const spec = pending;
|
|
1410
|
+
const settled = await settle(spec);
|
|
1411
|
+
const verdict = await judgeVersion(settled);
|
|
1412
|
+
const bestScore = bestOf(judged)?.verdict.score;
|
|
1413
|
+
const improved = isFiniteNumber(verdict.score) && (!isFiniteNumber(bestScore) || verdict.score > bestScore + minImprovement);
|
|
1414
|
+
const spent = settled.result.spentTotal;
|
|
1415
|
+
const measured = versions.usd === void 0 ? void 0 : await versions.usd(settled, signal);
|
|
1416
|
+
const usdSource = measured === void 0 ? "runtime" : "caller";
|
|
1417
|
+
const usd = measured === void 0 ? isFiniteNumber(spent?.usd) ? spent.usd : null : isFiniteNumber(measured) ? measured : null;
|
|
1418
|
+
const usdKnown = usd !== null && (measured !== void 0 || spent?.usdKnown !== false);
|
|
1419
|
+
const lineage = versionLineage(spec, firstFork);
|
|
1420
|
+
await appendLedger(ledgerPath, {
|
|
1421
|
+
kind: "judged",
|
|
1422
|
+
version: spec.version,
|
|
1423
|
+
runId: spec.runId,
|
|
1424
|
+
runDir: spec.runDir,
|
|
1425
|
+
settleDigest: settled.settleDigest,
|
|
1426
|
+
...spec.parent === void 0 ? {} : { parent: spec.parent },
|
|
1427
|
+
...spec.change?.id === void 0 ? {} : { changeId: spec.change.id },
|
|
1428
|
+
verdict,
|
|
1429
|
+
improved,
|
|
1430
|
+
usd,
|
|
1431
|
+
usdKnown,
|
|
1432
|
+
usdSource,
|
|
1433
|
+
...lineage === void 0 ? {} : { lineage }
|
|
1434
|
+
});
|
|
1435
|
+
judged.push(Object.freeze({
|
|
1436
|
+
...settled,
|
|
1437
|
+
verdict,
|
|
1438
|
+
improved,
|
|
1439
|
+
usd,
|
|
1440
|
+
usdKnown,
|
|
1441
|
+
usdSource,
|
|
1442
|
+
...lineage === void 0 ? {} : { lineage }
|
|
1443
|
+
}));
|
|
1444
|
+
pending = void 0;
|
|
1445
|
+
}
|
|
1446
|
+
const reason = stopReason(judged);
|
|
1447
|
+
if (reason !== void 0) {
|
|
1448
|
+
stopped = {
|
|
1449
|
+
kind: "stopped",
|
|
1450
|
+
reason,
|
|
1451
|
+
at: new Date(now()).toISOString(),
|
|
1452
|
+
best: (bestOf(judged) ?? judged[0]).version,
|
|
1453
|
+
spentUsd: spentOf(judged)
|
|
1454
|
+
};
|
|
1455
|
+
await appendLedger(ledgerPath, stopped);
|
|
1456
|
+
break;
|
|
1457
|
+
}
|
|
1458
|
+
const best = bestOf(judged) ?? judged[0];
|
|
1459
|
+
const last = judged[judged.length - 1];
|
|
1460
|
+
const change = await next({
|
|
1461
|
+
best,
|
|
1462
|
+
last,
|
|
1463
|
+
versions: Object.freeze([...judged])
|
|
1464
|
+
}, signal);
|
|
1465
|
+
if (typeof change?.id !== "string" || change.id.trim().length === 0) throw new ValidationError("supervisePursuit versions: next must return a change with an id");
|
|
1466
|
+
const version = judged.length + 1;
|
|
1467
|
+
const spec = {
|
|
1468
|
+
version,
|
|
1469
|
+
...pursuitVersionRun(runDir, runId, version),
|
|
1470
|
+
parent: {
|
|
1471
|
+
version: best.version,
|
|
1472
|
+
runId: best.runId,
|
|
1473
|
+
settleDigest: best.settleDigest
|
|
1474
|
+
},
|
|
1475
|
+
change
|
|
1476
|
+
};
|
|
1477
|
+
profiles.set(version, applyExactAgentProfileDiff(best.profile, change, "supervisePursuit versions"));
|
|
1478
|
+
await appendLedger(ledgerPath, {
|
|
1479
|
+
kind: "start",
|
|
1480
|
+
version,
|
|
1481
|
+
runId: spec.runId,
|
|
1482
|
+
runDir: spec.runDir,
|
|
1483
|
+
parent: spec.parent,
|
|
1484
|
+
change
|
|
1485
|
+
});
|
|
1486
|
+
pending = spec;
|
|
1487
|
+
}
|
|
1488
|
+
const bestVersion = judged.find((item) => item.version === stopped.best) ?? judged[0];
|
|
1489
|
+
const record = Object.freeze({
|
|
1490
|
+
lineageDir,
|
|
1491
|
+
judgeDigest: judge.digest,
|
|
1492
|
+
stop: canonicalStop(stop),
|
|
1493
|
+
startedAt: chain.startedAt,
|
|
1494
|
+
versions: Object.freeze([...judged]),
|
|
1495
|
+
best: bestVersion.version,
|
|
1496
|
+
spentUsd: stopped.spentUsd,
|
|
1497
|
+
stopped: Object.freeze({
|
|
1498
|
+
reason: stopped.reason,
|
|
1499
|
+
at: stopped.at
|
|
1500
|
+
})
|
|
1501
|
+
});
|
|
1502
|
+
const observerPath = resolve(bestVersion.runDir, "observer.jsonl");
|
|
1503
|
+
return Object.freeze({
|
|
1504
|
+
result: bestVersion.result,
|
|
1505
|
+
pursuit: projectPursuit(await new FileObserverJournal(observerPath, pursuitId).read()),
|
|
1506
|
+
observerPath,
|
|
1507
|
+
settlePath: resolve(bestVersion.runDir, SETTLE_RECORD_FILE),
|
|
1508
|
+
versions: record
|
|
1509
|
+
});
|
|
1510
|
+
/** Run the version unless its directory already settled, then read it back. */
|
|
1511
|
+
async function settle(spec) {
|
|
1512
|
+
if (await readSettleRecord(spec.runDir) === void 0) {
|
|
1513
|
+
if (signal.aborted) throw new RuntimeRunStateError(`supervisePursuit versions: version ${spec.version} was not started: ${abortMessage(signal)}`);
|
|
1514
|
+
if (spec.parent === void 0 || spec.change === void 0) await runOne(profile, task, {
|
|
1515
|
+
...base,
|
|
1516
|
+
...firstFork === void 0 ? {} : { fork: firstFork },
|
|
1517
|
+
signal
|
|
1518
|
+
});
|
|
1519
|
+
else {
|
|
1520
|
+
const parent = judged.find((item) => item.version === spec.parent.version);
|
|
1521
|
+
if (parent === void 0) throw new RuntimeRunStateError(`supervisePursuit versions: version ${spec.version}'s parent ${spec.parent.version} is not judged`);
|
|
1522
|
+
const fork = {
|
|
1523
|
+
runDir: parent.runDir,
|
|
1524
|
+
settleDigest: parent.settleDigest,
|
|
1525
|
+
change: spec.change,
|
|
1526
|
+
acceptUncertain: true
|
|
1527
|
+
};
|
|
1528
|
+
const options = {
|
|
1529
|
+
...base,
|
|
1530
|
+
runId: spec.runId,
|
|
1531
|
+
runDir: spec.runDir,
|
|
1532
|
+
fork,
|
|
1533
|
+
signal
|
|
1534
|
+
};
|
|
1535
|
+
if (versions.run === void 0) await runOne(parent.profile, task, options);
|
|
1536
|
+
else {
|
|
1537
|
+
const prepared = await prepareRunFork(parent.profile, task, options, fork);
|
|
1538
|
+
await versions.run(Object.freeze({
|
|
1539
|
+
version: spec.version,
|
|
1540
|
+
runId: spec.runId,
|
|
1541
|
+
runDir: spec.runDir,
|
|
1542
|
+
pursuitId,
|
|
1543
|
+
profile: prepared.profile,
|
|
1544
|
+
task,
|
|
1545
|
+
budget: base.budget,
|
|
1546
|
+
execution: prepared.execution,
|
|
1547
|
+
fork,
|
|
1548
|
+
parentProfile: parent.profile
|
|
1549
|
+
}), signal);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
const settled = await readSettled(spec, profiles);
|
|
1554
|
+
if (settled === void 0) throw new RuntimeRunStateError(`supervisePursuit versions: version ${spec.version} returned without a settle record in ${spec.runDir}`);
|
|
1555
|
+
return settled;
|
|
1556
|
+
}
|
|
1557
|
+
async function judgeVersion(settled) {
|
|
1558
|
+
const verdict = await judge.judge(settled, signal);
|
|
1559
|
+
if (typeof verdict !== "object" || verdict === null) throw new ValidationError("supervisePursuit versions: the judge returned no verdict");
|
|
1560
|
+
if (verdict.judgeDigest !== judge.digest) throw new ValidationError(`supervisePursuit versions: the verdict for version ${settled.version} carries ${String(verdict.judgeDigest)}, not the pinned judge ${judge.digest}`);
|
|
1561
|
+
if (verdict.score !== null && !isFiniteNumber(verdict.score)) throw new ValidationError("supervisePursuit versions: a verdict score must be a finite number or null");
|
|
1562
|
+
return JSON.parse(JSON.stringify(verdict));
|
|
1563
|
+
}
|
|
1564
|
+
function stopReason(done) {
|
|
1565
|
+
if (callerSignal?.aborted) return "aborted";
|
|
1566
|
+
if (done.length >= stop.maxVersions) return "max-versions";
|
|
1567
|
+
let stale = 0;
|
|
1568
|
+
for (let index = done.length - 1; index >= 0 && !done[index].improved; index -= 1) stale += 1;
|
|
1569
|
+
if (stale >= stop.patience) return "no-improvement";
|
|
1570
|
+
if (done.some((item) => item.usd === null)) return "spend-unknown";
|
|
1571
|
+
if (spentOf(done) >= stop.maxUsd) return "max-usd";
|
|
1572
|
+
if (deadline.signal.aborted || now() - startedAtMs >= stop.deadlineMs) return "deadline";
|
|
1573
|
+
}
|
|
1574
|
+
} finally {
|
|
1575
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1576
|
+
}
|
|
1577
|
+
} finally {
|
|
1578
|
+
await lock.release();
|
|
1579
|
+
}
|
|
1106
1580
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
* is present but is not a settle record is corruption and fails loud.
|
|
1110
|
-
*/
|
|
1111
|
-
async function readSettleRecord(runDir) {
|
|
1112
|
-
const path = resolve(runDir, SETTLE_RECORD_FILE);
|
|
1113
|
-
let text;
|
|
1581
|
+
async function readSettled(spec, profiles) {
|
|
1582
|
+
let bytes;
|
|
1114
1583
|
try {
|
|
1115
|
-
|
|
1584
|
+
bytes = await readFile(resolve(spec.runDir, SETTLE_RECORD_FILE));
|
|
1116
1585
|
} catch (error) {
|
|
1117
|
-
if (isNoEntError(error))
|
|
1118
|
-
throw
|
|
1586
|
+
if (!isNoEntError(error)) throw error;
|
|
1587
|
+
throw new RuntimeRunStateError(`supervisePursuit versions: version ${spec.version} has no settle record in ${spec.runDir}`);
|
|
1119
1588
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1589
|
+
const result = await readSettleRecord(spec.runDir);
|
|
1590
|
+
const profile = profiles.get(spec.version);
|
|
1591
|
+
if (profile === void 0) throw new RuntimeRunStateError(`supervisePursuit versions: version ${spec.version} has no recorded profile`);
|
|
1592
|
+
return Object.freeze({
|
|
1593
|
+
version: spec.version,
|
|
1594
|
+
runId: spec.runId,
|
|
1595
|
+
runDir: spec.runDir,
|
|
1596
|
+
settleDigest: sha256Bytes(bytes),
|
|
1597
|
+
result,
|
|
1598
|
+
profile,
|
|
1599
|
+
...spec.parent === void 0 ? {} : { parent: spec.parent },
|
|
1600
|
+
...spec.change === void 0 ? {} : { change: spec.change }
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
function versionLineage(spec, firstFork) {
|
|
1604
|
+
if (spec.parent !== void 0 && spec.change !== void 0) return {
|
|
1605
|
+
source: lineageSource(spec.change),
|
|
1606
|
+
runIds: [spec.parent.runId],
|
|
1607
|
+
profileDiffIds: [spec.change.id]
|
|
1608
|
+
};
|
|
1609
|
+
if (firstFork !== void 0) return {
|
|
1610
|
+
source: lineageSource(firstFork.change),
|
|
1611
|
+
profileDiffIds: [firstFork.change.id]
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
/** The change's author, in the lineage's words. A trace-derived change is a compound of runs. */
|
|
1615
|
+
function lineageSource(change) {
|
|
1616
|
+
const kind = change.source?.kind;
|
|
1617
|
+
return kind === "human" || kind === "optimizer" || kind === "frontier-author" ? kind : "compound";
|
|
1618
|
+
}
|
|
1619
|
+
function bestOf(done) {
|
|
1620
|
+
let best;
|
|
1621
|
+
for (const item of done) {
|
|
1622
|
+
if (!isFiniteNumber(item.verdict.score)) continue;
|
|
1623
|
+
if (best === void 0 || item.verdict.score > best.verdict.score) best = item;
|
|
1125
1624
|
}
|
|
1126
|
-
|
|
1127
|
-
if (typeof record !== "object" || record === null || typeof record.kind !== "string" || typeof record.tree !== "object" || record.tree === null || typeof record.tree.root !== "string") throw new Error(`supervisePursuit: ${path} is not a settle record (no kind or tree.root)`);
|
|
1128
|
-
return parsed;
|
|
1625
|
+
return best;
|
|
1129
1626
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1627
|
+
function spentOf(done) {
|
|
1628
|
+
return done.reduce((sum, item) => sum + (item.usd ?? 0), 0);
|
|
1629
|
+
}
|
|
1630
|
+
function canonicalStop(stop) {
|
|
1631
|
+
return {
|
|
1632
|
+
patience: stop.patience,
|
|
1633
|
+
maxVersions: stop.maxVersions,
|
|
1634
|
+
maxUsd: stop.maxUsd,
|
|
1635
|
+
deadlineMs: stop.deadlineMs,
|
|
1636
|
+
...stop.minImprovement === void 0 ? {} : { minImprovement: stop.minImprovement }
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
function resolveNamed(option, table, value, registry) {
|
|
1640
|
+
if (typeof value !== "string") return value;
|
|
1641
|
+
if (registry === void 0) throw new ConfigError(`supervisePursuit versions: ${option} = ${JSON.stringify(value)} names a registry entry, but no registry.${table} was provided`);
|
|
1642
|
+
const entry = registry.resolve(value);
|
|
1643
|
+
if (entry === void 0) throw new ConfigError(`supervisePursuit versions: ${option} = ${JSON.stringify(value)} is not in registry.${table}`);
|
|
1644
|
+
return entry;
|
|
1645
|
+
}
|
|
1646
|
+
async function readLedger(path) {
|
|
1133
1647
|
let text;
|
|
1134
1648
|
try {
|
|
1135
1649
|
text = await readFile(path, "utf8");
|
|
1136
1650
|
} catch (error) {
|
|
1137
|
-
if (isNoEntError(error)) return
|
|
1651
|
+
if (isNoEntError(error)) return [];
|
|
1138
1652
|
throw error;
|
|
1139
1653
|
}
|
|
1140
|
-
|
|
1654
|
+
return parseCommittedJsonLines(text, path);
|
|
1655
|
+
}
|
|
1656
|
+
async function appendLedger(path, line) {
|
|
1657
|
+
const endsWithRecord = await prepareJsonlAppend(path);
|
|
1658
|
+
const handle = await open(path, "a");
|
|
1141
1659
|
try {
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1660
|
+
await writeAllBytes(handle, `${endsWithRecord ? "\n" : ""}${JSON.stringify(line)}\n`);
|
|
1661
|
+
await handle.sync();
|
|
1662
|
+
} finally {
|
|
1663
|
+
await handle.close();
|
|
1145
1664
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
return
|
|
1665
|
+
}
|
|
1666
|
+
function isSha256(value) {
|
|
1667
|
+
return typeof value === "string" && /^sha256:[0-9a-f]{64}$/u.test(value);
|
|
1668
|
+
}
|
|
1669
|
+
function isFiniteNumber(value) {
|
|
1670
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
1671
|
+
}
|
|
1672
|
+
function abortMessage(signal) {
|
|
1673
|
+
const reason = signal.reason;
|
|
1674
|
+
return reason instanceof Error ? reason.message : String(reason ?? "aborted");
|
|
1149
1675
|
}
|
|
1150
1676
|
//#endregion
|
|
1151
1677
|
//#region src/durable/supervise-pursuit.ts
|
|
@@ -1189,12 +1715,14 @@ async function supervisePursuit(profile, task, opts) {
|
|
|
1189
1715
|
const runDir = opts.runDir.trim();
|
|
1190
1716
|
if (runDir.length === 0) throw new TypeError("supervisePursuit: runDir must be non-empty");
|
|
1191
1717
|
if (opts.retainedAtSettlement === "keep") throw new TypeError("supervisePursuit: retainedAtSettlement 'keep' cannot hold, because a settled pursuit is never re-entered");
|
|
1718
|
+
if (opts.versions !== void 0) return runPursuitVersions(profile, task, opts, supervisePursuit);
|
|
1192
1719
|
const observerPath = resolve(runDir, "observer.jsonl");
|
|
1193
1720
|
const settlePath = resolve(runDir, SETTLE_RECORD_FILE);
|
|
1194
1721
|
const failurePath = resolve(runDir, FAILURE_RECORD_FILE);
|
|
1195
|
-
const { pursuitId: _pursuitId, hooks, ...superviseOptions } = opts;
|
|
1722
|
+
const { pursuitId: _pursuitId, hooks, fork, versions: _versions, ...superviseOptions } = opts;
|
|
1196
1723
|
const runId = superviseOptions.runId ?? "supervise";
|
|
1197
1724
|
const now = superviseOptions.now ?? Date.now;
|
|
1725
|
+
const forked = fork === void 0 ? void 0 : await prepareRunFork(profile, task, superviseOptions, fork);
|
|
1198
1726
|
const lock = await acquireRunDirectoryLock(runDir, runId, now);
|
|
1199
1727
|
try {
|
|
1200
1728
|
const settled = await readSettleRecord(runDir);
|
|
@@ -1203,8 +1731,9 @@ async function supervisePursuit(profile, task, opts) {
|
|
|
1203
1731
|
await observer.journal.appendEvent(rootEvent(pursuitId, runId, "before", now()));
|
|
1204
1732
|
let result;
|
|
1205
1733
|
try {
|
|
1206
|
-
result = await supervise(profile, task, {
|
|
1734
|
+
result = await supervise(forked?.profile ?? profile, task, {
|
|
1207
1735
|
...superviseOptions,
|
|
1736
|
+
...forked === void 0 ? {} : { execution: forked.execution },
|
|
1208
1737
|
steerDir: superviseOptions.steerDir ?? supervisorRunDir(runDir, runId),
|
|
1209
1738
|
retainedAtSettlement: "release",
|
|
1210
1739
|
hooks: withPursuitContext(pursuitId, composeRuntimeHooks(observer.hooks, hooks))
|
|
@@ -1343,6 +1872,6 @@ function compareText(left, right) {
|
|
|
1343
1872
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
1344
1873
|
}
|
|
1345
1874
|
//#endregion
|
|
1346
|
-
export { FAILURE_RECORD_FILE, FileObserverJournal, ROOT_STREAM_FILE, RUN_DIRECTORY_LOCK_FILE, RunDirectoryLockedError, SETTLE_RECORD_FILE, SettledRunDirectoryError, SupervisePursuitError, acquireRunDirectoryLock, createFileObserverHooks, deriveExecutionId, discoverDurableSupervisionRun, handleChatTurn, observerRecordDigest, projectPursuit, readFailureRecord, readRootStream, readRootStreamReceipt, readRunDirectoryLock, readSettleRecord, runDirectoryHolderIsLive, settleRecordJson, supervisePursuit, verifyObserverRecords };
|
|
1875
|
+
export { FAILURE_RECORD_FILE, FORK_PARENT_UNCERTAIN_NODES_KEY, FileObserverJournal, PURSUIT_VERSIONS_FILE, ROOT_STREAM_FILE, RUN_DIRECTORY_LOCK_FILE, RUN_FORK_CORRELATION_KEYS, RunDirectoryLockedError, SETTLE_RECORD_FILE, SettledRunDirectoryError, SupervisePursuitError, acquireRunDirectoryLock, assertPursuitVersions, createFileObserverHooks, deriveExecutionId, discoverDurableSupervisionRun, handleChatTurn, observerRecordDigest, projectPursuit, pursuitVersionRun, readFailureRecord, readRootStream, readRootStreamReceipt, readRunDirectoryLock, readSettleRecord, runDirectoryHolderIsLive, settleRecordJson, supervisePursuit, verifyObserverRecords };
|
|
1347
1876
|
|
|
1348
1877
|
//# sourceMappingURL=durable.js.map
|