@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
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
import { O as runtimeFailureCauseSchema, b as captureRunWriteContext, c as readCommittedJsonLines, i as appendCommittedJsonLine, r as spawnJournalRecordEvents, tt as detachedSnapshot } from "./journal-store-BJPMUydp.js";
|
|
2
|
+
import { a as cloneSpend, n as addSpend, w as zeroSpend } from "./util-ebWMWzY0.js";
|
|
3
|
+
import { a as withPursuitContext } from "./runtime-hooks-CsW2pfFk.js";
|
|
4
|
+
import { isRecord } from "@tangle-network/agent-core";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
// src/durable/observer-journal.ts
|
|
8
|
+
/**
|
|
9
|
+
* Durable, append-only third-person history for one concrete Runtime execution.
|
|
10
|
+
* It consumes Runtime's existing hook stream and does not participate in execution
|
|
11
|
+
* decisions. A broken observer therefore cannot change what an agent is allowed to do.
|
|
12
|
+
*
|
|
13
|
+
* The write discipline deliberately matches `FileSpawnJournal`: serialized appends,
|
|
14
|
+
* torn-tail recovery, short-write handling, and fsync before acknowledgement. One
|
|
15
|
+
* execution owns one journal file; higher-level pursuit aggregation joins isolated
|
|
16
|
+
* journals by `pursuitId` instead of making independent processes share a write head.
|
|
17
|
+
*/
|
|
18
|
+
var FileObserverJournal = class FileObserverJournal {
|
|
19
|
+
path;
|
|
20
|
+
pursuitId;
|
|
21
|
+
writeContext = captureRunWriteContext();
|
|
22
|
+
tail = Promise.resolve();
|
|
23
|
+
initialized = false;
|
|
24
|
+
sequence = 0;
|
|
25
|
+
previousDigest;
|
|
26
|
+
appendFailure;
|
|
27
|
+
constructor(path, pursuitId) {
|
|
28
|
+
const stableId = pursuitId.trim();
|
|
29
|
+
if (stableId.length === 0) throw new TypeError("FileObserverJournal: pursuitId must be non-empty");
|
|
30
|
+
this.path = resolve(path);
|
|
31
|
+
this.pursuitId = stableId;
|
|
32
|
+
}
|
|
33
|
+
/** Open existing committed history by its recorded identity; never creates a file. */
|
|
34
|
+
static async open(path) {
|
|
35
|
+
for await (const record of readCommittedJsonLines(path, { allowMissing: true })) {
|
|
36
|
+
if (typeof record?.pursuitId !== "string") throw new TypeError("observer journal: missing pursuit identity");
|
|
37
|
+
const journal = new FileObserverJournal(path, record.pursuitId);
|
|
38
|
+
verifyObserverRecords([record], journal.pursuitId);
|
|
39
|
+
return journal;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
hooks() {
|
|
43
|
+
return withPursuitContext(this.pursuitId, {
|
|
44
|
+
onEvent: (event) => this.appendEvent(event).then(() => void 0),
|
|
45
|
+
onDecisionPoint: (point) => this.appendDecision(point).then(() => void 0)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
appendEvent(event) {
|
|
49
|
+
return this.enqueue("event", event);
|
|
50
|
+
}
|
|
51
|
+
appendDecision(point) {
|
|
52
|
+
return this.enqueue("decision", point);
|
|
53
|
+
}
|
|
54
|
+
async read() {
|
|
55
|
+
await this.tail;
|
|
56
|
+
this.assertComplete();
|
|
57
|
+
const records = [];
|
|
58
|
+
const verify = observerVerifier(this.pursuitId);
|
|
59
|
+
for await (const record of this.readExistingUnsafe()) {
|
|
60
|
+
verify(record);
|
|
61
|
+
records.push(record);
|
|
62
|
+
}
|
|
63
|
+
return Object.freeze(records);
|
|
64
|
+
}
|
|
65
|
+
async enqueue(kind, value) {
|
|
66
|
+
if (value.pursuitId !== this.pursuitId) throw new Error(`FileObserverJournal: ${kind} pursuitId ${String(value.pursuitId)} does not match ${this.pursuitId}`);
|
|
67
|
+
const snapshot = detachedSnapshot(value, "observer record");
|
|
68
|
+
const operation = this.tail.then(() => this.writeContext(async () => {
|
|
69
|
+
this.assertComplete();
|
|
70
|
+
await this.initialize();
|
|
71
|
+
const unsigned = {
|
|
72
|
+
schemaVersion: 1,
|
|
73
|
+
pursuitId: this.pursuitId,
|
|
74
|
+
sequence: this.sequence + 1,
|
|
75
|
+
kind,
|
|
76
|
+
observedAt: Date.now(),
|
|
77
|
+
...this.previousDigest ? { previousDigest: this.previousDigest } : {},
|
|
78
|
+
...kind === "event" ? { event: snapshot } : { decision: snapshot }
|
|
79
|
+
};
|
|
80
|
+
const record = Object.freeze({
|
|
81
|
+
...unsigned,
|
|
82
|
+
digest: observerRecordDigest(unsigned)
|
|
83
|
+
});
|
|
84
|
+
const fs = await import("node:fs/promises");
|
|
85
|
+
const { dirname } = await import("node:path");
|
|
86
|
+
await fs.mkdir(dirname(this.path), { recursive: true });
|
|
87
|
+
await appendCommittedJsonLine(this.path, record);
|
|
88
|
+
this.sequence = record.sequence;
|
|
89
|
+
this.previousDigest = record.digest;
|
|
90
|
+
return record;
|
|
91
|
+
})).catch((error) => {
|
|
92
|
+
this.appendFailure ??= toError(error);
|
|
93
|
+
throw error;
|
|
94
|
+
});
|
|
95
|
+
this.tail = operation.then(() => void 0, () => void 0);
|
|
96
|
+
return operation;
|
|
97
|
+
}
|
|
98
|
+
async initialize() {
|
|
99
|
+
if (this.initialized) return;
|
|
100
|
+
const verify = observerVerifier(this.pursuitId);
|
|
101
|
+
let tail;
|
|
102
|
+
for await (const record of this.readExistingUnsafe()) {
|
|
103
|
+
verify(record);
|
|
104
|
+
tail = record;
|
|
105
|
+
}
|
|
106
|
+
this.sequence = tail?.sequence ?? 0;
|
|
107
|
+
this.previousDigest = tail?.digest;
|
|
108
|
+
this.initialized = true;
|
|
109
|
+
}
|
|
110
|
+
readExistingUnsafe() {
|
|
111
|
+
return readCommittedJsonLines(this.path, { allowMissing: true });
|
|
112
|
+
}
|
|
113
|
+
assertComplete() {
|
|
114
|
+
if (!this.appendFailure) return;
|
|
115
|
+
throw new Error("FileObserverJournal: a prior durable append failed; observer completeness is unknown", { cause: this.appendFailure });
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
/** Verify identity, monotonic sequence, payload shape, and the complete digest chain. */
|
|
119
|
+
function verifyObserverRecords(records, pursuitId) {
|
|
120
|
+
const verify = observerVerifier(pursuitId);
|
|
121
|
+
for (const record of records) verify(record);
|
|
122
|
+
return Object.freeze([...records]);
|
|
123
|
+
}
|
|
124
|
+
/** The same full chain validation is used for replay, startup and in-memory evidence. */
|
|
125
|
+
function observerVerifier(pursuitId) {
|
|
126
|
+
let previousDigest;
|
|
127
|
+
let expectedSequence = 1;
|
|
128
|
+
return (record) => {
|
|
129
|
+
if (record.schemaVersion !== 1) throw new Error("observer journal: unsupported schemaVersion");
|
|
130
|
+
if (pursuitId !== void 0 && record.pursuitId !== pursuitId) throw new Error(`observer journal: pursuit identity mismatch at sequence ${record.sequence}`);
|
|
131
|
+
if (record.sequence !== expectedSequence) throw new Error(`observer journal: non-contiguous sequence ${record.sequence}; expected ${expectedSequence}`);
|
|
132
|
+
if (record.previousDigest !== previousDigest) throw new Error(`observer journal: digest-chain break at sequence ${record.sequence}`);
|
|
133
|
+
for (const kind of ["event", "decision"]) {
|
|
134
|
+
const payload = record[kind];
|
|
135
|
+
if (record.kind === kind === (payload === void 0)) throw new Error(`observer journal: invalid ${kind} payload at sequence ${record.sequence}`);
|
|
136
|
+
if (payload !== void 0 && payload.pursuitId !== record.pursuitId) throw new Error(`observer journal: nested ${kind} pursuit mismatch at sequence ${record.sequence}`);
|
|
137
|
+
}
|
|
138
|
+
const { digest, ...unsigned } = record;
|
|
139
|
+
if (digest !== observerRecordDigest(unsigned)) throw new Error(`observer journal: digest mismatch at sequence ${record.sequence}`);
|
|
140
|
+
previousDigest = digest;
|
|
141
|
+
expectedSequence += 1;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** Compute the canonical SHA-256 digest for an unsigned observer record. */
|
|
145
|
+
function observerRecordDigest(record) {
|
|
146
|
+
return createHash("sha256").update(JSON.stringify(record)).digest("hex");
|
|
147
|
+
}
|
|
148
|
+
/** Build the canonical durable observer hook in one call. */
|
|
149
|
+
function createFileObserverHooks(path, pursuitId) {
|
|
150
|
+
const journal = new FileObserverJournal(path, pursuitId);
|
|
151
|
+
return {
|
|
152
|
+
journal,
|
|
153
|
+
hooks: journal.hooks()
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function toError(error) {
|
|
157
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
158
|
+
}
|
|
159
|
+
// src/durable/observer-projection.ts
|
|
160
|
+
/**
|
|
161
|
+
* Fold one append-only execution journal into a deterministic operator projection.
|
|
162
|
+
*
|
|
163
|
+
* This is intentionally a READ model, not another state machine: it does not own
|
|
164
|
+
* execution, cannot steer agents, and can be rebuilt from the journal at any time.
|
|
165
|
+
* Projection verifies the complete hash chain first, so an operator view can never
|
|
166
|
+
* silently render a mutated or reordered observer history as trustworthy state.
|
|
167
|
+
*
|
|
168
|
+
* Topology comes only from Runtime's canonical `agent.spawn` facts. Terminal node
|
|
169
|
+
* state comes only from `agent.child`; concrete run state comes only from the root
|
|
170
|
+
* `agent.run` lifecycle emitted by `supervisePursuit`, one row per attempt. Node identity is
|
|
171
|
+
* scoped to the concrete Runtime run so independent trees may both contain `root:s0` without
|
|
172
|
+
* aliasing, and a node belongs to the attempt that spawned it.
|
|
173
|
+
*
|
|
174
|
+
* Usage, cost and timing are reported at the class the runtime measured them at. A missing
|
|
175
|
+
* class stays ABSENT and the run names the node in `spendGaps`; nothing here converts an
|
|
176
|
+
* unmeasured channel into a zero, because a fabricated zero is indistinguishable from free work.
|
|
177
|
+
*/
|
|
178
|
+
function projectPursuit(records) {
|
|
179
|
+
if (records.length === 0) throw new TypeError("projectPursuit: at least one observer record is required");
|
|
180
|
+
const pursuitId = records[0].pursuitId;
|
|
181
|
+
const verified = verifyObserverRecords(records, pursuitId);
|
|
182
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
183
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
184
|
+
let eventCount = 0;
|
|
185
|
+
let decisionCount = 0;
|
|
186
|
+
for (const record of verified) {
|
|
187
|
+
const observed = record.event ?? record.decision;
|
|
188
|
+
if (!observed) throw new Error(`projectPursuit: record ${record.sequence} has no observation`);
|
|
189
|
+
const run = currentAttempt(attempts, observed.runId, record);
|
|
190
|
+
run.lastSequence = record.sequence;
|
|
191
|
+
run.lastObservedAt = record.observedAt;
|
|
192
|
+
if (record.event) {
|
|
193
|
+
eventCount += 1;
|
|
194
|
+
run.eventCount += 1;
|
|
195
|
+
increment(run.targets, record.event.target);
|
|
196
|
+
projectRunActivity(run, record);
|
|
197
|
+
projectSpawnNode(nodes, record, run.attemptIndex);
|
|
198
|
+
projectNodeActivity(nodes, record);
|
|
199
|
+
projectSeat(run, nodes, record);
|
|
200
|
+
projectTurn(run, nodes, record);
|
|
201
|
+
} else if (record.decision) {
|
|
202
|
+
decisionCount += 1;
|
|
203
|
+
run.decisionCount += 1;
|
|
204
|
+
increment(run.decisions, record.decision.kind);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const byAttempt = /* @__PURE__ */ new Map();
|
|
208
|
+
for (const node of nodes.values()) {
|
|
209
|
+
const key = attemptKey(node.runId, node.attemptIndex);
|
|
210
|
+
const list = byAttempt.get(key);
|
|
211
|
+
if (list) list.push(node);
|
|
212
|
+
else byAttempt.set(key, [node]);
|
|
213
|
+
}
|
|
214
|
+
const first = verified[0];
|
|
215
|
+
const last = verified.at(-1);
|
|
216
|
+
return Object.freeze({
|
|
217
|
+
pursuitId,
|
|
218
|
+
sequence: last.sequence,
|
|
219
|
+
chainTip: last.digest,
|
|
220
|
+
firstObservedAt: first.observedAt,
|
|
221
|
+
lastObservedAt: last.observedAt,
|
|
222
|
+
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)) ?? []))),
|
|
223
|
+
nodes: Object.freeze([...nodes.values()].sort((a, b) => a.firstSequence - b.firstSequence || a.runId.localeCompare(b.runId) || a.id.localeCompare(b.id)).map(freezeNode)),
|
|
224
|
+
eventCount,
|
|
225
|
+
decisionCount
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function projectSeat(run, nodes, record) {
|
|
229
|
+
const event = record.event;
|
|
230
|
+
if (event?.target !== "agent.seat") return;
|
|
231
|
+
const payload = objectRecord(event.payload);
|
|
232
|
+
const phase = stringField(payload, "phase");
|
|
233
|
+
const owner = nodes.get(nodeKey(event.runId, stringField(payload, "nodeId") ?? "")) ?? run;
|
|
234
|
+
if (phase === "paused") {
|
|
235
|
+
const resumeAt = stringField(payload, "resumeAt");
|
|
236
|
+
if (resumeAt) owner.resumeAt = resumeAt;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (phase === "resumed") {
|
|
240
|
+
delete owner.resumeAt;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const raw = objectRecord(payload?.segment);
|
|
244
|
+
const seat = stringField(raw, "seat");
|
|
245
|
+
const harness = stringField(raw, "harness");
|
|
246
|
+
const provider = stringField(raw, "provider");
|
|
247
|
+
const model = stringField(raw, "model");
|
|
248
|
+
const startedAt = stringField(raw, "startedAt");
|
|
249
|
+
if (!seat || !harness || !provider || !model || !startedAt) return;
|
|
250
|
+
if (owner.segments === void 0) owner.segments = [];
|
|
251
|
+
const segments = owner.segments;
|
|
252
|
+
if (phase === "started") {
|
|
253
|
+
if (!segments.some((segment) => segment.startedAt === startedAt && segment.seat === seat)) segments.push({
|
|
254
|
+
seat,
|
|
255
|
+
harness,
|
|
256
|
+
provider,
|
|
257
|
+
model,
|
|
258
|
+
startedAt
|
|
259
|
+
});
|
|
260
|
+
} else if (phase === "ended") {
|
|
261
|
+
const endedAt = stringField(raw, "endedAt");
|
|
262
|
+
const reason = stringField(raw, "reason");
|
|
263
|
+
if (!endedAt || !reason) return;
|
|
264
|
+
const index = segments.findIndex((segment) => segment.startedAt === startedAt && segment.seat === seat);
|
|
265
|
+
if (index >= 0) segments[index] = {
|
|
266
|
+
seat,
|
|
267
|
+
harness,
|
|
268
|
+
provider,
|
|
269
|
+
model,
|
|
270
|
+
startedAt,
|
|
271
|
+
endedAt,
|
|
272
|
+
reason
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function attemptKey(runId, attemptIndex) {
|
|
277
|
+
return `${runId}\u0000${attemptIndex}`;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The attempt a record belongs to. An `agent.run` `before` opens a new attempt when the run has
|
|
281
|
+
* none or its latest attempt already settled; on an open attempt it is a resume. Every other
|
|
282
|
+
* record joins the latest attempt, and a journal that never emitted `before` gets attempt 0.
|
|
283
|
+
*/
|
|
284
|
+
function currentAttempt(attempts, runId, record) {
|
|
285
|
+
const list = attempts.get(runId) ?? [];
|
|
286
|
+
if (list.length === 0) attempts.set(runId, list);
|
|
287
|
+
const latest = list.at(-1);
|
|
288
|
+
const isStart = record.event?.target === "agent.run" && record.event.phase === "before";
|
|
289
|
+
if (latest !== void 0 && (!isStart || latest.status === "running")) {
|
|
290
|
+
if (isStart) {
|
|
291
|
+
if (latest.started) latest.resumeCount += 1;
|
|
292
|
+
latest.started = true;
|
|
293
|
+
}
|
|
294
|
+
return latest;
|
|
295
|
+
}
|
|
296
|
+
const created = {
|
|
297
|
+
runId,
|
|
298
|
+
attemptIndex: list.length,
|
|
299
|
+
resumeCount: 0,
|
|
300
|
+
started: isStart,
|
|
301
|
+
status: "running",
|
|
302
|
+
firstSequence: record.sequence,
|
|
303
|
+
lastSequence: record.sequence,
|
|
304
|
+
firstObservedAt: record.observedAt,
|
|
305
|
+
lastObservedAt: record.observedAt,
|
|
306
|
+
eventCount: 0,
|
|
307
|
+
decisionCount: 0,
|
|
308
|
+
targets: {},
|
|
309
|
+
decisions: {}
|
|
310
|
+
};
|
|
311
|
+
list.push(created);
|
|
312
|
+
return created;
|
|
313
|
+
}
|
|
314
|
+
function projectRunActivity(run, record) {
|
|
315
|
+
const event = record.event;
|
|
316
|
+
if (event?.target !== "agent.run") return;
|
|
317
|
+
const payload = objectRecord(event.payload);
|
|
318
|
+
const status = stringField(payload, "status");
|
|
319
|
+
const done = event.phase === "after" || status === "done";
|
|
320
|
+
if (!done && event.phase !== "error" && status !== "failed") return;
|
|
321
|
+
run.status = done ? "done" : "down";
|
|
322
|
+
run.settledAt = record.observedAt;
|
|
323
|
+
if (payload && Object.hasOwn(payload, "failure")) run.failure = runtimeFailureCauseSchema.parse(payload.failure);
|
|
324
|
+
const error = stringField(payload, "error");
|
|
325
|
+
if (error && (!done || run.failure !== void 0)) run.error = error;
|
|
326
|
+
}
|
|
327
|
+
function nodeKey(runId, nodeId) {
|
|
328
|
+
return `${runId}\u0000${nodeId}`;
|
|
329
|
+
}
|
|
330
|
+
function projectSpawnNode(nodes, record, attemptIndex) {
|
|
331
|
+
const event = record.event;
|
|
332
|
+
if (event?.target !== "agent.spawn") return;
|
|
333
|
+
const payload = objectRecord(event.payload);
|
|
334
|
+
const childId = stringField(payload, "childId");
|
|
335
|
+
if (!childId) return;
|
|
336
|
+
const key = nodeKey(event.runId, childId);
|
|
337
|
+
const existing = nodes.get(key);
|
|
338
|
+
if (existing) {
|
|
339
|
+
existing.lastSequence = record.sequence;
|
|
340
|
+
existing.lastObservedAt = record.observedAt;
|
|
341
|
+
existing.eventCount += 1;
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const label = stringField(payload, "label");
|
|
345
|
+
const runtime = stringField(payload, "runtime");
|
|
346
|
+
const depth = numberField(payload, "depth");
|
|
347
|
+
const assignmentId = stringField(payload, "assignmentId");
|
|
348
|
+
const successorOf = stringField(payload, "successorOf");
|
|
349
|
+
const attemptId = stringField(payload, "attemptId");
|
|
350
|
+
const startedAt = numberField(payload, "startedAt");
|
|
351
|
+
nodes.set(key, {
|
|
352
|
+
id: childId,
|
|
353
|
+
...event.parentId ? { parentId: event.parentId } : {},
|
|
354
|
+
runId: event.runId,
|
|
355
|
+
attemptIndex,
|
|
356
|
+
...label ? { label } : {},
|
|
357
|
+
...runtime ? { runtime } : {},
|
|
358
|
+
...depth !== void 0 ? { depth } : {},
|
|
359
|
+
...assignmentId ? { assignmentId } : {},
|
|
360
|
+
...successorOf ? { successorOf } : {},
|
|
361
|
+
...attemptId ? { attemptId } : {},
|
|
362
|
+
startedAt: startedAt ?? record.observedAt,
|
|
363
|
+
...payload && Object.hasOwn(payload, "identity") ? { identity: payload.identity } : {},
|
|
364
|
+
...payload && Object.hasOwn(payload, "budget") ? { budget: payload.budget } : {},
|
|
365
|
+
status: "running",
|
|
366
|
+
modelCalls: [],
|
|
367
|
+
firstSequence: record.sequence,
|
|
368
|
+
lastSequence: record.sequence,
|
|
369
|
+
firstObservedAt: record.observedAt,
|
|
370
|
+
lastObservedAt: record.observedAt,
|
|
371
|
+
eventCount: 1,
|
|
372
|
+
turnCount: 0
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
function projectNodeActivity(nodes, record) {
|
|
376
|
+
const event = record.event;
|
|
377
|
+
if (event === void 0) return;
|
|
378
|
+
if (event.target === "agent.spawn") return;
|
|
379
|
+
const payload = objectRecord(event.payload);
|
|
380
|
+
const nodeId = stringField(payload, "childId") ?? stringField(payload, "nodeId") ?? stringField(payload, "workerId");
|
|
381
|
+
if (!nodeId) return;
|
|
382
|
+
const node = nodes.get(nodeKey(event.runId, nodeId));
|
|
383
|
+
if (!node) return;
|
|
384
|
+
node.lastSequence = record.sequence;
|
|
385
|
+
node.lastObservedAt = record.observedAt;
|
|
386
|
+
node.eventCount += 1;
|
|
387
|
+
if (event.target !== "agent.child") return;
|
|
388
|
+
const status = stringField(payload, "status");
|
|
389
|
+
if (status !== "done" && status !== "down") return;
|
|
390
|
+
node.status = status;
|
|
391
|
+
node.settledAt = numberField(payload, "settledAt") ?? record.observedAt;
|
|
392
|
+
const startedAt = numberField(payload, "startedAt");
|
|
393
|
+
if (startedAt !== void 0) node.startedAt = startedAt;
|
|
394
|
+
if (payload && Object.hasOwn(payload, "spent")) node.spent = spendField(payload, "spent");
|
|
395
|
+
const metered = spendField(payload, "metered");
|
|
396
|
+
if (metered) node.ownInference = node.ownInference ? addSpend(node.ownInference, metered) : metered;
|
|
397
|
+
const runtime = stringField(payload, "runtime");
|
|
398
|
+
if (runtime) node.runtime = runtime;
|
|
399
|
+
const outRef = stringField(payload, "outRef");
|
|
400
|
+
if (outRef) node.outRef = outRef;
|
|
401
|
+
const score = numberField(payload, "score");
|
|
402
|
+
if (score !== void 0) node.score = score;
|
|
403
|
+
const valid = booleanField(payload, "valid");
|
|
404
|
+
if (valid !== void 0) node.valid = valid;
|
|
405
|
+
const reason = stringField(payload, "reason");
|
|
406
|
+
if (reason) node.reason = reason;
|
|
407
|
+
if (status === "done") node.failure = void 0;
|
|
408
|
+
else if (payload && Object.hasOwn(payload, "failure")) node.failure = runtimeFailureCauseSchema.parse(payload.failure);
|
|
409
|
+
const infra = booleanField(payload, "infra");
|
|
410
|
+
if (infra !== void 0) node.infra = infra;
|
|
411
|
+
const retained = stringField(payload, "retainedExecution");
|
|
412
|
+
if (retained === "pending" || retained === "released" || retained === "release-unconfirmed") node.retainedExecution = retained;
|
|
413
|
+
const cause = stringField(payload, "retainedPendingCause");
|
|
414
|
+
if (cause === "unobservable" || cause === "provider-contract" || cause === "request-rejected" || cause === "transport" || cause === "nested-recovery") node.retainedPendingCause = cause;
|
|
415
|
+
const releasedAt = numberField(payload, "releasedAt");
|
|
416
|
+
if (releasedAt !== void 0) node.releasedAt = releasedAt;
|
|
417
|
+
const budgetViolation = budgetViolationField(payload);
|
|
418
|
+
if (budgetViolation) node.budgetViolation = budgetViolation;
|
|
419
|
+
attachSettlementEvidence(node, payload);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Read the receipts a settlement carries. Each is taken as reported: an `unknown` receipt keeps
|
|
423
|
+
* its `status` and `reason` so a client can show WHY a model or backend is missing instead of
|
|
424
|
+
* showing nothing, and a receipt that never landed leaves every derived field absent.
|
|
425
|
+
*/
|
|
426
|
+
function attachSettlementEvidence(node, payload) {
|
|
427
|
+
const providerModel = payload?.providerModel;
|
|
428
|
+
if (providerModel && typeof providerModel === "object") node.providerModel = providerModel;
|
|
429
|
+
const trace = payload?.trace;
|
|
430
|
+
if (trace && typeof trace === "object") node.trace = trace;
|
|
431
|
+
const bindings = payload?.executionBindings;
|
|
432
|
+
if (Array.isArray(bindings) && bindings.length > 0) {
|
|
433
|
+
node.executionBindings = bindings;
|
|
434
|
+
const known = bindings.find((binding) => objectRecord(binding)?.status === "known");
|
|
435
|
+
if (known) {
|
|
436
|
+
node.placement = known.descriptor;
|
|
437
|
+
node.attemptId ??= known.attemptId;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const receipt = payload?.materialization;
|
|
441
|
+
if (!receipt || typeof receipt !== "object") return;
|
|
442
|
+
node.materialization = receipt;
|
|
443
|
+
if (receipt.status !== "known") return;
|
|
444
|
+
node.backend = receipt.backend;
|
|
445
|
+
if (receipt.model.status === "known") node.model = receipt.model.id;
|
|
446
|
+
node.execution = {
|
|
447
|
+
kind: receipt.execution.kind,
|
|
448
|
+
id: receipt.execution.id
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Fold one metered turn onto whoever drove it. `agent.turn` names its subject in `parentId` — the
|
|
453
|
+
* node making the call — not in the payload, because the caller IS the subject. A turn the run
|
|
454
|
+
* root drove belongs to no spawned node and lands on the run instead, so the run's totals stay
|
|
455
|
+
* complete without inventing a node for the root.
|
|
456
|
+
*/
|
|
457
|
+
function projectTurn(run, nodes, record) {
|
|
458
|
+
const event = record.event;
|
|
459
|
+
if (event?.target !== "agent.turn") return;
|
|
460
|
+
const payload = objectRecord(event.payload);
|
|
461
|
+
const spend = spendField(payload, "spend");
|
|
462
|
+
const subject = event.parentId;
|
|
463
|
+
if (subject === void 0) return;
|
|
464
|
+
const node = nodes.get(nodeKey(event.runId, subject));
|
|
465
|
+
if (!node) {
|
|
466
|
+
if (!spend) return;
|
|
467
|
+
run.rootInference = run.rootInference ? addSpend(run.rootInference, spend) : spend;
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
node.turnCount += 1;
|
|
471
|
+
node.lastSequence = record.sequence;
|
|
472
|
+
node.lastObservedAt = record.observedAt;
|
|
473
|
+
if (spend) {
|
|
474
|
+
node.ownInference = node.ownInference ? addSpend(node.ownInference, spend) : spend;
|
|
475
|
+
node.firstOutputAt ??= record.observedAt;
|
|
476
|
+
}
|
|
477
|
+
const reasoning = numberField(payload, "reasoningTokens");
|
|
478
|
+
if (reasoning !== void 0) node.reasoningTokens = (node.reasoningTokens ?? 0) + reasoning;
|
|
479
|
+
const firstTokenAt = numberField(payload, "firstTokenAt");
|
|
480
|
+
if (firstTokenAt !== void 0) node.firstTokenAt ??= firstTokenAt;
|
|
481
|
+
const model = stringField(payload, "model");
|
|
482
|
+
if (model) node.model ??= model;
|
|
483
|
+
const callId = stringField(payload, "callId");
|
|
484
|
+
if (callId && !node.modelCalls.includes(callId)) node.modelCalls.push(callId);
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* A node's whole reported cost: the child work its settlement reported plus the inference it drove
|
|
488
|
+
* itself. `undefined` when neither landed — the run's `spendGaps` then names the node, so an
|
|
489
|
+
* unaccounted node is visible instead of reading as free.
|
|
490
|
+
*/
|
|
491
|
+
function nodeTotal(node) {
|
|
492
|
+
if (node.spent && node.ownInference) return addSpend(node.spent, node.ownInference);
|
|
493
|
+
if (node.spent) return cloneSpend(node.spent);
|
|
494
|
+
if (node.ownInference) return cloneSpend(node.ownInference);
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* The run counted once, and each node's own share of it.
|
|
498
|
+
*
|
|
499
|
+
* A settled node's `spent` already contains the child work its nested tree reported, so summing
|
|
500
|
+
* every node would count a driver's descendants twice. Inclusive therefore sums only the run's
|
|
501
|
+
* TOP-LEVEL nodes — those whose parent is not another node of this run — plus the root's own
|
|
502
|
+
* turns. Exclusive subtracts each node's direct children from its own total, which telescopes:
|
|
503
|
+
* the exclusive entries sum back to `inclusive` exactly.
|
|
504
|
+
*/
|
|
505
|
+
function runTotals(run, nodes) {
|
|
506
|
+
const present = new Set(nodes.map((node) => node.id));
|
|
507
|
+
const children = /* @__PURE__ */ new Map();
|
|
508
|
+
for (const node of nodes) {
|
|
509
|
+
if (node.parentId === void 0 || !present.has(node.parentId)) continue;
|
|
510
|
+
const list = children.get(node.parentId);
|
|
511
|
+
if (list) list.push(node);
|
|
512
|
+
else children.set(node.parentId, [node]);
|
|
513
|
+
}
|
|
514
|
+
const exclusiveByNode = {};
|
|
515
|
+
let inclusive = run.rootInference ? cloneSpend(run.rootInference) : zeroSpend();
|
|
516
|
+
if (run.rootInference) exclusiveByNode[run.runId] = cloneSpend(run.rootInference);
|
|
517
|
+
for (const node of nodes) {
|
|
518
|
+
const total = nodeTotal(node);
|
|
519
|
+
if (total === void 0) continue;
|
|
520
|
+
if (node.parentId === void 0 || !present.has(node.parentId)) inclusive = addSpend(inclusive, total);
|
|
521
|
+
let exclusive = total;
|
|
522
|
+
for (const child of children.get(node.id) ?? []) {
|
|
523
|
+
const childTotal = nodeTotal(child);
|
|
524
|
+
if (childTotal !== void 0) exclusive = subtractSpend(exclusive, childTotal);
|
|
525
|
+
}
|
|
526
|
+
exclusiveByNode[node.id] = exclusive;
|
|
527
|
+
}
|
|
528
|
+
return Object.freeze({
|
|
529
|
+
inclusive: Object.freeze(inclusive),
|
|
530
|
+
exclusiveByNode: Object.freeze(exclusiveByNode)
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Remove a child's reported total from its parent's. Per channel, never below zero: a parent whose
|
|
535
|
+
* executor reported less than its children did is a reporting gap, and a negative exclusive share
|
|
536
|
+
* would be a fabricated number rather than a measurement.
|
|
537
|
+
*/
|
|
538
|
+
function subtractSpend(a, b) {
|
|
539
|
+
const tokens = { ...a.tokens };
|
|
540
|
+
tokens.input = Math.max(0, tokens.input - b.tokens.input);
|
|
541
|
+
tokens.output = Math.max(0, tokens.output - b.tokens.output);
|
|
542
|
+
if (a.tokens.cacheRead !== void 0) tokens.cacheRead = Math.max(0, a.tokens.cacheRead - (b.tokens.cacheRead ?? 0));
|
|
543
|
+
if (a.tokens.cacheWrite !== void 0) tokens.cacheWrite = Math.max(0, a.tokens.cacheWrite - (b.tokens.cacheWrite ?? 0));
|
|
544
|
+
if (a.tokens.freshInput !== void 0) tokens.freshInput = Math.max(0, a.tokens.freshInput - (b.tokens.freshInput ?? 0));
|
|
545
|
+
return {
|
|
546
|
+
iterations: Math.max(0, a.iterations - b.iterations),
|
|
547
|
+
tokens,
|
|
548
|
+
...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {},
|
|
549
|
+
usd: Math.max(0, a.usd - b.usd),
|
|
550
|
+
...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
|
|
551
|
+
...a.usdEstimated !== void 0 ? { usdEstimated: Math.max(0, a.usdEstimated - (b.usdEstimated ?? 0)) } : {},
|
|
552
|
+
ms: Math.max(0, a.ms - b.ms)
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
/** The nodes of one run whose accounting is incomplete, in the vocabulary the supervisor already
|
|
556
|
+
* uses: `never-settled` = no terminal record, so every channel is unaccounted; `unreported` = a
|
|
557
|
+
* record landed with a channel the provider did not report, so that channel is a floor. */
|
|
558
|
+
function runSpendGaps(nodes) {
|
|
559
|
+
const gaps = [];
|
|
560
|
+
for (const node of nodes) {
|
|
561
|
+
const label = node.label;
|
|
562
|
+
if (node.status === "running") {
|
|
563
|
+
gaps.push({
|
|
564
|
+
id: node.id,
|
|
565
|
+
...label !== void 0 ? { label } : {},
|
|
566
|
+
kind: "never-settled",
|
|
567
|
+
channels: ["tokens", "usd"]
|
|
568
|
+
});
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const total = nodeTotal(node);
|
|
572
|
+
const channels = [];
|
|
573
|
+
if (total === void 0 || total.tokensKnown === false) channels.push("tokens");
|
|
574
|
+
if (total === void 0 || total.usdKnown === false) channels.push("usd");
|
|
575
|
+
if (channels.length === 0) continue;
|
|
576
|
+
gaps.push({
|
|
577
|
+
id: node.id,
|
|
578
|
+
...label !== void 0 ? { label } : {},
|
|
579
|
+
kind: "unreported",
|
|
580
|
+
channels
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return gaps;
|
|
584
|
+
}
|
|
585
|
+
function usageOf(total, reasoning) {
|
|
586
|
+
return {
|
|
587
|
+
input: total.tokens.input,
|
|
588
|
+
output: total.tokens.output,
|
|
589
|
+
...total.tokens.cacheRead !== void 0 ? { cacheRead: total.tokens.cacheRead } : {},
|
|
590
|
+
...total.tokens.cacheWrite !== void 0 ? { cacheWrite: total.tokens.cacheWrite } : {},
|
|
591
|
+
...reasoning !== void 0 ? { reasoning } : {},
|
|
592
|
+
tokensKnown: total.tokensKnown !== false && total.tokens.tokensKnown !== false
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
function costOf(total) {
|
|
596
|
+
const usdKnown = total.usdKnown !== false;
|
|
597
|
+
const estimated = total.usdEstimated;
|
|
598
|
+
const provenance = usdKnown ? estimated === void 0 || estimated === 0 ? "reported" : "partial" : estimated === void 0 ? "unknown" : estimated >= total.usd ? "estimated" : "partial";
|
|
599
|
+
return {
|
|
600
|
+
usd: total.usd,
|
|
601
|
+
usdKnown,
|
|
602
|
+
...estimated !== void 0 ? { usdEstimated: estimated } : {},
|
|
603
|
+
provenance
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
/** The platform block, or nothing at all when the node's spend names no box channel. */
|
|
607
|
+
function platformOf(total) {
|
|
608
|
+
if (total.boxMinutesProvenance === void 0) return void 0;
|
|
609
|
+
return {
|
|
610
|
+
...total.boxMinutes !== void 0 ? { boxMinutes: total.boxMinutes } : {},
|
|
611
|
+
boxMinutesKnown: total.boxMinutesKnown === true,
|
|
612
|
+
provenance: total.boxMinutesProvenance
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function timingOf(node) {
|
|
616
|
+
if (node.startedAt === void 0) return void 0;
|
|
617
|
+
return {
|
|
618
|
+
startedAt: node.startedAt,
|
|
619
|
+
...node.firstOutputAt !== void 0 ? { firstOutputAt: node.firstOutputAt } : {},
|
|
620
|
+
...node.firstTokenAt !== void 0 ? { firstTokenAt: node.firstTokenAt } : {},
|
|
621
|
+
...node.settledAt !== void 0 ? { settledAt: node.settledAt } : {},
|
|
622
|
+
...node.settledAt !== void 0 ? { wallMs: Math.max(0, node.settledAt - node.startedAt) } : {}
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function freezeRun(run, nodes) {
|
|
626
|
+
const gaps = runSpendGaps(nodes);
|
|
627
|
+
const { rootInference: _rootInference, started: _started, segments, ...rest } = run;
|
|
628
|
+
return Object.freeze({
|
|
629
|
+
...rest,
|
|
630
|
+
...segments?.length ? { segments: Object.freeze(segments.map((segment) => Object.freeze(segment))) } : {},
|
|
631
|
+
targets: Object.freeze({ ...run.targets }),
|
|
632
|
+
decisions: Object.freeze({ ...run.decisions }),
|
|
633
|
+
totals: runTotals(run, nodes),
|
|
634
|
+
...gaps.length > 0 ? { spendGaps: Object.freeze(gaps) } : {}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
function freezeNode(node) {
|
|
638
|
+
const { modelCalls, segments, reasoningTokens, startedAt: _startedAt, attemptIndex: _attemptIndex, ...rest } = node;
|
|
639
|
+
const total = nodeTotal(node);
|
|
640
|
+
const timing = timingOf(node);
|
|
641
|
+
return Object.freeze({
|
|
642
|
+
...rest,
|
|
643
|
+
...segments?.length ? { segments: Object.freeze(segments.map((segment) => Object.freeze(segment))) } : {},
|
|
644
|
+
...modelCalls.length > 0 ? { modelCalls: Object.freeze([...modelCalls]) } : {},
|
|
645
|
+
...total !== void 0 ? {
|
|
646
|
+
usage: usageOf(total, reasoningTokens),
|
|
647
|
+
cost: costOf(total)
|
|
648
|
+
} : {},
|
|
649
|
+
...total !== void 0 && platformOf(total) !== void 0 ? { platform: platformOf(total) } : {},
|
|
650
|
+
...timing !== void 0 ? { timing } : {}
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
function increment(target, key) {
|
|
654
|
+
target[key] = (target[key] ?? 0) + 1;
|
|
655
|
+
}
|
|
656
|
+
function objectRecord(value) {
|
|
657
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
658
|
+
}
|
|
659
|
+
/** Read a journaled `Spend` without trusting the wire: a record missing the conserved channels is
|
|
660
|
+
* not a zero-cost turn, it is an unparseable one, and it must not enter a total. */
|
|
661
|
+
function spendField(value, key) {
|
|
662
|
+
const field = objectRecord(value?.[key]);
|
|
663
|
+
if (!field) return void 0;
|
|
664
|
+
const tokens = objectRecord(field.tokens);
|
|
665
|
+
if (typeof field.usd !== "number" || !Number.isFinite(field.usd)) return void 0;
|
|
666
|
+
if (!tokens || typeof tokens.input !== "number" || typeof tokens.output !== "number") return;
|
|
667
|
+
return cloneSpend(field);
|
|
668
|
+
}
|
|
669
|
+
/** Read a journaled overspend without trusting the wire: one malformed channel drops the record,
|
|
670
|
+
* so an unparseable entry is never reported as an overspend of some size. */
|
|
671
|
+
function budgetViolationField(value) {
|
|
672
|
+
const overspent = objectRecord(value?.budgetViolation)?.overspent;
|
|
673
|
+
if (!Array.isArray(overspent) || overspent.length === 0) return void 0;
|
|
674
|
+
const entries = [];
|
|
675
|
+
for (const raw of overspent) {
|
|
676
|
+
const entry = objectRecord(raw);
|
|
677
|
+
const channel = stringField(entry, "channel");
|
|
678
|
+
const reserved = numberField(entry, "reserved");
|
|
679
|
+
const spent = numberField(entry, "spent");
|
|
680
|
+
if (channel === void 0 || reserved === void 0 || spent === void 0) return void 0;
|
|
681
|
+
entries.push({
|
|
682
|
+
channel,
|
|
683
|
+
reserved,
|
|
684
|
+
spent
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
return { overspent: entries };
|
|
688
|
+
}
|
|
689
|
+
function stringField(value, key) {
|
|
690
|
+
const field = value?.[key];
|
|
691
|
+
return typeof field === "string" && field.length > 0 ? field : void 0;
|
|
692
|
+
}
|
|
693
|
+
function numberField(value, key) {
|
|
694
|
+
const field = value?.[key];
|
|
695
|
+
return typeof field === "number" && Number.isFinite(field) ? field : void 0;
|
|
696
|
+
}
|
|
697
|
+
function booleanField(value, key) {
|
|
698
|
+
const field = value?.[key];
|
|
699
|
+
return typeof field === "boolean" ? field : void 0;
|
|
700
|
+
}
|
|
701
|
+
// src/durable/supervision-discovery.ts
|
|
702
|
+
/**
|
|
703
|
+
* Discover the stable identities recorded by Runtime's durable supervision
|
|
704
|
+
* files. This is the developer-facing first step before calling
|
|
705
|
+
* `FileSpawnJournal.loadTree(root)`, `loadSpawnForest(journal, root)`, or
|
|
706
|
+
* `FileCoordinationLog.load(runId, ownerId)`.
|
|
707
|
+
*
|
|
708
|
+
* Missing files produce empty collections. A malformed committed JSONL record
|
|
709
|
+
* still fails loud through the same parser used by the runtime; a torn final
|
|
710
|
+
* append is ignored because it was never acknowledged as committed.
|
|
711
|
+
*/
|
|
712
|
+
async function discoverDurableSupervisionRun(runDir) {
|
|
713
|
+
if (typeof runDir !== "string" || runDir.trim().length === 0) throw new TypeError("discoverDurableSupervisionRun: runDir must be a non-empty string");
|
|
714
|
+
const canonicalRunDir = resolve(runDir);
|
|
715
|
+
const spawnJournalPath = `${canonicalRunDir}/spawn-journal.jsonl`;
|
|
716
|
+
const coordinationLogPath = `${canonicalRunDir}/coordination-log.jsonl`;
|
|
717
|
+
const nestedRoots = /* @__PURE__ */ new Set();
|
|
718
|
+
const rootsBegunAt = /* @__PURE__ */ new Map();
|
|
719
|
+
for await (const record of readCommittedJsonLines(spawnJournalPath, { allowMissing: true })) {
|
|
720
|
+
if (record.kind === "begin") {
|
|
721
|
+
if (typeof record.root !== "string" || record.root.length === 0) throw new Error(`${spawnJournalPath}: begin record has no non-empty string root identity`);
|
|
722
|
+
rootsBegunAt.set(record.root, typeof record.at === "string" ? record.at : null);
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
for (const event of spawnJournalRecordEvents(record)) {
|
|
726
|
+
if (!isRecord(event) || event.kind !== "spawned") continue;
|
|
727
|
+
if (!Object.hasOwn(event, "ownedTreeRoot")) continue;
|
|
728
|
+
if (typeof event.ownedTreeRoot !== "string" || event.ownedTreeRoot.length === 0) throw new Error(`${spawnJournalPath}: spawned event ownedTreeRoot must be a non-empty string when present`);
|
|
729
|
+
nestedRoots.add(event.ownedTreeRoot);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const streams = /* @__PURE__ */ new Map();
|
|
733
|
+
for await (const record of readCommittedJsonLines(coordinationLogPath, { allowMissing: true })) {
|
|
734
|
+
if (typeof record.runId !== "string" || record.runId.length === 0) throw new Error(`${coordinationLogPath}: record has no non-empty string runId identity`);
|
|
735
|
+
const stream = streams.get(record.runId) ?? {
|
|
736
|
+
owners: /* @__PURE__ */ new Set(),
|
|
737
|
+
unscopedRecords: 0,
|
|
738
|
+
recordCount: 0
|
|
739
|
+
};
|
|
740
|
+
stream.recordCount += 1;
|
|
741
|
+
if (record.ownerId === void 0) stream.unscopedRecords += 1;
|
|
742
|
+
else if (typeof record.ownerId === "string" && record.ownerId.length > 0) stream.owners.add(record.ownerId);
|
|
743
|
+
else throw new Error(`${coordinationLogPath}: ownerId must be a non-empty string when present`);
|
|
744
|
+
streams.set(record.runId, stream);
|
|
745
|
+
}
|
|
746
|
+
const coordinationStreams = [...streams.entries()].sort(([left], [right]) => compareText(left, right)).map(([runId, stream]) => Object.freeze({
|
|
747
|
+
runId,
|
|
748
|
+
ownerIds: Object.freeze([...stream.owners].sort(compareText)),
|
|
749
|
+
unscopedRecords: stream.unscopedRecords,
|
|
750
|
+
recordCount: stream.recordCount
|
|
751
|
+
}));
|
|
752
|
+
const observer = await FileObserverJournal.open(`${canonicalRunDir}/observer.jsonl`);
|
|
753
|
+
const roots = [...rootsBegunAt.keys()].filter((root) => !nestedRoots.has(root)).sort(compareText);
|
|
754
|
+
return Object.freeze({
|
|
755
|
+
runDir: canonicalRunDir,
|
|
756
|
+
...observer ? { observer: {
|
|
757
|
+
path: observer.path,
|
|
758
|
+
pursuitId: observer.pursuitId
|
|
759
|
+
} } : {},
|
|
760
|
+
spawnJournalPath,
|
|
761
|
+
coordinationLogPath,
|
|
762
|
+
roots: Object.freeze(roots),
|
|
763
|
+
rootsBegunAt: Object.freeze(roots.map((root) => rootsBegunAt.get(root) ?? null)),
|
|
764
|
+
coordinationStreams: Object.freeze(coordinationStreams)
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
function compareText(left, right) {
|
|
768
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
769
|
+
}
|
|
770
|
+
export { observerRecordDigest as a, createFileObserverHooks as i, projectPursuit as n, FileObserverJournal as r, discoverDurableSupervisionRun as t };
|
|
771
|
+
|
|
772
|
+
//# sourceMappingURL=supervision-discovery-CewADdEs.js.map
|