@plurnk/plurnk-digest 3.2.3 → 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/SPEC.md +35 -213
- package/dist/Digest.js +39 -39
- package/dist/Digest.js.map +1 -1
- package/dist/DigestArtifacts.d.ts +10 -0
- package/dist/DigestArtifacts.d.ts.map +1 -0
- package/dist/DigestArtifacts.js +452 -0
- package/dist/DigestArtifacts.js.map +1 -0
- package/dist/DigestFormat.d.ts +18 -0
- package/dist/DigestFormat.d.ts.map +1 -0
- package/dist/DigestFormat.js +142 -0
- package/dist/DigestFormat.js.map +1 -0
- package/dist/DigestLedgers.d.ts +21 -0
- package/dist/DigestLedgers.d.ts.map +1 -0
- package/dist/DigestLedgers.js +284 -0
- package/dist/DigestLedgers.js.map +1 -0
- package/dist/DigestRender.d.ts +15 -15
- package/dist/DigestRender.d.ts.map +1 -1
- package/dist/DigestRender.js +95 -967
- package/dist/DigestRender.js.map +1 -1
- package/dist/DigestRequiem.d.ts +1 -1
- package/dist/DigestRequiem.d.ts.map +1 -1
- package/dist/DigestRequiem.js +33 -33
- package/dist/DigestRequiem.js.map +1 -1
- package/dist/Share.js +1 -1
- package/dist/Share.js.map +1 -1
- package/dist/digest-paths.js +1 -1
- package/dist/digest-paths.js.map +1 -1
- package/dist/digest-rows.d.ts +22 -22
- package/dist/digest-rows.d.ts.map +1 -1
- package/dist/evidence.d.ts +3 -3
- package/dist/evidence.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/DigestRender.js
CHANGED
|
@@ -1,155 +1,24 @@
|
|
|
1
|
-
// Digest renderers ({§
|
|
1
|
+
// Digest renderers ({§share-packet-names}): the markdown, JSON, reasoning, and packet
|
|
2
2
|
// artifacts of one DigestModel, reading heavy evidence on demand through DigestEvidence.
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { EDIT_FORMS } from "./digest-rows.js";
|
|
8
|
-
function* projectRows(rows, project) {
|
|
9
|
-
for (const row of rows)
|
|
10
|
-
yield project(row);
|
|
11
|
-
}
|
|
3
|
+
import { isRecord } from "@plurnk/plurnk-contracts";
|
|
4
|
+
import { accounting, costSummary, environmentOf, envLine, indentLines, link, literal, parseJson, renderStream, rowProblem, terminalResult, usageSummary, wireLine, } from "./DigestFormat.js";
|
|
5
|
+
import { cacheBadge, cacheLedger, cacheLine, countsAsError, editCensus, editForm, loopHealth, renderEditCensus, renderEmissions, renderReasonings, renderRoom, } from "./DigestLedgers.js";
|
|
6
|
+
import { json, packetFiles, packetStems, request, turnLinks } from "./DigestArtifacts.js";
|
|
12
7
|
export default class DigestRender {
|
|
13
|
-
static
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
static
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
static
|
|
24
|
-
const stem = DigestRender.packetStems(m).get(turn.id);
|
|
25
|
-
if (stem === undefined)
|
|
26
|
-
return [];
|
|
27
|
-
return [
|
|
28
|
-
...(turn.has_packet === 1 ? [DigestRender.#link(from, `${stem}.request.md`, "Input")] : []),
|
|
29
|
-
...(turn.program !== null ? [DigestRender.#link(from, `${stem}.assistant.md`, "Output")] : []),
|
|
30
|
-
...(turn.has_reasoning === 1 ? [DigestRender.#link(from, `${stem}.reasoning.md`, "Reasoning")] : []),
|
|
31
|
-
];
|
|
32
|
-
}
|
|
33
|
-
static #summarize(text, n = 80) {
|
|
34
|
-
if (text === null || text === undefined)
|
|
35
|
-
return "";
|
|
36
|
-
const flat = (typeof text === "string" ? text : JSON.stringify(text) ?? "").replace(/\s+/g, " ").trim();
|
|
37
|
-
if (flat.length <= n)
|
|
38
|
-
return flat;
|
|
39
|
-
return `${flat.slice(0, n)}…`;
|
|
40
|
-
}
|
|
41
|
-
static #indentLines(text, prefix = " ") {
|
|
42
|
-
return text.split("\n").map((line) => line.length > 0 ? `${prefix}${line}` : line).join("\n");
|
|
43
|
-
}
|
|
44
|
-
static parseJson(s, fallback = null) {
|
|
45
|
-
if (s === null || s === undefined)
|
|
46
|
-
return fallback;
|
|
47
|
-
const text = typeof s === "string" ? s : Buffer.isBuffer(s) ? s.toString("utf8") : null;
|
|
48
|
-
if (text === null)
|
|
49
|
-
return fallback;
|
|
50
|
-
try {
|
|
51
|
-
return JSON.parse(text);
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return fallback;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
static #requestAccounting(row) {
|
|
58
|
-
if (row.state !== "settled" || row.accounting === null) {
|
|
59
|
-
throw new TypeError(`provider request ${row.id} is not settled`);
|
|
60
|
-
}
|
|
61
|
-
return row.accounting;
|
|
62
|
-
}
|
|
63
|
-
static #accounting(rows) {
|
|
64
|
-
return rows.some((row) => row.state !== "settled")
|
|
65
|
-
? null
|
|
66
|
-
: aggregateProviderAccounting(rows.map((row) => DigestRender.#requestAccounting(row)));
|
|
67
|
-
}
|
|
68
|
-
static usageSummary(accounting) {
|
|
69
|
-
if (accounting === null)
|
|
70
|
-
return "accounting=incomplete";
|
|
71
|
-
const usage = accounting.usage;
|
|
72
|
-
const known = accounting.knownUsage;
|
|
73
|
-
const quantity = (total, subtotal) => total !== undefined ? String(total) : subtotal === undefined ? "unknown" : `${subtotal}+?`;
|
|
74
|
-
return [
|
|
75
|
-
`input=${quantity(usage?.inputTokens, known?.inputTokens)}`,
|
|
76
|
-
`output=${quantity(usage?.outputTokens, known?.outputTokens)}`,
|
|
77
|
-
`reasoning=${quantity(usage?.outputTokenDetails?.reasoningTokens, known?.outputTokenDetails?.reasoningTokens)}`,
|
|
78
|
-
`cache-read=${quantity(usage?.inputTokenDetails?.cacheReadTokens, known?.inputTokenDetails?.cacheReadTokens)}`,
|
|
79
|
-
].join(" ");
|
|
80
|
-
}
|
|
81
|
-
static costSummary(accounting) {
|
|
82
|
-
if (accounting?.costUsd != null)
|
|
83
|
-
return `$${accounting.costUsd}`;
|
|
84
|
-
return accounting?.knownCostUsd == null ? "unknown" : `$${accounting.knownCostUsd} + ? (incomplete)`;
|
|
85
|
-
}
|
|
86
|
-
static #operationResult(raw, subject) {
|
|
87
|
-
try {
|
|
88
|
-
return Validator.assertOperationResult(DigestRender.parseJson(raw));
|
|
89
|
-
}
|
|
90
|
-
catch (cause) {
|
|
91
|
-
throw new Error(`${subject} does not contain a valid operation result`, { cause });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
static #rowProblem(row) {
|
|
95
|
-
const result = DigestRender.#operationResult(row.rx, `failed log entry ${row.id}`);
|
|
96
|
-
if (result.problem === undefined) {
|
|
97
|
-
throw new Error(`failed log entry ${row.id} does not contain Problem Details`);
|
|
98
|
-
}
|
|
99
|
-
return result.problem;
|
|
100
|
-
}
|
|
101
|
-
static #terminalResult(loop) {
|
|
102
|
-
return loop.terminal_result === null
|
|
103
|
-
? null
|
|
104
|
-
: DigestRender.#operationResult(loop.terminal_result, `terminal loop ${loop.id}`);
|
|
105
|
-
}
|
|
106
|
-
static #renderTarget(le) {
|
|
107
|
-
return le.target;
|
|
108
|
-
}
|
|
109
|
-
static #renderStream(le) {
|
|
110
|
-
if (!isExecutionOp(le.op))
|
|
111
|
-
return null;
|
|
112
|
-
const attrs = DigestRender.parseJson(le.attrs, {});
|
|
113
|
-
const stream = isRecord(attrs) ? attrs.stream : undefined;
|
|
114
|
-
return typeof stream === "string" ? stream : null;
|
|
115
|
-
}
|
|
116
|
-
// {§exec-env-scoped} — the environment a spawn received, as recorded on its row: host names by
|
|
117
|
-
// name, the Worker's own values, inherited values by their source, masked names. Host values
|
|
118
|
-
// stay in digest.json. Part of the rendered line, so spawns under different environments never
|
|
119
|
-
// collapse into one group.
|
|
120
|
-
static envLine(env) {
|
|
121
|
-
if (!isRecord(env))
|
|
122
|
-
return null;
|
|
123
|
-
const host = [];
|
|
124
|
-
const rest = [];
|
|
125
|
-
for (const [name, raw] of Object.entries(env).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
126
|
-
const record = isRecord(raw) ? raw : {};
|
|
127
|
-
const from = typeof record.from === "string" ? record.from : null;
|
|
128
|
-
if (record.source === "host")
|
|
129
|
-
host.push(name);
|
|
130
|
-
else if (record.source === "masked")
|
|
131
|
-
rest.push(`${name} (masked${from === null ? "" : ` by ${from}`})`);
|
|
132
|
-
else if (record.source === "modifier")
|
|
133
|
-
rest.push(`${name}=${DigestRender.#summarize(record.value, 60)} (modifier)`);
|
|
134
|
-
else if (record.source === "workspace")
|
|
135
|
-
rest.push(`${name}=${DigestRender.#summarize(record.value, 60)} (workspace)`);
|
|
136
|
-
else
|
|
137
|
-
rest.push(`${name}=${DigestRender.#summarize(record.value, 60)} (${from === null ? "worker" : `from ${from}`})`);
|
|
138
|
-
}
|
|
139
|
-
const parts = [...(host.length === 0 ? [] : [`host ${host.join(",")}`]), ...rest];
|
|
140
|
-
return `env: ${parts.length === 0 ? "(empty)" : parts.join(" · ")}`;
|
|
141
|
-
}
|
|
142
|
-
// The environment a spawn recorded on its output, found through the row's stream address.
|
|
143
|
-
static #environmentOf(le, m) {
|
|
144
|
-
const stream = DigestRender.#renderStream(le);
|
|
145
|
-
if (stream === null)
|
|
146
|
-
return undefined;
|
|
147
|
-
const worker = m.workersById.get(le.worker_id);
|
|
148
|
-
return worker === undefined ? undefined : m.environments.get(`${worker.workspace_id}:${stream}`);
|
|
149
|
-
}
|
|
8
|
+
static parseJson = parseJson;
|
|
9
|
+
static usageSummary = usageSummary;
|
|
10
|
+
static costSummary = costSummary;
|
|
11
|
+
static envLine = envLine;
|
|
12
|
+
static wireLine = wireLine;
|
|
13
|
+
static editForm = editForm;
|
|
14
|
+
static cacheLedger = cacheLedger;
|
|
15
|
+
static packetStems = packetStems;
|
|
16
|
+
static request = request;
|
|
17
|
+
static packetFiles = packetFiles;
|
|
18
|
+
static json = json;
|
|
150
19
|
static #renderOpLine(le, label = le.op ?? "source artifact", environment) {
|
|
151
|
-
const target =
|
|
152
|
-
const stream =
|
|
20
|
+
const target = le.target ?? "—";
|
|
21
|
+
const stream = renderStream(le);
|
|
153
22
|
const state = le.state !== "resolved" ? ` state=${le.state}` : "";
|
|
154
23
|
const outcome = le.outcome !== null ? ` outcome=${le.outcome}` : "";
|
|
155
24
|
const streamLink = stream === null ? "" : ` stream=${stream}`;
|
|
@@ -159,31 +28,31 @@ export default class DigestRender {
|
|
|
159
28
|
// the waterfall explains WHY each failure happened without opening packets.
|
|
160
29
|
let errLine = "";
|
|
161
30
|
if (le.status_rx >= 400) {
|
|
162
|
-
errLine = `\n -> ${
|
|
31
|
+
errLine = `\n -> ${rowProblem(le).detail.trim()}`;
|
|
163
32
|
}
|
|
164
|
-
const
|
|
165
|
-
const envText =
|
|
33
|
+
const env = envLine(environment);
|
|
34
|
+
const envText = env === null ? "" : `\n ${env}`;
|
|
166
35
|
const decision = le.proposalDecision;
|
|
167
36
|
const approval = decision === undefined ? "" : `\n decision=${decision.decision} resolver=${decision.resolver} at=${decision.decided_at}${decision.body === null ? "" : " body override recorded"}${decision.outcome === null ? "" : ` outcome=${decision.outcome}`}`;
|
|
168
37
|
return ` ← [${le.origin}] ${label}[${le.status_rx}] ${target}${source}${state}${outcome}${streamLink}${fail}${errLine}${envText}${approval}`;
|
|
169
38
|
}
|
|
170
39
|
static #renderGroupedOpLine(row, m) {
|
|
171
|
-
const parsedAttrs =
|
|
40
|
+
const parsedAttrs = parseJson(row.attrs, {});
|
|
172
41
|
const attrs = isRecord(parsedAttrs) ? parsedAttrs : {};
|
|
173
|
-
// {§
|
|
42
|
+
// {§turn-source-resources}: the announcement of an admitted emission, and whether the model retired it.
|
|
174
43
|
if (row.op === "READ" && attrs.kind === "emission") {
|
|
175
|
-
return DigestRender.#renderOpLine(row, row.projection_active === 1 ? "emission" : "emission (killed)",
|
|
44
|
+
return DigestRender.#renderOpLine(row, row.projection_active === 1 ? "emission" : "emission (killed)", environmentOf(row, m));
|
|
176
45
|
}
|
|
177
|
-
// {§
|
|
46
|
+
// {§turn-source-resources}: the turn's reasoning landed as a row, and whether the model retired it.
|
|
178
47
|
if (row.op === "READ" && attrs.kind === "reasoning") {
|
|
179
|
-
return DigestRender.#renderOpLine(row, row.projection_active === 1 ? "reasoning" : "reasoning (killed)",
|
|
48
|
+
return DigestRender.#renderOpLine(row, row.projection_active === 1 ? "reasoning" : "reasoning (killed)", environmentOf(row, m));
|
|
180
49
|
}
|
|
181
50
|
const materialized = row.origin === "_plurnk" && row.op === "EDIT" && attrs.kind === "entry_materialized";
|
|
182
51
|
const actionlessKind = row.op === null ? attrs.kind : null;
|
|
183
52
|
const label = actionlessKind === "emissionAttempt"
|
|
184
53
|
? "emission attempt"
|
|
185
54
|
: row.op ?? `unrecognized actionless row (kind=${JSON.stringify(actionlessKind) ?? "absent"})`;
|
|
186
|
-
return DigestRender.#renderOpLine(row, materialized ? "materialized entry" : label,
|
|
55
|
+
return DigestRender.#renderOpLine(row, materialized ? "materialized entry" : label, environmentOf(row, m));
|
|
187
56
|
}
|
|
188
57
|
// Human triage is not a row dump. Preserve every row in digest.json, but
|
|
189
58
|
// collapse consecutive identical rendered outcomes in the Markdown waterfall. Using the
|
|
@@ -206,37 +75,13 @@ export default class DigestRender {
|
|
|
206
75
|
return count === 1 ? line : `${line} ×${count} (seq ${firstSeq}–${lastSeq})`;
|
|
207
76
|
});
|
|
208
77
|
}
|
|
209
|
-
//
|
|
210
|
-
// vs whether it still concluded. A green that limped across on 16 errors is a FAILING artifact
|
|
211
|
-
// wearing a passing badge; the digest must make that impossible to miss. errors = ≥400 op rows;
|
|
212
|
-
// errorItems = minted op='error' rows (truncation/budget/steer/cycle — the strike signals).
|
|
213
|
-
// {§digest-executor-evidence} (#436) — a failed command's completion rows carry the
|
|
214
|
-
// executor's problem identity; a red test run is the loop's normal work, not a
|
|
215
|
-
// defect. They stay visible but never count as errors in health or the errs
|
|
216
|
-
// badge — the digest mirror of the strike rail's exemption (#425 F1).
|
|
217
|
-
static #isExecutorEvidence(le) {
|
|
218
|
-
if (le.status_rx < 400 || le.rx === null)
|
|
219
|
-
return false;
|
|
220
|
-
const rx = DigestRender.parseJson(le.rx, null);
|
|
221
|
-
const problem = isRecord(rx) && isRecord(rx.problem) ? rx.problem : null;
|
|
222
|
-
return typeof problem?.type === "string" && problem.type.startsWith("https://problems.plurnk.xyz/executor/");
|
|
223
|
-
}
|
|
224
|
-
// {§digest-capacity-evidence} (#1096) — a 413 is the capacity contract working: a result that did
|
|
225
|
-
// not fit, the over-budget mandate, a limit met. It renders like any row and never counts as an
|
|
226
|
-
// error, an error item or in the errs badge; a loop that concluded under a binding budget is CLEAN.
|
|
227
|
-
static #isCapacityEvidence(le) {
|
|
228
|
-
return le.status_rx === 413;
|
|
229
|
-
}
|
|
230
|
-
static #countsAsError(le) {
|
|
231
|
-
return le.status_rx >= 400 && !DigestRender.#isExecutorEvidence(le) && !DigestRender.#isCapacityEvidence(le);
|
|
232
|
-
}
|
|
233
|
-
// {§digest-storage} — size, free pages, vacuum mode and the largest tables, one line.
|
|
78
|
+
// {§share-packet-names} — size, free pages, vacuum mode and the largest tables, one line.
|
|
234
79
|
static #storageLine(storage) {
|
|
235
80
|
const mb = (bytes) => `${(bytes / 1_048_576).toFixed(1)} MB`;
|
|
236
81
|
const mode = ["off", "full", "incremental"][storage.auto_vacuum] ?? String(storage.auto_vacuum);
|
|
237
82
|
return `Storage: ${mb(storage.bytes)} (free ${mb(storage.free_bytes)}, auto_vacuum ${mode}) · largest: ${storage.tables.map(({ name, bytes }) => `${name} ${mb(bytes)}`).join(", ")}`;
|
|
238
83
|
}
|
|
239
|
-
// {§
|
|
84
|
+
// {§session-wait-timing} — claim to first model turn: a stall between them is a number, not a gap.
|
|
240
85
|
static #wakeLatency(loop, m) {
|
|
241
86
|
if (loop.claimed_at === null)
|
|
242
87
|
return null;
|
|
@@ -246,213 +91,27 @@ export default class DigestRender {
|
|
|
246
91
|
const seconds = (Date.parse(first.timestamp) - Date.parse(loop.claimed_at)) / 1000;
|
|
247
92
|
return `Claimed: ${loop.claimed_at} · first model turn +${seconds.toFixed(1)} s`;
|
|
248
93
|
}
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
static #
|
|
252
|
-
|
|
253
|
-
if (packet === null)
|
|
254
|
-
return null;
|
|
255
|
-
const messages = packet.messages();
|
|
256
|
-
if (messages.length === 0)
|
|
94
|
+
// Among its loop's model turns, this one's ordinal: the model's own count, which skips the
|
|
95
|
+
// harness turns a reader would otherwise have to subtract.
|
|
96
|
+
static #modelTurnOrdinal(turn, m) {
|
|
97
|
+
if (turn.producer !== "model")
|
|
257
98
|
return null;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
static #commonPrefixLength(left, right) {
|
|
263
|
-
const bound = Math.min(left.length, right.length);
|
|
264
|
-
let n = 0;
|
|
265
|
-
while (n < bound && left.charCodeAt(n) === right.charCodeAt(n))
|
|
266
|
-
n += 1;
|
|
267
|
-
return n;
|
|
268
|
-
}
|
|
269
|
-
// {§digest-edit-census}: the form an EDIT authored, read from its stored marker and pattern.
|
|
270
|
-
static editForm(row) {
|
|
271
|
-
if (row.pattern !== null)
|
|
272
|
-
return "pattern";
|
|
273
|
-
if (row.line_marker === null)
|
|
274
|
-
return "whole";
|
|
275
|
-
const marker = DigestRender.parseJson(row.line_marker, { marks: [] });
|
|
276
|
-
const marks = isRecord(marker) && Array.isArray(marker.marks) ? marker.marks : [];
|
|
277
|
-
if (marks.length === 1) {
|
|
278
|
-
const [mark] = marks;
|
|
279
|
-
if (typeof mark === "string")
|
|
280
|
-
return "hash";
|
|
281
|
-
if (mark === -1)
|
|
282
|
-
return "append";
|
|
283
|
-
if (mark === 0)
|
|
284
|
-
return "prepend";
|
|
285
|
-
return "line";
|
|
286
|
-
}
|
|
287
|
-
if (marks.length === 2)
|
|
288
|
-
return "range";
|
|
289
|
-
if (marks.length === 3)
|
|
290
|
-
return "offset";
|
|
291
|
-
if (marks.length === 4 && marks[0] === marks[2] && marks[1] === 1 && marks[3] === 1)
|
|
292
|
-
return "insert";
|
|
293
|
-
return "column";
|
|
294
|
-
}
|
|
295
|
-
static #editCensusCache = new WeakMap();
|
|
296
|
-
// {§digest-edit-census}: per worker, every model EDIT by authored form and status, and the
|
|
297
|
-
// revisits — an EDIT of a path the same worker edited within its previous two model turns.
|
|
298
|
-
static #editCensus(m) {
|
|
299
|
-
const cached = DigestRender.#editCensusCache.get(m);
|
|
300
|
-
if (cached !== undefined)
|
|
301
|
-
return cached;
|
|
302
|
-
const loopWorker = new Map(m.loops.map((loop) => [loop.id, loop.worker_id]));
|
|
303
|
-
// A worker's model turns in order; the ordinal is the distance the revisit window counts in.
|
|
304
|
-
const ordinal = new Map();
|
|
305
|
-
const perWorker = new Map();
|
|
306
|
-
for (const turn of [...m.turns].toSorted((a, b) => a.id - b.id)) {
|
|
307
|
-
if (turn.producer !== "model")
|
|
308
|
-
continue;
|
|
309
|
-
const workerId = loopWorker.get(turn.loop_id);
|
|
310
|
-
if (workerId === undefined)
|
|
311
|
-
throw new TypeError(`turn ${turn.id} has no loop in scope`);
|
|
312
|
-
const next = (perWorker.get(workerId) ?? 0) + 1;
|
|
313
|
-
perWorker.set(workerId, next);
|
|
314
|
-
ordinal.set(turn.id, next);
|
|
315
|
-
}
|
|
316
|
-
const byWorker = new Map();
|
|
317
|
-
const formById = new Map();
|
|
318
|
-
const revisitById = new Set();
|
|
319
|
-
for (const [workerId, rows] of m.editRowsByWorker) {
|
|
320
|
-
const census = { edits: 0, refused: 0, revisits: 0, forms: Object.fromEntries(EDIT_FORMS.map((form) => [form, 0])) };
|
|
321
|
-
const lastEdited = new Map();
|
|
322
|
-
for (const row of rows) {
|
|
323
|
-
const form = DigestRender.editForm(row);
|
|
324
|
-
formById.set(row.id, form);
|
|
325
|
-
census.edits += 1;
|
|
326
|
-
census.forms[form] = (census.forms[form] ?? 0) + 1;
|
|
327
|
-
if (row.status_rx >= 400)
|
|
328
|
-
census.refused += 1;
|
|
329
|
-
const at = ordinal.get(row.turn_id);
|
|
330
|
-
if (at === undefined)
|
|
331
|
-
throw new TypeError(`EDIT ${row.id} sits on a turn no model produced`);
|
|
332
|
-
if (row.pathname !== null) {
|
|
333
|
-
const previous = lastEdited.get(row.pathname);
|
|
334
|
-
if (previous !== undefined && previous < at && at - previous <= 2) {
|
|
335
|
-
census.revisits += 1;
|
|
336
|
-
revisitById.add(row.id);
|
|
337
|
-
}
|
|
338
|
-
lastEdited.set(row.pathname, at);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
byWorker.set(workerId, census);
|
|
342
|
-
}
|
|
343
|
-
const result = { byWorker, formById, revisitById };
|
|
344
|
-
DigestRender.#editCensusCache.set(m, result);
|
|
345
|
-
return result;
|
|
346
|
-
}
|
|
347
|
-
static #renderEditCensus(census) {
|
|
348
|
-
if (census === undefined || census.edits === 0)
|
|
349
|
-
return "(no edits)";
|
|
350
|
-
const forms = EDIT_FORMS.filter((form) => (census.forms[form] ?? 0) > 0).map((form) => `${form}=${census.forms[form] ?? 0}`).join(" ");
|
|
351
|
-
return `${census.edits} · ${forms} · refused=${census.refused} · revisits=${census.revisits}`;
|
|
352
|
-
}
|
|
353
|
-
static #cacheLedgerCache = new WeakMap();
|
|
354
|
-
// {§digest-cache-ledger} — estimate adjacent prefixes with at most two prompts resident per loop.
|
|
355
|
-
static cacheLedger(m) {
|
|
356
|
-
const cached = DigestRender.#cacheLedgerCache.get(m);
|
|
357
|
-
if (cached !== undefined)
|
|
358
|
-
return cached;
|
|
359
|
-
const turnsById = new Map(m.turns.map((turn) => [turn.id, turn]));
|
|
360
|
-
const ledger = new Map();
|
|
361
|
-
for (const loop of m.loops) {
|
|
362
|
-
let memo;
|
|
363
|
-
const promptOf = (turn) => {
|
|
364
|
-
if (memo?.turnId !== turn.id)
|
|
365
|
-
memo = { turnId: turn.id, text: DigestRender.#promptText(turn, m) };
|
|
366
|
-
return memo.text;
|
|
367
|
-
};
|
|
368
|
-
// undefined: no previous request in this loop; null: the previous request's packet is not valid.
|
|
369
|
-
let previousText;
|
|
370
|
-
for (const request of m.requestsByLoop.get(loop.id) ?? []) {
|
|
371
|
-
const turn = turnsById.get(request.turn_id);
|
|
372
|
-
if (turn === undefined)
|
|
373
|
-
throw new TypeError(`provider request ${request.id} has no turn in scope`);
|
|
374
|
-
const text = request.kind === "emission" ? promptOf(turn) : null;
|
|
375
|
-
const adjacentPrefixTokensEstimate = text === null || request.usage_input === null
|
|
376
|
-
? null
|
|
377
|
-
: previousText === undefined
|
|
378
|
-
? 0
|
|
379
|
-
: previousText === null
|
|
380
|
-
? null
|
|
381
|
-
: text.length === 0
|
|
382
|
-
? 0
|
|
383
|
-
: Math.round(request.usage_input * m.evidence.textWeight(text.slice(0, DigestRender.#commonPrefixLength(previousText, text))) / m.evidence.textWeight(text));
|
|
384
|
-
ledger.set(request.id, {
|
|
385
|
-
adjacentPrefixTokensEstimate,
|
|
386
|
-
cachedTokens: request.usage_input_cache_read,
|
|
387
|
-
inputTokens: request.usage_input,
|
|
388
|
-
});
|
|
389
|
-
previousText = text;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
for (const request of m.providerRequests) {
|
|
393
|
-
if (!ledger.has(request.id))
|
|
394
|
-
throw new TypeError(`provider request ${request.id} has no loop in scope`);
|
|
395
|
-
}
|
|
396
|
-
DigestRender.#cacheLedgerCache.set(m, ledger);
|
|
397
|
-
return ledger;
|
|
398
|
-
}
|
|
399
|
-
static #cacheEntries(requests, m) {
|
|
400
|
-
const ledger = DigestRender.cacheLedger(m);
|
|
401
|
-
return requests.map((request) => ledger.get(request.id));
|
|
402
|
-
}
|
|
403
|
-
// {§digest-cache-ledger} — sum each provider-reported counter; one absent quantity makes that sum unknown.
|
|
404
|
-
static #cacheBadge(turn, m) {
|
|
405
|
-
const entries = DigestRender.#cacheEntries(m.requestsByTurn.get(turn.id) ?? [], m);
|
|
406
|
-
if (entries.length === 0)
|
|
407
|
-
return "";
|
|
408
|
-
const sum = (values) => values.some((value) => value === null)
|
|
409
|
-
? "?"
|
|
410
|
-
: String(values.reduce((total, value) => total + (value ?? 0), 0));
|
|
411
|
-
return ` cache=${sum(entries.map((entry) => entry.cachedTokens))}/${sum(entries.map((entry) => entry.inputTokens))}`;
|
|
412
|
-
}
|
|
413
|
-
// {§digest-cache-ledger} — one workspace line; unreported requests are named and left out of the percentage.
|
|
414
|
-
static #cacheLine(requests, m) {
|
|
415
|
-
const entries = DigestRender.#cacheEntries(requests, m);
|
|
416
|
-
const reported = entries.filter((entry) => entry.cachedTokens !== null && entry.inputTokens !== null);
|
|
417
|
-
const unreported = entries.length - reported.length;
|
|
418
|
-
const cached = reported.reduce((total, entry) => total + (entry.cachedTokens ?? 0), 0);
|
|
419
|
-
const input = reported.reduce((total, entry) => total + (entry.inputTokens ?? 0), 0);
|
|
420
|
-
const pct = input === 0 ? "n/a" : `${((100 * cached) / input).toFixed(1)}%`;
|
|
421
|
-
const plural = (n) => n === 1 ? "request" : "requests";
|
|
422
|
-
return [
|
|
423
|
-
`Cache: ${cached} of ${input} reported input tokens read from cache (${pct}) over ${reported.length} ${plural(reported.length)}`,
|
|
424
|
-
unreported > 0 ? `${unreported} missing input or cache usage (excluded)` : null,
|
|
425
|
-
].filter((part) => part !== null).join(" · ");
|
|
426
|
-
}
|
|
427
|
-
static #loopHealth(loop, m) {
|
|
428
|
-
let errors = 0;
|
|
429
|
-
let errorItems = 0;
|
|
430
|
-
for (const t of m.turnsByLoop.get(loop.id) ?? []) {
|
|
431
|
-
for (const le of m.logEntriesByTurn.get(t.id) ?? []) {
|
|
432
|
-
if (DigestRender.#countsAsError(le))
|
|
433
|
-
errors += 1;
|
|
434
|
-
if (le.op === "error" && !DigestRender.#isCapacityEvidence(le))
|
|
435
|
-
errorItems += 1;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
const s = loop.status;
|
|
439
|
-
const verdict = s >= 400 ? `FAILED(${s})`
|
|
440
|
-
: s >= 200 && s < 300 ? (errors > 0 ? "DEGENERATE-WIN" : "CLEAN")
|
|
441
|
-
: `status=${s}`;
|
|
442
|
-
return { errors, errorItems, verdict };
|
|
99
|
+
const siblings = (m.turnsByLoop.get(turn.loop_id) ?? []).filter((t) => t.producer === "model");
|
|
100
|
+
const index = siblings.findIndex((t) => t.id === turn.id);
|
|
101
|
+
return index < 0 ? null : index + 1;
|
|
443
102
|
}
|
|
444
103
|
static #renderTurnLine(turn, m) {
|
|
445
104
|
const { packet, packetFailure } = m.evidence.packet(turn);
|
|
446
105
|
const assistant = packet?.assistant ?? null;
|
|
447
106
|
const content = assistant?.content ?? "";
|
|
448
107
|
const reasoning = assistant?.reasoning ?? null;
|
|
449
|
-
const
|
|
450
|
-
const tokens =
|
|
451
|
-
const cost = ` cost=${
|
|
108
|
+
const acct = accounting(m.requestsByTurn.get(turn.id) ?? []);
|
|
109
|
+
const tokens = usageSummary(acct);
|
|
110
|
+
const cost = ` cost=${costSummary(acct)}`;
|
|
452
111
|
const finishReason = turn.finish_reason ?? "—";
|
|
453
112
|
// Render only observed transport metadata; absence makes no claim
|
|
454
|
-
// about endpoint-owned settings. {§
|
|
455
|
-
const tm =
|
|
113
|
+
// about endpoint-owned settings. {§provider-instantiation}
|
|
114
|
+
const tm = parseJson(turn.meta ?? "null", null);
|
|
456
115
|
const railsFlag = isRecord(tm) ? tm.railsAttached : undefined;
|
|
457
116
|
const attached = typeof railsFlag === "boolean" || typeof railsFlag === "string" ? railsFlag : undefined;
|
|
458
117
|
const rails = attached === undefined || attached === false ? ""
|
|
@@ -461,7 +120,7 @@ export default class DigestRender {
|
|
|
461
120
|
// {§outside-text}: the weight the model was told, so a digest reader sees the discard at a glance.
|
|
462
121
|
const outside = turn.outside === null ? "" : ` outside=${m.evidence.textWeight(turn.outside)} tok`;
|
|
463
122
|
const errs = (m.logEntriesByTurn.get(turn.id) ?? [])
|
|
464
|
-
.filter((le) =>
|
|
123
|
+
.filter((le) => countsAsError(le)).length;
|
|
465
124
|
const errBadge = errs > 0 ? ` ⚠ errs=${errs}` : "";
|
|
466
125
|
const attempts = m.attemptsByTurn.get(turn.id) ?? [];
|
|
467
126
|
const rejected = attempts.filter((attempt) => attempt.accepted === 0).length;
|
|
@@ -476,12 +135,12 @@ export default class DigestRender {
|
|
|
476
135
|
? ""
|
|
477
136
|
: ` ⚠ ${attemptConditions.join(" ")}/${attempts.length}`;
|
|
478
137
|
const packetBadge = packetFailure === null ? "" : " ⚠ packet=invalid";
|
|
479
|
-
const stem =
|
|
138
|
+
const stem = packetStems(m).get(turn.id);
|
|
480
139
|
const modelTurn = DigestRender.#modelTurnOrdinal(turn, m);
|
|
481
140
|
const provenance = [modelTurn === null ? null : `model turn ${modelTurn}`, stem].filter((part) => part !== null).join(" · ");
|
|
482
141
|
const lifecycle = `T${turn.sequence}${provenance === "" ? "" : ` (${provenance})`}: producer=${turn.producer} kind=${turn.kind} status=${turn.status}${turn.completed_at === null ? " state=open" : ""}`;
|
|
483
142
|
const head = turn.kind === "inference"
|
|
484
|
-
? `${lifecycle} finish=${finishReason}${rails} model=${model} ${tokens}${cost}${
|
|
143
|
+
? `${lifecycle} finish=${finishReason}${rails} model=${model} ${tokens}${cost}${cacheBadge(turn, m)}${outside}${errBadge}${attemptBadge}${packetBadge}`
|
|
485
144
|
: `${lifecycle}${errBadge}${packetBadge}`;
|
|
486
145
|
let summary = null;
|
|
487
146
|
if (packetFailure !== null) {
|
|
@@ -492,7 +151,7 @@ export default class DigestRender {
|
|
|
492
151
|
}
|
|
493
152
|
else if (content.length > 0) {
|
|
494
153
|
summary = content.includes("\n")
|
|
495
|
-
? ` ↳ emission:\n${
|
|
154
|
+
? ` ↳ emission:\n${indentLines(content.trimEnd(), " ")}`
|
|
496
155
|
: ` ↳ emission: ${content.trim()}`;
|
|
497
156
|
}
|
|
498
157
|
else if (assistant !== null) {
|
|
@@ -506,60 +165,36 @@ export default class DigestRender {
|
|
|
506
165
|
}
|
|
507
166
|
const reasoningLine = reasoning && reasoning.length > 0
|
|
508
167
|
? (reasoning.includes("\n")
|
|
509
|
-
? ` ↳ reasoning:\n${
|
|
168
|
+
? ` ↳ reasoning:\n${indentLines(reasoning.trimEnd(), " ")}`
|
|
510
169
|
: ` ↳ reasoning: ${reasoning.trim()}`)
|
|
511
170
|
: null;
|
|
512
|
-
// {§provider-
|
|
513
|
-
const
|
|
171
|
+
// {§provider-failure-normalization} — an empty emission is read from what the wire carried, never guessed at.
|
|
172
|
+
const wire = content.length === 0 && packet?.assistant != null ? wireLine(packet.assistantRaw) : null;
|
|
514
173
|
const roles = packet?.messages().map(({ role }) => role).join(" → ");
|
|
515
174
|
const requestLine = packet === null ? null
|
|
516
175
|
: ` ↳ request: ${roles || "input sections not retained"} (${stem}.request.md)`;
|
|
517
176
|
const opLines = DigestRender.#renderOpLines(m.logEntriesByTurn.get(turn.id) ?? [], m);
|
|
518
|
-
return [head, ...(requestLine ? [requestLine] : []), ...(summary ? [summary] : []), ...(reasoningLine ? [reasoningLine] : []), ...(
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const
|
|
526
|
-
const emptyChunks = typeof wire.emptyChunks === "number" ? wire.emptyChunks : 0;
|
|
527
|
-
const fields = isRecord(wire.fields) ? wire.fields : {};
|
|
528
|
-
const channels = isRecord(wire.channels) ? wire.channels : {};
|
|
529
|
-
const toolCalls = Array.isArray(wire.toolCalls) ? wire.toolCalls : [];
|
|
530
|
-
const unmappedChunks = Array.isArray(wire.unmappedChunks) ? wire.unmappedChunks : [];
|
|
531
|
-
const parts = [
|
|
532
|
-
`${chunks} chunks`,
|
|
533
|
-
emptyChunks > 0 ? `${emptyChunks} carried nothing` : null,
|
|
534
|
-
...Object.entries(fields).map(([field, count]) => `${field}×${String(count)}`),
|
|
535
|
-
...toolCalls.map((call) => {
|
|
536
|
-
const name = isRecord(call) && typeof call.name === "string" ? call.name : "?";
|
|
537
|
-
return `tool call ${name}(${DigestRender.#summarize(isRecord(call) ? call.arguments : undefined, 60)})`;
|
|
538
|
-
}),
|
|
539
|
-
...Object.entries(channels).map(([channel, text]) => `${channel}: ${DigestRender.#summarize(text, 60)}`),
|
|
540
|
-
unmappedChunks.length > 0 ? `${unmappedChunks.length} unmapped frames retained` : null,
|
|
541
|
-
].filter((part) => part !== null);
|
|
542
|
-
return ` ↳ wire: ${parts.join(", ")}`;
|
|
543
|
-
}
|
|
544
|
-
static #renderWorkerShape(worker, m) {
|
|
545
|
-
// every worker has exactly one rollup row — digest_worker_rollups is FROM workers
|
|
546
|
-
const roll = m.workerRollups.get(worker.id);
|
|
547
|
-
const opMix = (m.opMixByWorker.get(worker.id) ?? []).map((o) => `${o.op}=${o.n}`).join(" ");
|
|
548
|
-
const requests = m.requestsByWorker.get(worker.id) ?? [];
|
|
549
|
-
const accounting = DigestRender.#accounting(requests);
|
|
177
|
+
return [head, ...(requestLine ? [requestLine] : []), ...(summary ? [summary] : []), ...(reasoningLine ? [reasoningLine] : []), ...(wire ? [wire] : []), ...opLines].join("\n");
|
|
178
|
+
}
|
|
179
|
+
static #renderSessionShape(session, m) {
|
|
180
|
+
// every session has exactly one rollup row — digest_session_rollups is FROM sessions
|
|
181
|
+
const roll = m.sessionRollups.get(session.id);
|
|
182
|
+
const opMix = (m.opMixBySession.get(session.id) ?? []).map((o) => `${o.op}=${o.n}`).join(" ");
|
|
183
|
+
const requests = m.requestsBySession.get(session.id) ?? [];
|
|
184
|
+
const acct = accounting(requests);
|
|
550
185
|
const usageStr = requests.length === 0
|
|
551
186
|
? "no provider requests"
|
|
552
|
-
:
|
|
187
|
+
: usageSummary(acct);
|
|
553
188
|
const costStr = requests.length === 0
|
|
554
189
|
? "n/a"
|
|
555
|
-
:
|
|
190
|
+
: costSummary(acct);
|
|
556
191
|
// {§digest-forensic-fidelity} (#461): settled exchanges that carry no usage at
|
|
557
192
|
// all (errored/aborted) billed server-side invisibly; say so, never price-as-zero.
|
|
558
193
|
const usageless = requests.filter((row) => row.state === "settled" && row.usage_total === null).length;
|
|
559
194
|
const usagelessStr = usageless > 0
|
|
560
195
|
? ` (+${usageless} usage-less request${usageless === 1 ? "" : "s"} — server-side spend unrecorded)`
|
|
561
196
|
: "";
|
|
562
|
-
// {§digest-
|
|
197
|
+
// {§digest-forensic-fidelity} (#473): a dollar figure without its basis reads as billed
|
|
563
198
|
// truth; carry the kind so an estimate can never impersonate a charge.
|
|
564
199
|
const kinds = new Set(requests
|
|
565
200
|
.filter((row) => row.state === "settled" && row.cost_kind !== null)
|
|
@@ -571,7 +206,7 @@ export default class DigestRender {
|
|
|
571
206
|
: kinds.has("charged")
|
|
572
207
|
? " (charged)"
|
|
573
208
|
: "";
|
|
574
|
-
// {§digest-
|
|
209
|
+
// {§digest-forensic-fidelity} (#473): provider-level errors are absorbed by retries below
|
|
575
210
|
// the packet stream, so only an aggregate line makes a rate-limit storm visible.
|
|
576
211
|
const wireErrors = requests.filter((row) => row.outcome === "error").length;
|
|
577
212
|
const wireStr = requests.length === 0
|
|
@@ -584,85 +219,16 @@ export default class DigestRender {
|
|
|
584
219
|
`Tokens: ${usageStr}`,
|
|
585
220
|
`Cost: ${costStr}${kindStr}${usagelessStr}`,
|
|
586
221
|
`Wire: ${wireStr}`,
|
|
587
|
-
`Room: ${
|
|
222
|
+
`Room: ${renderRoom(session, m)}`,
|
|
588
223
|
`Op mix: ${opMix.length > 0 ? opMix : "(no ops)"}`,
|
|
589
|
-
`EDITs: ${
|
|
590
|
-
`Emissions: ${
|
|
591
|
-
`Reasonings: ${
|
|
224
|
+
`EDITs: ${renderEditCensus(editCensus(m).bySession.get(session.id))}`,
|
|
225
|
+
`Emissions: ${renderEmissions(session, m)}`,
|
|
226
|
+
`Reasonings: ${renderReasonings(session, m)}`,
|
|
592
227
|
].join("\n");
|
|
593
228
|
}
|
|
594
|
-
// {§digest-room-line} — the room in provider tokens: the largest budget the model was shown, at its
|
|
595
|
-
// request's own ratio, against the capacity; the wall's estimate against the provider's count; and the
|
|
596
|
-
// requests the estimate put under the wall while the count was over it.
|
|
597
|
-
static #renderRoom(worker, m) {
|
|
598
|
-
const measured = DigestRender.#roomMeasures(worker, m);
|
|
599
|
-
if (measured.length === 0)
|
|
600
|
-
return "(no measured requests)";
|
|
601
|
-
const room = Math.max(...measured.map(({ weight, budget, count, capacity }) => budget * count / weight / capacity));
|
|
602
|
-
const errors = measured.map(({ estimate, count }) => (estimate - count) / count);
|
|
603
|
-
const missed = measured.filter(({ estimate, count, wall }) => estimate <= wall && count > wall).length;
|
|
604
|
-
const percent = (ratio) => `${ratio < 0 ? "-" : "+"}${Math.abs(100 * ratio).toFixed(1)}%`;
|
|
605
|
-
return `budget up to ${Math.round(100 * room)}% of capacity · wall estimate ${percent(Math.min(...errors))} to ${percent(Math.max(...errors))} of the count`
|
|
606
|
-
+ (missed === 0 ? "" : ` ⚠ ${missed} over the wall the estimate admitted`);
|
|
607
|
-
}
|
|
608
|
-
// One measure per packet-bearing inference turn whose request records a known capacity and wall and a
|
|
609
|
-
// provider count: the exact preflight measurement, else the reported input.
|
|
610
|
-
static #roomMeasures(worker, m) {
|
|
611
|
-
const measures = [];
|
|
612
|
-
for (const loop of m.loopsByWorker.get(worker.id) ?? []) {
|
|
613
|
-
for (const turn of m.turnsByLoop.get(loop.id) ?? []) {
|
|
614
|
-
if (turn.kind !== "inference" || turn.has_packet !== 1)
|
|
615
|
-
continue;
|
|
616
|
-
const packet = m.evidence.packet(turn).packet;
|
|
617
|
-
if (packet === null || packet.budget === null || packet.budget === 0 || packet.weight === 0)
|
|
618
|
-
continue;
|
|
619
|
-
const call = m.modelCalls.findLast((row) => row.turn_id === turn.id && row.kind === "emission" && row.capacity !== null);
|
|
620
|
-
const capacity = DigestRender.parseJson(call?.capacity);
|
|
621
|
-
const inputCapacity = isRecord(capacity) ? capacity.inputCapacity : undefined;
|
|
622
|
-
const inputWall = isRecord(capacity) ? capacity.inputWall : undefined;
|
|
623
|
-
if (typeof inputCapacity !== "number" || typeof inputWall !== "number")
|
|
624
|
-
continue;
|
|
625
|
-
const reported = (m.requestsByTurn.get(turn.id) ?? [])
|
|
626
|
-
.findLast((row) => row.kind === "emission" && row.outcome === "response" && row.usage_input !== null)?.usage_input ?? null;
|
|
627
|
-
const prompt = isRecord(capacity) && isRecord(capacity.prompt) ? capacity.prompt : null;
|
|
628
|
-
const count = prompt?.kind === "exact" ? (typeof prompt.tokens === "number" ? prompt.tokens : null) : reported;
|
|
629
|
-
if (count === null || count === 0)
|
|
630
|
-
continue;
|
|
631
|
-
measures.push({
|
|
632
|
-
weight: packet.weight, budget: packet.budget, capacity: inputCapacity, wall: inputWall, count,
|
|
633
|
-
estimate: Math.ceil(packet.weight * inputCapacity / packet.budget),
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
return measures;
|
|
638
|
-
}
|
|
639
|
-
// {§emission-row} — announced, retired by the model, and echoed back as headings in its own text.
|
|
640
|
-
static #renderEmissions(worker, m) {
|
|
641
|
-
const rows = m.emissionRows.filter((row) => row.worker_id === worker.id);
|
|
642
|
-
const killed = rows.filter((row) => row.active === 0).length;
|
|
643
|
-
const echoes = (m.loopsByWorker.get(worker.id) ?? [])
|
|
644
|
-
.flatMap((loop) => m.turnsByLoop.get(loop.id) ?? [])
|
|
645
|
-
.reduce((sum, turn) => sum + turn.packetEchoes, 0);
|
|
646
|
-
return `${rows.length} announced · ${killed} killed · ${echoes} header echo${echoes === 1 ? "" : "es"}`;
|
|
647
|
-
}
|
|
648
|
-
// {§reasoning-row} — landed, retired by the model, and turns that reasoned.
|
|
649
|
-
static #renderReasonings(worker, m) {
|
|
650
|
-
const rows = m.reasoningRows.filter((row) => row.worker_id === worker.id);
|
|
651
|
-
const killed = rows.filter((row) => row.active === 0).length;
|
|
652
|
-
const reasonedTurns = (m.loopsByWorker.get(worker.id) ?? [])
|
|
653
|
-
.flatMap((loop) => m.turnsByLoop.get(loop.id) ?? [])
|
|
654
|
-
.filter((turn) => turn.has_reasoning === 1).length;
|
|
655
|
-
if (reasonedTurns === 0 && rows.length === 0)
|
|
656
|
-
return "(no reasoning)";
|
|
657
|
-
if (rows.length === 0)
|
|
658
|
-
return `0 of ${reasonedTurns} landed (unbudgeted or disabled)`;
|
|
659
|
-
if (reasonedTurns > rows.length)
|
|
660
|
-
return `${rows.length} of ${reasonedTurns} landed · ${killed} killed`;
|
|
661
|
-
return `${rows.length} landed · ${killed} killed`;
|
|
662
|
-
}
|
|
663
229
|
static waterfall(m) {
|
|
664
230
|
const lines = [];
|
|
665
|
-
lines.push(
|
|
231
|
+
lines.push("# plurnk-service digest");
|
|
666
232
|
lines.push("");
|
|
667
233
|
lines.push(`DB: ${m.dbPath}`);
|
|
668
234
|
lines.push(DigestRender.#storageLine(m.storage));
|
|
@@ -670,13 +236,13 @@ export default class DigestRender {
|
|
|
670
236
|
const erroredCalls = m.inferenceCalls.filter((call) => call.state === "error").length;
|
|
671
237
|
const pendingCalls = m.inferenceCalls.filter((call) => call.state === "pending").length;
|
|
672
238
|
const bareCalls = m.modelCalls.filter((call) => call.kind === "bare").length;
|
|
673
|
-
const pendingRequests = m.providerRequests.filter((
|
|
239
|
+
const pendingRequests = m.providerRequests.filter((req) => req.state === "pending").length;
|
|
674
240
|
const packetFailures = m.turns.filter((turn) => m.evidence.packet(turn).packetFailure !== null).length;
|
|
675
|
-
lines.push(`Workspaces: ${m.workspaces.length}
|
|
241
|
+
lines.push(`Workspaces: ${m.workspaces.length} Sessions: ${m.sessions.length} Loops: ${m.loops.length} Turns: ${m.turns.length} Inference calls: ${m.inferenceCalls.length} (${bareCalls} BARE, ${erroredCalls} errored, ${pendingCalls} open) Emission attempts: ${m.turnAttempts.length} (${rejectedAttempts} rejected) Provider requests: ${m.providerRequests.length} (${pendingRequests} open) Log entries: ${m.logEntries.length}`);
|
|
676
242
|
if (packetFailures > 0)
|
|
677
243
|
lines.push(`Stored packet failures: ${packetFailures}`);
|
|
678
244
|
lines.push(`Search: channels=${m.search.channel_entries} attached=${m.search.derivation_complete} (indexed=${m.search.indexed} excluded=${m.search.excluded} unsearchable=${m.search.unsearchable} failed=${m.search.failed}) unattached=${m.search.unfinished} artifacts=${m.search.derivation_artifacts_complete} complete/${m.search.derivation_artifacts_building} building`);
|
|
679
|
-
const health = m.loops.map((l) =>
|
|
245
|
+
const health = m.loops.map((l) => loopHealth(l, m));
|
|
680
246
|
const clean = health.filter((h) => h.verdict === "CLEAN").length;
|
|
681
247
|
const degen = health.filter((h) => h.verdict === "DEGENERATE-WIN").length;
|
|
682
248
|
const failed = health.filter((h) => h.verdict.startsWith("FAILED")).length;
|
|
@@ -687,28 +253,28 @@ export default class DigestRender {
|
|
|
687
253
|
lines.push("");
|
|
688
254
|
lines.push(`## Workspace #${workspace.id} — ${workspace.name}`);
|
|
689
255
|
lines.push("");
|
|
690
|
-
lines.push(
|
|
691
|
-
const
|
|
692
|
-
for (const
|
|
256
|
+
lines.push(cacheLine(m.requestsByWorkspace.get(workspace.id) ?? [], m));
|
|
257
|
+
const workspaceSessions = m.sessionsByWorkspace.get(workspace.id) ?? [];
|
|
258
|
+
for (const session of workspaceSessions) {
|
|
693
259
|
lines.push("");
|
|
694
|
-
lines.push(`###
|
|
695
|
-
lines.push(`Owner: ${
|
|
260
|
+
lines.push(`### Session #${session.id} — ${session.name}`);
|
|
261
|
+
lines.push(`Owner: ${session.owner}`);
|
|
696
262
|
lines.push("");
|
|
697
263
|
lines.push("```");
|
|
698
|
-
lines.push(DigestRender.#
|
|
264
|
+
lines.push(DigestRender.#renderSessionShape(session, m));
|
|
699
265
|
lines.push("```");
|
|
700
|
-
const
|
|
701
|
-
for (const loop of
|
|
702
|
-
const terminal =
|
|
266
|
+
const sessionLoops = m.loopsBySession.get(session.id) ?? [];
|
|
267
|
+
for (const loop of sessionLoops) {
|
|
268
|
+
const terminal = terminalResult(loop);
|
|
703
269
|
lines.push("");
|
|
704
|
-
const h =
|
|
270
|
+
const h = loopHealth(loop, m);
|
|
705
271
|
const badge = h.verdict === "CLEAN"
|
|
706
272
|
? " — CLEAN"
|
|
707
273
|
: ` — ${h.verdict === "DEGENERATE-WIN" ? "⚠ DEGENERATE-WIN" : h.verdict} (${h.errors} errors, ${h.errorItems} error-items)`;
|
|
708
274
|
lines.push(`#### Loop ${loop.sequence} (id=${loop.id}, status=${loop.status})${badge}`);
|
|
709
275
|
lines.push("");
|
|
710
276
|
if (loop.prompt.includes("\n")) {
|
|
711
|
-
lines.push("Prompt:\n" +
|
|
277
|
+
lines.push("Prompt:\n" + indentLines(loop.prompt.trimEnd(), " "));
|
|
712
278
|
}
|
|
713
279
|
else {
|
|
714
280
|
lines.push(`Prompt: ${loop.prompt}`);
|
|
@@ -722,12 +288,12 @@ export default class DigestRender {
|
|
|
722
288
|
lines.push("");
|
|
723
289
|
lines.push("| Turn | Packet artifacts | Physical requests |", "| --- | --- | --- |");
|
|
724
290
|
for (const turn of m.turnsByLoop.get(loop.id) ?? []) {
|
|
725
|
-
const requests = (m.requestsByTurn.get(turn.id) ?? []).map((
|
|
726
|
-
lines.push(`| ${turn.sequence} (${turn.kind}) | ${
|
|
291
|
+
const requests = (m.requestsByTurn.get(turn.id) ?? []).map((req) => link("digest.md", `requests/${req.id}.md`, `${req.id}: ${req.kind} ${req.outcome ?? req.state}`));
|
|
292
|
+
lines.push(`| ${turn.sequence} (${turn.kind}) | ${turnLinks(m, turn, "digest.md").join(" · ") || "—"} | ${requests.join(" · ") || "—"} |`);
|
|
727
293
|
}
|
|
728
294
|
lines.push("");
|
|
729
295
|
const turnLines = (m.turnsByLoop.get(loop.id) ?? []).map((t) => DigestRender.#renderTurnLine(t, m));
|
|
730
|
-
lines.push(
|
|
296
|
+
lines.push(literal(turnLines.join("\n"), ""));
|
|
731
297
|
}
|
|
732
298
|
}
|
|
733
299
|
}
|
|
@@ -735,20 +301,20 @@ export default class DigestRender {
|
|
|
735
301
|
}
|
|
736
302
|
static reasoning(m) {
|
|
737
303
|
const lines = [];
|
|
738
|
-
lines.push(
|
|
304
|
+
lines.push("# plurnk-service reasoning");
|
|
739
305
|
lines.push("");
|
|
740
306
|
lines.push("Turn chronology with every provider attempt. Rejected attempts remain explicit forensic evidence.");
|
|
741
307
|
for (const t of m.turns) {
|
|
742
308
|
const loop = m.loopsById.get(t.loop_id);
|
|
743
|
-
const
|
|
309
|
+
const session = loop ? m.sessionsById.get(loop.session_id) : undefined;
|
|
744
310
|
lines.push("");
|
|
745
|
-
lines.push(`##
|
|
311
|
+
lines.push(`## Session ${session?.id ?? "?"} / Loop ${loop?.sequence ?? "?"} / Turn ${t.sequence} (id=${t.id}, producer=${t.producer}, kind=${t.kind})`);
|
|
746
312
|
if (t.kind !== "inference") {
|
|
747
313
|
lines.push("");
|
|
748
314
|
lines.push("(operation turn; no provider inference)");
|
|
749
315
|
const reasoning = m.evidence.reasoning(t);
|
|
750
316
|
if (reasoning !== null)
|
|
751
|
-
lines.push("",
|
|
317
|
+
lines.push("", literal(reasoning));
|
|
752
318
|
continue;
|
|
753
319
|
}
|
|
754
320
|
const attempts = m.attemptsByTurn.get(t.id) ?? [];
|
|
@@ -761,16 +327,16 @@ export default class DigestRender {
|
|
|
761
327
|
if (packetFailure !== null)
|
|
762
328
|
lines.push("(stored provider packet is invalid; see its packet artifacts)");
|
|
763
329
|
else if (typeof reasoning === "string" && reasoning.length > 0)
|
|
764
|
-
lines.push(
|
|
330
|
+
lines.push(literal(reasoning));
|
|
765
331
|
else
|
|
766
332
|
lines.push("(no admitted provider reasoning)");
|
|
767
333
|
continue;
|
|
768
334
|
}
|
|
769
335
|
for (const attempt of attempts) {
|
|
770
336
|
const retainedResponse = m.evidence.response(attempt.model_call_id);
|
|
771
|
-
const response =
|
|
337
|
+
const response = parseJson(retainedResponse, {});
|
|
772
338
|
const assistant = isRecord(response) && isRecord(response.assistant) ? response.assistant : null;
|
|
773
|
-
const parsedErrors =
|
|
339
|
+
const parsedErrors = parseJson(attempt.parse_errors, []);
|
|
774
340
|
const parseErrors = Array.isArray(parsedErrors) ? parsedErrors : [];
|
|
775
341
|
lines.push("");
|
|
776
342
|
const disposition = attempt.state === "error"
|
|
@@ -781,14 +347,14 @@ export default class DigestRender {
|
|
|
781
347
|
? "admitted"
|
|
782
348
|
: "rejected";
|
|
783
349
|
lines.push(`### Attempt ${attempt.sequence} - ${disposition}`);
|
|
784
|
-
const parsedAttributions =
|
|
350
|
+
const parsedAttributions = parseJson(attempt.attributions, []);
|
|
785
351
|
const attributions = Array.isArray(parsedAttributions) ? parsedAttributions : [];
|
|
786
352
|
lines.push(`Attributions: ${attributions.length === 0 ? "(none)" : attributions.join(", ")}`);
|
|
787
353
|
if (attempt.failure !== null) {
|
|
788
|
-
lines.push(`Failure: ${JSON.stringify(
|
|
354
|
+
lines.push(`Failure: ${JSON.stringify(parseJson(attempt.failure, attempt.failure))}`);
|
|
789
355
|
}
|
|
790
|
-
for (const
|
|
791
|
-
lines.push(`Physical request ${
|
|
356
|
+
for (const req of m.requestsByAttempt.get(attempt.id) ?? []) {
|
|
357
|
+
lines.push(`Physical request ${req.sequence}: [${req.state === "settled" ? req.outcome : req.state} evidence](requests/${req.id}.md)`);
|
|
792
358
|
}
|
|
793
359
|
if (attempt.accepted !== 1) {
|
|
794
360
|
for (const error of parseErrors) {
|
|
@@ -800,11 +366,11 @@ export default class DigestRender {
|
|
|
800
366
|
}
|
|
801
367
|
const reasoning = assistant?.reasoning ?? null;
|
|
802
368
|
if (typeof reasoning === "string" && reasoning.length > 0)
|
|
803
|
-
lines.push(
|
|
369
|
+
lines.push(literal(reasoning));
|
|
804
370
|
else if (retainedResponse === null && attempt.state === "response")
|
|
805
371
|
lines.push("(response evidence is not retained; readable reasoning availability is unknown)");
|
|
806
372
|
else {
|
|
807
|
-
const reasoningTokens =
|
|
373
|
+
const reasoningTokens = accounting(m.requestsByAttempt.get(attempt.id) ?? [])?.usage?.outputTokenDetails?.reasoningTokens;
|
|
808
374
|
lines.push(reasoningTokens !== undefined && reasoningTokens > 0
|
|
809
375
|
? `(provider reported ${reasoningTokens} reasoning tokens; no readable reasoning content returned)`
|
|
810
376
|
: attempt.state === "error" ? "(no admitted reasoning; partial output, when available, is in physical-request evidence)"
|
|
@@ -814,443 +380,5 @@ export default class DigestRender {
|
|
|
814
380
|
}
|
|
815
381
|
return lines.join("\n");
|
|
816
382
|
}
|
|
817
|
-
// {§share-packet-names}: render the owned envelope, never infer roles from filenames or body text.
|
|
818
|
-
static request(messages, preamble = []) {
|
|
819
|
-
const blocks = messages.map(({ role, content }, index) => {
|
|
820
|
-
return `### ${index + 1}. ${role}\n\n${DigestRender.#literal(content)}`;
|
|
821
|
-
});
|
|
822
|
-
return ["# Request", ...preamble, "## Input messages", messages.map(({ role }) => role).join(" → "),
|
|
823
|
-
messages.length === 0 ? "No input message sections are retained; the original input cannot be reconstructed."
|
|
824
|
-
: "Stored text-message envelope, not a transport capture. Native payloads and provider transformations are not shown.",
|
|
825
|
-
...blocks, ""].join("\n\n");
|
|
826
|
-
}
|
|
827
|
-
// {§digest-navigation}: navigation surrounds evidence; channel files stay verbatim.
|
|
828
|
-
static #requestPreamble(m, turn) {
|
|
829
|
-
const stems = DigestRender.packetStems(m);
|
|
830
|
-
const stem = stems.get(turn.id);
|
|
831
|
-
const from = `${stem}.request.md`;
|
|
832
|
-
const loop = m.loopsById.get(turn.loop_id);
|
|
833
|
-
const siblings = m.turns.filter((row) => row.has_packet === 1
|
|
834
|
-
&& m.loopsById.get(row.loop_id)?.worker_id === loop.worker_id).toSorted((a, b) => a.id - b.id);
|
|
835
|
-
const index = siblings.findIndex(({ id }) => id === turn.id);
|
|
836
|
-
const previous = siblings[index - 1];
|
|
837
|
-
const next = siblings[index + 1];
|
|
838
|
-
const navigation = [
|
|
839
|
-
DigestRender.#link(from, "digest.md", "Digest"),
|
|
840
|
-
...(previous === undefined ? [] : [DigestRender.#link(from, `${stems.get(previous.id)}.request.md`, "Previous")]),
|
|
841
|
-
...(next === undefined ? [] : [DigestRender.#link(from, `${stems.get(next.id)}.request.md`, "Next")]),
|
|
842
|
-
...DigestRender.#turnLinks(m, turn, from).filter((link) => !link.startsWith("[Input]")),
|
|
843
|
-
];
|
|
844
|
-
const lines = [posix.basename(stem), navigation.join(" · ")];
|
|
845
|
-
const requests = m.requestsByTurn.get(turn.id) ?? [];
|
|
846
|
-
if (requests.length > 0) {
|
|
847
|
-
const table = ["| Request | Call | Admission | Outcome |", "| --- | --- | --- | --- |"];
|
|
848
|
-
for (const request of requests) {
|
|
849
|
-
table.push(`| ${DigestRender.#link(from, `requests/${request.id}.md`, String(request.id))} | ${request.kind} ${request.inference_call_id} | ${DigestRender.#admission(m, request)} | ${request.outcome ?? request.state} |`);
|
|
850
|
-
}
|
|
851
|
-
lines.push("## Attempts", table.join("\n"));
|
|
852
|
-
}
|
|
853
|
-
for (const attempt of m.attemptsByTurn.get(turn.id) ?? []) {
|
|
854
|
-
if (attempt.accepted === 1)
|
|
855
|
-
continue;
|
|
856
|
-
const ordinal = String(attempt.sequence).padStart(3, "0");
|
|
857
|
-
const prefix = `${stem}.attempt${ordinal}`;
|
|
858
|
-
if (attempt.state !== "response") {
|
|
859
|
-
lines.push(`### Emission attempt ${attempt.sequence}: ${attempt.state}`, DigestRender.#link(from, `${prefix}.${attempt.state}.json`, "Call evidence"));
|
|
860
|
-
if (attempt.failure !== null)
|
|
861
|
-
lines.push(DigestRender.#literal(attempt.failure, "json"));
|
|
862
|
-
continue;
|
|
863
|
-
}
|
|
864
|
-
const admission = attempt.accepted === 0 ? "rejected" : "unadmitted";
|
|
865
|
-
lines.push(`### Emission attempt ${attempt.sequence}: ${admission}`, m.evidence.response(attempt.model_call_id) === null ? "Response evidence is not retained."
|
|
866
|
-
: DigestRender.#link(from, `${prefix}.${admission}.assistant.md`, "Exact output"), DigestRender.#literal(JSON.stringify(DigestRender.parseJson(attempt.parse_errors, []), null, 2), "json"));
|
|
867
|
-
}
|
|
868
|
-
return lines;
|
|
869
|
-
}
|
|
870
|
-
static #admission(m, request) {
|
|
871
|
-
if (request.kind === "bare")
|
|
872
|
-
return "not an emission";
|
|
873
|
-
if (request.outcome !== "response")
|
|
874
|
-
return "not admitted";
|
|
875
|
-
const call = m.modelCalls.find(({ id }) => id === request.inference_call_id);
|
|
876
|
-
return call?.accepted === 1 ? "admitted" : call?.accepted === 0 ? "rejected" : "not admitted";
|
|
877
|
-
}
|
|
878
|
-
static #physicalRequest(m, request, evidence) {
|
|
879
|
-
const from = `requests/${request.id}.md`;
|
|
880
|
-
const turn = m.turns.find(({ id }) => id === request.turn_id);
|
|
881
|
-
const fields = isRecord(evidence) ? evidence : {};
|
|
882
|
-
const capture = isRecord(fields.request) ? fields.request : undefined;
|
|
883
|
-
const lines = [
|
|
884
|
-
`# Physical request ${request.id}`,
|
|
885
|
-
[DigestRender.#link(from, "digest.md", "Digest"),
|
|
886
|
-
DigestRender.#link(from, `requests/${request.id}.json`, "Complete evidence")].join(" · "),
|
|
887
|
-
...(turn === undefined ? [] : [`Originating turn: ${DigestRender.#turnLinks(m, turn, from).join(" · ") || "no packet artifacts retained"}`]),
|
|
888
|
-
DigestRender.#literal(JSON.stringify({
|
|
889
|
-
provider: request.provider, model: request.model, kind: request.kind,
|
|
890
|
-
inferenceCallId: request.inference_call_id, sequence: request.sequence,
|
|
891
|
-
state: request.state, outcome: request.outcome, admission: DigestRender.#admission(m, request),
|
|
892
|
-
startedAt: request.started_at, completedAt: request.completed_at,
|
|
893
|
-
...(request.payloads_retired_at == null ? {} : { payloadsRetiredAt: request.payloads_retired_at }),
|
|
894
|
-
}, null, 2), "json"),
|
|
895
|
-
"## Accounting",
|
|
896
|
-
request.state === "pending" ? "Request is unsettled; accounting is not yet available."
|
|
897
|
-
: DigestRender.#literal(JSON.stringify(DigestRender.#requestAccounting(request), null, 2), "json"),
|
|
898
|
-
"## Dispatched request",
|
|
899
|
-
typeof capture?.body !== "string" ? "Dispatched request body was not retained."
|
|
900
|
-
: `${String(capture.method)} ${String(capture.origin)}\n\nExact serialized body; URL details and request headers are not captured.\n\n${DigestRender.#literal(capture.body, "json")}`,
|
|
901
|
-
];
|
|
902
|
-
const call = m.modelCalls.find(({ id }) => id === request.inference_call_id);
|
|
903
|
-
if (request.outcome === "response" && call?.accepted === 0) {
|
|
904
|
-
lines.push("## Admission", "The provider returned a response, but Plurnk rejected its emission.", DigestRender.#literal(JSON.stringify(DigestRender.parseJson(call.parse_errors, []), null, 2), "json"));
|
|
905
|
-
}
|
|
906
|
-
if (fields.error !== undefined)
|
|
907
|
-
lines.push("## Failure", DigestRender.#literal(JSON.stringify(fields.error, null, 2), "json"));
|
|
908
|
-
if (evidence === null)
|
|
909
|
-
lines.push("## Response", request.payloads_retired_at == null
|
|
910
|
-
? "No settled response evidence is retained."
|
|
911
|
-
: `Payload retention applied at ${request.payloads_retired_at}; request identity and accounting remain.`);
|
|
912
|
-
else {
|
|
913
|
-
if (request.outcome === "error")
|
|
914
|
-
lines.push("Partial output; this physical request failed. It is not an admitted response.");
|
|
915
|
-
for (const [field, heading] of [["reasoning", "Reasoning"], ["content", "Content"]]) {
|
|
916
|
-
lines.push(`## ${heading}`, typeof fields[field] === "string"
|
|
917
|
-
? DigestRender.#literal(fields[field]) : `No readable ${field} is retained in this request record.`);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
return `${lines.join("\n\n")}\n`;
|
|
921
|
-
}
|
|
922
|
-
// Per-turn forensic files. turnOps is the source authority; PacketWire
|
|
923
|
-
// reproduces provider request slots, and assistantRaw preserves provider bytes.
|
|
924
|
-
// {§share-packet-names}: the stem each turn's packet files carry is its log coordinate, worker, loop and
|
|
925
|
-
// turn, as `log:///<loop>/<turn>/…` addresses it; a digest spanning workspaces nests one folder per
|
|
926
|
-
// workspace. Shared with the turn lines so a reader never counts files by hand.
|
|
927
|
-
static #stemCache = new WeakMap();
|
|
928
|
-
static packetStems(m) {
|
|
929
|
-
const cached = DigestRender.#stemCache.get(m);
|
|
930
|
-
if (cached !== undefined)
|
|
931
|
-
return cached;
|
|
932
|
-
const nested = m.workspaces.length > 1;
|
|
933
|
-
// {§share-packet-names} — a workspace is commonly named by a path (`~/ptl/x`), which cannot name a
|
|
934
|
-
// file: the stem carries a slug of the name (#1001), the digest text keeps the name verbatim, and two
|
|
935
|
-
// names that slug alike are told apart by the row's id.
|
|
936
|
-
const slugged = new Map();
|
|
937
|
-
const segment = (name, id) => {
|
|
938
|
-
const slug = name.replace(/^~\//u, "").replace(/[^A-Za-z0-9_.-]+/gu, "-").replace(/^[^A-Za-z0-9_]+/u, "").replace(/-+$/u, "");
|
|
939
|
-
const base = slug.length === 0 ? String(id) : slug;
|
|
940
|
-
const owner = slugged.get(base);
|
|
941
|
-
if (owner === undefined) {
|
|
942
|
-
slugged.set(base, id);
|
|
943
|
-
return base;
|
|
944
|
-
}
|
|
945
|
-
return owner === id ? base : `${base}-${id}`;
|
|
946
|
-
};
|
|
947
|
-
const stems = new Map();
|
|
948
|
-
const taken = new Set();
|
|
949
|
-
for (const turn of m.turns.filter((row) => row.has_packet === 1 || row.program !== null || row.has_reasoning === 1).toSorted((a, b) => a.id - b.id)) {
|
|
950
|
-
const loop = m.loopsById.get(turn.loop_id);
|
|
951
|
-
const worker = loop === undefined ? undefined : m.workersById.get(loop.worker_id);
|
|
952
|
-
if (loop === undefined || worker === undefined)
|
|
953
|
-
throw new TypeError(`turn ${turn.id} has no loop or worker in scope`);
|
|
954
|
-
const workspace = m.workspaces.find(({ id }) => id === worker.workspace_id);
|
|
955
|
-
const stem = `${nested ? `${segment(workspace?.name ?? String(worker.workspace_id), worker.workspace_id)}/` : ""}${segment(worker.name, worker.id)}-${loop.sequence}-${turn.sequence}`;
|
|
956
|
-
if (taken.has(stem))
|
|
957
|
-
throw new TypeError(`two turns share the packet name ${stem}`);
|
|
958
|
-
taken.add(stem);
|
|
959
|
-
stems.set(turn.id, stem);
|
|
960
|
-
}
|
|
961
|
-
DigestRender.#stemCache.set(m, stems);
|
|
962
|
-
return stems;
|
|
963
|
-
}
|
|
964
|
-
// Among its loop's model turns, this one's ordinal: the model's own count, which skips the
|
|
965
|
-
// harness turns a reader would otherwise have to subtract.
|
|
966
|
-
static #modelTurnOrdinal(turn, m) {
|
|
967
|
-
if (turn.producer !== "model")
|
|
968
|
-
return null;
|
|
969
|
-
const siblings = (m.turnsByLoop.get(turn.loop_id) ?? []).filter((t) => t.producer === "model");
|
|
970
|
-
const index = siblings.findIndex((t) => t.id === turn.id);
|
|
971
|
-
return index < 0 ? null : index + 1;
|
|
972
|
-
}
|
|
973
|
-
static packetFiles(m) {
|
|
974
|
-
const written = [];
|
|
975
|
-
const stems = DigestRender.packetStems(m);
|
|
976
|
-
const write = (file, body) => {
|
|
977
|
-
mkdirSync(dirname(join(m.digestDir, file)), { recursive: true });
|
|
978
|
-
writeFileSync(join(m.digestDir, file), body);
|
|
979
|
-
written.push(file);
|
|
980
|
-
};
|
|
981
|
-
m.turns
|
|
982
|
-
.map((turn) => ({ turn, source: turn.program }))
|
|
983
|
-
.filter(({ turn, source }) => turn.has_packet === 1 || source !== null || turn.has_reasoning === 1)
|
|
984
|
-
.toSorted((a, b) => a.turn.id - b.turn.id)
|
|
985
|
-
.forEach(({ turn, source }) => {
|
|
986
|
-
const padded = stems.get(turn.id);
|
|
987
|
-
const files = [];
|
|
988
|
-
const { packet, packetFailure } = m.evidence.packet(turn);
|
|
989
|
-
const preamble = turn.has_packet === 1 ? DigestRender.#requestPreamble(m, turn) : [];
|
|
990
|
-
if (packetFailure !== null) {
|
|
991
|
-
files.push([`${padded}.request.md`, ["# Request", ...preamble, "Stored packet is invalid; no input messages were reconstructed.",
|
|
992
|
-
DigestRender.#literal(JSON.stringify(packetFailure.error, null, 2), "json"),
|
|
993
|
-
DigestRender.#link(`${padded}.request.md`, `${padded}.packet.raw.txt`, "Stored packet")].join("\n\n")], [`${padded}.packet.raw.txt`, packetFailure.raw], [`${padded}.packet.invalid.json`, JSON.stringify({
|
|
994
|
-
turnId: turn.id,
|
|
995
|
-
error: packetFailure.error,
|
|
996
|
-
}, null, 2)]);
|
|
997
|
-
}
|
|
998
|
-
if (packet !== null) {
|
|
999
|
-
// {§packet-wire-envelope} — the exact text messages the request carried; a stored packet
|
|
1000
|
-
// whose log cannot be projected is evidence of its own, never a reason to stop the digest.
|
|
1001
|
-
try {
|
|
1002
|
-
const messages = packet.messages();
|
|
1003
|
-
if (messages.length > 0)
|
|
1004
|
-
files.push([`${padded}.system.md`, packet.slot("system")], [`${padded}.user.md`, packet.slot("user")], [`${padded}.wire.json`, JSON.stringify(messages, null, 2)]);
|
|
1005
|
-
files.push([`${padded}.request.md`, DigestRender.request(messages, [
|
|
1006
|
-
...preamble,
|
|
1007
|
-
...(packet.attachments.length === 0 ? [] : ["## Native attachments", "Stored descriptors; selection alone does not prove transport or provider acceptance.", DigestRender.#literal(JSON.stringify(packet.attachments, null, 2), "json")]),
|
|
1008
|
-
])]);
|
|
1009
|
-
}
|
|
1010
|
-
catch (cause) {
|
|
1011
|
-
files.push([`${padded}.wire.invalid.json`, JSON.stringify({ turnId: turn.id, error: cause instanceof Error ? cause.message : String(cause) }, null, 2)]);
|
|
1012
|
-
files.push([`${padded}.request.md`, ["# Request", ...preamble,
|
|
1013
|
-
"Stored input could not be projected; no message order was reconstructed.",
|
|
1014
|
-
DigestRender.#link(`${padded}.request.md`, `${padded}.wire.invalid.json`, "Projection failure")].join("\n\n")]);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
if (source !== null) {
|
|
1018
|
-
files.push([`${padded}.assistant.md`, source]);
|
|
1019
|
-
}
|
|
1020
|
-
const reasoning = m.evidence.reasoning(turn);
|
|
1021
|
-
if (reasoning !== null)
|
|
1022
|
-
files.push([`${padded}.reasoning.md`, reasoning]);
|
|
1023
|
-
if (packet?.assistant != null) {
|
|
1024
|
-
if (source !== null && packet.assistant.content !== source) {
|
|
1025
|
-
throw new TypeError(`turn ${turn.id} packet assistant differs from its turnOps source`);
|
|
1026
|
-
}
|
|
1027
|
-
files.push([`${padded}.assistantRaw.json`, JSON.stringify(packet.assistantRaw, null, 2)]);
|
|
1028
|
-
}
|
|
1029
|
-
else if (packet !== null) {
|
|
1030
|
-
files.push([
|
|
1031
|
-
`${padded}.response.md`,
|
|
1032
|
-
`# ${padded} — request only\n\nNo provider response was admitted. Rejected attempt evidence, when present, is written separately.\n`,
|
|
1033
|
-
]);
|
|
1034
|
-
}
|
|
1035
|
-
for (const [file, body] of files)
|
|
1036
|
-
write(file, body);
|
|
1037
|
-
for (const attempt of m.attemptsByTurn.get(turn.id) ?? []) {
|
|
1038
|
-
if (attempt.accepted === 1)
|
|
1039
|
-
continue;
|
|
1040
|
-
const attemptPadded = String(attempt.sequence).padStart(3, "0");
|
|
1041
|
-
if (attempt.state !== "response") {
|
|
1042
|
-
write(`${padded}.attempt${attemptPadded}.${attempt.state}.json`, JSON.stringify({
|
|
1043
|
-
state: attempt.state,
|
|
1044
|
-
failure: DigestRender.parseJson(attempt.failure),
|
|
1045
|
-
attributions: DigestRender.parseJson(attempt.attributions, []),
|
|
1046
|
-
openedAt: attempt.timestamp,
|
|
1047
|
-
completedAt: attempt.completed_at,
|
|
1048
|
-
}, null, 2));
|
|
1049
|
-
continue;
|
|
1050
|
-
}
|
|
1051
|
-
const retainedResponse = m.evidence.response(attempt.model_call_id);
|
|
1052
|
-
const admission = attempt.accepted === 0 ? "rejected" : "unadmitted";
|
|
1053
|
-
const prefix = `${padded}.attempt${attemptPadded}.${admission}`;
|
|
1054
|
-
write(`${prefix}.parse-errors.json`, JSON.stringify(DigestRender.parseJson(attempt.parse_errors, []), null, 2));
|
|
1055
|
-
write(`${prefix}.attributions.json`, JSON.stringify(DigestRender.parseJson(attempt.attributions, []), null, 2));
|
|
1056
|
-
if (retainedResponse === null) {
|
|
1057
|
-
write(`${prefix}.response.md`, `# Emission attempt ${attempt.sequence}: ${admission}\n\nResponse evidence is not retained.\n`);
|
|
1058
|
-
continue;
|
|
1059
|
-
}
|
|
1060
|
-
const response = DigestRender.parseJson(retainedResponse, {});
|
|
1061
|
-
const assistant = isRecord(response) && isRecord(response.assistant) ? response.assistant : null;
|
|
1062
|
-
const attemptFiles = [
|
|
1063
|
-
[
|
|
1064
|
-
`${prefix}.assistant.md`,
|
|
1065
|
-
typeof assistant?.content === "string" ? assistant.content : "",
|
|
1066
|
-
],
|
|
1067
|
-
[`${prefix}.response.json`, JSON.stringify(response, null, 2)],
|
|
1068
|
-
];
|
|
1069
|
-
for (const [file, body] of attemptFiles)
|
|
1070
|
-
write(file, body);
|
|
1071
|
-
}
|
|
1072
|
-
});
|
|
1073
|
-
for (const request of m.providerRequests) {
|
|
1074
|
-
const evidence = DigestRender.parseJson(m.evidence.request(request.id));
|
|
1075
|
-
write(`requests/${request.id}.md`, DigestRender.#physicalRequest(m, request, evidence));
|
|
1076
|
-
write(`requests/${request.id}.json`, JSON.stringify({
|
|
1077
|
-
id: request.id,
|
|
1078
|
-
inferenceCallId: request.inference_call_id,
|
|
1079
|
-
sequence: request.sequence,
|
|
1080
|
-
provider: request.provider,
|
|
1081
|
-
model: request.model,
|
|
1082
|
-
state: request.state,
|
|
1083
|
-
startedAt: request.started_at,
|
|
1084
|
-
completedAt: request.completed_at,
|
|
1085
|
-
accounting: request.state === "settled" ? DigestRender.#requestAccounting(request) : null,
|
|
1086
|
-
evidence,
|
|
1087
|
-
}, null, 2));
|
|
1088
|
-
}
|
|
1089
|
-
return written;
|
|
1090
|
-
}
|
|
1091
|
-
static *json(m) {
|
|
1092
|
-
const fields = {
|
|
1093
|
-
dbPath: m.dbPath,
|
|
1094
|
-
storage: m.storage,
|
|
1095
|
-
search: m.search,
|
|
1096
|
-
workspaces: m.workspaces.map((s) => ({
|
|
1097
|
-
id: s.id,
|
|
1098
|
-
name: s.name,
|
|
1099
|
-
accounting: DigestRender.#accounting(m.requestsByWorkspace.get(s.id) ?? []),
|
|
1100
|
-
})),
|
|
1101
|
-
workers: m.workers.map((r) => ({
|
|
1102
|
-
id: r.id,
|
|
1103
|
-
workspace_id: r.workspace_id,
|
|
1104
|
-
name: r.name,
|
|
1105
|
-
owner: r.owner,
|
|
1106
|
-
accounting: DigestRender.#accounting(m.requestsByWorker.get(r.id) ?? []),
|
|
1107
|
-
edit_census: DigestRender.#editCensus(m).byWorker.get(r.id) ?? null,
|
|
1108
|
-
})),
|
|
1109
|
-
loops: m.loops.map((l) => ({
|
|
1110
|
-
id: l.id, worker_id: l.worker_id, sequence: l.sequence, status: l.status,
|
|
1111
|
-
prompt: l.prompt,
|
|
1112
|
-
terminated_by: l.terminated_by,
|
|
1113
|
-
claimed_at: l.claimed_at,
|
|
1114
|
-
terminated_at: l.terminated_at,
|
|
1115
|
-
result: DigestRender.#terminalResult(l),
|
|
1116
|
-
accounting: DigestRender.#accounting(m.requestsByLoop.get(l.id) ?? []),
|
|
1117
|
-
})),
|
|
1118
|
-
turns: projectRows(m.turns, (t) => {
|
|
1119
|
-
const { packet, packetFailure } = m.evidence.packet(t);
|
|
1120
|
-
return {
|
|
1121
|
-
id: t.id, loop_id: t.loop_id, sequence: t.sequence,
|
|
1122
|
-
// {§share-packet-names}: the stem of this turn's packet files, or null when it wrote none.
|
|
1123
|
-
artifact: DigestRender.packetStems(m).get(t.id) ?? null,
|
|
1124
|
-
producer: t.producer, kind: t.kind,
|
|
1125
|
-
program: t.program,
|
|
1126
|
-
status: t.status, completed_at: t.completed_at,
|
|
1127
|
-
accounting: DigestRender.#accounting(m.requestsByTurn.get(t.id) ?? []),
|
|
1128
|
-
finish_reason: t.finish_reason, model: t.model,
|
|
1129
|
-
attributions: packet?.attributions ?? [],
|
|
1130
|
-
attachments: packet === null ? null : packet.attachments ?? [],
|
|
1131
|
-
packet_failure: packetFailure,
|
|
1132
|
-
// Preserve the opaque provider and engine metadata for aggregate
|
|
1133
|
-
// tooling. {§meta-passthrough}, {§operator-grammar}
|
|
1134
|
-
meta: DigestRender.parseJson(t.meta ?? "null", null),
|
|
1135
|
-
};
|
|
1136
|
-
}),
|
|
1137
|
-
inference_calls: m.inferenceCalls.map((call) => ({
|
|
1138
|
-
id: call.id,
|
|
1139
|
-
workspace_id: call.workspace_id,
|
|
1140
|
-
turn_id: call.turn_id,
|
|
1141
|
-
sequence: call.sequence,
|
|
1142
|
-
kind: call.kind,
|
|
1143
|
-
state: call.state,
|
|
1144
|
-
attributions: DigestRender.parseJson(call.attributions, []),
|
|
1145
|
-
request_model: call.request_model,
|
|
1146
|
-
accounting: DigestRender.#accounting(m.requestsByInferenceCall.get(call.id) ?? []),
|
|
1147
|
-
timestamp: call.timestamp,
|
|
1148
|
-
completed_at: call.completed_at,
|
|
1149
|
-
})),
|
|
1150
|
-
model_calls: projectRows(m.modelCalls, (call) => ({
|
|
1151
|
-
id: call.id,
|
|
1152
|
-
turn_id: call.turn_id,
|
|
1153
|
-
sequence: call.sequence,
|
|
1154
|
-
kind: call.kind,
|
|
1155
|
-
state: call.state,
|
|
1156
|
-
response: DigestRender.parseJson(m.evidence.response(call.id)),
|
|
1157
|
-
failure: DigestRender.parseJson(call.failure),
|
|
1158
|
-
attributions: DigestRender.parseJson(call.attributions, []),
|
|
1159
|
-
accounting: DigestRender.#accounting(m.requestsByInferenceCall.get(call.id) ?? []),
|
|
1160
|
-
finish_reason: call.finish_reason,
|
|
1161
|
-
model: call.model,
|
|
1162
|
-
request_model: call.request_model,
|
|
1163
|
-
response_model: call.response_model,
|
|
1164
|
-
timestamp: call.timestamp,
|
|
1165
|
-
completed_at: call.completed_at,
|
|
1166
|
-
turn_attempt_id: call.turn_attempt_id,
|
|
1167
|
-
accepted: call.accepted === null ? null : call.accepted === 1,
|
|
1168
|
-
parse_errors: DigestRender.parseJson(call.parse_errors, []),
|
|
1169
|
-
log_entry_id: call.log_entry_id,
|
|
1170
|
-
})),
|
|
1171
|
-
turn_attempts: projectRows(m.turnAttempts, (attempt) => ({
|
|
1172
|
-
id: attempt.id,
|
|
1173
|
-
turn_id: attempt.turn_id,
|
|
1174
|
-
sequence: attempt.sequence,
|
|
1175
|
-
state: attempt.state,
|
|
1176
|
-
accepted: attempt.accepted === null ? null : attempt.accepted === 1,
|
|
1177
|
-
response: DigestRender.parseJson(m.evidence.response(attempt.model_call_id)),
|
|
1178
|
-
failure: DigestRender.parseJson(attempt.failure),
|
|
1179
|
-
parse_errors: DigestRender.parseJson(attempt.parse_errors, []),
|
|
1180
|
-
attributions: DigestRender.parseJson(attempt.attributions, []),
|
|
1181
|
-
accounting: DigestRender.#accounting(m.requestsByAttempt.get(attempt.id) ?? []),
|
|
1182
|
-
finish_reason: attempt.finish_reason,
|
|
1183
|
-
model: attempt.model,
|
|
1184
|
-
timestamp: attempt.timestamp,
|
|
1185
|
-
completed_at: attempt.completed_at,
|
|
1186
|
-
})),
|
|
1187
|
-
provider_requests: m.providerRequests.map((request) => ({
|
|
1188
|
-
id: request.id,
|
|
1189
|
-
evidence: `requests/${request.id}.json`,
|
|
1190
|
-
inference_call_id: request.inference_call_id,
|
|
1191
|
-
turn_attempt_id: request.turn_attempt_id,
|
|
1192
|
-
kind: request.kind,
|
|
1193
|
-
sequence: request.sequence,
|
|
1194
|
-
state: request.state,
|
|
1195
|
-
accounting: request.state === "settled"
|
|
1196
|
-
? DigestRender.#requestAccounting(request)
|
|
1197
|
-
: null,
|
|
1198
|
-
...DigestRender.cacheLedger(m).get(request.id),
|
|
1199
|
-
started_at: request.started_at,
|
|
1200
|
-
completed_at: request.completed_at,
|
|
1201
|
-
...(request.payloads_retired_at == null ? {} : { payloadsRetiredAt: request.payloads_retired_at }),
|
|
1202
|
-
})),
|
|
1203
|
-
log_entries: m.logEntries.map((le) => ({
|
|
1204
|
-
id: le.id, worker_id: le.worker_id, loop_id: le.loop_id,
|
|
1205
|
-
turn_id: le.turn_id, sequence: le.sequence, origin: le.origin,
|
|
1206
|
-
source: le.source, model_call_id: le.model_call_id,
|
|
1207
|
-
...(le.inherited_history === undefined ? {} : { inherited_history: le.inherited_history === 1 }),
|
|
1208
|
-
...(le.ambient_event_id === undefined ? {} : { ambient_event_id: le.ambient_event_id }),
|
|
1209
|
-
attrs: DigestRender.parseJson(le.attrs, {}),
|
|
1210
|
-
op: le.op, target: DigestRender.#renderTarget(le),
|
|
1211
|
-
status_rx: le.status_rx, state: le.state, outcome: le.outcome,
|
|
1212
|
-
...(le.proposalDecision === undefined ? {} : { proposalDecision: le.proposalDecision }),
|
|
1213
|
-
initial_folded: DigestRender.parseJson(le.initial_folded, []),
|
|
1214
|
-
projection: {
|
|
1215
|
-
active: le.projection_active === 1,
|
|
1216
|
-
folded: DigestRender.parseJson(le.projection_folded, []),
|
|
1217
|
-
},
|
|
1218
|
-
...(DigestRender.#renderStream(le) === null
|
|
1219
|
-
? {}
|
|
1220
|
-
: { stream: DigestRender.#renderStream(le) }),
|
|
1221
|
-
...(DigestRender.#environmentOf(le, m) === undefined ? {} : { env: DigestRender.#environmentOf(le, m) }),
|
|
1222
|
-
...(le.status_rx >= 400 ? { problem: DigestRender.#rowProblem(le) } : {}),
|
|
1223
|
-
...(DigestRender.#editCensus(m).formById.has(le.id)
|
|
1224
|
-
? { edit_form: DigestRender.#editCensus(m).formById.get(le.id), edit_revisit: DigestRender.#editCensus(m).revisitById.has(le.id) }
|
|
1225
|
-
: {}),
|
|
1226
|
-
})),
|
|
1227
|
-
log_curation_effects: m.curationEffects.map(({ active_before, active_after, folded_before, folded_after, ...effect }) => ({
|
|
1228
|
-
...effect,
|
|
1229
|
-
active_before: active_before === 1,
|
|
1230
|
-
active_after: active_after === 1,
|
|
1231
|
-
folded_before: DigestRender.parseJson(folded_before, []),
|
|
1232
|
-
folded_after: DigestRender.parseJson(folded_after, []),
|
|
1233
|
-
})),
|
|
1234
|
-
};
|
|
1235
|
-
yield "{";
|
|
1236
|
-
let fieldSeparator = "";
|
|
1237
|
-
for (const [name, value] of Object.entries(fields)) {
|
|
1238
|
-
yield `${fieldSeparator}\n ${JSON.stringify(name)}: `;
|
|
1239
|
-
if (typeof value === "object" && value !== null && Symbol.iterator in value) {
|
|
1240
|
-
yield "[";
|
|
1241
|
-
let itemSeparator = "";
|
|
1242
|
-
for (const row of value) {
|
|
1243
|
-
yield `${itemSeparator}\n${JSON.stringify(row, null, 2).replace(/^/gm, " ")}`;
|
|
1244
|
-
itemSeparator = ",";
|
|
1245
|
-
}
|
|
1246
|
-
yield itemSeparator === "" ? "]" : "\n ]";
|
|
1247
|
-
}
|
|
1248
|
-
else {
|
|
1249
|
-
yield JSON.stringify(value, null, 2);
|
|
1250
|
-
}
|
|
1251
|
-
fieldSeparator = ",";
|
|
1252
|
-
}
|
|
1253
|
-
yield "\n}\n";
|
|
1254
|
-
}
|
|
1255
383
|
}
|
|
1256
384
|
//# sourceMappingURL=DigestRender.js.map
|