@tangle-network/agent-runtime 0.349.0 → 0.352.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/dist/{activation-wBipBkCf.d.ts → activation-BidzGkUQ.d.ts} +2 -24
- package/dist/{activation-tNGW73Y9.js → activation-C6zNC2kx.js} +12 -14
- package/dist/{activation-tNGW73Y9.js.map → activation-C6zNC2kx.js.map} +1 -1
- package/dist/agent.d.ts +8 -12
- package/dist/agent.js +9 -12
- package/dist/agent.js.map +1 -1
- package/dist/{analyst-loop-DZw5QWT9.js → analyst-loop-BZvXCDKy.js} +2 -3
- package/dist/{analyst-loop-DZw5QWT9.js.map → analyst-loop-BZvXCDKy.js.map} +1 -1
- package/dist/analyst-loop.d.ts +3 -5
- package/dist/analyst-loop.js +1 -2
- package/dist/{bundle-1xaJEgjq.js → bundle-DeDkJbsr.js} +3 -4
- package/dist/{bundle-1xaJEgjq.js.map → bundle-DeDkJbsr.js.map} +1 -1
- package/dist/candidate-execution/index.d.ts +13 -33
- package/dist/candidate-execution/index.js +16 -18
- package/dist/candidate-execution/index.js.map +1 -1
- package/dist/chat-turn.d.ts +2 -0
- package/dist/chat-turn.js +1 -0
- package/dist/{coordination-manager-B2Z_MmL9.js → coordination-manager-B7QadgrI.js} +101 -132
- package/dist/{coordination-manager-B2Z_MmL9.js.map → coordination-manager-B7QadgrI.js.map} +1 -1
- package/dist/{delegate-Dr-63jFl.js → delegate-CoscoyEo.js} +19 -21
- package/dist/delegate-CoscoyEo.js.map +1 -0
- package/dist/durable.d.ts +16 -149
- package/dist/durable.js +57 -980
- package/dist/durable.js.map +1 -1
- package/dist/{errors-DodWX-cb.js → errors-AzSuGI23.js} +5 -3
- package/dist/errors-AzSuGI23.js.map +1 -0
- package/dist/execution-handle-Bd2_nUB6.js +167 -0
- package/dist/execution-handle-Bd2_nUB6.js.map +1 -0
- package/dist/execution-handle-CujvqgCn.d.ts +130 -0
- package/dist/{graph-DPejYB3C.js → graph-mojMML8e.js} +192 -14
- package/dist/graph-mojMML8e.js.map +1 -0
- package/dist/{improve-DwSahPi1.js → improve-Dm5ZE8mr.js} +36 -53
- package/dist/{improve-DwSahPi1.js.map → improve-Dm5ZE8mr.js.map} +1 -1
- package/dist/{improvement-adapter-sIthWkcN.d.ts → improvement-adapter-D5GRU1eY.d.ts} +2 -6
- package/dist/index.d.ts +41 -89
- package/dist/index.js +45 -61
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +39 -59
- package/dist/intelligence.js +24 -33
- package/dist/intelligence.js.map +1 -1
- package/dist/journal-store-BJPMUydp.js +3925 -0
- package/dist/journal-store-BJPMUydp.js.map +1 -0
- package/dist/kernel.d.ts +5 -5
- package/dist/kernel.js +12 -11
- package/dist/{loop-runner-CP6MfO1b.js → loop-runner-5cL_vZi0.js} +5 -6
- package/dist/{loop-runner-CP6MfO1b.js.map → loop-runner-5cL_vZi0.js.map} +1 -1
- package/dist/{loop-runner-C6pGq-2l.d.ts → loop-runner-DyLD-aNb.d.ts} +3 -5
- package/dist/loop-runner-bin.d.ts +5 -8
- package/dist/loop-runner-bin.js +2 -3
- package/dist/mcp/bin.js +6 -8
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +8 -18
- package/dist/mcp/index.js +17 -25
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/memory-bin.js +4 -6
- package/dist/mcp/memory-bin.js.map +1 -1
- package/dist/{model-policy-CyCUwHed.js → model-policy-C-RGAUW4.js} +11 -13
- package/dist/{model-policy-CyCUwHed.js.map → model-policy-C-RGAUW4.js.map} +1 -1
- package/dist/placement.d.ts +12 -14
- package/dist/placement.js +1 -2
- package/dist/platform.d.ts +7 -13
- package/dist/platform.js +3 -6
- package/dist/platform.js.map +1 -1
- package/dist/{prepare-B9wWffjO.js → prepare-XWwsOh8O.js} +50 -69
- package/dist/{prepare-B9wWffjO.js.map → prepare-XWwsOh8O.js.map} +1 -1
- package/dist/{profile-JLJd2wFG.js → profile-C01SQZKa.js} +58 -92
- package/dist/profile-C01SQZKa.js.map +1 -0
- package/dist/{profile-materialization-bPm0gGLO.d.ts → profile-materialization-BRY22Oh8.d.ts} +1 -3
- package/dist/profiles.d.ts +25 -12
- package/dist/profiles.js +52 -11
- package/dist/profiles.js.map +1 -1
- package/dist/{protected-model-port-DVVh-hEk.d.ts → protected-model-port-IKqhTypD.d.ts} +2 -14
- package/dist/{protected-redaction-DFCeo4vg.js → protected-redaction-DilFZch5.js} +2 -3
- package/dist/{protected-redaction-DFCeo4vg.js.map → protected-redaction-DilFZch5.js.map} +1 -1
- package/dist/{provision-supervisor-C4v2vqDo.js → provision-supervisor-D9pO-iPl.js} +11 -15
- package/dist/{provision-supervisor-C4v2vqDo.js.map → provision-supervisor-D9pO-iPl.js.map} +1 -1
- package/dist/{run-cancellation-CzFhQGMa.js → run-cancellation-CoJRKvih.js} +6 -7
- package/dist/{run-cancellation-CzFhQGMa.js.map → run-cancellation-CoJRKvih.js.map} +1 -1
- package/dist/{runtime-CsvGCQpl.js → runtime-BJPXHWbc.js} +652 -215
- package/dist/runtime-BJPXHWbc.js.map +1 -0
- package/dist/{runtime-hooks-tXpAarhW.js → runtime-hooks-CsW2pfFk.js} +2 -3
- package/dist/{runtime-hooks-tXpAarhW.js.map → runtime-hooks-CsW2pfFk.js.map} +1 -1
- package/dist/{runtime-hooks-nsqxSUFL.d.ts → runtime-hooks-ZQqPw-0g.d.ts} +1 -3
- package/dist/{sandbox-profile-compat-DtuJWFrM.js → sandbox-profile-compat-CXjozdWL.js} +52 -653
- package/dist/sandbox-profile-compat-CXjozdWL.js.map +1 -0
- package/dist/{search-executor-CCb82Umc.js → search-executor-CQEHMXoT.js} +12 -15
- package/dist/{search-executor-CCb82Umc.js.map → search-executor-CQEHMXoT.js.map} +1 -1
- package/dist/{server-CKPwZU-I.js → server-U9NGgudQ.js} +19 -30
- package/dist/{server-CKPwZU-I.js.map → server-U9NGgudQ.js.map} +1 -1
- package/dist/{structural-rollout-DylC_fwU.js → structural-rollout-wMSxE7bn.js} +71 -70
- package/dist/structural-rollout-wMSxE7bn.js.map +1 -0
- package/dist/{substrate-CGDFTSRh.js → substrate-Db2pp6y3.js} +2 -3
- package/dist/{substrate-CGDFTSRh.js.map → substrate-Db2pp6y3.js.map} +1 -1
- package/dist/{supervise-BGfm2GlY.js → supervise-CNNhX0ab.js} +460 -195
- package/dist/supervise-CNNhX0ab.js.map +1 -0
- package/dist/supervision-discovery-CewADdEs.js +772 -0
- package/dist/supervision-discovery-CewADdEs.js.map +1 -0
- package/dist/{supervisor-wS9Ehz_x.js → supervisor-rK5Y8en6.js} +521 -4320
- package/dist/supervisor-rK5Y8en6.js.map +1 -0
- package/dist/{task-queue-0fKkoweu.d.ts → task-queue-kefGy79U.d.ts} +1 -3
- package/dist/task-queue.d.ts +1 -1
- package/dist/task-queue.js +1 -2
- package/dist/testing.d.ts +7 -15
- package/dist/testing.js +241 -244
- package/dist/testing.js.map +1 -1
- package/dist/tool-loop.d.ts +16 -19
- package/dist/tool-loop.js +2 -3
- package/dist/{tool-server-XV4aJwoQ.js → tool-server-CZWd_Opp.js} +3 -4
- package/dist/{tool-server-XV4aJwoQ.js.map → tool-server-CZWd_Opp.js.map} +1 -1
- package/dist/{top-snapshot-CflmaDgk.js → top-snapshot-DcV7VG4m.js} +179 -163
- package/dist/top-snapshot-DcV7VG4m.js.map +1 -0
- package/dist/tui/bin.js +50 -81
- package/dist/tui/bin.js.map +1 -1
- package/dist/tui/index.d.ts +27 -58
- package/dist/tui/index.js +2 -2
- package/dist/{types-C8BGOCBn.d.ts → types-0uzVKtFW.d.ts} +388 -484
- package/dist/{types-Cwrea-bU.d.ts → types-qsqfyQoY.d.ts} +1 -3
- package/dist/util-ebWMWzY0.js +624 -0
- package/dist/util-ebWMWzY0.js.map +1 -0
- package/dist/workspace-store/index.d.ts +12 -22
- package/dist/workspace-store/index.js +13 -10
- package/dist/workspace-store/index.js.map +1 -1
- package/dist/{workspace-streams-BiS4ABah.d.ts → workspace-streams-Dq2Fzk3c.d.ts} +1 -11
- package/package.json +31 -26
- package/skills/agent-graphs/SKILL.md +17 -4
- package/skills/be-ambitious/SKILL.md +46 -0
- package/skills/graph-designer/SKILL.md +9 -2
- package/dist/delegate-Dr-63jFl.js.map +0 -1
- package/dist/errors-DodWX-cb.js.map +0 -1
- package/dist/graph-DPejYB3C.js.map +0 -1
- package/dist/profile-JLJd2wFG.js.map +0 -1
- package/dist/record-events-D93DkpAx.js +0 -263
- package/dist/record-events-D93DkpAx.js.map +0 -1
- package/dist/runtime-CsvGCQpl.js.map +0 -1
- package/dist/sandbox-profile-compat-DtuJWFrM.js.map +0 -1
- package/dist/structural-rollout-DylC_fwU.js.map +0 -1
- package/dist/supervise-BGfm2GlY.js.map +0 -1
- package/dist/supervisor-wS9Ehz_x.js.map +0 -1
- package/dist/top-snapshot-CflmaDgk.js.map +0 -1
package/dist/durable.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { f as RuntimeRunStateError, m as ValidationError } from "./errors-
|
|
4
|
-
import {
|
|
5
|
-
import { a as withPursuitContext, t as composeRuntimeHooks } from "./runtime-hooks-
|
|
6
|
-
import { a as applyExactAgentProfileDiff } from "./profile-
|
|
7
|
-
import { a as superviseRootProfile,
|
|
8
|
-
import { a as runCancelRequestFile, o as runCancellationFile, v as supervisorRunDir, x as workerCancellationsDir } from "./run-cancellation-
|
|
9
|
-
import { i as superviseAgentGraph } from "./graph-
|
|
1
|
+
import { C as runGenerationPath, D as runtimeFailureCause, S as mutateRunClaim, Tt as assertValidBudget, _ as publishExclusiveDurableFile, a as isNoEntError, rt as contentAddress, t as FileSpawnJournal, v as syncDurableDirectory, w as runWithWriteClaim, x as fenceRunWrite, y as writeAtomicDurableFile } from "./journal-store-BJPMUydp.js";
|
|
2
|
+
import { Zr as authoredProfileDigest, fr as failedItems, i as RunParkedError, pr as isFiniteNumber, pt as loadSpawnForest, r as assertResumeLimits, s as readRunParking } from "./supervisor-rK5Y8en6.js";
|
|
3
|
+
import { f as RuntimeRunStateError, m as ValidationError } from "./errors-AzSuGI23.js";
|
|
4
|
+
import { c as errorMessage } from "./util-ebWMWzY0.js";
|
|
5
|
+
import { a as withPursuitContext, t as composeRuntimeHooks } from "./runtime-hooks-CsW2pfFk.js";
|
|
6
|
+
import { a as applyExactAgentProfileDiff } from "./profile-C01SQZKa.js";
|
|
7
|
+
import { a as superviseRootProfile, gt as readRootStreamReceipt, ht as readRootStream, t as supervise } from "./supervise-CNNhX0ab.js";
|
|
8
|
+
import { a as runCancelRequestFile, o as runCancellationFile, v as supervisorRunDir, x as workerCancellationsDir } from "./run-cancellation-CoJRKvih.js";
|
|
9
|
+
import { a as graphExecutionOptions, i as superviseAgentGraph } from "./graph-mojMML8e.js";
|
|
10
|
+
import { n as handleChatTurn, t as deriveExecutionId } from "./execution-handle-Bd2_nUB6.js";
|
|
11
|
+
import { a as observerRecordDigest, i as createFileObserverHooks, n as projectPursuit, r as FileObserverJournal, t as discoverDurableSupervisionRun } from "./supervision-discovery-CewADdEs.js";
|
|
10
12
|
import { agentProfileSchema, canonicalAgentProfileDigest, canonicalCandidateDigest, canonicalCandidateJson, sha256Bytes } from "@tangle-network/agent-interface";
|
|
11
|
-
import { isRecord } from "@tangle-network/agent-core";
|
|
12
|
-
import { createHash } from "node:crypto";
|
|
13
13
|
import { closeSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, unlinkSync } from "node:fs";
|
|
14
14
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
15
15
|
import { access, readFile, realpath } from "node:fs/promises";
|
|
@@ -17,876 +17,7 @@ import { SearchRecorder, developmentClaim, incumbent, openSearchLedger, parseSea
|
|
|
17
17
|
import { pathToFileURL } from "node:url";
|
|
18
18
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
19
19
|
import { lockHolderVerdict, lockIdentity, parseLockHolderRecord, readLockHolderIdentity } from "@tangle-network/agent-eval/lock-holder";
|
|
20
|
-
|
|
21
|
-
const encoder = new TextEncoder();
|
|
22
|
-
function encodeLine(event) {
|
|
23
|
-
return encoder.encode(`${JSON.stringify(event)}\n`);
|
|
24
|
-
}
|
|
25
|
-
function defaultLog(message, meta) {
|
|
26
|
-
if (meta) console.error(message, meta);
|
|
27
|
-
else console.error(message);
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Preserve the useful part of a thrown HTTP Response at the stream boundary.
|
|
31
|
-
*
|
|
32
|
-
* Product routes commonly throw a typed Response for an upstream failure. A
|
|
33
|
-
* Response is not an Error, so String(response) produces the unusable
|
|
34
|
-
* "[object Response]" and hides the status that decides the next action.
|
|
35
|
-
* Read a clone so the caller's response body remains available to its owner.
|
|
36
|
-
*/
|
|
37
|
-
async function thrownErrorMessage(error) {
|
|
38
|
-
if (!(error instanceof Response)) return error instanceof Error ? error.message : String(error);
|
|
39
|
-
const prefix = `HTTP ${error.status}`;
|
|
40
|
-
try {
|
|
41
|
-
const text = await error.clone().text();
|
|
42
|
-
if (!text.trim()) return prefix;
|
|
43
|
-
try {
|
|
44
|
-
const body = JSON.parse(text);
|
|
45
|
-
const nested = typeof body.error === "object" && body.error !== null ? body.error.message : body.error;
|
|
46
|
-
const message = typeof nested === "string" ? nested : typeof body.message === "string" ? body.message : void 0;
|
|
47
|
-
if (message) return `${prefix}: ${message.slice(0, 500)}`;
|
|
48
|
-
} catch {}
|
|
49
|
-
} catch {}
|
|
50
|
-
return prefix;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Run one chat turn. Returns immediately with a `ReadableStream` body;
|
|
54
|
-
* execution starts while the stream is constructed. Backend
|
|
55
|
-
* failures surface as `error` + `session.run.failed` events.
|
|
56
|
-
*/
|
|
57
|
-
function handleChatTurn(input) {
|
|
58
|
-
const log = input.log ?? defaultLog;
|
|
59
|
-
const { identity, hooks } = input;
|
|
60
|
-
return {
|
|
61
|
-
body: new ReadableStream({ start: async (controller) => {
|
|
62
|
-
const emit = async (event) => {
|
|
63
|
-
controller.enqueue(encodeLine(event));
|
|
64
|
-
if (hooks.onEvent) try {
|
|
65
|
-
await hooks.onEvent(event);
|
|
66
|
-
} catch (err) {
|
|
67
|
-
log("[chat-engine] onEvent hook threw", { error: err instanceof Error ? err.message : String(err) });
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
try {
|
|
71
|
-
await emit({
|
|
72
|
-
type: "session.run.started",
|
|
73
|
-
data: {
|
|
74
|
-
sessionId: identity.sessionId,
|
|
75
|
-
tenantId: identity.tenantId,
|
|
76
|
-
turnIndex: identity.turnIndex
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
const producer = hooks.produce();
|
|
80
|
-
let failureData;
|
|
81
|
-
for await (const event of producer.stream) {
|
|
82
|
-
if (event.type === "session.run.failed") {
|
|
83
|
-
if (!failureData) await emit({
|
|
84
|
-
type: "error",
|
|
85
|
-
data: event.data
|
|
86
|
-
});
|
|
87
|
-
failureData = {
|
|
88
|
-
...failureData,
|
|
89
|
-
...event.data
|
|
90
|
-
};
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (event.type === "error") failureData = {
|
|
94
|
-
...failureData,
|
|
95
|
-
...event.data
|
|
96
|
-
};
|
|
97
|
-
if (event.type === "session.run.completed") continue;
|
|
98
|
-
await emit(event);
|
|
99
|
-
}
|
|
100
|
-
const rawFinal = producer.finalText();
|
|
101
|
-
const finalText = hooks.transformFinalText ? await hooks.transformFinalText(rawFinal) : rawFinal;
|
|
102
|
-
await hooks.persistAssistantMessage({
|
|
103
|
-
identity,
|
|
104
|
-
finalText
|
|
105
|
-
});
|
|
106
|
-
if (hooks.onTurnComplete) try {
|
|
107
|
-
await hooks.onTurnComplete({
|
|
108
|
-
identity,
|
|
109
|
-
finalText
|
|
110
|
-
});
|
|
111
|
-
} catch (err) {
|
|
112
|
-
log("[chat-engine] onTurnComplete threw", { error: err instanceof Error ? err.message : String(err) });
|
|
113
|
-
}
|
|
114
|
-
await emit({
|
|
115
|
-
type: failureData ? "session.run.failed" : "session.run.completed",
|
|
116
|
-
data: {
|
|
117
|
-
...failureData,
|
|
118
|
-
sessionId: identity.sessionId
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
} catch (err) {
|
|
122
|
-
const message = await thrownErrorMessage(err);
|
|
123
|
-
log("[chat-engine] turn failed", { error: message });
|
|
124
|
-
await emit({
|
|
125
|
-
type: "error",
|
|
126
|
-
data: { message }
|
|
127
|
-
});
|
|
128
|
-
await emit({
|
|
129
|
-
type: "session.run.failed",
|
|
130
|
-
data: {
|
|
131
|
-
sessionId: identity.sessionId,
|
|
132
|
-
message
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
} finally {
|
|
136
|
-
if (hooks.traceFlush) {
|
|
137
|
-
const flush = hooks.traceFlush().catch((err) => log("[chat-engine] traceFlush threw", { error: err instanceof Error ? err.message : String(err) }));
|
|
138
|
-
if (input.waitUntil) input.waitUntil(flush);
|
|
139
|
-
else await flush;
|
|
140
|
-
}
|
|
141
|
-
controller.close();
|
|
142
|
-
}
|
|
143
|
-
} }),
|
|
144
|
-
contentType: "application/x-ndjson"
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
//#endregion
|
|
148
|
-
//#region src/durable/execution-handle.ts
|
|
149
|
-
/**
|
|
150
|
-
* Derive a stable execution id from the run identity.
|
|
151
|
-
* The same `(projectId, sessionId, turnIndex)` tuple yields the same id.
|
|
152
|
-
*
|
|
153
|
-
* Use the result as both `PromptOptions.executionId` and
|
|
154
|
-
* `PromptOptions.turnId` on the first dispatch.
|
|
155
|
-
* The execution id addresses the server-side execution for reconnect and
|
|
156
|
-
* replay; the turn id makes a repeated dispatch idempotent.
|
|
157
|
-
* An execution id alone does not make a repeated POST idempotent.
|
|
158
|
-
*
|
|
159
|
-
* Format is readable, not hashed: operators grepping orchestrator logs
|
|
160
|
-
* for `gtm-agent:thread-abc:3` find the run without translating an
|
|
161
|
-
* opaque id. Components are URL-encoded so delimiters inside caller ids
|
|
162
|
-
* cannot collapse distinct tuples. The final id is limited to the
|
|
163
|
-
* orchestrator replay route's 256-byte maximum. Execution ids are not a
|
|
164
|
-
* secrecy boundary.
|
|
165
|
-
*
|
|
166
|
-
* Wire integration:
|
|
167
|
-
* - Initial dispatch: pass the result as `executionId` and `turnId`.
|
|
168
|
-
* - Stream replay: pass it as `executionId` with `lastEventId`.
|
|
169
|
-
*
|
|
170
|
-
* @throws `TypeError` when either string id is blank.
|
|
171
|
-
* @throws `RangeError` when `turnIndex` is invalid or the result exceeds 256 bytes.
|
|
172
|
-
*/
|
|
173
|
-
function deriveExecutionId(input) {
|
|
174
|
-
if (input.projectId.trim().length === 0) throw new TypeError("projectId must be a non-empty string");
|
|
175
|
-
if (input.sessionId.trim().length === 0) throw new TypeError("sessionId must be a non-empty string");
|
|
176
|
-
if (!Number.isSafeInteger(input.turnIndex) || input.turnIndex < 0) throw new RangeError("turnIndex must be a non-negative safe integer");
|
|
177
|
-
const executionId = [
|
|
178
|
-
encodeURIComponent(input.projectId),
|
|
179
|
-
encodeURIComponent(input.sessionId),
|
|
180
|
-
String(input.turnIndex)
|
|
181
|
-
].join(":");
|
|
182
|
-
if (executionId.length > 256) throw new RangeError("derived execution id must not exceed 256 bytes");
|
|
183
|
-
return executionId;
|
|
184
|
-
}
|
|
185
|
-
//#endregion
|
|
186
|
-
//#region src/durable/observer-journal.ts
|
|
187
|
-
/**
|
|
188
|
-
* Durable, append-only third-person history for one concrete Runtime execution.
|
|
189
|
-
* It consumes Runtime's existing hook stream and does not participate in execution
|
|
190
|
-
* decisions. A broken observer therefore cannot change what an agent is allowed to do.
|
|
191
|
-
*
|
|
192
|
-
* The write discipline deliberately matches `FileSpawnJournal`: serialized appends,
|
|
193
|
-
* torn-tail recovery, short-write handling, and fsync before acknowledgement. One
|
|
194
|
-
* execution owns one journal file; higher-level pursuit aggregation joins isolated
|
|
195
|
-
* journals by `pursuitId` instead of making independent processes share a write head.
|
|
196
|
-
*/
|
|
197
|
-
var FileObserverJournal = class {
|
|
198
|
-
path;
|
|
199
|
-
pursuitId;
|
|
200
|
-
writeContext = captureRunWriteContext();
|
|
201
|
-
tail = Promise.resolve();
|
|
202
|
-
initialized = false;
|
|
203
|
-
sequence = 0;
|
|
204
|
-
previousDigest;
|
|
205
|
-
appendFailure;
|
|
206
|
-
constructor(path, pursuitId) {
|
|
207
|
-
const stableId = pursuitId.trim();
|
|
208
|
-
if (stableId.length === 0) throw new TypeError("FileObserverJournal: pursuitId must be non-empty");
|
|
209
|
-
this.path = resolve(path);
|
|
210
|
-
this.pursuitId = stableId;
|
|
211
|
-
}
|
|
212
|
-
hooks() {
|
|
213
|
-
return withPursuitContext(this.pursuitId, {
|
|
214
|
-
onEvent: (event) => this.appendEvent(event).then(() => void 0),
|
|
215
|
-
onDecisionPoint: (point) => this.appendDecision(point).then(() => void 0)
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
appendEvent(event) {
|
|
219
|
-
return this.enqueue("event", event);
|
|
220
|
-
}
|
|
221
|
-
appendDecision(point) {
|
|
222
|
-
return this.enqueue("decision", point);
|
|
223
|
-
}
|
|
224
|
-
async read() {
|
|
225
|
-
await this.tail;
|
|
226
|
-
this.assertComplete();
|
|
227
|
-
const records = [];
|
|
228
|
-
const verify = observerVerifier(this.pursuitId);
|
|
229
|
-
for await (const record of this.readExistingUnsafe()) {
|
|
230
|
-
verify(record);
|
|
231
|
-
records.push(record);
|
|
232
|
-
}
|
|
233
|
-
return Object.freeze(records);
|
|
234
|
-
}
|
|
235
|
-
enqueue(kind, value) {
|
|
236
|
-
if (value.pursuitId !== this.pursuitId) return Promise.reject(/* @__PURE__ */ new Error(`FileObserverJournal: ${kind} pursuitId ${String(value.pursuitId)} does not match ${this.pursuitId}`));
|
|
237
|
-
let snapshot;
|
|
238
|
-
try {
|
|
239
|
-
snapshot = detachedSnapshot(value, "observer record");
|
|
240
|
-
} catch (error) {
|
|
241
|
-
return Promise.reject(error);
|
|
242
|
-
}
|
|
243
|
-
let result;
|
|
244
|
-
const operation = this.tail.then(() => this.writeContext(async () => {
|
|
245
|
-
this.assertComplete();
|
|
246
|
-
try {
|
|
247
|
-
await this.initialize();
|
|
248
|
-
} catch (error) {
|
|
249
|
-
this.appendFailure ??= toError(error);
|
|
250
|
-
throw error;
|
|
251
|
-
}
|
|
252
|
-
const unsigned = {
|
|
253
|
-
schemaVersion: 1,
|
|
254
|
-
pursuitId: this.pursuitId,
|
|
255
|
-
sequence: this.sequence + 1,
|
|
256
|
-
kind,
|
|
257
|
-
observedAt: Date.now(),
|
|
258
|
-
...this.previousDigest ? { previousDigest: this.previousDigest } : {},
|
|
259
|
-
...kind === "event" ? { event: snapshot } : { decision: snapshot }
|
|
260
|
-
};
|
|
261
|
-
const record = Object.freeze({
|
|
262
|
-
...unsigned,
|
|
263
|
-
digest: observerRecordDigest(unsigned)
|
|
264
|
-
});
|
|
265
|
-
try {
|
|
266
|
-
const fs = await import("node:fs/promises");
|
|
267
|
-
const { dirname } = await import("node:path");
|
|
268
|
-
await fs.mkdir(dirname(this.path), { recursive: true });
|
|
269
|
-
await appendCommittedJsonLine(this.path, record);
|
|
270
|
-
} catch (error) {
|
|
271
|
-
this.appendFailure ??= toError(error);
|
|
272
|
-
throw error;
|
|
273
|
-
}
|
|
274
|
-
this.sequence = record.sequence;
|
|
275
|
-
this.previousDigest = record.digest;
|
|
276
|
-
result = record;
|
|
277
|
-
}));
|
|
278
|
-
this.tail = operation.then(() => void 0, () => void 0);
|
|
279
|
-
return operation.then(() => {
|
|
280
|
-
if (!result) throw new Error("FileObserverJournal: append completed without a record");
|
|
281
|
-
return result;
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
async initialize() {
|
|
285
|
-
if (this.initialized) return;
|
|
286
|
-
const verify = observerVerifier(this.pursuitId);
|
|
287
|
-
let tail;
|
|
288
|
-
for await (const record of this.readExistingUnsafe()) {
|
|
289
|
-
verify(record);
|
|
290
|
-
tail = record;
|
|
291
|
-
}
|
|
292
|
-
this.sequence = tail?.sequence ?? 0;
|
|
293
|
-
this.previousDigest = tail?.digest;
|
|
294
|
-
this.initialized = true;
|
|
295
|
-
}
|
|
296
|
-
readExistingUnsafe() {
|
|
297
|
-
return readCommittedJsonLines(this.path, { allowMissing: true });
|
|
298
|
-
}
|
|
299
|
-
assertComplete() {
|
|
300
|
-
if (!this.appendFailure) return;
|
|
301
|
-
throw new Error("FileObserverJournal: a prior durable append failed; observer completeness is unknown", { cause: this.appendFailure });
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
/** Verify identity, monotonic sequence, payload shape, and the complete digest chain. */
|
|
305
|
-
function verifyObserverRecords(records, pursuitId) {
|
|
306
|
-
const verify = observerVerifier(pursuitId);
|
|
307
|
-
for (const record of records) verify(record);
|
|
308
|
-
return Object.freeze([...records]);
|
|
309
|
-
}
|
|
310
|
-
/** The same full chain validation is used for replay, startup and in-memory evidence. */
|
|
311
|
-
function observerVerifier(pursuitId) {
|
|
312
|
-
let previousDigest;
|
|
313
|
-
let expectedSequence = 1;
|
|
314
|
-
return (record) => {
|
|
315
|
-
if (record.schemaVersion !== 1) throw new Error("observer journal: unsupported schemaVersion");
|
|
316
|
-
if (pursuitId !== void 0 && record.pursuitId !== pursuitId) throw new Error(`observer journal: pursuit identity mismatch at sequence ${record.sequence}`);
|
|
317
|
-
if (record.sequence !== expectedSequence) throw new Error(`observer journal: non-contiguous sequence ${record.sequence}; expected ${expectedSequence}`);
|
|
318
|
-
if (record.previousDigest !== previousDigest) throw new Error(`observer journal: digest-chain break at sequence ${record.sequence}`);
|
|
319
|
-
if (record.kind === "event" === (record.event === void 0)) throw new Error(`observer journal: invalid event payload at sequence ${record.sequence}`);
|
|
320
|
-
if (record.kind === "decision" === (record.decision === void 0)) throw new Error(`observer journal: invalid decision payload at sequence ${record.sequence}`);
|
|
321
|
-
if (record.event !== void 0 && record.event.pursuitId !== record.pursuitId) throw new Error(`observer journal: nested event pursuit mismatch at sequence ${record.sequence}`);
|
|
322
|
-
if (record.decision !== void 0 && record.decision.pursuitId !== record.pursuitId) throw new Error(`observer journal: nested decision pursuit mismatch at sequence ${record.sequence}`);
|
|
323
|
-
const { digest, ...unsigned } = record;
|
|
324
|
-
if (digest !== observerRecordDigest(unsigned)) throw new Error(`observer journal: digest mismatch at sequence ${record.sequence}`);
|
|
325
|
-
previousDigest = digest;
|
|
326
|
-
expectedSequence += 1;
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
/** Compute the canonical SHA-256 digest for an unsigned observer record. */
|
|
330
|
-
function observerRecordDigest(record) {
|
|
331
|
-
return createHash("sha256").update(JSON.stringify(record)).digest("hex");
|
|
332
|
-
}
|
|
333
|
-
/** Build the canonical durable observer hook in one call. */
|
|
334
|
-
function createFileObserverHooks(path, pursuitId) {
|
|
335
|
-
const journal = new FileObserverJournal(path, pursuitId);
|
|
336
|
-
return {
|
|
337
|
-
journal,
|
|
338
|
-
hooks: journal.hooks()
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
function toError(error) {
|
|
342
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
343
|
-
}
|
|
344
|
-
//#endregion
|
|
345
|
-
//#region src/durable/observer-projection.ts
|
|
346
|
-
/**
|
|
347
|
-
* Fold one append-only execution journal into a deterministic operator projection.
|
|
348
|
-
*
|
|
349
|
-
* This is intentionally a READ model, not another state machine: it does not own
|
|
350
|
-
* execution, cannot steer agents, and can be rebuilt from the journal at any time.
|
|
351
|
-
* Projection verifies the complete hash chain first, so an operator view can never
|
|
352
|
-
* silently render a mutated or reordered observer history as trustworthy state.
|
|
353
|
-
*
|
|
354
|
-
* Topology comes only from Runtime's canonical `agent.spawn` facts. Terminal node
|
|
355
|
-
* state comes only from `agent.child`; concrete run state comes only from the root
|
|
356
|
-
* `agent.run` lifecycle emitted by `supervisePursuit`, one row per attempt. Node identity is
|
|
357
|
-
* scoped to the concrete Runtime run so independent trees may both contain `root:s0` without
|
|
358
|
-
* aliasing, and a node belongs to the attempt that spawned it.
|
|
359
|
-
*
|
|
360
|
-
* Usage, cost and timing are reported at the class the runtime measured them at. A missing
|
|
361
|
-
* class stays ABSENT and the run names the node in `spendGaps`; nothing here converts an
|
|
362
|
-
* unmeasured channel into a zero, because a fabricated zero is indistinguishable from free work.
|
|
363
|
-
*/
|
|
364
|
-
function projectPursuit(records) {
|
|
365
|
-
if (records.length === 0) throw new TypeError("projectPursuit: at least one observer record is required");
|
|
366
|
-
const pursuitId = records[0].pursuitId;
|
|
367
|
-
const verified = verifyObserverRecords(records, pursuitId);
|
|
368
|
-
const attempts = /* @__PURE__ */ new Map();
|
|
369
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
370
|
-
let eventCount = 0;
|
|
371
|
-
let decisionCount = 0;
|
|
372
|
-
for (const record of verified) {
|
|
373
|
-
const observed = record.event ?? record.decision;
|
|
374
|
-
if (!observed) throw new Error(`projectPursuit: record ${record.sequence} has no observation`);
|
|
375
|
-
const run = currentAttempt(attempts, observed.runId, record);
|
|
376
|
-
run.lastSequence = record.sequence;
|
|
377
|
-
run.lastObservedAt = record.observedAt;
|
|
378
|
-
if (record.event) {
|
|
379
|
-
eventCount += 1;
|
|
380
|
-
run.eventCount += 1;
|
|
381
|
-
increment(run.targets, record.event.target);
|
|
382
|
-
projectRunActivity(run, record);
|
|
383
|
-
projectSpawnNode(nodes, record, run.attemptIndex);
|
|
384
|
-
projectNodeActivity(nodes, record);
|
|
385
|
-
projectSeat(run, nodes, record);
|
|
386
|
-
projectTurn(run, nodes, record);
|
|
387
|
-
} else if (record.decision) {
|
|
388
|
-
decisionCount += 1;
|
|
389
|
-
run.decisionCount += 1;
|
|
390
|
-
increment(run.decisions, record.decision.kind);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
const byAttempt = /* @__PURE__ */ new Map();
|
|
394
|
-
for (const node of nodes.values()) {
|
|
395
|
-
const key = attemptKey(node.runId, node.attemptIndex);
|
|
396
|
-
const list = byAttempt.get(key);
|
|
397
|
-
if (list) list.push(node);
|
|
398
|
-
else byAttempt.set(key, [node]);
|
|
399
|
-
}
|
|
400
|
-
const first = verified[0];
|
|
401
|
-
const last = verified.at(-1);
|
|
402
|
-
return Object.freeze({
|
|
403
|
-
pursuitId,
|
|
404
|
-
sequence: last.sequence,
|
|
405
|
-
chainTip: last.digest,
|
|
406
|
-
firstObservedAt: first.observedAt,
|
|
407
|
-
lastObservedAt: last.observedAt,
|
|
408
|
-
runs: Object.freeze([...attempts.values()].flat().sort((a, b) => a.firstSequence - b.firstSequence || a.runId.localeCompare(b.runId)).map((run) => freezeRun(run, byAttempt.get(attemptKey(run.runId, run.attemptIndex)) ?? []))),
|
|
409
|
-
nodes: Object.freeze([...nodes.values()].sort((a, b) => a.firstSequence - b.firstSequence || a.runId.localeCompare(b.runId) || a.id.localeCompare(b.id)).map(freezeNode)),
|
|
410
|
-
eventCount,
|
|
411
|
-
decisionCount
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
function projectSeat(run, nodes, record) {
|
|
415
|
-
const event = record.event;
|
|
416
|
-
if (event?.target !== "agent.seat") return;
|
|
417
|
-
const payload = objectRecord(event.payload);
|
|
418
|
-
const phase = stringField(payload, "phase");
|
|
419
|
-
const owner = nodes.get(nodeKey(event.runId, stringField(payload, "nodeId") ?? "")) ?? run;
|
|
420
|
-
if (phase === "paused") {
|
|
421
|
-
const resumeAt = stringField(payload, "resumeAt");
|
|
422
|
-
if (resumeAt) owner.resumeAt = resumeAt;
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
if (phase === "resumed") {
|
|
426
|
-
delete owner.resumeAt;
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
const raw = objectRecord(payload?.segment);
|
|
430
|
-
const seat = stringField(raw, "seat");
|
|
431
|
-
const harness = stringField(raw, "harness");
|
|
432
|
-
const provider = stringField(raw, "provider");
|
|
433
|
-
const model = stringField(raw, "model");
|
|
434
|
-
const startedAt = stringField(raw, "startedAt");
|
|
435
|
-
if (!seat || !harness || !provider || !model || !startedAt) return;
|
|
436
|
-
if (owner.segments === void 0) owner.segments = [];
|
|
437
|
-
const segments = owner.segments;
|
|
438
|
-
if (phase === "started") {
|
|
439
|
-
if (!segments.some((segment) => segment.startedAt === startedAt && segment.seat === seat)) segments.push({
|
|
440
|
-
seat,
|
|
441
|
-
harness,
|
|
442
|
-
provider,
|
|
443
|
-
model,
|
|
444
|
-
startedAt
|
|
445
|
-
});
|
|
446
|
-
} else if (phase === "ended") {
|
|
447
|
-
const endedAt = stringField(raw, "endedAt");
|
|
448
|
-
const reason = stringField(raw, "reason");
|
|
449
|
-
if (!endedAt || !reason) return;
|
|
450
|
-
const index = segments.findIndex((segment) => segment.startedAt === startedAt && segment.seat === seat);
|
|
451
|
-
if (index >= 0) segments[index] = {
|
|
452
|
-
seat,
|
|
453
|
-
harness,
|
|
454
|
-
provider,
|
|
455
|
-
model,
|
|
456
|
-
startedAt,
|
|
457
|
-
endedAt,
|
|
458
|
-
reason
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
function attemptKey(runId, attemptIndex) {
|
|
463
|
-
return `${runId}\u0000${attemptIndex}`;
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* The attempt a record belongs to. An `agent.run` `before` opens a new attempt when the run has
|
|
467
|
-
* none or its latest attempt already settled; on an open attempt it is a resume. Every other
|
|
468
|
-
* record joins the latest attempt, and a journal that never emitted `before` gets attempt 0.
|
|
469
|
-
*/
|
|
470
|
-
function currentAttempt(attempts, runId, record) {
|
|
471
|
-
const list = attempts.get(runId) ?? [];
|
|
472
|
-
if (list.length === 0) attempts.set(runId, list);
|
|
473
|
-
const latest = list.at(-1);
|
|
474
|
-
const isStart = record.event?.target === "agent.run" && record.event.phase === "before";
|
|
475
|
-
if (latest !== void 0 && (!isStart || latest.status === "running")) {
|
|
476
|
-
if (isStart) {
|
|
477
|
-
if (latest.started) latest.resumeCount += 1;
|
|
478
|
-
latest.started = true;
|
|
479
|
-
}
|
|
480
|
-
return latest;
|
|
481
|
-
}
|
|
482
|
-
const created = {
|
|
483
|
-
runId,
|
|
484
|
-
attemptIndex: list.length,
|
|
485
|
-
resumeCount: 0,
|
|
486
|
-
started: isStart,
|
|
487
|
-
status: "running",
|
|
488
|
-
firstSequence: record.sequence,
|
|
489
|
-
lastSequence: record.sequence,
|
|
490
|
-
firstObservedAt: record.observedAt,
|
|
491
|
-
lastObservedAt: record.observedAt,
|
|
492
|
-
eventCount: 0,
|
|
493
|
-
decisionCount: 0,
|
|
494
|
-
targets: {},
|
|
495
|
-
decisions: {}
|
|
496
|
-
};
|
|
497
|
-
list.push(created);
|
|
498
|
-
return created;
|
|
499
|
-
}
|
|
500
|
-
function projectRunActivity(run, record) {
|
|
501
|
-
const event = record.event;
|
|
502
|
-
if (event?.target !== "agent.run") return;
|
|
503
|
-
const payload = objectRecord(event.payload);
|
|
504
|
-
const status = stringField(payload, "status");
|
|
505
|
-
if (event.phase === "after" || status === "done") {
|
|
506
|
-
run.status = "done";
|
|
507
|
-
run.settledAt = record.observedAt;
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
if (event.phase !== "error" && status !== "failed") return;
|
|
511
|
-
run.status = "down";
|
|
512
|
-
run.settledAt = record.observedAt;
|
|
513
|
-
const error = stringField(payload, "error");
|
|
514
|
-
if (error) run.error = error;
|
|
515
|
-
}
|
|
516
|
-
function nodeKey(runId, nodeId) {
|
|
517
|
-
return `${runId}\u0000${nodeId}`;
|
|
518
|
-
}
|
|
519
|
-
function projectSpawnNode(nodes, record, attemptIndex) {
|
|
520
|
-
const event = record.event;
|
|
521
|
-
if (event?.target !== "agent.spawn") return;
|
|
522
|
-
const payload = objectRecord(event.payload);
|
|
523
|
-
const childId = stringField(payload, "childId");
|
|
524
|
-
if (!childId) return;
|
|
525
|
-
const key = nodeKey(event.runId, childId);
|
|
526
|
-
const existing = nodes.get(key);
|
|
527
|
-
if (existing) {
|
|
528
|
-
existing.lastSequence = record.sequence;
|
|
529
|
-
existing.lastObservedAt = record.observedAt;
|
|
530
|
-
existing.eventCount += 1;
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
const label = stringField(payload, "label");
|
|
534
|
-
const runtime = stringField(payload, "runtime");
|
|
535
|
-
const depth = numberField(payload, "depth");
|
|
536
|
-
const assignmentId = stringField(payload, "assignmentId");
|
|
537
|
-
const successorOf = stringField(payload, "successorOf");
|
|
538
|
-
const attemptId = stringField(payload, "attemptId");
|
|
539
|
-
const startedAt = numberField(payload, "startedAt");
|
|
540
|
-
nodes.set(key, {
|
|
541
|
-
id: childId,
|
|
542
|
-
...event.parentId ? { parentId: event.parentId } : {},
|
|
543
|
-
runId: event.runId,
|
|
544
|
-
attemptIndex,
|
|
545
|
-
...label ? { label } : {},
|
|
546
|
-
...runtime ? { runtime } : {},
|
|
547
|
-
...depth !== void 0 ? { depth } : {},
|
|
548
|
-
...assignmentId ? { assignmentId } : {},
|
|
549
|
-
...successorOf ? { successorOf } : {},
|
|
550
|
-
...attemptId ? { attemptId } : {},
|
|
551
|
-
startedAt: startedAt ?? record.observedAt,
|
|
552
|
-
...payload && Object.hasOwn(payload, "identity") ? { identity: payload.identity } : {},
|
|
553
|
-
...payload && Object.hasOwn(payload, "budget") ? { budget: payload.budget } : {},
|
|
554
|
-
status: "running",
|
|
555
|
-
modelCalls: [],
|
|
556
|
-
firstSequence: record.sequence,
|
|
557
|
-
lastSequence: record.sequence,
|
|
558
|
-
firstObservedAt: record.observedAt,
|
|
559
|
-
lastObservedAt: record.observedAt,
|
|
560
|
-
eventCount: 1,
|
|
561
|
-
turnCount: 0
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
function projectNodeActivity(nodes, record) {
|
|
565
|
-
const event = record.event;
|
|
566
|
-
if (event === void 0) return;
|
|
567
|
-
if (event.target === "agent.spawn") return;
|
|
568
|
-
const payload = objectRecord(event.payload);
|
|
569
|
-
const nodeId = stringField(payload, "childId") ?? stringField(payload, "nodeId") ?? stringField(payload, "workerId");
|
|
570
|
-
if (!nodeId) return;
|
|
571
|
-
const node = nodes.get(nodeKey(event.runId, nodeId));
|
|
572
|
-
if (!node) return;
|
|
573
|
-
node.lastSequence = record.sequence;
|
|
574
|
-
node.lastObservedAt = record.observedAt;
|
|
575
|
-
node.eventCount += 1;
|
|
576
|
-
if (event.target !== "agent.child") return;
|
|
577
|
-
const status = stringField(payload, "status");
|
|
578
|
-
if (status !== "done" && status !== "down") return;
|
|
579
|
-
node.status = status;
|
|
580
|
-
node.settledAt = numberField(payload, "settledAt") ?? record.observedAt;
|
|
581
|
-
const startedAt = numberField(payload, "startedAt");
|
|
582
|
-
if (startedAt !== void 0) node.startedAt = startedAt;
|
|
583
|
-
if (payload && Object.hasOwn(payload, "spent")) node.spent = spendField(payload, "spent");
|
|
584
|
-
const metered = spendField(payload, "metered");
|
|
585
|
-
if (metered) node.ownInference = node.ownInference ? addSpend(node.ownInference, metered) : metered;
|
|
586
|
-
const runtime = stringField(payload, "runtime");
|
|
587
|
-
if (runtime) node.runtime = runtime;
|
|
588
|
-
const outRef = stringField(payload, "outRef");
|
|
589
|
-
if (outRef) node.outRef = outRef;
|
|
590
|
-
const score = numberField(payload, "score");
|
|
591
|
-
if (score !== void 0) node.score = score;
|
|
592
|
-
const valid = booleanField(payload, "valid");
|
|
593
|
-
if (valid !== void 0) node.valid = valid;
|
|
594
|
-
const reason = stringField(payload, "reason");
|
|
595
|
-
if (reason) node.reason = reason;
|
|
596
|
-
const infra = booleanField(payload, "infra");
|
|
597
|
-
if (infra !== void 0) node.infra = infra;
|
|
598
|
-
const retained = stringField(payload, "retainedExecution");
|
|
599
|
-
if (retained === "pending" || retained === "released" || retained === "release-unconfirmed") node.retainedExecution = retained;
|
|
600
|
-
const cause = stringField(payload, "retainedPendingCause");
|
|
601
|
-
if (cause === "unobservable" || cause === "provider-contract" || cause === "request-rejected" || cause === "transport" || cause === "nested-recovery") node.retainedPendingCause = cause;
|
|
602
|
-
const releasedAt = numberField(payload, "releasedAt");
|
|
603
|
-
if (releasedAt !== void 0) node.releasedAt = releasedAt;
|
|
604
|
-
const budgetViolation = budgetViolationField(payload);
|
|
605
|
-
if (budgetViolation) node.budgetViolation = budgetViolation;
|
|
606
|
-
attachSettlementEvidence(node, payload);
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Read the receipts a settlement carries. Each is taken as reported: an `unknown` receipt keeps
|
|
610
|
-
* its `status` and `reason` so a client can show WHY a model or backend is missing instead of
|
|
611
|
-
* showing nothing, and a receipt that never landed leaves every derived field absent.
|
|
612
|
-
*/
|
|
613
|
-
function attachSettlementEvidence(node, payload) {
|
|
614
|
-
const providerModel = payload?.providerModel;
|
|
615
|
-
if (providerModel && typeof providerModel === "object") node.providerModel = providerModel;
|
|
616
|
-
const trace = payload?.trace;
|
|
617
|
-
if (trace && typeof trace === "object") node.trace = trace;
|
|
618
|
-
const bindings = payload?.executionBindings;
|
|
619
|
-
if (Array.isArray(bindings) && bindings.length > 0) {
|
|
620
|
-
node.executionBindings = bindings;
|
|
621
|
-
const known = bindings.find((binding) => objectRecord(binding)?.status === "known");
|
|
622
|
-
if (known) {
|
|
623
|
-
node.placement = known.descriptor;
|
|
624
|
-
node.attemptId ??= known.attemptId;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
const receipt = payload?.materialization;
|
|
628
|
-
if (!receipt || typeof receipt !== "object") return;
|
|
629
|
-
node.materialization = receipt;
|
|
630
|
-
if (receipt.status !== "known") return;
|
|
631
|
-
node.backend = receipt.backend;
|
|
632
|
-
if (receipt.model.status === "known") node.model = receipt.model.id;
|
|
633
|
-
node.execution = {
|
|
634
|
-
kind: receipt.execution.kind,
|
|
635
|
-
id: receipt.execution.id
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Fold one metered turn onto whoever drove it. `agent.turn` names its subject in `parentId` — the
|
|
640
|
-
* node making the call — not in the payload, because the caller IS the subject. A turn the run
|
|
641
|
-
* root drove belongs to no spawned node and lands on the run instead, so the run's totals stay
|
|
642
|
-
* complete without inventing a node for the root.
|
|
643
|
-
*/
|
|
644
|
-
function projectTurn(run, nodes, record) {
|
|
645
|
-
const event = record.event;
|
|
646
|
-
if (event?.target !== "agent.turn") return;
|
|
647
|
-
const payload = objectRecord(event.payload);
|
|
648
|
-
const spend = spendField(payload, "spend");
|
|
649
|
-
const subject = event.parentId;
|
|
650
|
-
if (subject === void 0) return;
|
|
651
|
-
const node = nodes.get(nodeKey(event.runId, subject));
|
|
652
|
-
if (!node) {
|
|
653
|
-
if (!spend) return;
|
|
654
|
-
run.rootInference = run.rootInference ? addSpend(run.rootInference, spend) : spend;
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
node.turnCount += 1;
|
|
658
|
-
node.lastSequence = record.sequence;
|
|
659
|
-
node.lastObservedAt = record.observedAt;
|
|
660
|
-
if (spend) {
|
|
661
|
-
node.ownInference = node.ownInference ? addSpend(node.ownInference, spend) : spend;
|
|
662
|
-
node.firstOutputAt ??= record.observedAt;
|
|
663
|
-
}
|
|
664
|
-
const reasoning = numberField(payload, "reasoningTokens");
|
|
665
|
-
if (reasoning !== void 0) node.reasoningTokens = (node.reasoningTokens ?? 0) + reasoning;
|
|
666
|
-
const firstTokenAt = numberField(payload, "firstTokenAt");
|
|
667
|
-
if (firstTokenAt !== void 0) node.firstTokenAt ??= firstTokenAt;
|
|
668
|
-
const model = stringField(payload, "model");
|
|
669
|
-
if (model) node.model ??= model;
|
|
670
|
-
const callId = stringField(payload, "callId");
|
|
671
|
-
if (callId && !node.modelCalls.includes(callId)) node.modelCalls.push(callId);
|
|
672
|
-
}
|
|
673
|
-
/**
|
|
674
|
-
* A node's whole reported cost: the child work its settlement reported plus the inference it drove
|
|
675
|
-
* itself. `undefined` when neither landed — the run's `spendGaps` then names the node, so an
|
|
676
|
-
* unaccounted node is visible instead of reading as free.
|
|
677
|
-
*/
|
|
678
|
-
function nodeTotal(node) {
|
|
679
|
-
if (node.spent && node.ownInference) return addSpend(node.spent, node.ownInference);
|
|
680
|
-
if (node.spent) return cloneSpend(node.spent);
|
|
681
|
-
if (node.ownInference) return cloneSpend(node.ownInference);
|
|
682
|
-
}
|
|
683
|
-
/**
|
|
684
|
-
* The run counted once, and each node's own share of it.
|
|
685
|
-
*
|
|
686
|
-
* A settled node's `spent` already contains the child work its nested tree reported, so summing
|
|
687
|
-
* every node would count a driver's descendants twice. Inclusive therefore sums only the run's
|
|
688
|
-
* TOP-LEVEL nodes — those whose parent is not another node of this run — plus the root's own
|
|
689
|
-
* turns. Exclusive subtracts each node's direct children from its own total, which telescopes:
|
|
690
|
-
* the exclusive entries sum back to `inclusive` exactly.
|
|
691
|
-
*/
|
|
692
|
-
function runTotals(run, nodes) {
|
|
693
|
-
const present = new Set(nodes.map((node) => node.id));
|
|
694
|
-
const children = /* @__PURE__ */ new Map();
|
|
695
|
-
for (const node of nodes) {
|
|
696
|
-
if (node.parentId === void 0 || !present.has(node.parentId)) continue;
|
|
697
|
-
const list = children.get(node.parentId);
|
|
698
|
-
if (list) list.push(node);
|
|
699
|
-
else children.set(node.parentId, [node]);
|
|
700
|
-
}
|
|
701
|
-
const exclusiveByNode = {};
|
|
702
|
-
let inclusive = run.rootInference ? cloneSpend(run.rootInference) : zeroSpend();
|
|
703
|
-
if (run.rootInference) exclusiveByNode[run.runId] = cloneSpend(run.rootInference);
|
|
704
|
-
for (const node of nodes) {
|
|
705
|
-
const total = nodeTotal(node);
|
|
706
|
-
if (total === void 0) continue;
|
|
707
|
-
if (node.parentId === void 0 || !present.has(node.parentId)) inclusive = addSpend(inclusive, total);
|
|
708
|
-
let exclusive = total;
|
|
709
|
-
for (const child of children.get(node.id) ?? []) {
|
|
710
|
-
const childTotal = nodeTotal(child);
|
|
711
|
-
if (childTotal !== void 0) exclusive = subtractSpend(exclusive, childTotal);
|
|
712
|
-
}
|
|
713
|
-
exclusiveByNode[node.id] = exclusive;
|
|
714
|
-
}
|
|
715
|
-
return Object.freeze({
|
|
716
|
-
inclusive: Object.freeze(inclusive),
|
|
717
|
-
exclusiveByNode: Object.freeze(exclusiveByNode)
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* Remove a child's reported total from its parent's. Per channel, never below zero: a parent whose
|
|
722
|
-
* executor reported less than its children did is a reporting gap, and a negative exclusive share
|
|
723
|
-
* would be a fabricated number rather than a measurement.
|
|
724
|
-
*/
|
|
725
|
-
function subtractSpend(a, b) {
|
|
726
|
-
const tokens = { ...a.tokens };
|
|
727
|
-
tokens.input = Math.max(0, tokens.input - b.tokens.input);
|
|
728
|
-
tokens.output = Math.max(0, tokens.output - b.tokens.output);
|
|
729
|
-
if (a.tokens.cacheRead !== void 0) tokens.cacheRead = Math.max(0, a.tokens.cacheRead - (b.tokens.cacheRead ?? 0));
|
|
730
|
-
if (a.tokens.cacheWrite !== void 0) tokens.cacheWrite = Math.max(0, a.tokens.cacheWrite - (b.tokens.cacheWrite ?? 0));
|
|
731
|
-
if (a.tokens.freshInput !== void 0) tokens.freshInput = Math.max(0, a.tokens.freshInput - (b.tokens.freshInput ?? 0));
|
|
732
|
-
return {
|
|
733
|
-
iterations: Math.max(0, a.iterations - b.iterations),
|
|
734
|
-
tokens,
|
|
735
|
-
...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {},
|
|
736
|
-
usd: Math.max(0, a.usd - b.usd),
|
|
737
|
-
...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
|
|
738
|
-
...a.usdEstimated !== void 0 ? { usdEstimated: Math.max(0, a.usdEstimated - (b.usdEstimated ?? 0)) } : {},
|
|
739
|
-
ms: Math.max(0, a.ms - b.ms)
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
/** The nodes of one run whose accounting is incomplete, in the vocabulary the supervisor already
|
|
743
|
-
* uses: `never-settled` = no terminal record, so every channel is unaccounted; `unreported` = a
|
|
744
|
-
* record landed with a channel the provider did not report, so that channel is a floor. */
|
|
745
|
-
function runSpendGaps(nodes) {
|
|
746
|
-
const gaps = [];
|
|
747
|
-
for (const node of nodes) {
|
|
748
|
-
const label = node.label;
|
|
749
|
-
if (node.status === "running") {
|
|
750
|
-
gaps.push({
|
|
751
|
-
id: node.id,
|
|
752
|
-
...label !== void 0 ? { label } : {},
|
|
753
|
-
kind: "never-settled",
|
|
754
|
-
channels: ["tokens", "usd"]
|
|
755
|
-
});
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
const total = nodeTotal(node);
|
|
759
|
-
const channels = [];
|
|
760
|
-
if (total === void 0 || total.tokensKnown === false) channels.push("tokens");
|
|
761
|
-
if (total === void 0 || total.usdKnown === false) channels.push("usd");
|
|
762
|
-
if (channels.length === 0) continue;
|
|
763
|
-
gaps.push({
|
|
764
|
-
id: node.id,
|
|
765
|
-
...label !== void 0 ? { label } : {},
|
|
766
|
-
kind: "unreported",
|
|
767
|
-
channels
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
return gaps;
|
|
771
|
-
}
|
|
772
|
-
function usageOf(total, reasoning) {
|
|
773
|
-
return {
|
|
774
|
-
input: total.tokens.input,
|
|
775
|
-
output: total.tokens.output,
|
|
776
|
-
...total.tokens.cacheRead !== void 0 ? { cacheRead: total.tokens.cacheRead } : {},
|
|
777
|
-
...total.tokens.cacheWrite !== void 0 ? { cacheWrite: total.tokens.cacheWrite } : {},
|
|
778
|
-
...reasoning !== void 0 ? { reasoning } : {},
|
|
779
|
-
tokensKnown: total.tokensKnown !== false && total.tokens.tokensKnown !== false
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
function costOf(total) {
|
|
783
|
-
const usdKnown = total.usdKnown !== false;
|
|
784
|
-
const estimated = total.usdEstimated;
|
|
785
|
-
const provenance = usdKnown ? estimated === void 0 || estimated === 0 ? "reported" : "partial" : estimated === void 0 ? "unknown" : estimated >= total.usd ? "estimated" : "partial";
|
|
786
|
-
return {
|
|
787
|
-
usd: total.usd,
|
|
788
|
-
usdKnown,
|
|
789
|
-
...estimated !== void 0 ? { usdEstimated: estimated } : {},
|
|
790
|
-
provenance
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
/** The platform block, or nothing at all when the node's spend names no box channel. */
|
|
794
|
-
function platformOf(total) {
|
|
795
|
-
if (total.boxMinutesProvenance === void 0) return void 0;
|
|
796
|
-
return {
|
|
797
|
-
...total.boxMinutes !== void 0 ? { boxMinutes: total.boxMinutes } : {},
|
|
798
|
-
boxMinutesKnown: total.boxMinutesKnown === true,
|
|
799
|
-
provenance: total.boxMinutesProvenance
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
function timingOf(node) {
|
|
803
|
-
if (node.startedAt === void 0) return void 0;
|
|
804
|
-
return {
|
|
805
|
-
startedAt: node.startedAt,
|
|
806
|
-
...node.firstOutputAt !== void 0 ? { firstOutputAt: node.firstOutputAt } : {},
|
|
807
|
-
...node.firstTokenAt !== void 0 ? { firstTokenAt: node.firstTokenAt } : {},
|
|
808
|
-
...node.settledAt !== void 0 ? { settledAt: node.settledAt } : {},
|
|
809
|
-
...node.settledAt !== void 0 ? { wallMs: Math.max(0, node.settledAt - node.startedAt) } : {}
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
function freezeRun(run, nodes) {
|
|
813
|
-
const gaps = runSpendGaps(nodes);
|
|
814
|
-
const { rootInference: _rootInference, started: _started, segments, ...rest } = run;
|
|
815
|
-
return Object.freeze({
|
|
816
|
-
...rest,
|
|
817
|
-
...segments?.length ? { segments: Object.freeze(segments.map((segment) => Object.freeze(segment))) } : {},
|
|
818
|
-
targets: Object.freeze({ ...run.targets }),
|
|
819
|
-
decisions: Object.freeze({ ...run.decisions }),
|
|
820
|
-
totals: runTotals(run, nodes),
|
|
821
|
-
...gaps.length > 0 ? { spendGaps: Object.freeze(gaps) } : {}
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
function freezeNode(node) {
|
|
825
|
-
const { modelCalls, segments, reasoningTokens, startedAt: _startedAt, attemptIndex: _attemptIndex, ...rest } = node;
|
|
826
|
-
const total = nodeTotal(node);
|
|
827
|
-
const timing = timingOf(node);
|
|
828
|
-
return Object.freeze({
|
|
829
|
-
...rest,
|
|
830
|
-
...segments?.length ? { segments: Object.freeze(segments.map((segment) => Object.freeze(segment))) } : {},
|
|
831
|
-
...modelCalls.length > 0 ? { modelCalls: Object.freeze([...modelCalls]) } : {},
|
|
832
|
-
...total !== void 0 ? {
|
|
833
|
-
usage: usageOf(total, reasoningTokens),
|
|
834
|
-
cost: costOf(total)
|
|
835
|
-
} : {},
|
|
836
|
-
...total !== void 0 && platformOf(total) !== void 0 ? { platform: platformOf(total) } : {},
|
|
837
|
-
...timing !== void 0 ? { timing } : {}
|
|
838
|
-
});
|
|
839
|
-
}
|
|
840
|
-
function increment(target, key) {
|
|
841
|
-
target[key] = (target[key] ?? 0) + 1;
|
|
842
|
-
}
|
|
843
|
-
function objectRecord(value) {
|
|
844
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
845
|
-
}
|
|
846
|
-
/** Read a journaled `Spend` without trusting the wire: a record missing the conserved channels is
|
|
847
|
-
* not a zero-cost turn, it is an unparseable one, and it must not enter a total. */
|
|
848
|
-
function spendField(value, key) {
|
|
849
|
-
const field = objectRecord(value?.[key]);
|
|
850
|
-
if (!field) return void 0;
|
|
851
|
-
const tokens = objectRecord(field.tokens);
|
|
852
|
-
if (typeof field.usd !== "number" || !Number.isFinite(field.usd)) return void 0;
|
|
853
|
-
if (!tokens || typeof tokens.input !== "number" || typeof tokens.output !== "number") return;
|
|
854
|
-
return cloneSpend(field);
|
|
855
|
-
}
|
|
856
|
-
/** Read a journaled overspend without trusting the wire: one malformed channel drops the record,
|
|
857
|
-
* so an unparseable entry is never reported as an overspend of some size. */
|
|
858
|
-
function budgetViolationField(value) {
|
|
859
|
-
const overspent = objectRecord(value?.budgetViolation)?.overspent;
|
|
860
|
-
if (!Array.isArray(overspent) || overspent.length === 0) return void 0;
|
|
861
|
-
const entries = [];
|
|
862
|
-
for (const raw of overspent) {
|
|
863
|
-
const entry = objectRecord(raw);
|
|
864
|
-
const channel = stringField(entry, "channel");
|
|
865
|
-
const reserved = numberField(entry, "reserved");
|
|
866
|
-
const spent = numberField(entry, "spent");
|
|
867
|
-
if (channel === void 0 || reserved === void 0 || spent === void 0) return void 0;
|
|
868
|
-
entries.push({
|
|
869
|
-
channel,
|
|
870
|
-
reserved,
|
|
871
|
-
spent
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
return { overspent: entries };
|
|
875
|
-
}
|
|
876
|
-
function stringField(value, key) {
|
|
877
|
-
const field = value?.[key];
|
|
878
|
-
return typeof field === "string" && field.length > 0 ? field : void 0;
|
|
879
|
-
}
|
|
880
|
-
function numberField(value, key) {
|
|
881
|
-
const field = value?.[key];
|
|
882
|
-
return typeof field === "number" && Number.isFinite(field) ? field : void 0;
|
|
883
|
-
}
|
|
884
|
-
function booleanField(value, key) {
|
|
885
|
-
const field = value?.[key];
|
|
886
|
-
return typeof field === "boolean" ? field : void 0;
|
|
887
|
-
}
|
|
888
|
-
//#endregion
|
|
889
|
-
//#region src/durable/settle-record.ts
|
|
20
|
+
// src/durable/settle-record.ts
|
|
890
21
|
/** The settle record: the returned `SupervisedResult` as canonical JSON, written once. */
|
|
891
22
|
const SETTLE_RECORD_FILE = "result.json";
|
|
892
23
|
/** The failure record: the most recent throw, replaced by a later throw. */
|
|
@@ -1012,8 +143,7 @@ async function readFailureRecord(runDir) {
|
|
|
1012
143
|
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`);
|
|
1013
144
|
return parsed;
|
|
1014
145
|
}
|
|
1015
|
-
|
|
1016
|
-
//#region src/durable/pursuit-version-evidence.ts
|
|
146
|
+
// src/durable/pursuit-version-evidence.ts
|
|
1017
147
|
/** The chain's directory beside the first version's: the ledger and its content blobs. */
|
|
1018
148
|
function pursuitVersionsLedgerPath(runDir) {
|
|
1019
149
|
return resolve(`${resolve(runDir.trim())}.search`, "ledger.jsonl");
|
|
@@ -1142,8 +272,7 @@ async function exists(path) {
|
|
|
1142
272
|
function isSha256(value) {
|
|
1143
273
|
return typeof value === "string" && /^sha256:[0-9a-f]{64}$/u.test(value);
|
|
1144
274
|
}
|
|
1145
|
-
|
|
1146
|
-
//#region src/durable/pursuit-version-review.ts
|
|
275
|
+
// src/durable/pursuit-version-review.ts
|
|
1147
276
|
/** Where `'review-of-best'` mounts a review: `inputs/review/version-<n>.md`. One review lives in
|
|
1148
277
|
* a profile at a time. */
|
|
1149
278
|
const REVIEW_DIR = "inputs/review/";
|
|
@@ -1220,8 +349,7 @@ function reviewPage(version, score, verdict) {
|
|
|
1220
349
|
]
|
|
1221
350
|
].join("\n");
|
|
1222
351
|
}
|
|
1223
|
-
|
|
1224
|
-
//#region src/durable/pursuit-version-run.ts
|
|
352
|
+
// src/durable/pursuit-version-run.ts
|
|
1225
353
|
/**
|
|
1226
354
|
* One pursuit version chain at run time: running a version in its directory, reading it back
|
|
1227
355
|
* and checking it is this node's run, judging it, turning it into the kernel's cell result, and
|
|
@@ -1483,8 +611,7 @@ var VersionChainRun = class {
|
|
|
1483
611
|
return chain;
|
|
1484
612
|
}
|
|
1485
613
|
};
|
|
1486
|
-
|
|
1487
|
-
//#region src/durable/pursuit-versions.ts
|
|
614
|
+
// src/durable/pursuit-versions.ts
|
|
1488
615
|
/**
|
|
1489
616
|
* The pursuit version chain, on Eval's search kernel (`runSearch`).
|
|
1490
617
|
*
|
|
@@ -1923,8 +1050,7 @@ function identicalFailureStreak(versions, count) {
|
|
|
1923
1050
|
const [first] = failures;
|
|
1924
1051
|
return first !== null && first !== void 0 && failures.every((failure) => failure === first) ? first : null;
|
|
1925
1052
|
}
|
|
1926
|
-
|
|
1927
|
-
//#region src/durable/run-fork.ts
|
|
1053
|
+
// src/durable/run-fork.ts
|
|
1928
1054
|
/**
|
|
1929
1055
|
* The `execution.correlation` keys a fork records on its root. Runtime writes them; a caller that
|
|
1930
1056
|
* supplies one is refused. `lineageRootRunId` is the first run of the chain of parents, so the
|
|
@@ -2025,8 +1151,7 @@ function contains(outer, inner) {
|
|
|
2025
1151
|
const path = relative(outer, inner);
|
|
2026
1152
|
return path === "" || !isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`);
|
|
2027
1153
|
}
|
|
2028
|
-
|
|
2029
|
-
//#region src/durable/run-lock.ts
|
|
1154
|
+
// src/durable/run-lock.ts
|
|
2030
1155
|
/** The lock file `supervisePursuit` holds inside a run directory for the life of one call. */
|
|
2031
1156
|
const RUN_DIRECTORY_LOCK_FILE = "supervise.lock";
|
|
2032
1157
|
/** The directory is held by a live process. `holder` is what that process recorded. */
|
|
@@ -2198,8 +1323,7 @@ function removeIfPresent(path) {
|
|
|
2198
1323
|
if (!isNoEntError(error)) throw error;
|
|
2199
1324
|
}
|
|
2200
1325
|
}
|
|
2201
|
-
|
|
2202
|
-
//#region src/durable/pursuit-observer-delivery.ts
|
|
1326
|
+
// src/durable/pursuit-observer-delivery.ts
|
|
2203
1327
|
/** The Intelligence route that persists a delivered projection on its run spine. */
|
|
2204
1328
|
const PURSUIT_OBSERVER_DELIVERY_PATH = "/v1/ingest/pursuit-observer";
|
|
2205
1329
|
const DEFAULT_INTERVAL_MS = 6e4;
|
|
@@ -2305,8 +1429,7 @@ function startPursuitObserverDelivery(delivery, read, warn = (message) => consol
|
|
|
2305
1429
|
}
|
|
2306
1430
|
};
|
|
2307
1431
|
}
|
|
2308
|
-
|
|
2309
|
-
//#region src/durable/run-reopen.ts
|
|
1432
|
+
// src/durable/run-reopen.ts
|
|
2310
1433
|
/** The directory that holds a run's archived settle records. */
|
|
2311
1434
|
const SETTLE_ARCHIVE_DIR = "settles";
|
|
2312
1435
|
/** Refuse a reopen that names no operator or reason, or that is combined with a fork or versions. */
|
|
@@ -2420,8 +1543,7 @@ async function readBytes(path) {
|
|
|
2420
1543
|
throw error;
|
|
2421
1544
|
}
|
|
2422
1545
|
}
|
|
2423
|
-
|
|
2424
|
-
//#region src/durable/supervise-pursuit.ts
|
|
1546
|
+
// src/durable/supervise-pursuit.ts
|
|
2425
1547
|
/** A failed Runtime execution whose complete third-person projection was retained. */
|
|
2426
1548
|
var SupervisePursuitError = class extends Error {
|
|
2427
1549
|
pursuit;
|
|
@@ -2496,23 +1618,40 @@ async function supervisePursuit(profile, task, opts) {
|
|
|
2496
1618
|
* the graph from its spawn journal. `result.json` holds the graph's supervised result, the same
|
|
2497
1619
|
* record every supervised run leaves; the edge ledger is the journal's `edge` events and the
|
|
2498
1620
|
* returned `result.ledger`. Version chains and forks are `supervisePursuit` features and are not
|
|
2499
|
-
* offered here.
|
|
1621
|
+
* offered here. An explicit `reopen` uses the same archived-settle and resume contract as
|
|
1622
|
+
* `supervisePursuit`; ordinary re-entry of a settled graph stays refused.
|
|
2500
1623
|
*/
|
|
2501
1624
|
async function superviseGraphPursuit(graph, opts) {
|
|
2502
1625
|
assertPursuitDirectory(opts, "superviseGraphPursuit");
|
|
2503
|
-
|
|
2504
|
-
|
|
1626
|
+
if (opts.reopen !== void 0) assertRunReopen(opts.reopen, {});
|
|
1627
|
+
const { pursuitId: _pursuitId, hooks: _hooks, observerDelivery: _observerDelivery, brain, reopen, ...graphOptions } = opts;
|
|
1628
|
+
return runDurablePursuit(opts, ({ hooks, steerDir }) => superviseAgentGraph(reopen?.budget === void 0 ? graph : {
|
|
1629
|
+
...graph,
|
|
1630
|
+
budget: reopen.budget
|
|
1631
|
+
}, graphExecutionOptions({
|
|
2505
1632
|
...graphOptions,
|
|
2506
1633
|
runId: graphOptions.runId ?? "supervise",
|
|
1634
|
+
...reopen === void 0 ? {} : { resume: true },
|
|
2507
1635
|
steerDir,
|
|
2508
1636
|
retainedAtSettlement: "release",
|
|
2509
1637
|
hooks
|
|
2510
|
-
}, brain), (result) => result.result)
|
|
1638
|
+
}), brain), (result) => result.result, reopen === void 0 ? void 0 : (observer) => reopenSettledRun({
|
|
1639
|
+
runDir: opts.runDir.trim(),
|
|
1640
|
+
runId: opts.runId ?? "supervise",
|
|
1641
|
+
reopen,
|
|
1642
|
+
journal: opts.journal,
|
|
1643
|
+
limits: {
|
|
1644
|
+
budget: graph.budget,
|
|
1645
|
+
...opts.reservationPolicy === void 0 ? {} : { reservationPolicy: opts.reservationPolicy }
|
|
1646
|
+
},
|
|
1647
|
+
observer,
|
|
1648
|
+
pursuitId: opts.pursuitId.trim(),
|
|
1649
|
+
now: opts.now ?? Date.now
|
|
1650
|
+
}));
|
|
2511
1651
|
}
|
|
2512
1652
|
/** The pursuit identity and run directory both durable entries require. */
|
|
2513
1653
|
function assertPursuitDirectory(opts, entry) {
|
|
2514
|
-
if (opts.
|
|
2515
|
-
if (opts.runDir.trim().length === 0) throw new TypeError(`${entry}: runDir must be non-empty`);
|
|
1654
|
+
for (const field of ["pursuitId", "runDir"]) if (opts[field].trim().length === 0) throw new TypeError(`${entry}: ${field} must be non-empty`);
|
|
2516
1655
|
if (opts.retainedAtSettlement === "keep") throw new TypeError(`${entry}: retainedAtSettlement 'keep' cannot hold, because a settled pursuit is never re-entered`);
|
|
2517
1656
|
}
|
|
2518
1657
|
/**
|
|
@@ -2553,17 +1692,16 @@ async function runDurablePursuit(opts, execute, settled, reopen) {
|
|
|
2553
1692
|
throw error;
|
|
2554
1693
|
}
|
|
2555
1694
|
let pursuit;
|
|
2556
|
-
let observerError;
|
|
2557
1695
|
try {
|
|
2558
1696
|
await observer.journal.appendEvent(rootEvent(pursuitId, runId, "error", now(), {
|
|
2559
1697
|
status: "failed",
|
|
2560
|
-
error: errorMessage(error)
|
|
1698
|
+
error: errorMessage(error),
|
|
1699
|
+
failure: runtimeFailureCause(error)
|
|
2561
1700
|
}));
|
|
2562
1701
|
pursuit = projectPursuit(await observer.journal.read());
|
|
2563
|
-
} catch (
|
|
2564
|
-
observerError
|
|
1702
|
+
} catch (observerError) {
|
|
1703
|
+
throw new Error("supervisePursuit: Runtime failed and durable observer completeness could not be proven", { cause: new AggregateError([error, observerError]) });
|
|
2565
1704
|
}
|
|
2566
|
-
if (observerError !== void 0 || pursuit === void 0) throw new Error("supervisePursuit: Runtime failed and durable observer completeness could not be proven", { cause: new AggregateError(observerError === void 0 ? [error] : [error, observerError]) });
|
|
2567
1705
|
try {
|
|
2568
1706
|
const rootStream = await readRootStreamReceipt(runDir);
|
|
2569
1707
|
await writeFailureRecord(runDir, {
|
|
@@ -2581,12 +1719,19 @@ async function runDurablePursuit(opts, execute, settled, reopen) {
|
|
|
2581
1719
|
}
|
|
2582
1720
|
throw new SupervisePursuitError(error, pursuit, observerPath, failurePath);
|
|
2583
1721
|
}
|
|
1722
|
+
const terminal = settled(result);
|
|
2584
1723
|
try {
|
|
2585
|
-
await writeSettleRecord(runDir,
|
|
1724
|
+
await writeSettleRecord(runDir, terminal);
|
|
2586
1725
|
} catch (cause) {
|
|
2587
1726
|
throw new Error(`supervisePursuit: Runtime settled but the settle record ${settlePath} could not be written`, { cause });
|
|
2588
1727
|
}
|
|
2589
|
-
await observer.journal.appendEvent(rootEvent(pursuitId, runId, "after", now(), {
|
|
1728
|
+
await observer.journal.appendEvent(rootEvent(pursuitId, runId, "after", now(), {
|
|
1729
|
+
status: "done",
|
|
1730
|
+
...terminal.kind === "no-winner" && terminal.reason === "driver-failed" && terminal.error.failure ? {
|
|
1731
|
+
error: terminal.error.message,
|
|
1732
|
+
failure: terminal.error.failure
|
|
1733
|
+
} : {}
|
|
1734
|
+
}));
|
|
2590
1735
|
terminalState = "done";
|
|
2591
1736
|
return Object.freeze({
|
|
2592
1737
|
result,
|
|
@@ -2614,74 +1759,6 @@ function rootEvent(pursuitId, runId, phase, timestamp, payload) {
|
|
|
2614
1759
|
function errorName(error) {
|
|
2615
1760
|
return error instanceof Error ? error.name : "NonError";
|
|
2616
1761
|
}
|
|
2617
|
-
//#endregion
|
|
2618
|
-
//#region src/durable/supervision-discovery.ts
|
|
2619
|
-
/**
|
|
2620
|
-
* Discover the stable identities recorded by Runtime's durable supervision
|
|
2621
|
-
* files. This is the developer-facing first step before calling
|
|
2622
|
-
* `FileSpawnJournal.loadTree(root)`, `loadSpawnForest(journal, root)`, or
|
|
2623
|
-
* `FileCoordinationLog.load(runId, ownerId)`.
|
|
2624
|
-
*
|
|
2625
|
-
* Missing files produce empty collections. A malformed committed JSONL record
|
|
2626
|
-
* still fails loud through the same parser used by the runtime; a torn final
|
|
2627
|
-
* append is ignored because it was never acknowledged as committed.
|
|
2628
|
-
*/
|
|
2629
|
-
async function discoverDurableSupervisionRun(runDir) {
|
|
2630
|
-
if (typeof runDir !== "string" || runDir.trim().length === 0) throw new TypeError("discoverDurableSupervisionRun: runDir must be a non-empty string");
|
|
2631
|
-
const canonicalRunDir = resolve(runDir);
|
|
2632
|
-
const spawnJournalPath = `${canonicalRunDir}/spawn-journal.jsonl`;
|
|
2633
|
-
const coordinationLogPath = `${canonicalRunDir}/coordination-log.jsonl`;
|
|
2634
|
-
const allRoots = /* @__PURE__ */ new Set();
|
|
2635
|
-
const nestedRoots = /* @__PURE__ */ new Set();
|
|
2636
|
-
const rootsBegunAt = /* @__PURE__ */ new Map();
|
|
2637
|
-
for await (const record of readCommittedJsonLines(spawnJournalPath, { allowMissing: true })) {
|
|
2638
|
-
if (record.kind === "begin") {
|
|
2639
|
-
if (typeof record.root !== "string" || record.root.length === 0) throw new Error(`${spawnJournalPath}: begin record has no non-empty string root identity`);
|
|
2640
|
-
allRoots.add(record.root);
|
|
2641
|
-
rootsBegunAt.set(record.root, typeof record.at === "string" ? record.at : null);
|
|
2642
|
-
continue;
|
|
2643
|
-
}
|
|
2644
|
-
for (const event of spawnJournalRecordEvents(record)) {
|
|
2645
|
-
if (!isRecord(event) || event.kind !== "spawned") continue;
|
|
2646
|
-
if (!Object.hasOwn(event, "ownedTreeRoot")) continue;
|
|
2647
|
-
if (typeof event.ownedTreeRoot !== "string" || event.ownedTreeRoot.length === 0) throw new Error(`${spawnJournalPath}: spawned event ownedTreeRoot must be a non-empty string when present`);
|
|
2648
|
-
nestedRoots.add(event.ownedTreeRoot);
|
|
2649
|
-
}
|
|
2650
|
-
}
|
|
2651
|
-
const streams = /* @__PURE__ */ new Map();
|
|
2652
|
-
for await (const record of readCommittedJsonLines(coordinationLogPath, { allowMissing: true })) {
|
|
2653
|
-
if (typeof record.runId !== "string" || record.runId.length === 0) throw new Error(`${coordinationLogPath}: record has no non-empty string runId identity`);
|
|
2654
|
-
const stream = streams.get(record.runId) ?? {
|
|
2655
|
-
owners: /* @__PURE__ */ new Set(),
|
|
2656
|
-
unscopedRecords: 0,
|
|
2657
|
-
recordCount: 0
|
|
2658
|
-
};
|
|
2659
|
-
stream.recordCount += 1;
|
|
2660
|
-
if (record.ownerId === void 0) stream.unscopedRecords += 1;
|
|
2661
|
-
else if (typeof record.ownerId === "string" && record.ownerId.length > 0) stream.owners.add(record.ownerId);
|
|
2662
|
-
else throw new Error(`${coordinationLogPath}: ownerId must be a non-empty string when present`);
|
|
2663
|
-
streams.set(record.runId, stream);
|
|
2664
|
-
}
|
|
2665
|
-
const coordinationStreams = [...streams.entries()].sort(([left], [right]) => compareText(left, right)).map(([runId, stream]) => Object.freeze({
|
|
2666
|
-
runId,
|
|
2667
|
-
ownerIds: Object.freeze([...stream.owners].sort(compareText)),
|
|
2668
|
-
unscopedRecords: stream.unscopedRecords,
|
|
2669
|
-
recordCount: stream.recordCount
|
|
2670
|
-
}));
|
|
2671
|
-
const roots = [...allRoots].filter((root) => !nestedRoots.has(root)).sort(compareText);
|
|
2672
|
-
return Object.freeze({
|
|
2673
|
-
runDir: canonicalRunDir,
|
|
2674
|
-
spawnJournalPath,
|
|
2675
|
-
coordinationLogPath,
|
|
2676
|
-
roots: Object.freeze(roots),
|
|
2677
|
-
rootsBegunAt: Object.freeze(roots.map((root) => rootsBegunAt.get(root) ?? null)),
|
|
2678
|
-
coordinationStreams: Object.freeze(coordinationStreams)
|
|
2679
|
-
});
|
|
2680
|
-
}
|
|
2681
|
-
function compareText(left, right) {
|
|
2682
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
2683
|
-
}
|
|
2684
|
-
//#endregion
|
|
2685
1762
|
export { FileObserverJournal, RUN_FORK_CORRELATION_KEYS, RunDirectoryLockedError, RunParkedError, SettledRunDirectoryError, SupervisePursuitError, acquireRunDirectoryLock, assertPursuitVersions, deriveExecutionId, discoverDurableSupervisionRun, handleChatTurn, observerRecordDigest, projectPursuit, readFailureRecord, readRootStream, readRootStreamReceipt, readRunDirectoryLock, readRunParking, readSettleRecord, runDirectoryHolderIsLive, superviseGraphPursuit, supervisePursuit };
|
|
2686
1763
|
|
|
2687
1764
|
//# sourceMappingURL=durable.js.map
|