@tangle-network/agent-runtime 0.350.2 → 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-DyiHrTlS.d.ts → activation-BidzGkUQ.d.ts} +2 -2
- package/dist/{activation-CfJMyzEs.js → activation-C6zNC2kx.js} +4 -4
- package/dist/{activation-CfJMyzEs.js.map → activation-C6zNC2kx.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -3
- package/dist/{bundle-Ze8pd07a.js → bundle-DeDkJbsr.js} +2 -2
- package/dist/{bundle-Ze8pd07a.js.map → bundle-DeDkJbsr.js.map} +1 -1
- package/dist/candidate-execution/index.js +4 -4
- package/dist/chat-turn.d.ts +2 -0
- package/dist/chat-turn.js +1 -0
- package/dist/{coordination-manager-BkVfWlMz.js → coordination-manager-B7QadgrI.js} +4 -4
- package/dist/{coordination-manager-BkVfWlMz.js.map → coordination-manager-B7QadgrI.js.map} +1 -1
- package/dist/{delegate-DgEie4ad.js → delegate-CoscoyEo.js} +3 -3
- package/dist/{delegate-DgEie4ad.js.map → delegate-CoscoyEo.js.map} +1 -1
- package/dist/durable.d.ts +7 -130
- package/dist/durable.js +24 -952
- package/dist/durable.js.map +1 -1
- package/dist/{errors-wuhGvpE3.js → errors-AzSuGI23.js} +4 -1
- 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-Cx0FhKSE.js → graph-mojMML8e.js} +7 -6
- package/dist/{graph-Cx0FhKSE.js.map → graph-mojMML8e.js.map} +1 -1
- package/dist/{improve-i0HU1-aa.js → improve-Dm5ZE8mr.js} +10 -9
- package/dist/{improve-i0HU1-aa.js.map → improve-Dm5ZE8mr.js.map} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +13 -11
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +2 -2
- package/dist/intelligence.js +7 -6
- 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 +1 -1
- package/dist/kernel.js +11 -10
- package/dist/{loop-runner-BwN9MhdP.js → loop-runner-5cL_vZi0.js} +3 -3
- package/dist/{loop-runner-BwN9MhdP.js.map → loop-runner-5cL_vZi0.js.map} +1 -1
- package/dist/{loop-runner-DvfOXVx9.d.ts → loop-runner-DyLD-aNb.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +4 -4
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +8 -8
- package/dist/mcp/memory-bin.js +2 -2
- package/dist/{model-policy-GSxWwP-b.js → model-policy-C-RGAUW4.js} +2 -2
- package/dist/{model-policy-GSxWwP-b.js.map → model-policy-C-RGAUW4.js.map} +1 -1
- package/dist/{prepare-DE8TQnLu.js → prepare-XWwsOh8O.js} +4 -3
- package/dist/{prepare-DE8TQnLu.js.map → prepare-XWwsOh8O.js.map} +1 -1
- package/dist/{profile-CAhZDW2T.js → profile-C01SQZKa.js} +3 -3
- package/dist/{profile-CAhZDW2T.js.map → profile-C01SQZKa.js.map} +1 -1
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +2 -2
- package/dist/{provision-supervisor-Bde_FF0R.js → provision-supervisor-D9pO-iPl.js} +7 -7
- package/dist/{provision-supervisor-Bde_FF0R.js.map → provision-supervisor-D9pO-iPl.js.map} +1 -1
- package/dist/{run-cancellation-BLy_Zz-w.js → run-cancellation-CoJRKvih.js} +2 -2
- package/dist/{run-cancellation-BLy_Zz-w.js.map → run-cancellation-CoJRKvih.js.map} +1 -1
- package/dist/{runtime-6Ec9sHRe.js → runtime-BJPXHWbc.js} +41 -37
- package/dist/{runtime-6Ec9sHRe.js.map → runtime-BJPXHWbc.js.map} +1 -1
- package/dist/{sandbox-profile-compat-BxMQ1VXv.js → sandbox-profile-compat-CXjozdWL.js} +4 -623
- package/dist/{sandbox-profile-compat-BxMQ1VXv.js.map → sandbox-profile-compat-CXjozdWL.js.map} +1 -1
- package/dist/{search-executor-B7g_5Wjl.js → search-executor-CQEHMXoT.js} +7 -5
- package/dist/{search-executor-B7g_5Wjl.js.map → search-executor-CQEHMXoT.js.map} +1 -1
- package/dist/{server-fvEMwGMQ.js → server-U9NGgudQ.js} +6 -6
- package/dist/{server-fvEMwGMQ.js.map → server-U9NGgudQ.js.map} +1 -1
- package/dist/{structural-rollout-B86bSixU.js → structural-rollout-wMSxE7bn.js} +7 -5
- package/dist/{structural-rollout-B86bSixU.js.map → structural-rollout-wMSxE7bn.js.map} +1 -1
- package/dist/{supervise-CaeZcgAW.js → supervise-CNNhX0ab.js} +9 -9
- package/dist/{supervise-CaeZcgAW.js.map → supervise-CNNhX0ab.js.map} +1 -1
- package/dist/supervision-discovery-CewADdEs.js +772 -0
- package/dist/supervision-discovery-CewADdEs.js.map +1 -0
- package/dist/{supervisor-C1OePYE-.js → supervisor-rK5Y8en6.js} +105 -3789
- package/dist/supervisor-rK5Y8en6.js.map +1 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +13 -13
- package/dist/{tool-server-CwuYeW_Z.js → tool-server-CZWd_Opp.js} +2 -2
- package/dist/{tool-server-CwuYeW_Z.js.map → tool-server-CZWd_Opp.js.map} +1 -1
- package/dist/{top-snapshot-ChwpTr7A.js → top-snapshot-DcV7VG4m.js} +178 -161
- package/dist/top-snapshot-DcV7VG4m.js.map +1 -0
- package/dist/tui/bin.js +45 -74
- package/dist/tui/bin.js.map +1 -1
- package/dist/tui/index.d.ts +25 -54
- package/dist/tui/index.js +2 -2
- package/dist/{types-Fowuwdz2.d.ts → types-0uzVKtFW.d.ts} +30 -7
- package/dist/util-ebWMWzY0.js +624 -0
- package/dist/util-ebWMWzY0.js.map +1 -0
- package/dist/workspace-store/index.js +9 -3
- package/dist/workspace-store/index.js.map +1 -1
- package/package.json +17 -12
- package/dist/errors-wuhGvpE3.js.map +0 -1
- package/dist/record-events-B2GUgYdz.js +0 -261
- package/dist/record-events-B2GUgYdz.js.map +0 -1
- package/dist/supervisor-C1OePYE-.js.map +0 -1
- package/dist/top-snapshot-ChwpTr7A.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 {
|
|
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
5
|
import { a as withPursuitContext, t as composeRuntimeHooks } from "./runtime-hooks-CsW2pfFk.js";
|
|
6
|
-
import { a as applyExactAgentProfileDiff } from "./profile-
|
|
7
|
-
import { a as superviseRootProfile, gt as readRootStreamReceipt, ht as readRootStream, t as supervise } from "./supervise-
|
|
8
|
-
import { a as runCancelRequestFile, o as runCancellationFile, v as supervisorRunDir, x as workerCancellationsDir } from "./run-cancellation-
|
|
9
|
-
import { a as graphExecutionOptions, i as superviseAgentGraph } from "./graph-
|
|
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,873 +17,6 @@ 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
|
-
// src/durable/chat-engine.ts
|
|
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
|
-
// src/durable/execution-handle.ts
|
|
148
|
-
/**
|
|
149
|
-
* Derive a stable execution id from the run identity.
|
|
150
|
-
* The same `(projectId, sessionId, turnIndex)` tuple yields the same id.
|
|
151
|
-
*
|
|
152
|
-
* Use the result as both `PromptOptions.executionId` and
|
|
153
|
-
* `PromptOptions.turnId` on the first dispatch.
|
|
154
|
-
* The execution id addresses the server-side execution for reconnect and
|
|
155
|
-
* replay; the turn id makes a repeated dispatch idempotent.
|
|
156
|
-
* An execution id alone does not make a repeated POST idempotent.
|
|
157
|
-
*
|
|
158
|
-
* Format is readable, not hashed: operators grepping orchestrator logs
|
|
159
|
-
* for `gtm-agent:thread-abc:3` find the run without translating an
|
|
160
|
-
* opaque id. Components are URL-encoded so delimiters inside caller ids
|
|
161
|
-
* cannot collapse distinct tuples. The final id is limited to the
|
|
162
|
-
* orchestrator replay route's 256-byte maximum. Execution ids are not a
|
|
163
|
-
* secrecy boundary.
|
|
164
|
-
*
|
|
165
|
-
* Wire integration:
|
|
166
|
-
* - Initial dispatch: pass the result as `executionId` and `turnId`.
|
|
167
|
-
* - Stream replay: pass it as `executionId` with `lastEventId`.
|
|
168
|
-
*
|
|
169
|
-
* @throws `TypeError` when either string id is blank.
|
|
170
|
-
* @throws `RangeError` when `turnIndex` is invalid or the result exceeds 256 bytes.
|
|
171
|
-
*/
|
|
172
|
-
function deriveExecutionId(input) {
|
|
173
|
-
if (input.projectId.trim().length === 0) throw new TypeError("projectId must be a non-empty string");
|
|
174
|
-
if (input.sessionId.trim().length === 0) throw new TypeError("sessionId must be a non-empty string");
|
|
175
|
-
if (!Number.isSafeInteger(input.turnIndex) || input.turnIndex < 0) throw new RangeError("turnIndex must be a non-negative safe integer");
|
|
176
|
-
const executionId = [
|
|
177
|
-
encodeURIComponent(input.projectId),
|
|
178
|
-
encodeURIComponent(input.sessionId),
|
|
179
|
-
String(input.turnIndex)
|
|
180
|
-
].join(":");
|
|
181
|
-
if (executionId.length > 256) throw new RangeError("derived execution id must not exceed 256 bytes");
|
|
182
|
-
return executionId;
|
|
183
|
-
}
|
|
184
|
-
// src/durable/observer-journal.ts
|
|
185
|
-
/**
|
|
186
|
-
* Durable, append-only third-person history for one concrete Runtime execution.
|
|
187
|
-
* It consumes Runtime's existing hook stream and does not participate in execution
|
|
188
|
-
* decisions. A broken observer therefore cannot change what an agent is allowed to do.
|
|
189
|
-
*
|
|
190
|
-
* The write discipline deliberately matches `FileSpawnJournal`: serialized appends,
|
|
191
|
-
* torn-tail recovery, short-write handling, and fsync before acknowledgement. One
|
|
192
|
-
* execution owns one journal file; higher-level pursuit aggregation joins isolated
|
|
193
|
-
* journals by `pursuitId` instead of making independent processes share a write head.
|
|
194
|
-
*/
|
|
195
|
-
var FileObserverJournal = class {
|
|
196
|
-
path;
|
|
197
|
-
pursuitId;
|
|
198
|
-
writeContext = captureRunWriteContext();
|
|
199
|
-
tail = Promise.resolve();
|
|
200
|
-
initialized = false;
|
|
201
|
-
sequence = 0;
|
|
202
|
-
previousDigest;
|
|
203
|
-
appendFailure;
|
|
204
|
-
constructor(path, pursuitId) {
|
|
205
|
-
const stableId = pursuitId.trim();
|
|
206
|
-
if (stableId.length === 0) throw new TypeError("FileObserverJournal: pursuitId must be non-empty");
|
|
207
|
-
this.path = resolve(path);
|
|
208
|
-
this.pursuitId = stableId;
|
|
209
|
-
}
|
|
210
|
-
hooks() {
|
|
211
|
-
return withPursuitContext(this.pursuitId, {
|
|
212
|
-
onEvent: (event) => this.appendEvent(event).then(() => void 0),
|
|
213
|
-
onDecisionPoint: (point) => this.appendDecision(point).then(() => void 0)
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
appendEvent(event) {
|
|
217
|
-
return this.enqueue("event", event);
|
|
218
|
-
}
|
|
219
|
-
appendDecision(point) {
|
|
220
|
-
return this.enqueue("decision", point);
|
|
221
|
-
}
|
|
222
|
-
async read() {
|
|
223
|
-
await this.tail;
|
|
224
|
-
this.assertComplete();
|
|
225
|
-
const records = [];
|
|
226
|
-
const verify = observerVerifier(this.pursuitId);
|
|
227
|
-
for await (const record of this.readExistingUnsafe()) {
|
|
228
|
-
verify(record);
|
|
229
|
-
records.push(record);
|
|
230
|
-
}
|
|
231
|
-
return Object.freeze(records);
|
|
232
|
-
}
|
|
233
|
-
enqueue(kind, value) {
|
|
234
|
-
if (value.pursuitId !== this.pursuitId) return Promise.reject(/* @__PURE__ */ new Error(`FileObserverJournal: ${kind} pursuitId ${String(value.pursuitId)} does not match ${this.pursuitId}`));
|
|
235
|
-
let snapshot;
|
|
236
|
-
try {
|
|
237
|
-
snapshot = detachedSnapshot(value, "observer record");
|
|
238
|
-
} catch (error) {
|
|
239
|
-
return Promise.reject(error);
|
|
240
|
-
}
|
|
241
|
-
let result;
|
|
242
|
-
const operation = this.tail.then(() => this.writeContext(async () => {
|
|
243
|
-
this.assertComplete();
|
|
244
|
-
try {
|
|
245
|
-
await this.initialize();
|
|
246
|
-
} catch (error) {
|
|
247
|
-
this.appendFailure ??= toError(error);
|
|
248
|
-
throw error;
|
|
249
|
-
}
|
|
250
|
-
const unsigned = {
|
|
251
|
-
schemaVersion: 1,
|
|
252
|
-
pursuitId: this.pursuitId,
|
|
253
|
-
sequence: this.sequence + 1,
|
|
254
|
-
kind,
|
|
255
|
-
observedAt: Date.now(),
|
|
256
|
-
...this.previousDigest ? { previousDigest: this.previousDigest } : {},
|
|
257
|
-
...kind === "event" ? { event: snapshot } : { decision: snapshot }
|
|
258
|
-
};
|
|
259
|
-
const record = Object.freeze({
|
|
260
|
-
...unsigned,
|
|
261
|
-
digest: observerRecordDigest(unsigned)
|
|
262
|
-
});
|
|
263
|
-
try {
|
|
264
|
-
const fs = await import("node:fs/promises");
|
|
265
|
-
const { dirname } = await import("node:path");
|
|
266
|
-
await fs.mkdir(dirname(this.path), { recursive: true });
|
|
267
|
-
await appendCommittedJsonLine(this.path, record);
|
|
268
|
-
} catch (error) {
|
|
269
|
-
this.appendFailure ??= toError(error);
|
|
270
|
-
throw error;
|
|
271
|
-
}
|
|
272
|
-
this.sequence = record.sequence;
|
|
273
|
-
this.previousDigest = record.digest;
|
|
274
|
-
result = record;
|
|
275
|
-
}));
|
|
276
|
-
this.tail = operation.then(() => void 0, () => void 0);
|
|
277
|
-
return operation.then(() => {
|
|
278
|
-
if (!result) throw new Error("FileObserverJournal: append completed without a record");
|
|
279
|
-
return result;
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
async initialize() {
|
|
283
|
-
if (this.initialized) return;
|
|
284
|
-
const verify = observerVerifier(this.pursuitId);
|
|
285
|
-
let tail;
|
|
286
|
-
for await (const record of this.readExistingUnsafe()) {
|
|
287
|
-
verify(record);
|
|
288
|
-
tail = record;
|
|
289
|
-
}
|
|
290
|
-
this.sequence = tail?.sequence ?? 0;
|
|
291
|
-
this.previousDigest = tail?.digest;
|
|
292
|
-
this.initialized = true;
|
|
293
|
-
}
|
|
294
|
-
readExistingUnsafe() {
|
|
295
|
-
return readCommittedJsonLines(this.path, { allowMissing: true });
|
|
296
|
-
}
|
|
297
|
-
assertComplete() {
|
|
298
|
-
if (!this.appendFailure) return;
|
|
299
|
-
throw new Error("FileObserverJournal: a prior durable append failed; observer completeness is unknown", { cause: this.appendFailure });
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
/** Verify identity, monotonic sequence, payload shape, and the complete digest chain. */
|
|
303
|
-
function verifyObserverRecords(records, pursuitId) {
|
|
304
|
-
const verify = observerVerifier(pursuitId);
|
|
305
|
-
for (const record of records) verify(record);
|
|
306
|
-
return Object.freeze([...records]);
|
|
307
|
-
}
|
|
308
|
-
/** The same full chain validation is used for replay, startup and in-memory evidence. */
|
|
309
|
-
function observerVerifier(pursuitId) {
|
|
310
|
-
let previousDigest;
|
|
311
|
-
let expectedSequence = 1;
|
|
312
|
-
return (record) => {
|
|
313
|
-
if (record.schemaVersion !== 1) throw new Error("observer journal: unsupported schemaVersion");
|
|
314
|
-
if (pursuitId !== void 0 && record.pursuitId !== pursuitId) throw new Error(`observer journal: pursuit identity mismatch at sequence ${record.sequence}`);
|
|
315
|
-
if (record.sequence !== expectedSequence) throw new Error(`observer journal: non-contiguous sequence ${record.sequence}; expected ${expectedSequence}`);
|
|
316
|
-
if (record.previousDigest !== previousDigest) throw new Error(`observer journal: digest-chain break at sequence ${record.sequence}`);
|
|
317
|
-
if (record.kind === "event" === (record.event === void 0)) throw new Error(`observer journal: invalid event payload at sequence ${record.sequence}`);
|
|
318
|
-
if (record.kind === "decision" === (record.decision === void 0)) throw new Error(`observer journal: invalid decision payload at sequence ${record.sequence}`);
|
|
319
|
-
if (record.event !== void 0 && record.event.pursuitId !== record.pursuitId) throw new Error(`observer journal: nested event pursuit mismatch at sequence ${record.sequence}`);
|
|
320
|
-
if (record.decision !== void 0 && record.decision.pursuitId !== record.pursuitId) throw new Error(`observer journal: nested decision pursuit mismatch at sequence ${record.sequence}`);
|
|
321
|
-
const { digest, ...unsigned } = record;
|
|
322
|
-
if (digest !== observerRecordDigest(unsigned)) throw new Error(`observer journal: digest mismatch at sequence ${record.sequence}`);
|
|
323
|
-
previousDigest = digest;
|
|
324
|
-
expectedSequence += 1;
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
/** Compute the canonical SHA-256 digest for an unsigned observer record. */
|
|
328
|
-
function observerRecordDigest(record) {
|
|
329
|
-
return createHash("sha256").update(JSON.stringify(record)).digest("hex");
|
|
330
|
-
}
|
|
331
|
-
/** Build the canonical durable observer hook in one call. */
|
|
332
|
-
function createFileObserverHooks(path, pursuitId) {
|
|
333
|
-
const journal = new FileObserverJournal(path, pursuitId);
|
|
334
|
-
return {
|
|
335
|
-
journal,
|
|
336
|
-
hooks: journal.hooks()
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
function toError(error) {
|
|
340
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
341
|
-
}
|
|
342
|
-
// src/durable/observer-projection.ts
|
|
343
|
-
/**
|
|
344
|
-
* Fold one append-only execution journal into a deterministic operator projection.
|
|
345
|
-
*
|
|
346
|
-
* This is intentionally a READ model, not another state machine: it does not own
|
|
347
|
-
* execution, cannot steer agents, and can be rebuilt from the journal at any time.
|
|
348
|
-
* Projection verifies the complete hash chain first, so an operator view can never
|
|
349
|
-
* silently render a mutated or reordered observer history as trustworthy state.
|
|
350
|
-
*
|
|
351
|
-
* Topology comes only from Runtime's canonical `agent.spawn` facts. Terminal node
|
|
352
|
-
* state comes only from `agent.child`; concrete run state comes only from the root
|
|
353
|
-
* `agent.run` lifecycle emitted by `supervisePursuit`, one row per attempt. Node identity is
|
|
354
|
-
* scoped to the concrete Runtime run so independent trees may both contain `root:s0` without
|
|
355
|
-
* aliasing, and a node belongs to the attempt that spawned it.
|
|
356
|
-
*
|
|
357
|
-
* Usage, cost and timing are reported at the class the runtime measured them at. A missing
|
|
358
|
-
* class stays ABSENT and the run names the node in `spendGaps`; nothing here converts an
|
|
359
|
-
* unmeasured channel into a zero, because a fabricated zero is indistinguishable from free work.
|
|
360
|
-
*/
|
|
361
|
-
function projectPursuit(records) {
|
|
362
|
-
if (records.length === 0) throw new TypeError("projectPursuit: at least one observer record is required");
|
|
363
|
-
const pursuitId = records[0].pursuitId;
|
|
364
|
-
const verified = verifyObserverRecords(records, pursuitId);
|
|
365
|
-
const attempts = /* @__PURE__ */ new Map();
|
|
366
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
367
|
-
let eventCount = 0;
|
|
368
|
-
let decisionCount = 0;
|
|
369
|
-
for (const record of verified) {
|
|
370
|
-
const observed = record.event ?? record.decision;
|
|
371
|
-
if (!observed) throw new Error(`projectPursuit: record ${record.sequence} has no observation`);
|
|
372
|
-
const run = currentAttempt(attempts, observed.runId, record);
|
|
373
|
-
run.lastSequence = record.sequence;
|
|
374
|
-
run.lastObservedAt = record.observedAt;
|
|
375
|
-
if (record.event) {
|
|
376
|
-
eventCount += 1;
|
|
377
|
-
run.eventCount += 1;
|
|
378
|
-
increment(run.targets, record.event.target);
|
|
379
|
-
projectRunActivity(run, record);
|
|
380
|
-
projectSpawnNode(nodes, record, run.attemptIndex);
|
|
381
|
-
projectNodeActivity(nodes, record);
|
|
382
|
-
projectSeat(run, nodes, record);
|
|
383
|
-
projectTurn(run, nodes, record);
|
|
384
|
-
} else if (record.decision) {
|
|
385
|
-
decisionCount += 1;
|
|
386
|
-
run.decisionCount += 1;
|
|
387
|
-
increment(run.decisions, record.decision.kind);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
const byAttempt = /* @__PURE__ */ new Map();
|
|
391
|
-
for (const node of nodes.values()) {
|
|
392
|
-
const key = attemptKey(node.runId, node.attemptIndex);
|
|
393
|
-
const list = byAttempt.get(key);
|
|
394
|
-
if (list) list.push(node);
|
|
395
|
-
else byAttempt.set(key, [node]);
|
|
396
|
-
}
|
|
397
|
-
const first = verified[0];
|
|
398
|
-
const last = verified.at(-1);
|
|
399
|
-
return Object.freeze({
|
|
400
|
-
pursuitId,
|
|
401
|
-
sequence: last.sequence,
|
|
402
|
-
chainTip: last.digest,
|
|
403
|
-
firstObservedAt: first.observedAt,
|
|
404
|
-
lastObservedAt: last.observedAt,
|
|
405
|
-
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)) ?? []))),
|
|
406
|
-
nodes: Object.freeze([...nodes.values()].sort((a, b) => a.firstSequence - b.firstSequence || a.runId.localeCompare(b.runId) || a.id.localeCompare(b.id)).map(freezeNode)),
|
|
407
|
-
eventCount,
|
|
408
|
-
decisionCount
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
function projectSeat(run, nodes, record) {
|
|
412
|
-
const event = record.event;
|
|
413
|
-
if (event?.target !== "agent.seat") return;
|
|
414
|
-
const payload = objectRecord(event.payload);
|
|
415
|
-
const phase = stringField(payload, "phase");
|
|
416
|
-
const owner = nodes.get(nodeKey(event.runId, stringField(payload, "nodeId") ?? "")) ?? run;
|
|
417
|
-
if (phase === "paused") {
|
|
418
|
-
const resumeAt = stringField(payload, "resumeAt");
|
|
419
|
-
if (resumeAt) owner.resumeAt = resumeAt;
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
if (phase === "resumed") {
|
|
423
|
-
delete owner.resumeAt;
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
const raw = objectRecord(payload?.segment);
|
|
427
|
-
const seat = stringField(raw, "seat");
|
|
428
|
-
const harness = stringField(raw, "harness");
|
|
429
|
-
const provider = stringField(raw, "provider");
|
|
430
|
-
const model = stringField(raw, "model");
|
|
431
|
-
const startedAt = stringField(raw, "startedAt");
|
|
432
|
-
if (!seat || !harness || !provider || !model || !startedAt) return;
|
|
433
|
-
if (owner.segments === void 0) owner.segments = [];
|
|
434
|
-
const segments = owner.segments;
|
|
435
|
-
if (phase === "started") {
|
|
436
|
-
if (!segments.some((segment) => segment.startedAt === startedAt && segment.seat === seat)) segments.push({
|
|
437
|
-
seat,
|
|
438
|
-
harness,
|
|
439
|
-
provider,
|
|
440
|
-
model,
|
|
441
|
-
startedAt
|
|
442
|
-
});
|
|
443
|
-
} else if (phase === "ended") {
|
|
444
|
-
const endedAt = stringField(raw, "endedAt");
|
|
445
|
-
const reason = stringField(raw, "reason");
|
|
446
|
-
if (!endedAt || !reason) return;
|
|
447
|
-
const index = segments.findIndex((segment) => segment.startedAt === startedAt && segment.seat === seat);
|
|
448
|
-
if (index >= 0) segments[index] = {
|
|
449
|
-
seat,
|
|
450
|
-
harness,
|
|
451
|
-
provider,
|
|
452
|
-
model,
|
|
453
|
-
startedAt,
|
|
454
|
-
endedAt,
|
|
455
|
-
reason
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
function attemptKey(runId, attemptIndex) {
|
|
460
|
-
return `${runId}\u0000${attemptIndex}`;
|
|
461
|
-
}
|
|
462
|
-
/**
|
|
463
|
-
* The attempt a record belongs to. An `agent.run` `before` opens a new attempt when the run has
|
|
464
|
-
* none or its latest attempt already settled; on an open attempt it is a resume. Every other
|
|
465
|
-
* record joins the latest attempt, and a journal that never emitted `before` gets attempt 0.
|
|
466
|
-
*/
|
|
467
|
-
function currentAttempt(attempts, runId, record) {
|
|
468
|
-
const list = attempts.get(runId) ?? [];
|
|
469
|
-
if (list.length === 0) attempts.set(runId, list);
|
|
470
|
-
const latest = list.at(-1);
|
|
471
|
-
const isStart = record.event?.target === "agent.run" && record.event.phase === "before";
|
|
472
|
-
if (latest !== void 0 && (!isStart || latest.status === "running")) {
|
|
473
|
-
if (isStart) {
|
|
474
|
-
if (latest.started) latest.resumeCount += 1;
|
|
475
|
-
latest.started = true;
|
|
476
|
-
}
|
|
477
|
-
return latest;
|
|
478
|
-
}
|
|
479
|
-
const created = {
|
|
480
|
-
runId,
|
|
481
|
-
attemptIndex: list.length,
|
|
482
|
-
resumeCount: 0,
|
|
483
|
-
started: isStart,
|
|
484
|
-
status: "running",
|
|
485
|
-
firstSequence: record.sequence,
|
|
486
|
-
lastSequence: record.sequence,
|
|
487
|
-
firstObservedAt: record.observedAt,
|
|
488
|
-
lastObservedAt: record.observedAt,
|
|
489
|
-
eventCount: 0,
|
|
490
|
-
decisionCount: 0,
|
|
491
|
-
targets: {},
|
|
492
|
-
decisions: {}
|
|
493
|
-
};
|
|
494
|
-
list.push(created);
|
|
495
|
-
return created;
|
|
496
|
-
}
|
|
497
|
-
function projectRunActivity(run, record) {
|
|
498
|
-
const event = record.event;
|
|
499
|
-
if (event?.target !== "agent.run") return;
|
|
500
|
-
const payload = objectRecord(event.payload);
|
|
501
|
-
const status = stringField(payload, "status");
|
|
502
|
-
if (event.phase === "after" || status === "done") {
|
|
503
|
-
run.status = "done";
|
|
504
|
-
run.settledAt = record.observedAt;
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
if (event.phase !== "error" && status !== "failed") return;
|
|
508
|
-
run.status = "down";
|
|
509
|
-
run.settledAt = record.observedAt;
|
|
510
|
-
const error = stringField(payload, "error");
|
|
511
|
-
if (error) run.error = error;
|
|
512
|
-
}
|
|
513
|
-
function nodeKey(runId, nodeId) {
|
|
514
|
-
return `${runId}\u0000${nodeId}`;
|
|
515
|
-
}
|
|
516
|
-
function projectSpawnNode(nodes, record, attemptIndex) {
|
|
517
|
-
const event = record.event;
|
|
518
|
-
if (event?.target !== "agent.spawn") return;
|
|
519
|
-
const payload = objectRecord(event.payload);
|
|
520
|
-
const childId = stringField(payload, "childId");
|
|
521
|
-
if (!childId) return;
|
|
522
|
-
const key = nodeKey(event.runId, childId);
|
|
523
|
-
const existing = nodes.get(key);
|
|
524
|
-
if (existing) {
|
|
525
|
-
existing.lastSequence = record.sequence;
|
|
526
|
-
existing.lastObservedAt = record.observedAt;
|
|
527
|
-
existing.eventCount += 1;
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
const label = stringField(payload, "label");
|
|
531
|
-
const runtime = stringField(payload, "runtime");
|
|
532
|
-
const depth = numberField(payload, "depth");
|
|
533
|
-
const assignmentId = stringField(payload, "assignmentId");
|
|
534
|
-
const successorOf = stringField(payload, "successorOf");
|
|
535
|
-
const attemptId = stringField(payload, "attemptId");
|
|
536
|
-
const startedAt = numberField(payload, "startedAt");
|
|
537
|
-
nodes.set(key, {
|
|
538
|
-
id: childId,
|
|
539
|
-
...event.parentId ? { parentId: event.parentId } : {},
|
|
540
|
-
runId: event.runId,
|
|
541
|
-
attemptIndex,
|
|
542
|
-
...label ? { label } : {},
|
|
543
|
-
...runtime ? { runtime } : {},
|
|
544
|
-
...depth !== void 0 ? { depth } : {},
|
|
545
|
-
...assignmentId ? { assignmentId } : {},
|
|
546
|
-
...successorOf ? { successorOf } : {},
|
|
547
|
-
...attemptId ? { attemptId } : {},
|
|
548
|
-
startedAt: startedAt ?? record.observedAt,
|
|
549
|
-
...payload && Object.hasOwn(payload, "identity") ? { identity: payload.identity } : {},
|
|
550
|
-
...payload && Object.hasOwn(payload, "budget") ? { budget: payload.budget } : {},
|
|
551
|
-
status: "running",
|
|
552
|
-
modelCalls: [],
|
|
553
|
-
firstSequence: record.sequence,
|
|
554
|
-
lastSequence: record.sequence,
|
|
555
|
-
firstObservedAt: record.observedAt,
|
|
556
|
-
lastObservedAt: record.observedAt,
|
|
557
|
-
eventCount: 1,
|
|
558
|
-
turnCount: 0
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
function projectNodeActivity(nodes, record) {
|
|
562
|
-
const event = record.event;
|
|
563
|
-
if (event === void 0) return;
|
|
564
|
-
if (event.target === "agent.spawn") return;
|
|
565
|
-
const payload = objectRecord(event.payload);
|
|
566
|
-
const nodeId = stringField(payload, "childId") ?? stringField(payload, "nodeId") ?? stringField(payload, "workerId");
|
|
567
|
-
if (!nodeId) return;
|
|
568
|
-
const node = nodes.get(nodeKey(event.runId, nodeId));
|
|
569
|
-
if (!node) return;
|
|
570
|
-
node.lastSequence = record.sequence;
|
|
571
|
-
node.lastObservedAt = record.observedAt;
|
|
572
|
-
node.eventCount += 1;
|
|
573
|
-
if (event.target !== "agent.child") return;
|
|
574
|
-
const status = stringField(payload, "status");
|
|
575
|
-
if (status !== "done" && status !== "down") return;
|
|
576
|
-
node.status = status;
|
|
577
|
-
node.settledAt = numberField(payload, "settledAt") ?? record.observedAt;
|
|
578
|
-
const startedAt = numberField(payload, "startedAt");
|
|
579
|
-
if (startedAt !== void 0) node.startedAt = startedAt;
|
|
580
|
-
if (payload && Object.hasOwn(payload, "spent")) node.spent = spendField(payload, "spent");
|
|
581
|
-
const metered = spendField(payload, "metered");
|
|
582
|
-
if (metered) node.ownInference = node.ownInference ? addSpend(node.ownInference, metered) : metered;
|
|
583
|
-
const runtime = stringField(payload, "runtime");
|
|
584
|
-
if (runtime) node.runtime = runtime;
|
|
585
|
-
const outRef = stringField(payload, "outRef");
|
|
586
|
-
if (outRef) node.outRef = outRef;
|
|
587
|
-
const score = numberField(payload, "score");
|
|
588
|
-
if (score !== void 0) node.score = score;
|
|
589
|
-
const valid = booleanField(payload, "valid");
|
|
590
|
-
if (valid !== void 0) node.valid = valid;
|
|
591
|
-
const reason = stringField(payload, "reason");
|
|
592
|
-
if (reason) node.reason = reason;
|
|
593
|
-
if (status === "done") node.failure = void 0;
|
|
594
|
-
else if (payload && Object.hasOwn(payload, "failure")) node.failure = runtimeFailureCauseSchema.parse(payload.failure);
|
|
595
|
-
const infra = booleanField(payload, "infra");
|
|
596
|
-
if (infra !== void 0) node.infra = infra;
|
|
597
|
-
const retained = stringField(payload, "retainedExecution");
|
|
598
|
-
if (retained === "pending" || retained === "released" || retained === "release-unconfirmed") node.retainedExecution = retained;
|
|
599
|
-
const cause = stringField(payload, "retainedPendingCause");
|
|
600
|
-
if (cause === "unobservable" || cause === "provider-contract" || cause === "request-rejected" || cause === "transport" || cause === "nested-recovery") node.retainedPendingCause = cause;
|
|
601
|
-
const releasedAt = numberField(payload, "releasedAt");
|
|
602
|
-
if (releasedAt !== void 0) node.releasedAt = releasedAt;
|
|
603
|
-
const budgetViolation = budgetViolationField(payload);
|
|
604
|
-
if (budgetViolation) node.budgetViolation = budgetViolation;
|
|
605
|
-
attachSettlementEvidence(node, payload);
|
|
606
|
-
}
|
|
607
|
-
/**
|
|
608
|
-
* Read the receipts a settlement carries. Each is taken as reported: an `unknown` receipt keeps
|
|
609
|
-
* its `status` and `reason` so a client can show WHY a model or backend is missing instead of
|
|
610
|
-
* showing nothing, and a receipt that never landed leaves every derived field absent.
|
|
611
|
-
*/
|
|
612
|
-
function attachSettlementEvidence(node, payload) {
|
|
613
|
-
const providerModel = payload?.providerModel;
|
|
614
|
-
if (providerModel && typeof providerModel === "object") node.providerModel = providerModel;
|
|
615
|
-
const trace = payload?.trace;
|
|
616
|
-
if (trace && typeof trace === "object") node.trace = trace;
|
|
617
|
-
const bindings = payload?.executionBindings;
|
|
618
|
-
if (Array.isArray(bindings) && bindings.length > 0) {
|
|
619
|
-
node.executionBindings = bindings;
|
|
620
|
-
const known = bindings.find((binding) => objectRecord(binding)?.status === "known");
|
|
621
|
-
if (known) {
|
|
622
|
-
node.placement = known.descriptor;
|
|
623
|
-
node.attemptId ??= known.attemptId;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
const receipt = payload?.materialization;
|
|
627
|
-
if (!receipt || typeof receipt !== "object") return;
|
|
628
|
-
node.materialization = receipt;
|
|
629
|
-
if (receipt.status !== "known") return;
|
|
630
|
-
node.backend = receipt.backend;
|
|
631
|
-
if (receipt.model.status === "known") node.model = receipt.model.id;
|
|
632
|
-
node.execution = {
|
|
633
|
-
kind: receipt.execution.kind,
|
|
634
|
-
id: receipt.execution.id
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Fold one metered turn onto whoever drove it. `agent.turn` names its subject in `parentId` — the
|
|
639
|
-
* node making the call — not in the payload, because the caller IS the subject. A turn the run
|
|
640
|
-
* root drove belongs to no spawned node and lands on the run instead, so the run's totals stay
|
|
641
|
-
* complete without inventing a node for the root.
|
|
642
|
-
*/
|
|
643
|
-
function projectTurn(run, nodes, record) {
|
|
644
|
-
const event = record.event;
|
|
645
|
-
if (event?.target !== "agent.turn") return;
|
|
646
|
-
const payload = objectRecord(event.payload);
|
|
647
|
-
const spend = spendField(payload, "spend");
|
|
648
|
-
const subject = event.parentId;
|
|
649
|
-
if (subject === void 0) return;
|
|
650
|
-
const node = nodes.get(nodeKey(event.runId, subject));
|
|
651
|
-
if (!node) {
|
|
652
|
-
if (!spend) return;
|
|
653
|
-
run.rootInference = run.rootInference ? addSpend(run.rootInference, spend) : spend;
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
node.turnCount += 1;
|
|
657
|
-
node.lastSequence = record.sequence;
|
|
658
|
-
node.lastObservedAt = record.observedAt;
|
|
659
|
-
if (spend) {
|
|
660
|
-
node.ownInference = node.ownInference ? addSpend(node.ownInference, spend) : spend;
|
|
661
|
-
node.firstOutputAt ??= record.observedAt;
|
|
662
|
-
}
|
|
663
|
-
const reasoning = numberField(payload, "reasoningTokens");
|
|
664
|
-
if (reasoning !== void 0) node.reasoningTokens = (node.reasoningTokens ?? 0) + reasoning;
|
|
665
|
-
const firstTokenAt = numberField(payload, "firstTokenAt");
|
|
666
|
-
if (firstTokenAt !== void 0) node.firstTokenAt ??= firstTokenAt;
|
|
667
|
-
const model = stringField(payload, "model");
|
|
668
|
-
if (model) node.model ??= model;
|
|
669
|
-
const callId = stringField(payload, "callId");
|
|
670
|
-
if (callId && !node.modelCalls.includes(callId)) node.modelCalls.push(callId);
|
|
671
|
-
}
|
|
672
|
-
/**
|
|
673
|
-
* A node's whole reported cost: the child work its settlement reported plus the inference it drove
|
|
674
|
-
* itself. `undefined` when neither landed — the run's `spendGaps` then names the node, so an
|
|
675
|
-
* unaccounted node is visible instead of reading as free.
|
|
676
|
-
*/
|
|
677
|
-
function nodeTotal(node) {
|
|
678
|
-
if (node.spent && node.ownInference) return addSpend(node.spent, node.ownInference);
|
|
679
|
-
if (node.spent) return cloneSpend(node.spent);
|
|
680
|
-
if (node.ownInference) return cloneSpend(node.ownInference);
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* The run counted once, and each node's own share of it.
|
|
684
|
-
*
|
|
685
|
-
* A settled node's `spent` already contains the child work its nested tree reported, so summing
|
|
686
|
-
* every node would count a driver's descendants twice. Inclusive therefore sums only the run's
|
|
687
|
-
* TOP-LEVEL nodes — those whose parent is not another node of this run — plus the root's own
|
|
688
|
-
* turns. Exclusive subtracts each node's direct children from its own total, which telescopes:
|
|
689
|
-
* the exclusive entries sum back to `inclusive` exactly.
|
|
690
|
-
*/
|
|
691
|
-
function runTotals(run, nodes) {
|
|
692
|
-
const present = new Set(nodes.map((node) => node.id));
|
|
693
|
-
const children = /* @__PURE__ */ new Map();
|
|
694
|
-
for (const node of nodes) {
|
|
695
|
-
if (node.parentId === void 0 || !present.has(node.parentId)) continue;
|
|
696
|
-
const list = children.get(node.parentId);
|
|
697
|
-
if (list) list.push(node);
|
|
698
|
-
else children.set(node.parentId, [node]);
|
|
699
|
-
}
|
|
700
|
-
const exclusiveByNode = {};
|
|
701
|
-
let inclusive = run.rootInference ? cloneSpend(run.rootInference) : zeroSpend();
|
|
702
|
-
if (run.rootInference) exclusiveByNode[run.runId] = cloneSpend(run.rootInference);
|
|
703
|
-
for (const node of nodes) {
|
|
704
|
-
const total = nodeTotal(node);
|
|
705
|
-
if (total === void 0) continue;
|
|
706
|
-
if (node.parentId === void 0 || !present.has(node.parentId)) inclusive = addSpend(inclusive, total);
|
|
707
|
-
let exclusive = total;
|
|
708
|
-
for (const child of children.get(node.id) ?? []) {
|
|
709
|
-
const childTotal = nodeTotal(child);
|
|
710
|
-
if (childTotal !== void 0) exclusive = subtractSpend(exclusive, childTotal);
|
|
711
|
-
}
|
|
712
|
-
exclusiveByNode[node.id] = exclusive;
|
|
713
|
-
}
|
|
714
|
-
return Object.freeze({
|
|
715
|
-
inclusive: Object.freeze(inclusive),
|
|
716
|
-
exclusiveByNode: Object.freeze(exclusiveByNode)
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
/**
|
|
720
|
-
* Remove a child's reported total from its parent's. Per channel, never below zero: a parent whose
|
|
721
|
-
* executor reported less than its children did is a reporting gap, and a negative exclusive share
|
|
722
|
-
* would be a fabricated number rather than a measurement.
|
|
723
|
-
*/
|
|
724
|
-
function subtractSpend(a, b) {
|
|
725
|
-
const tokens = { ...a.tokens };
|
|
726
|
-
tokens.input = Math.max(0, tokens.input - b.tokens.input);
|
|
727
|
-
tokens.output = Math.max(0, tokens.output - b.tokens.output);
|
|
728
|
-
if (a.tokens.cacheRead !== void 0) tokens.cacheRead = Math.max(0, a.tokens.cacheRead - (b.tokens.cacheRead ?? 0));
|
|
729
|
-
if (a.tokens.cacheWrite !== void 0) tokens.cacheWrite = Math.max(0, a.tokens.cacheWrite - (b.tokens.cacheWrite ?? 0));
|
|
730
|
-
if (a.tokens.freshInput !== void 0) tokens.freshInput = Math.max(0, a.tokens.freshInput - (b.tokens.freshInput ?? 0));
|
|
731
|
-
return {
|
|
732
|
-
iterations: Math.max(0, a.iterations - b.iterations),
|
|
733
|
-
tokens,
|
|
734
|
-
...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {},
|
|
735
|
-
usd: Math.max(0, a.usd - b.usd),
|
|
736
|
-
...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
|
|
737
|
-
...a.usdEstimated !== void 0 ? { usdEstimated: Math.max(0, a.usdEstimated - (b.usdEstimated ?? 0)) } : {},
|
|
738
|
-
ms: Math.max(0, a.ms - b.ms)
|
|
739
|
-
};
|
|
740
|
-
}
|
|
741
|
-
/** The nodes of one run whose accounting is incomplete, in the vocabulary the supervisor already
|
|
742
|
-
* uses: `never-settled` = no terminal record, so every channel is unaccounted; `unreported` = a
|
|
743
|
-
* record landed with a channel the provider did not report, so that channel is a floor. */
|
|
744
|
-
function runSpendGaps(nodes) {
|
|
745
|
-
const gaps = [];
|
|
746
|
-
for (const node of nodes) {
|
|
747
|
-
const label = node.label;
|
|
748
|
-
if (node.status === "running") {
|
|
749
|
-
gaps.push({
|
|
750
|
-
id: node.id,
|
|
751
|
-
...label !== void 0 ? { label } : {},
|
|
752
|
-
kind: "never-settled",
|
|
753
|
-
channels: ["tokens", "usd"]
|
|
754
|
-
});
|
|
755
|
-
continue;
|
|
756
|
-
}
|
|
757
|
-
const total = nodeTotal(node);
|
|
758
|
-
const channels = [];
|
|
759
|
-
if (total === void 0 || total.tokensKnown === false) channels.push("tokens");
|
|
760
|
-
if (total === void 0 || total.usdKnown === false) channels.push("usd");
|
|
761
|
-
if (channels.length === 0) continue;
|
|
762
|
-
gaps.push({
|
|
763
|
-
id: node.id,
|
|
764
|
-
...label !== void 0 ? { label } : {},
|
|
765
|
-
kind: "unreported",
|
|
766
|
-
channels
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
return gaps;
|
|
770
|
-
}
|
|
771
|
-
function usageOf(total, reasoning) {
|
|
772
|
-
return {
|
|
773
|
-
input: total.tokens.input,
|
|
774
|
-
output: total.tokens.output,
|
|
775
|
-
...total.tokens.cacheRead !== void 0 ? { cacheRead: total.tokens.cacheRead } : {},
|
|
776
|
-
...total.tokens.cacheWrite !== void 0 ? { cacheWrite: total.tokens.cacheWrite } : {},
|
|
777
|
-
...reasoning !== void 0 ? { reasoning } : {},
|
|
778
|
-
tokensKnown: total.tokensKnown !== false && total.tokens.tokensKnown !== false
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
function costOf(total) {
|
|
782
|
-
const usdKnown = total.usdKnown !== false;
|
|
783
|
-
const estimated = total.usdEstimated;
|
|
784
|
-
const provenance = usdKnown ? estimated === void 0 || estimated === 0 ? "reported" : "partial" : estimated === void 0 ? "unknown" : estimated >= total.usd ? "estimated" : "partial";
|
|
785
|
-
return {
|
|
786
|
-
usd: total.usd,
|
|
787
|
-
usdKnown,
|
|
788
|
-
...estimated !== void 0 ? { usdEstimated: estimated } : {},
|
|
789
|
-
provenance
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
/** The platform block, or nothing at all when the node's spend names no box channel. */
|
|
793
|
-
function platformOf(total) {
|
|
794
|
-
if (total.boxMinutesProvenance === void 0) return void 0;
|
|
795
|
-
return {
|
|
796
|
-
...total.boxMinutes !== void 0 ? { boxMinutes: total.boxMinutes } : {},
|
|
797
|
-
boxMinutesKnown: total.boxMinutesKnown === true,
|
|
798
|
-
provenance: total.boxMinutesProvenance
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
function timingOf(node) {
|
|
802
|
-
if (node.startedAt === void 0) return void 0;
|
|
803
|
-
return {
|
|
804
|
-
startedAt: node.startedAt,
|
|
805
|
-
...node.firstOutputAt !== void 0 ? { firstOutputAt: node.firstOutputAt } : {},
|
|
806
|
-
...node.firstTokenAt !== void 0 ? { firstTokenAt: node.firstTokenAt } : {},
|
|
807
|
-
...node.settledAt !== void 0 ? { settledAt: node.settledAt } : {},
|
|
808
|
-
...node.settledAt !== void 0 ? { wallMs: Math.max(0, node.settledAt - node.startedAt) } : {}
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
function freezeRun(run, nodes) {
|
|
812
|
-
const gaps = runSpendGaps(nodes);
|
|
813
|
-
const { rootInference: _rootInference, started: _started, segments, ...rest } = run;
|
|
814
|
-
return Object.freeze({
|
|
815
|
-
...rest,
|
|
816
|
-
...segments?.length ? { segments: Object.freeze(segments.map((segment) => Object.freeze(segment))) } : {},
|
|
817
|
-
targets: Object.freeze({ ...run.targets }),
|
|
818
|
-
decisions: Object.freeze({ ...run.decisions }),
|
|
819
|
-
totals: runTotals(run, nodes),
|
|
820
|
-
...gaps.length > 0 ? { spendGaps: Object.freeze(gaps) } : {}
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
function freezeNode(node) {
|
|
824
|
-
const { modelCalls, segments, reasoningTokens, startedAt: _startedAt, attemptIndex: _attemptIndex, ...rest } = node;
|
|
825
|
-
const total = nodeTotal(node);
|
|
826
|
-
const timing = timingOf(node);
|
|
827
|
-
return Object.freeze({
|
|
828
|
-
...rest,
|
|
829
|
-
...segments?.length ? { segments: Object.freeze(segments.map((segment) => Object.freeze(segment))) } : {},
|
|
830
|
-
...modelCalls.length > 0 ? { modelCalls: Object.freeze([...modelCalls]) } : {},
|
|
831
|
-
...total !== void 0 ? {
|
|
832
|
-
usage: usageOf(total, reasoningTokens),
|
|
833
|
-
cost: costOf(total)
|
|
834
|
-
} : {},
|
|
835
|
-
...total !== void 0 && platformOf(total) !== void 0 ? { platform: platformOf(total) } : {},
|
|
836
|
-
...timing !== void 0 ? { timing } : {}
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
function increment(target, key) {
|
|
840
|
-
target[key] = (target[key] ?? 0) + 1;
|
|
841
|
-
}
|
|
842
|
-
function objectRecord(value) {
|
|
843
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
844
|
-
}
|
|
845
|
-
/** Read a journaled `Spend` without trusting the wire: a record missing the conserved channels is
|
|
846
|
-
* not a zero-cost turn, it is an unparseable one, and it must not enter a total. */
|
|
847
|
-
function spendField(value, key) {
|
|
848
|
-
const field = objectRecord(value?.[key]);
|
|
849
|
-
if (!field) return void 0;
|
|
850
|
-
const tokens = objectRecord(field.tokens);
|
|
851
|
-
if (typeof field.usd !== "number" || !Number.isFinite(field.usd)) return void 0;
|
|
852
|
-
if (!tokens || typeof tokens.input !== "number" || typeof tokens.output !== "number") return;
|
|
853
|
-
return cloneSpend(field);
|
|
854
|
-
}
|
|
855
|
-
/** Read a journaled overspend without trusting the wire: one malformed channel drops the record,
|
|
856
|
-
* so an unparseable entry is never reported as an overspend of some size. */
|
|
857
|
-
function budgetViolationField(value) {
|
|
858
|
-
const overspent = objectRecord(value?.budgetViolation)?.overspent;
|
|
859
|
-
if (!Array.isArray(overspent) || overspent.length === 0) return void 0;
|
|
860
|
-
const entries = [];
|
|
861
|
-
for (const raw of overspent) {
|
|
862
|
-
const entry = objectRecord(raw);
|
|
863
|
-
const channel = stringField(entry, "channel");
|
|
864
|
-
const reserved = numberField(entry, "reserved");
|
|
865
|
-
const spent = numberField(entry, "spent");
|
|
866
|
-
if (channel === void 0 || reserved === void 0 || spent === void 0) return void 0;
|
|
867
|
-
entries.push({
|
|
868
|
-
channel,
|
|
869
|
-
reserved,
|
|
870
|
-
spent
|
|
871
|
-
});
|
|
872
|
-
}
|
|
873
|
-
return { overspent: entries };
|
|
874
|
-
}
|
|
875
|
-
function stringField(value, key) {
|
|
876
|
-
const field = value?.[key];
|
|
877
|
-
return typeof field === "string" && field.length > 0 ? field : void 0;
|
|
878
|
-
}
|
|
879
|
-
function numberField(value, key) {
|
|
880
|
-
const field = value?.[key];
|
|
881
|
-
return typeof field === "number" && Number.isFinite(field) ? field : void 0;
|
|
882
|
-
}
|
|
883
|
-
function booleanField(value, key) {
|
|
884
|
-
const field = value?.[key];
|
|
885
|
-
return typeof field === "boolean" ? field : void 0;
|
|
886
|
-
}
|
|
887
20
|
// src/durable/settle-record.ts
|
|
888
21
|
/** The settle record: the returned `SupervisedResult` as canonical JSON, written once. */
|
|
889
22
|
const SETTLE_RECORD_FILE = "result.json";
|
|
@@ -2518,8 +1651,7 @@ async function superviseGraphPursuit(graph, opts) {
|
|
|
2518
1651
|
}
|
|
2519
1652
|
/** The pursuit identity and run directory both durable entries require. */
|
|
2520
1653
|
function assertPursuitDirectory(opts, entry) {
|
|
2521
|
-
if (opts.
|
|
2522
|
-
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`);
|
|
2523
1655
|
if (opts.retainedAtSettlement === "keep") throw new TypeError(`${entry}: retainedAtSettlement 'keep' cannot hold, because a settled pursuit is never re-entered`);
|
|
2524
1656
|
}
|
|
2525
1657
|
/**
|
|
@@ -2560,17 +1692,16 @@ async function runDurablePursuit(opts, execute, settled, reopen) {
|
|
|
2560
1692
|
throw error;
|
|
2561
1693
|
}
|
|
2562
1694
|
let pursuit;
|
|
2563
|
-
let observerError;
|
|
2564
1695
|
try {
|
|
2565
1696
|
await observer.journal.appendEvent(rootEvent(pursuitId, runId, "error", now(), {
|
|
2566
1697
|
status: "failed",
|
|
2567
|
-
error: errorMessage(error)
|
|
1698
|
+
error: errorMessage(error),
|
|
1699
|
+
failure: runtimeFailureCause(error)
|
|
2568
1700
|
}));
|
|
2569
1701
|
pursuit = projectPursuit(await observer.journal.read());
|
|
2570
|
-
} catch (
|
|
2571
|
-
observerError
|
|
1702
|
+
} catch (observerError) {
|
|
1703
|
+
throw new Error("supervisePursuit: Runtime failed and durable observer completeness could not be proven", { cause: new AggregateError([error, observerError]) });
|
|
2572
1704
|
}
|
|
2573
|
-
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]) });
|
|
2574
1705
|
try {
|
|
2575
1706
|
const rootStream = await readRootStreamReceipt(runDir);
|
|
2576
1707
|
await writeFailureRecord(runDir, {
|
|
@@ -2588,12 +1719,19 @@ async function runDurablePursuit(opts, execute, settled, reopen) {
|
|
|
2588
1719
|
}
|
|
2589
1720
|
throw new SupervisePursuitError(error, pursuit, observerPath, failurePath);
|
|
2590
1721
|
}
|
|
1722
|
+
const terminal = settled(result);
|
|
2591
1723
|
try {
|
|
2592
|
-
await writeSettleRecord(runDir,
|
|
1724
|
+
await writeSettleRecord(runDir, terminal);
|
|
2593
1725
|
} catch (cause) {
|
|
2594
1726
|
throw new Error(`supervisePursuit: Runtime settled but the settle record ${settlePath} could not be written`, { cause });
|
|
2595
1727
|
}
|
|
2596
|
-
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
|
+
}));
|
|
2597
1735
|
terminalState = "done";
|
|
2598
1736
|
return Object.freeze({
|
|
2599
1737
|
result,
|
|
@@ -2621,72 +1759,6 @@ function rootEvent(pursuitId, runId, phase, timestamp, payload) {
|
|
|
2621
1759
|
function errorName(error) {
|
|
2622
1760
|
return error instanceof Error ? error.name : "NonError";
|
|
2623
1761
|
}
|
|
2624
|
-
// src/durable/supervision-discovery.ts
|
|
2625
|
-
/**
|
|
2626
|
-
* Discover the stable identities recorded by Runtime's durable supervision
|
|
2627
|
-
* files. This is the developer-facing first step before calling
|
|
2628
|
-
* `FileSpawnJournal.loadTree(root)`, `loadSpawnForest(journal, root)`, or
|
|
2629
|
-
* `FileCoordinationLog.load(runId, ownerId)`.
|
|
2630
|
-
*
|
|
2631
|
-
* Missing files produce empty collections. A malformed committed JSONL record
|
|
2632
|
-
* still fails loud through the same parser used by the runtime; a torn final
|
|
2633
|
-
* append is ignored because it was never acknowledged as committed.
|
|
2634
|
-
*/
|
|
2635
|
-
async function discoverDurableSupervisionRun(runDir) {
|
|
2636
|
-
if (typeof runDir !== "string" || runDir.trim().length === 0) throw new TypeError("discoverDurableSupervisionRun: runDir must be a non-empty string");
|
|
2637
|
-
const canonicalRunDir = resolve(runDir);
|
|
2638
|
-
const spawnJournalPath = `${canonicalRunDir}/spawn-journal.jsonl`;
|
|
2639
|
-
const coordinationLogPath = `${canonicalRunDir}/coordination-log.jsonl`;
|
|
2640
|
-
const allRoots = /* @__PURE__ */ new Set();
|
|
2641
|
-
const nestedRoots = /* @__PURE__ */ new Set();
|
|
2642
|
-
const rootsBegunAt = /* @__PURE__ */ new Map();
|
|
2643
|
-
for await (const record of readCommittedJsonLines(spawnJournalPath, { allowMissing: true })) {
|
|
2644
|
-
if (record.kind === "begin") {
|
|
2645
|
-
if (typeof record.root !== "string" || record.root.length === 0) throw new Error(`${spawnJournalPath}: begin record has no non-empty string root identity`);
|
|
2646
|
-
allRoots.add(record.root);
|
|
2647
|
-
rootsBegunAt.set(record.root, typeof record.at === "string" ? record.at : null);
|
|
2648
|
-
continue;
|
|
2649
|
-
}
|
|
2650
|
-
for (const event of spawnJournalRecordEvents(record)) {
|
|
2651
|
-
if (!isRecord(event) || event.kind !== "spawned") continue;
|
|
2652
|
-
if (!Object.hasOwn(event, "ownedTreeRoot")) continue;
|
|
2653
|
-
if (typeof event.ownedTreeRoot !== "string" || event.ownedTreeRoot.length === 0) throw new Error(`${spawnJournalPath}: spawned event ownedTreeRoot must be a non-empty string when present`);
|
|
2654
|
-
nestedRoots.add(event.ownedTreeRoot);
|
|
2655
|
-
}
|
|
2656
|
-
}
|
|
2657
|
-
const streams = /* @__PURE__ */ new Map();
|
|
2658
|
-
for await (const record of readCommittedJsonLines(coordinationLogPath, { allowMissing: true })) {
|
|
2659
|
-
if (typeof record.runId !== "string" || record.runId.length === 0) throw new Error(`${coordinationLogPath}: record has no non-empty string runId identity`);
|
|
2660
|
-
const stream = streams.get(record.runId) ?? {
|
|
2661
|
-
owners: /* @__PURE__ */ new Set(),
|
|
2662
|
-
unscopedRecords: 0,
|
|
2663
|
-
recordCount: 0
|
|
2664
|
-
};
|
|
2665
|
-
stream.recordCount += 1;
|
|
2666
|
-
if (record.ownerId === void 0) stream.unscopedRecords += 1;
|
|
2667
|
-
else if (typeof record.ownerId === "string" && record.ownerId.length > 0) stream.owners.add(record.ownerId);
|
|
2668
|
-
else throw new Error(`${coordinationLogPath}: ownerId must be a non-empty string when present`);
|
|
2669
|
-
streams.set(record.runId, stream);
|
|
2670
|
-
}
|
|
2671
|
-
const coordinationStreams = [...streams.entries()].sort(([left], [right]) => compareText(left, right)).map(([runId, stream]) => Object.freeze({
|
|
2672
|
-
runId,
|
|
2673
|
-
ownerIds: Object.freeze([...stream.owners].sort(compareText)),
|
|
2674
|
-
unscopedRecords: stream.unscopedRecords,
|
|
2675
|
-
recordCount: stream.recordCount
|
|
2676
|
-
}));
|
|
2677
|
-
const roots = [...allRoots].filter((root) => !nestedRoots.has(root)).sort(compareText);
|
|
2678
|
-
return Object.freeze({
|
|
2679
|
-
runDir: canonicalRunDir,
|
|
2680
|
-
spawnJournalPath,
|
|
2681
|
-
coordinationLogPath,
|
|
2682
|
-
roots: Object.freeze(roots),
|
|
2683
|
-
rootsBegunAt: Object.freeze(roots.map((root) => rootsBegunAt.get(root) ?? null)),
|
|
2684
|
-
coordinationStreams: Object.freeze(coordinationStreams)
|
|
2685
|
-
});
|
|
2686
|
-
}
|
|
2687
|
-
function compareText(left, right) {
|
|
2688
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
2689
|
-
}
|
|
2690
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 };
|
|
2691
1763
|
|
|
2692
1764
|
//# sourceMappingURL=durable.js.map
|