@plurnk/plurnk-service 1.28.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.defaults +7 -18
- package/INSTALL.md +65 -15
- package/README.md +8 -5
- package/SPEC.md +217 -449
- package/dist/build-info.json +1 -1
- package/dist/content/body-preview.d.ts +5 -0
- package/dist/content/body-preview.d.ts.map +1 -1
- package/dist/content/body-preview.js +25 -0
- package/dist/content/body-preview.js.map +1 -1
- package/dist/content/line-marker.js +1 -1
- package/dist/content/line-marker.js.map +1 -1
- package/dist/content/path-mimetype.js +1 -1
- package/dist/content/path-mimetype.js.map +1 -1
- package/dist/core/AdmittedTurnExecutor.d.ts +4 -3
- package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
- package/dist/core/AdmittedTurnExecutor.js +19 -17
- package/dist/core/AdmittedTurnExecutor.js.map +1 -1
- package/dist/core/BareBatchRunner.d.ts +2 -2
- package/dist/core/BareBatchRunner.d.ts.map +1 -1
- package/dist/core/BareBatchRunner.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts +5 -0
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +5 -0
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +10 -0
- package/dist/core/DataStatementRunner.d.ts.map +1 -1
- package/dist/core/DataStatementRunner.js +8 -3
- package/dist/core/DataStatementRunner.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +2 -2
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +21 -15
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Engine.d.ts +1 -1
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +1 -1
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/ExecutorRegistry.d.ts +3 -17
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +6 -6
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/HostPaths.d.ts +2 -0
- package/dist/core/HostPaths.d.ts.map +1 -1
- package/dist/core/HostPaths.js +14 -0
- package/dist/core/HostPaths.js.map +1 -1
- package/dist/core/KillHandler.d.ts.map +1 -1
- package/dist/core/KillHandler.js +9 -3
- package/dist/core/KillHandler.js.map +1 -1
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +8 -2
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ResourceSelector.d.ts.map +1 -1
- package/dist/core/ResourceSelector.js +4 -2
- package/dist/core/ResourceSelector.js.map +1 -1
- package/dist/core/ResourceTransfers.d.ts.map +1 -1
- package/dist/core/ResourceTransfers.js +2 -2
- package/dist/core/ResourceTransfers.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +5 -4
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +9 -9
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/ToolResources.js +5 -5
- package/dist/core/ToolResources.js.map +1 -1
- package/dist/core/Turn.js +1 -1
- package/dist/core/Turn.js.map +1 -1
- package/dist/core/TurnOps.d.ts +1 -1
- package/dist/core/TurnOps.d.ts.map +1 -1
- package/dist/core/TurnOps.js +4 -12
- package/dist/core/TurnOps.js.map +1 -1
- package/dist/core/TurnRunner.d.ts +1 -2
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +11 -24
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/WorkerControlHandler.d.ts.map +1 -1
- package/dist/core/WorkerControlHandler.js +5 -1
- package/dist/core/WorkerControlHandler.js.map +1 -1
- package/dist/core/caps/DbNotifyCaps.js +2 -2
- package/dist/core/caps/DbNotifyCaps.js.map +1 -1
- package/dist/core/caps/DbProjectionCaps.js +1 -1
- package/dist/core/caps/DbProjectionCaps.js.map +1 -1
- package/dist/core/env-defaults.d.ts +2 -1
- package/dist/core/env-defaults.d.ts.map +1 -1
- package/dist/core/env-defaults.js +17 -9
- package/dist/core/env-defaults.js.map +1 -1
- package/dist/core/packet-wire.d.ts +1 -2
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +9 -6
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +7 -0
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +21 -0
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/unregistered-scheme.d.ts +4 -0
- package/dist/core/unregistered-scheme.d.ts.map +1 -0
- package/dist/core/unregistered-scheme.js +14 -0
- package/dist/core/unregistered-scheme.js.map +1 -0
- package/dist/evidence/EvidenceReader.d.ts +14 -0
- package/dist/evidence/EvidenceReader.d.ts.map +1 -0
- package/dist/evidence/EvidenceReader.js +134 -0
- package/dist/evidence/EvidenceReader.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +17 -6
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.d.ts +1 -1
- package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +11 -6
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +49 -5
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/{Skill.d.ts → ResourceTreeScheme.d.ts} +5 -6
- package/dist/schemes/ResourceTreeScheme.d.ts.map +1 -0
- package/dist/schemes/{Skill.js → ResourceTreeScheme.js} +63 -43
- package/dist/schemes/ResourceTreeScheme.js.map +1 -0
- package/dist/schemes/Worker.js +1 -1
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.js +9 -9
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +1 -1
- package/dist/schemes/_entry-find.js +5 -5
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +1 -1
- package/dist/schemes/_entry-fts.d.ts.map +1 -1
- package/dist/schemes/_entry-fts.js +7 -3
- package/dist/schemes/_entry-fts.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +3 -5
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.js +4 -4
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/server/AgentRoots.d.ts +2 -1
- package/dist/server/AgentRoots.d.ts.map +1 -1
- package/dist/server/AgentRoots.js +13 -10
- package/dist/server/AgentRoots.js.map +1 -1
- package/dist/server/ConfigurationDiagnostics.d.ts +4 -3
- package/dist/server/ConfigurationDiagnostics.d.ts.map +1 -1
- package/dist/server/ConfigurationDiagnostics.js +15 -8
- package/dist/server/ConfigurationDiagnostics.js.map +1 -1
- package/dist/server/Daemon.d.ts +13 -8
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +91 -49
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/EnvFunctionality.d.ts +2 -2
- package/dist/server/EnvFunctionality.d.ts.map +1 -1
- package/dist/server/EnvFunctionality.js +2 -2
- package/dist/server/EnvFunctionality.js.map +1 -1
- package/dist/server/Functionality.d.ts +3 -2
- package/dist/server/Functionality.d.ts.map +1 -1
- package/dist/server/Functionality.js +1 -1
- package/dist/server/Functionality.js.map +1 -1
- package/dist/server/HttpListener.d.ts +2 -0
- package/dist/server/HttpListener.d.ts.map +1 -1
- package/dist/server/HttpListener.js +17 -9
- package/dist/server/HttpListener.js.map +1 -1
- package/dist/server/MembersFunctionality.d.ts +1 -1
- package/dist/server/MembersFunctionality.d.ts.map +1 -1
- package/dist/server/MembersFunctionality.js.map +1 -1
- package/dist/server/ModuleHost.d.ts +33 -0
- package/dist/server/ModuleHost.d.ts.map +1 -0
- package/dist/server/ModuleHost.js +2 -0
- package/dist/server/ModuleHost.js.map +1 -0
- package/dist/server/PlurnkSkill.d.ts.map +1 -1
- package/dist/server/PlurnkSkill.js +54 -6
- package/dist/server/PlurnkSkill.js.map +1 -1
- package/dist/server/ServiceModules.d.ts +0 -2
- package/dist/server/ServiceModules.d.ts.map +1 -1
- package/dist/server/ServiceModules.js +5 -9
- package/dist/server/ServiceModules.js.map +1 -1
- package/dist/server/WorkspacePlugins.d.ts +2 -11
- package/dist/server/WorkspacePlugins.d.ts.map +1 -1
- package/dist/server/WorkspacePlugins.js.map +1 -1
- package/dist/server/WorkspaceResidency.d.ts +2 -1
- package/dist/server/WorkspaceResidency.d.ts.map +1 -1
- package/dist/server/WorkspaceStorage.d.ts +1 -0
- package/dist/server/WorkspaceStorage.d.ts.map +1 -1
- package/dist/server/WorkspaceStorage.js +31 -5
- package/dist/server/WorkspaceStorage.js.map +1 -1
- package/dist/server/module-discovery.d.ts +11 -3
- package/dist/server/module-discovery.d.ts.map +1 -1
- package/dist/server/module-discovery.js +32 -18
- package/dist/server/module-discovery.js.map +1 -1
- package/dist/server/workspace-storage.sql +5 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +37 -34
- package/dist/service.js.map +1 -1
- package/migrations/016_selection_statements.sql +43 -0
- package/migrations/017_skills_module.sql +5 -0
- package/package.json +71 -72
- package/dist/core/EmissionHead.d.ts +0 -8
- package/dist/core/EmissionHead.d.ts.map +0 -1
- package/dist/core/EmissionHead.js +0 -29
- package/dist/core/EmissionHead.js.map +0 -1
- package/dist/core/operation-target-groups.d.ts +0 -3
- package/dist/core/operation-target-groups.d.ts.map +0 -1
- package/dist/core/operation-target-groups.js +0 -77
- package/dist/core/operation-target-groups.js.map +0 -1
- package/dist/digest/Digest.d.ts +0 -10
- package/dist/digest/Digest.d.ts.map +0 -1
- package/dist/digest/Digest.js +0 -252
- package/dist/digest/Digest.js.map +0 -1
- package/dist/digest/DigestEvidence.d.ts +0 -11
- package/dist/digest/DigestEvidence.d.ts.map +0 -1
- package/dist/digest/DigestEvidence.js +0 -66
- package/dist/digest/DigestEvidence.js.map +0 -1
- package/dist/digest/DigestRender.d.ts +0 -18
- package/dist/digest/DigestRender.d.ts.map +0 -1
- package/dist/digest/DigestRender.js +0 -1035
- package/dist/digest/DigestRender.js.map +0 -1
- package/dist/digest/DigestRequiem.d.ts +0 -13
- package/dist/digest/DigestRequiem.d.ts.map +0 -1
- package/dist/digest/DigestRequiem.js +0 -337
- package/dist/digest/DigestRequiem.js.map +0 -1
- package/dist/digest/digest-db.d.ts +0 -3
- package/dist/digest/digest-db.d.ts.map +0 -1
- package/dist/digest/digest-db.js +0 -68
- package/dist/digest/digest-db.js.map +0 -1
- package/dist/digest/digest-paths.d.ts +0 -6
- package/dist/digest/digest-paths.d.ts.map +0 -1
- package/dist/digest/digest-paths.js +0 -14
- package/dist/digest/digest-paths.js.map +0 -1
- package/dist/digest/digest-rows.d.ts +0 -346
- package/dist/digest/digest-rows.d.ts.map +0 -1
- package/dist/digest/digest-rows.js +0 -2
- package/dist/digest/digest-rows.js.map +0 -1
- package/dist/schemes/Skill.d.ts.map +0 -1
- package/dist/schemes/Skill.js.map +0 -1
- package/dist/server/DaemonModule.d.ts +0 -101
- package/dist/server/DaemonModule.d.ts.map +0 -1
- package/dist/server/DaemonModule.js +0 -2
- package/dist/server/DaemonModule.js.map +0 -1
- package/dist/server/SkillSource.d.ts +0 -37
- package/dist/server/SkillSource.d.ts.map +0 -1
- package/dist/server/SkillSource.js +0 -267
- package/dist/server/SkillSource.js.map +0 -1
- package/dist/server/SkillsFunctionality.d.ts +0 -45
- package/dist/server/SkillsFunctionality.d.ts.map +0 -1
- package/dist/server/SkillsFunctionality.js +0 -405
- package/dist/server/SkillsFunctionality.js.map +0 -1
- package/dist/server/skills-problems.d.ts +0 -8
- package/dist/server/skills-problems.d.ts.map +0 -1
- package/dist/server/skills-problems.js +0 -18
- package/dist/server/skills-problems.js.map +0 -1
- package/dist/share/Share.d.ts +0 -15
- package/dist/share/Share.d.ts.map +0 -1
- package/dist/share/Share.js +0 -106
- package/dist/share/Share.js.map +0 -1
- package/dist/share/share.sql +0 -6
- package/docs/skills.md +0 -73
- /package/dist/{digest → evidence}/digest.sql +0 -0
|
@@ -1,1035 +0,0 @@
|
|
|
1
|
-
// Digest renderers ({§digest-programmatic-surface}): the markdown, JSON, reasoning, and packet
|
|
2
|
-
// artifacts of one DigestModel, reading heavy evidence on demand through DigestEvidence.
|
|
3
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import PacketWire from "../core/packet-wire.js";
|
|
6
|
-
import FabricatedLog from "../core/FabricatedLog.js";
|
|
7
|
-
import StoredPacket from "../core/StoredPacket.js";
|
|
8
|
-
import { contentWeight } from "../core/content-weight.js";
|
|
9
|
-
import { renderTarget } from "../core/plurnk-uri.js";
|
|
10
|
-
import { aggregateProviderAccounting, } from "@plurnk/plurnk-providers";
|
|
11
|
-
import { providerRequestFromStorageRow, } from "../core/provider-accounting.js";
|
|
12
|
-
import { Validator } from "@plurnk/plurnk-contracts";
|
|
13
|
-
import { EDIT_FORMS } from "./digest-rows.js";
|
|
14
|
-
import { isExecutionOp } from "@plurnk/plurnk-contracts";
|
|
15
|
-
function* projectRows(rows, project) {
|
|
16
|
-
for (const row of rows)
|
|
17
|
-
yield project(row);
|
|
18
|
-
}
|
|
19
|
-
export default class DigestRender {
|
|
20
|
-
static #summarize(text, n = 80) {
|
|
21
|
-
if (text === null || text === undefined)
|
|
22
|
-
return "";
|
|
23
|
-
const flat = String(text).replace(/\s+/g, " ").trim();
|
|
24
|
-
if (flat.length <= n)
|
|
25
|
-
return flat;
|
|
26
|
-
return `${flat.slice(0, n)}…`;
|
|
27
|
-
}
|
|
28
|
-
static #indentLines(text, prefix = " ") {
|
|
29
|
-
return text.split("\n").map((line) => line.length > 0 ? `${prefix}${line}` : line).join("\n");
|
|
30
|
-
}
|
|
31
|
-
static parseJson(s, fallback = null) {
|
|
32
|
-
if (s === null || s === undefined)
|
|
33
|
-
return fallback;
|
|
34
|
-
try {
|
|
35
|
-
return JSON.parse(String(s));
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return fallback;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
static #requestAccounting(row) {
|
|
42
|
-
if (row.state !== "settled" || row.outcome === null || row.cost_kind === null) {
|
|
43
|
-
throw new TypeError(`digest: provider request ${row.id} is not settled`);
|
|
44
|
-
}
|
|
45
|
-
return providerRequestFromStorageRow(row);
|
|
46
|
-
}
|
|
47
|
-
static #accounting(rows) {
|
|
48
|
-
return rows.some((row) => row.state !== "settled")
|
|
49
|
-
? null
|
|
50
|
-
: aggregateProviderAccounting(rows.map((row) => DigestRender.#requestAccounting(row)));
|
|
51
|
-
}
|
|
52
|
-
static usageSummary(accounting) {
|
|
53
|
-
if (accounting === null)
|
|
54
|
-
return "accounting=incomplete";
|
|
55
|
-
const usage = accounting.usage;
|
|
56
|
-
const known = accounting.knownUsage;
|
|
57
|
-
const quantity = (total, subtotal) => total !== undefined ? String(total) : subtotal === undefined ? "unknown" : `${subtotal}+?`;
|
|
58
|
-
return [
|
|
59
|
-
`input=${quantity(usage?.inputTokens, known?.inputTokens)}`,
|
|
60
|
-
`output=${quantity(usage?.outputTokens, known?.outputTokens)}`,
|
|
61
|
-
`reasoning=${quantity(usage?.outputTokenDetails?.reasoningTokens, known?.outputTokenDetails?.reasoningTokens)}`,
|
|
62
|
-
`cache-read=${quantity(usage?.inputTokenDetails?.cacheReadTokens, known?.inputTokenDetails?.cacheReadTokens)}`,
|
|
63
|
-
].join(" ");
|
|
64
|
-
}
|
|
65
|
-
static costSummary(accounting) {
|
|
66
|
-
if (accounting?.costUsd != null)
|
|
67
|
-
return `$${accounting.costUsd}`;
|
|
68
|
-
return accounting?.knownCostUsd == null ? "unknown" : `$${accounting.knownCostUsd} + ? (incomplete)`;
|
|
69
|
-
}
|
|
70
|
-
static #operationResult(raw, subject) {
|
|
71
|
-
try {
|
|
72
|
-
return Validator.assertOperationResult(DigestRender.parseJson(raw));
|
|
73
|
-
}
|
|
74
|
-
catch (cause) {
|
|
75
|
-
throw new Error(`digest: ${subject} does not contain a valid operation result`, { cause });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
static #rowProblem(row) {
|
|
79
|
-
const result = DigestRender.#operationResult(row.rx, `failed log entry ${row.id}`);
|
|
80
|
-
if (result.problem === undefined) {
|
|
81
|
-
throw new Error(`digest: failed log entry ${row.id} does not contain Problem Details`);
|
|
82
|
-
}
|
|
83
|
-
return result.problem;
|
|
84
|
-
}
|
|
85
|
-
static #terminalResult(loop) {
|
|
86
|
-
return loop.terminal_result === null
|
|
87
|
-
? null
|
|
88
|
-
: DigestRender.#operationResult(loop.terminal_result, `terminal loop ${loop.id}`);
|
|
89
|
-
}
|
|
90
|
-
static #renderTarget(le) {
|
|
91
|
-
return renderTarget(le);
|
|
92
|
-
}
|
|
93
|
-
static #renderStream(le) {
|
|
94
|
-
if (!isExecutionOp(le.op))
|
|
95
|
-
return null;
|
|
96
|
-
const stream = DigestRender.parseJson(le.attrs, {}).stream;
|
|
97
|
-
return typeof stream === "string" ? stream : null;
|
|
98
|
-
}
|
|
99
|
-
// {§exec-env-scoped} — the environment a spawn received, as recorded on its row: host names by
|
|
100
|
-
// name, the Worker's own values, inherited values by their source, masked names. Host values
|
|
101
|
-
// stay in digest.json. Part of the rendered line, so spawns under different environments never
|
|
102
|
-
// collapse into one group.
|
|
103
|
-
static envLine(env) {
|
|
104
|
-
if (env === undefined || env === null || typeof env !== "object")
|
|
105
|
-
return null;
|
|
106
|
-
const host = [];
|
|
107
|
-
const rest = [];
|
|
108
|
-
for (const [name, raw] of Object.entries(env).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
109
|
-
const record = raw;
|
|
110
|
-
const from = typeof record.from === "string" ? record.from : null;
|
|
111
|
-
if (record.source === "host")
|
|
112
|
-
host.push(name);
|
|
113
|
-
else if (record.source === "masked")
|
|
114
|
-
rest.push(`${name} (masked${from === null ? "" : ` by ${from}`})`);
|
|
115
|
-
else if (record.source === "modifier")
|
|
116
|
-
rest.push(`${name}=${DigestRender.#summarize(record.value, 60)} (modifier)`);
|
|
117
|
-
else if (record.source === "workspace")
|
|
118
|
-
rest.push(`${name}=${DigestRender.#summarize(record.value, 60)} (workspace)`);
|
|
119
|
-
else
|
|
120
|
-
rest.push(`${name}=${DigestRender.#summarize(record.value, 60)} (${from === null ? "worker" : `from ${from}`})`);
|
|
121
|
-
}
|
|
122
|
-
const parts = [...(host.length === 0 ? [] : [`host ${host.join(",")}`]), ...rest];
|
|
123
|
-
return `env: ${parts.length === 0 ? "(empty)" : parts.join(" · ")}`;
|
|
124
|
-
}
|
|
125
|
-
// The environment a spawn recorded on its output, found through the row's stream address.
|
|
126
|
-
static #environmentOf(le, m) {
|
|
127
|
-
const stream = DigestRender.#renderStream(le);
|
|
128
|
-
if (stream === null)
|
|
129
|
-
return undefined;
|
|
130
|
-
const worker = m.workersById.get(le.worker_id);
|
|
131
|
-
return worker === undefined ? undefined : m.environments.get(`${worker.workspace_id}:${stream}`);
|
|
132
|
-
}
|
|
133
|
-
static #renderOpLine(le, label = le.op ?? "source artifact", environment) {
|
|
134
|
-
const target = DigestRender.#renderTarget(le) ?? "—";
|
|
135
|
-
const stream = DigestRender.#renderStream(le);
|
|
136
|
-
const state = le.state !== "resolved" ? ` state=${le.state}` : "";
|
|
137
|
-
const outcome = le.outcome !== null ? ` outcome=${le.outcome}` : "";
|
|
138
|
-
const streamLink = stream === null ? "" : ` stream=${stream}`;
|
|
139
|
-
const source = le.source === null ? "" : ` source=${le.source}`;
|
|
140
|
-
const fail = le.status_rx >= 400 ? " ✗" : "";
|
|
141
|
-
// For failed outcomes, surface the Problem Details explanation from rx so
|
|
142
|
-
// the waterfall explains WHY each failure happened without opening packets.
|
|
143
|
-
let errLine = "";
|
|
144
|
-
if (le.status_rx >= 400) {
|
|
145
|
-
errLine = `\n -> ${DigestRender.#rowProblem(le).detail.trim()}`;
|
|
146
|
-
}
|
|
147
|
-
const envLine = DigestRender.envLine(environment);
|
|
148
|
-
const envText = envLine === null ? "" : `\n ${envLine}`;
|
|
149
|
-
return ` ← [${le.origin}] ${label}[${le.status_rx}] ${target}${source}${state}${outcome}${streamLink}${fail}${errLine}${envText}`;
|
|
150
|
-
}
|
|
151
|
-
static #renderGroupedOpLine(row, m) {
|
|
152
|
-
const attrs = DigestRender.parseJson(row.attrs, {});
|
|
153
|
-
// {§emission-row}: the announcement of an admitted emission, and whether the model retired it.
|
|
154
|
-
if (row.op === "READ" && attrs.kind === "emission") {
|
|
155
|
-
return DigestRender.#renderOpLine(row, row.projection_active === 1 ? "emission" : "emission (killed)", DigestRender.#environmentOf(row, m));
|
|
156
|
-
}
|
|
157
|
-
// {§reasoning-row}: the turn's reasoning landed as a row, and whether the model retired it.
|
|
158
|
-
if (row.op === "READ" && attrs.kind === "reasoning") {
|
|
159
|
-
return DigestRender.#renderOpLine(row, row.projection_active === 1 ? "reasoning" : "reasoning (killed)", DigestRender.#environmentOf(row, m));
|
|
160
|
-
}
|
|
161
|
-
const materialized = row.origin === "_plurnk" && row.op === "EDIT" && attrs.kind === "entry_materialized";
|
|
162
|
-
const actionlessKind = row.op === null ? attrs.kind : null;
|
|
163
|
-
const label = actionlessKind === "emissionAttempt"
|
|
164
|
-
? "emission attempt"
|
|
165
|
-
: row.op ?? `unrecognized actionless row (kind=${JSON.stringify(actionlessKind) ?? "absent"})`;
|
|
166
|
-
return DigestRender.#renderOpLine(row, materialized ? "materialized entry" : label, DigestRender.#environmentOf(row, m));
|
|
167
|
-
}
|
|
168
|
-
// Human triage is not a row dump. Preserve every row in digest.json, but
|
|
169
|
-
// collapse consecutive identical rendered outcomes in the Markdown waterfall. Using the
|
|
170
|
-
// rendered line itself as the key keeps actor, complete target, lifecycle,
|
|
171
|
-
// stream, and visible failure detail structurally aligned with the grouping.
|
|
172
|
-
static #renderOpLines(rows, m) {
|
|
173
|
-
const runs = [];
|
|
174
|
-
for (const row of rows) {
|
|
175
|
-
const line = DigestRender.#renderGroupedOpLine(row, m);
|
|
176
|
-
const last = runs.at(-1);
|
|
177
|
-
if (last !== undefined && last.line === line) {
|
|
178
|
-
last.count++;
|
|
179
|
-
last.lastSeq = row.sequence;
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
runs.push({ line, count: 1, firstSeq: row.sequence, lastSeq: row.sequence });
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return runs.map(({ line, count, firstSeq, lastSeq }) => {
|
|
186
|
-
return count === 1 ? line : `${line} ×${count} (seq ${firstSeq}–${lastSeq})`;
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
// The "degenerate win" lens (owner ask): a loop's health = how many errors/strikes it earned
|
|
190
|
-
// vs whether it still concluded. A green that limped across on 16 errors is a FAILING artifact
|
|
191
|
-
// wearing a passing badge; the digest must make that impossible to miss. errors = ≥400 op rows;
|
|
192
|
-
// errorItems = minted op='error' rows (truncation/budget/steer/cycle — the strike signals).
|
|
193
|
-
// {§digest-executor-evidence} (#436) — a failed command's completion rows carry the
|
|
194
|
-
// executor's problem identity; a red test run is the loop's normal work, not a
|
|
195
|
-
// defect. They stay visible but never count as errors in health or the errs
|
|
196
|
-
// badge — the digest mirror of the strike rail's exemption (#425 F1).
|
|
197
|
-
static #isExecutorEvidence(le) {
|
|
198
|
-
if (le.status_rx < 400 || le.rx === null)
|
|
199
|
-
return false;
|
|
200
|
-
const rx = DigestRender.parseJson(le.rx, null);
|
|
201
|
-
return typeof rx?.problem?.type === "string"
|
|
202
|
-
&& rx.problem.type.startsWith("https://problems.plurnk.xyz/executor/");
|
|
203
|
-
}
|
|
204
|
-
// {§digest-storage} — size, free pages, vacuum mode and the largest tables, one line.
|
|
205
|
-
static #storageLine(storage) {
|
|
206
|
-
const mb = (bytes) => `${(bytes / 1_048_576).toFixed(1)} MB`;
|
|
207
|
-
const mode = ["off", "full", "incremental"][storage.auto_vacuum] ?? String(storage.auto_vacuum);
|
|
208
|
-
return `Storage: ${mb(storage.bytes)} (free ${mb(storage.free_bytes)}, auto_vacuum ${mode}) · largest: ${storage.tables.map(({ name, bytes }) => `${name} ${mb(bytes)}`).join(", ")}`;
|
|
209
|
-
}
|
|
210
|
-
// {§loop-claim-latency} — claim to first model turn: a stall between them is a number, not a gap.
|
|
211
|
-
static #wakeLatency(loop, m) {
|
|
212
|
-
if (loop.claimed_at === null)
|
|
213
|
-
return null;
|
|
214
|
-
const first = (m.turnsByLoop.get(loop.id) ?? []).filter((t) => t.kind === "inference").sort((a, b) => a.sequence - b.sequence)[0];
|
|
215
|
-
if (first === undefined)
|
|
216
|
-
return `Claimed: ${loop.claimed_at} · no model turn`;
|
|
217
|
-
const seconds = (Date.parse(first.timestamp) - Date.parse(loop.claimed_at)) / 1000;
|
|
218
|
-
return `Claimed: ${loop.claimed_at} · first model turn +${seconds.toFixed(1)} s`;
|
|
219
|
-
}
|
|
220
|
-
// {§digest-cache-ledger} — the prompt a turn's request carried: its wire messages in order, each role
|
|
221
|
-
// above its content, the text `.wire.json` holds; null when no valid packet is stored.
|
|
222
|
-
static #promptText(turn, m) {
|
|
223
|
-
const { packet } = m.evidence.packet(turn);
|
|
224
|
-
if (packet === null)
|
|
225
|
-
return null;
|
|
226
|
-
return PacketWire.packetToWireMessages(packet, DigestRender.#emissionsOf(turn, m))
|
|
227
|
-
.map(({ role, content }) => `${role}\n${content}`)
|
|
228
|
-
.join("\n");
|
|
229
|
-
}
|
|
230
|
-
// {§emission-row} — the emissions the turn's worker announced; a stored request places those its log
|
|
231
|
-
// section carried.
|
|
232
|
-
static #emissionsOf(turn, m) {
|
|
233
|
-
const loop = m.loopsById.get(turn.loop_id);
|
|
234
|
-
if (loop === undefined)
|
|
235
|
-
throw new Error(`digest: turn ${turn.id} names loop ${turn.loop_id}, which the digest did not read`);
|
|
236
|
-
return m.emissionsByWorker.get(loop.worker_id) ?? new Map();
|
|
237
|
-
}
|
|
238
|
-
static #commonPrefixLength(left, right) {
|
|
239
|
-
const bound = Math.min(left.length, right.length);
|
|
240
|
-
let n = 0;
|
|
241
|
-
while (n < bound && left.charCodeAt(n) === right.charCodeAt(n))
|
|
242
|
-
n += 1;
|
|
243
|
-
return n;
|
|
244
|
-
}
|
|
245
|
-
// {§digest-edit-census}: the form an EDIT authored, read from its stored marker and pattern.
|
|
246
|
-
static editForm(row) {
|
|
247
|
-
if (row.pattern !== null)
|
|
248
|
-
return "pattern";
|
|
249
|
-
if (row.line_marker === null)
|
|
250
|
-
return "whole";
|
|
251
|
-
const { marks } = DigestRender.parseJson(row.line_marker, { marks: [] });
|
|
252
|
-
if (marks.length === 1) {
|
|
253
|
-
const [mark] = marks;
|
|
254
|
-
if (typeof mark === "string")
|
|
255
|
-
return "hash";
|
|
256
|
-
if (mark === -1)
|
|
257
|
-
return "append";
|
|
258
|
-
if (mark === 0)
|
|
259
|
-
return "prepend";
|
|
260
|
-
return "line";
|
|
261
|
-
}
|
|
262
|
-
if (marks.length === 2)
|
|
263
|
-
return "range";
|
|
264
|
-
if (marks.length === 3)
|
|
265
|
-
return "offset";
|
|
266
|
-
if (marks.length === 4 && marks[0] === marks[2] && marks[1] === 1 && marks[3] === 1)
|
|
267
|
-
return "insert";
|
|
268
|
-
return "column";
|
|
269
|
-
}
|
|
270
|
-
static #editCensusCache = new WeakMap();
|
|
271
|
-
// {§digest-edit-census}: per worker, every model EDIT by authored form and status, and the
|
|
272
|
-
// revisits — an EDIT of a path the same worker edited within its previous two model turns.
|
|
273
|
-
static #editCensus(m) {
|
|
274
|
-
const cached = DigestRender.#editCensusCache.get(m);
|
|
275
|
-
if (cached !== undefined)
|
|
276
|
-
return cached;
|
|
277
|
-
const loopWorker = new Map(m.loops.map((loop) => [loop.id, loop.worker_id]));
|
|
278
|
-
// A worker's model turns in order; the ordinal is the distance the revisit window counts in.
|
|
279
|
-
const ordinal = new Map();
|
|
280
|
-
const perWorker = new Map();
|
|
281
|
-
for (const turn of [...m.turns].sort((a, b) => a.id - b.id)) {
|
|
282
|
-
if (turn.producer !== "model")
|
|
283
|
-
continue;
|
|
284
|
-
const workerId = loopWorker.get(turn.loop_id);
|
|
285
|
-
if (workerId === undefined)
|
|
286
|
-
throw new TypeError(`digest: turn ${turn.id} has no loop in scope`);
|
|
287
|
-
const next = (perWorker.get(workerId) ?? 0) + 1;
|
|
288
|
-
perWorker.set(workerId, next);
|
|
289
|
-
ordinal.set(turn.id, next);
|
|
290
|
-
}
|
|
291
|
-
const byWorker = new Map();
|
|
292
|
-
const formById = new Map();
|
|
293
|
-
const revisitById = new Set();
|
|
294
|
-
for (const [workerId, rows] of m.editRowsByWorker) {
|
|
295
|
-
const census = { edits: 0, refused: 0, revisits: 0, forms: Object.fromEntries(EDIT_FORMS.map((form) => [form, 0])) };
|
|
296
|
-
const lastEdited = new Map();
|
|
297
|
-
for (const row of rows) {
|
|
298
|
-
const form = DigestRender.editForm(row);
|
|
299
|
-
formById.set(row.id, form);
|
|
300
|
-
census.edits += 1;
|
|
301
|
-
census.forms[form] += 1;
|
|
302
|
-
if (row.status_rx >= 400)
|
|
303
|
-
census.refused += 1;
|
|
304
|
-
const at = ordinal.get(row.turn_id);
|
|
305
|
-
if (at === undefined)
|
|
306
|
-
throw new TypeError(`digest: EDIT ${row.id} sits on a turn no model produced`);
|
|
307
|
-
if (row.pathname !== null) {
|
|
308
|
-
const previous = lastEdited.get(row.pathname);
|
|
309
|
-
if (previous !== undefined && previous < at && at - previous <= 2) {
|
|
310
|
-
census.revisits += 1;
|
|
311
|
-
revisitById.add(row.id);
|
|
312
|
-
}
|
|
313
|
-
lastEdited.set(row.pathname, at);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
byWorker.set(workerId, census);
|
|
317
|
-
}
|
|
318
|
-
const result = { byWorker, formById, revisitById };
|
|
319
|
-
DigestRender.#editCensusCache.set(m, result);
|
|
320
|
-
return result;
|
|
321
|
-
}
|
|
322
|
-
static #renderEditCensus(census) {
|
|
323
|
-
if (census === undefined || census.edits === 0)
|
|
324
|
-
return "(no edits)";
|
|
325
|
-
const forms = EDIT_FORMS.filter((form) => census.forms[form] > 0).map((form) => `${form}=${census.forms[form]}`).join(" ");
|
|
326
|
-
return `${census.edits} · ${forms} · refused=${census.refused} · revisits=${census.revisits}`;
|
|
327
|
-
}
|
|
328
|
-
static #cacheLedgerCache = new WeakMap();
|
|
329
|
-
// {§digest-cache-ledger} — estimate adjacent prefixes with at most two prompts resident per loop.
|
|
330
|
-
static cacheLedger(m) {
|
|
331
|
-
const cached = DigestRender.#cacheLedgerCache.get(m);
|
|
332
|
-
if (cached !== undefined)
|
|
333
|
-
return cached;
|
|
334
|
-
const turnsById = new Map(m.turns.map((turn) => [turn.id, turn]));
|
|
335
|
-
const ledger = new Map();
|
|
336
|
-
for (const loop of m.loops) {
|
|
337
|
-
let memo;
|
|
338
|
-
const promptOf = (turn) => {
|
|
339
|
-
if (memo?.turnId !== turn.id)
|
|
340
|
-
memo = { turnId: turn.id, text: DigestRender.#promptText(turn, m) };
|
|
341
|
-
return memo.text;
|
|
342
|
-
};
|
|
343
|
-
// undefined: no previous request in this loop; null: the previous request's packet is not valid.
|
|
344
|
-
let previousText;
|
|
345
|
-
for (const request of m.requestsByLoop.get(loop.id) ?? []) {
|
|
346
|
-
const turn = turnsById.get(request.turn_id);
|
|
347
|
-
if (turn === undefined)
|
|
348
|
-
throw new TypeError(`digest: provider request ${request.id} has no turn in scope`);
|
|
349
|
-
const text = request.kind === "emission" ? promptOf(turn) : null;
|
|
350
|
-
const adjacentPrefixTokensEstimate = text === null || request.usage_input === null
|
|
351
|
-
? null
|
|
352
|
-
: previousText === undefined
|
|
353
|
-
? 0
|
|
354
|
-
: previousText === null
|
|
355
|
-
? null
|
|
356
|
-
: text.length === 0
|
|
357
|
-
? 0
|
|
358
|
-
: Math.round(request.usage_input * contentWeight(text.slice(0, DigestRender.#commonPrefixLength(previousText, text))) / contentWeight(text));
|
|
359
|
-
ledger.set(request.id, {
|
|
360
|
-
adjacentPrefixTokensEstimate,
|
|
361
|
-
cachedTokens: request.usage_input_cache_read,
|
|
362
|
-
inputTokens: request.usage_input,
|
|
363
|
-
});
|
|
364
|
-
previousText = text;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
for (const request of m.providerRequests) {
|
|
368
|
-
if (!ledger.has(request.id))
|
|
369
|
-
throw new TypeError(`digest: provider request ${request.id} has no loop in scope`);
|
|
370
|
-
}
|
|
371
|
-
DigestRender.#cacheLedgerCache.set(m, ledger);
|
|
372
|
-
return ledger;
|
|
373
|
-
}
|
|
374
|
-
static #cacheEntries(requests, m) {
|
|
375
|
-
const ledger = DigestRender.cacheLedger(m);
|
|
376
|
-
return requests.map((request) => ledger.get(request.id));
|
|
377
|
-
}
|
|
378
|
-
// {§digest-cache-ledger} — sum each provider-reported counter; one absent quantity makes that sum unknown.
|
|
379
|
-
static #cacheBadge(turn, m) {
|
|
380
|
-
const entries = DigestRender.#cacheEntries(m.requestsByTurn.get(turn.id) ?? [], m);
|
|
381
|
-
if (entries.length === 0)
|
|
382
|
-
return "";
|
|
383
|
-
const sum = (values) => values.some((value) => value === null)
|
|
384
|
-
? "?"
|
|
385
|
-
: String(values.reduce((total, value) => total + value, 0));
|
|
386
|
-
return ` cache=${sum(entries.map((entry) => entry.cachedTokens))}/${sum(entries.map((entry) => entry.inputTokens))}`;
|
|
387
|
-
}
|
|
388
|
-
// {§digest-cache-ledger} — one workspace line; unreported requests are named and left out of the percentage.
|
|
389
|
-
static #cacheLine(requests, m) {
|
|
390
|
-
const entries = DigestRender.#cacheEntries(requests, m);
|
|
391
|
-
const reported = entries.filter((entry) => entry.cachedTokens !== null && entry.inputTokens !== null);
|
|
392
|
-
const unreported = entries.length - reported.length;
|
|
393
|
-
const cached = reported.reduce((total, entry) => total + entry.cachedTokens, 0);
|
|
394
|
-
const input = reported.reduce((total, entry) => total + entry.inputTokens, 0);
|
|
395
|
-
const pct = input === 0 ? "n/a" : `${((100 * cached) / input).toFixed(1)}%`;
|
|
396
|
-
const plural = (n) => n === 1 ? "request" : "requests";
|
|
397
|
-
return [
|
|
398
|
-
`Cache: ${cached} of ${input} reported input tokens read from cache (${pct}) over ${reported.length} ${plural(reported.length)}`,
|
|
399
|
-
unreported > 0 ? `${unreported} missing input or cache usage (excluded)` : null,
|
|
400
|
-
].filter((part) => part !== null).join(" · ");
|
|
401
|
-
}
|
|
402
|
-
static #loopHealth(loop, m) {
|
|
403
|
-
let errors = 0;
|
|
404
|
-
let errorItems = 0;
|
|
405
|
-
for (const t of m.turnsByLoop.get(loop.id) ?? []) {
|
|
406
|
-
for (const le of m.logEntriesByTurn.get(t.id) ?? []) {
|
|
407
|
-
if (le.status_rx >= 400 && !DigestRender.#isExecutorEvidence(le))
|
|
408
|
-
errors += 1;
|
|
409
|
-
if (le.op === "error")
|
|
410
|
-
errorItems += 1;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
const s = loop.status;
|
|
414
|
-
const verdict = s >= 400 ? `FAILED(${s})`
|
|
415
|
-
: s >= 200 && s < 300 ? (errors > 0 ? "DEGENERATE-WIN" : "CLEAN")
|
|
416
|
-
: `status=${s}`;
|
|
417
|
-
return { errors, errorItems, verdict };
|
|
418
|
-
}
|
|
419
|
-
static #renderTurnLine(turn, m) {
|
|
420
|
-
const { packet, packetFailure } = m.evidence.packet(turn);
|
|
421
|
-
const assistant = packet !== null && StoredPacket.isAdmitted(packet) ? packet.assistant : null;
|
|
422
|
-
const content = assistant?.content ?? "";
|
|
423
|
-
const reasoning = assistant?.reasoning ?? null;
|
|
424
|
-
const accounting = DigestRender.#accounting(m.requestsByTurn.get(turn.id) ?? []);
|
|
425
|
-
const tokens = DigestRender.usageSummary(accounting);
|
|
426
|
-
const cost = ` cost=${DigestRender.costSummary(accounting)}`;
|
|
427
|
-
const finishReason = turn.finish_reason ?? "—";
|
|
428
|
-
// Render only observed transport metadata; absence makes no claim
|
|
429
|
-
// about endpoint-owned settings. {§operator-grammar}
|
|
430
|
-
const tm = DigestRender.parseJson(turn.meta ?? "null", null);
|
|
431
|
-
const attached = tm?.railsAttached;
|
|
432
|
-
const rails = attached === undefined || attached === false ? ""
|
|
433
|
-
: ` rails=${attached === true ? "client" : attached}`;
|
|
434
|
-
const model = turn.model ?? "—";
|
|
435
|
-
// {§outside-text}: the weight the model was told, so a digest reader sees the discard at a glance.
|
|
436
|
-
const outside = turn.outside === null ? "" : ` outside=${contentWeight(turn.outside)} tok`;
|
|
437
|
-
const errs = (m.logEntriesByTurn.get(turn.id) ?? [])
|
|
438
|
-
.filter((le) => le.status_rx >= 400 && !DigestRender.#isExecutorEvidence(le)).length;
|
|
439
|
-
const errBadge = errs > 0 ? ` ⚠ errs=${errs}` : "";
|
|
440
|
-
const attempts = m.attemptsByTurn.get(turn.id) ?? [];
|
|
441
|
-
const rejected = attempts.filter((attempt) => attempt.accepted === 0).length;
|
|
442
|
-
const errored = attempts.filter((attempt) => attempt.state === "error").length;
|
|
443
|
-
const pending = attempts.filter((attempt) => attempt.state === "pending").length;
|
|
444
|
-
const attemptConditions = [
|
|
445
|
-
rejected > 0 ? `rejected-emissions=${rejected}` : null,
|
|
446
|
-
errored > 0 ? `call-errors=${errored}` : null,
|
|
447
|
-
pending > 0 ? `open-calls=${pending}` : null,
|
|
448
|
-
].filter((part) => part !== null);
|
|
449
|
-
const attemptBadge = attemptConditions.length === 0
|
|
450
|
-
? ""
|
|
451
|
-
: ` ⚠ ${attemptConditions.join(" ")}/${attempts.length}`;
|
|
452
|
-
const packetBadge = packetFailure === null ? "" : " ⚠ packet=invalid";
|
|
453
|
-
const stem = DigestRender.packetStems(m).get(turn.id);
|
|
454
|
-
const modelTurn = DigestRender.#modelTurnOrdinal(turn, m);
|
|
455
|
-
const provenance = [modelTurn === null ? null : `model turn ${modelTurn}`, stem].filter((part) => part !== null).join(" · ");
|
|
456
|
-
const lifecycle = `T${turn.sequence}${provenance === "" ? "" : ` (${provenance})`}: producer=${turn.producer} kind=${turn.kind} status=${turn.status}${turn.completed_at === null ? " state=open" : ""}`;
|
|
457
|
-
const head = turn.kind === "inference"
|
|
458
|
-
? `${lifecycle} finish=${finishReason}${rails} model=${model} ${tokens}${cost}${DigestRender.#cacheBadge(turn, m)}${outside}${errBadge}${attemptBadge}${packetBadge}`
|
|
459
|
-
: `${lifecycle}${errBadge}${packetBadge}`;
|
|
460
|
-
let summary = null;
|
|
461
|
-
if (packetFailure !== null) {
|
|
462
|
-
summary = ` ↳ provider packet: invalid stored evidence (${packetFailure.error.message})`;
|
|
463
|
-
}
|
|
464
|
-
else if (packet === null) {
|
|
465
|
-
summary = null;
|
|
466
|
-
}
|
|
467
|
-
else if (content.length > 0) {
|
|
468
|
-
summary = content.includes("\n")
|
|
469
|
-
? ` ↳ emission:\n${DigestRender.#indentLines(content.trimEnd(), " ")}`
|
|
470
|
-
: ` ↳ emission: ${content.trim()}`;
|
|
471
|
-
}
|
|
472
|
-
else if (assistant !== null) {
|
|
473
|
-
summary = " ↳ emission: (admitted empty)";
|
|
474
|
-
}
|
|
475
|
-
else if (rejected > 0) {
|
|
476
|
-
summary = ` ↳ emission: (none admitted; ${rejected} rejected)`;
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
summary = " ↳ emission: (none admitted)";
|
|
480
|
-
}
|
|
481
|
-
const reasoningLine = reasoning && reasoning.length > 0
|
|
482
|
-
? (reasoning.includes("\n")
|
|
483
|
-
? ` ↳ reasoning:\n${DigestRender.#indentLines(reasoning.trimEnd(), " ")}`
|
|
484
|
-
: ` ↳ reasoning: ${reasoning.trim()}`)
|
|
485
|
-
: null;
|
|
486
|
-
// {§provider-wire-emission} — an empty emission is read from what the wire carried, never guessed at.
|
|
487
|
-
const wireLine = content.length === 0 && packet !== null && StoredPacket.isAdmitted(packet) ? DigestRender.wireLine(packet.assistantRaw) : null;
|
|
488
|
-
const opLines = DigestRender.#renderOpLines(m.logEntriesByTurn.get(turn.id) ?? [], m);
|
|
489
|
-
return [head, ...(summary ? [summary] : []), ...(reasoningLine ? [reasoningLine] : []), ...(wireLine ? [wireLine] : []), ...opLines].join("\n");
|
|
490
|
-
}
|
|
491
|
-
// {§provider-wire-emission} — public so the line can be witnessed on its own.
|
|
492
|
-
static wireLine(assistantRaw) {
|
|
493
|
-
const wire = typeof assistantRaw === "object" && assistantRaw !== null ? assistantRaw.wire : undefined;
|
|
494
|
-
if (typeof wire !== "object" || wire === null)
|
|
495
|
-
return null;
|
|
496
|
-
const { chunks, emptyChunks, fields, channels, toolCalls, unmappedChunks } = wire;
|
|
497
|
-
const parts = [
|
|
498
|
-
`${chunks ?? 0} chunks`,
|
|
499
|
-
(emptyChunks ?? 0) > 0 ? `${emptyChunks} carried nothing` : null,
|
|
500
|
-
...Object.entries(fields ?? {}).map(([field, count]) => `${field}×${count}`),
|
|
501
|
-
...(toolCalls ?? []).map((call) => `tool call ${call.name ?? "?"}(${DigestRender.#summarize(call.arguments, 60)})`),
|
|
502
|
-
...Object.entries(channels ?? {}).map(([channel, text]) => `${channel}: ${DigestRender.#summarize(text, 60)}`),
|
|
503
|
-
(unmappedChunks?.length ?? 0) > 0 ? `${unmappedChunks.length} unmapped frames retained` : null,
|
|
504
|
-
].filter((part) => part !== null);
|
|
505
|
-
return ` ↳ wire: ${parts.join(", ")}`;
|
|
506
|
-
}
|
|
507
|
-
static #renderWorkerShape(worker, m) {
|
|
508
|
-
// every worker has exactly one rollup row — digest_worker_rollups is FROM workers
|
|
509
|
-
const roll = m.workerRollups.get(worker.id);
|
|
510
|
-
const opMix = (m.opMixByWorker.get(worker.id) ?? []).map((o) => `${o.op}=${o.n}`).join(" ");
|
|
511
|
-
const requests = m.requestsByWorker.get(worker.id) ?? [];
|
|
512
|
-
const accounting = DigestRender.#accounting(requests);
|
|
513
|
-
const usageStr = requests.length === 0
|
|
514
|
-
? "no provider requests"
|
|
515
|
-
: DigestRender.usageSummary(accounting);
|
|
516
|
-
const costStr = requests.length === 0
|
|
517
|
-
? "n/a"
|
|
518
|
-
: DigestRender.costSummary(accounting);
|
|
519
|
-
// {§digest-forensic-fidelity} (#461): settled exchanges that carry no usage at
|
|
520
|
-
// all (errored/aborted) billed server-side invisibly; say so, never price-as-zero.
|
|
521
|
-
const usageless = requests.filter((row) => row.state === "settled" && row.usage_total === null).length;
|
|
522
|
-
const usagelessStr = usageless > 0
|
|
523
|
-
? ` (+${usageless} usage-less request${usageless === 1 ? "" : "s"} — server-side spend unrecorded)`
|
|
524
|
-
: "";
|
|
525
|
-
// {§digest-cost-kind} (#473): a dollar figure without its basis reads as billed
|
|
526
|
-
// truth; carry the kind so an estimate can never impersonate a charge.
|
|
527
|
-
const kinds = new Set(requests
|
|
528
|
-
.filter((row) => row.state === "settled" && row.cost_kind !== null)
|
|
529
|
-
.map((row) => row.cost_kind));
|
|
530
|
-
const kindStr = costStr === "n/a" || costStr === "unknown"
|
|
531
|
-
? ""
|
|
532
|
-
: kinds.has("estimated")
|
|
533
|
-
? " (estimated — catalog rates)"
|
|
534
|
-
: kinds.has("charged")
|
|
535
|
-
? " (charged)"
|
|
536
|
-
: "";
|
|
537
|
-
// {§digest-wire-line} (#473): provider-level errors are absorbed by retries below
|
|
538
|
-
// the packet stream, so only an aggregate line makes a rate-limit storm visible.
|
|
539
|
-
const wireErrors = requests.filter((row) => row.outcome === "error").length;
|
|
540
|
-
const wireStr = requests.length === 0
|
|
541
|
-
? "(no provider requests)"
|
|
542
|
-
: `${requests.length} request${requests.length === 1 ? "" : "s"} · ${wireErrors} error${wireErrors === 1 ? "" : "s"}${wireErrors > 0 ? ` (${Math.round((100 * wireErrors) / requests.length)}%)` : ""}`;
|
|
543
|
-
return [
|
|
544
|
-
`Loops: ${roll.loops}`,
|
|
545
|
-
`Turns: ${roll.turns}`,
|
|
546
|
-
`Last turn: ${roll.last_status !== null ? `status=${roll.last_status}` : "(none)"}`,
|
|
547
|
-
`Tokens: ${usageStr}`,
|
|
548
|
-
`Cost: ${costStr}${kindStr}${usagelessStr}`,
|
|
549
|
-
`Wire: ${wireStr}`,
|
|
550
|
-
`Op mix: ${opMix.length > 0 ? opMix : "(no ops)"}`,
|
|
551
|
-
`EDITs: ${DigestRender.#renderEditCensus(DigestRender.#editCensus(m).byWorker.get(worker.id))}`,
|
|
552
|
-
`Emissions: ${DigestRender.#renderEmissions(worker, m)}`,
|
|
553
|
-
`Reasonings: ${DigestRender.#renderReasonings(worker, m)}`,
|
|
554
|
-
].join("\n");
|
|
555
|
-
}
|
|
556
|
-
// {§emission-row} — announced, retired by the model, and echoed back as headings in its own text.
|
|
557
|
-
static #renderEmissions(worker, m) {
|
|
558
|
-
const rows = m.emissionRows.filter((row) => row.worker_id === worker.id);
|
|
559
|
-
const killed = rows.filter((row) => row.active === 0).length;
|
|
560
|
-
const echoes = (m.loopsByWorker.get(worker.id) ?? [])
|
|
561
|
-
.flatMap((loop) => m.turnsByLoop.get(loop.id) ?? [])
|
|
562
|
-
.reduce((sum, turn) => sum + FabricatedLog.echoes(turn.outside ?? ""), 0);
|
|
563
|
-
return `${rows.length} announced · ${killed} killed · ${echoes} header echo${echoes === 1 ? "" : "es"}`;
|
|
564
|
-
}
|
|
565
|
-
// {§reasoning-row} — landed, retired by the model, and turns that reasoned.
|
|
566
|
-
static #renderReasonings(worker, m) {
|
|
567
|
-
const rows = m.reasoningRows.filter((row) => row.worker_id === worker.id);
|
|
568
|
-
const killed = rows.filter((row) => row.active === 0).length;
|
|
569
|
-
const reasonedTurns = (m.loopsByWorker.get(worker.id) ?? [])
|
|
570
|
-
.flatMap((loop) => m.turnsByLoop.get(loop.id) ?? [])
|
|
571
|
-
.filter((turn) => turn.has_reasoning === 1).length;
|
|
572
|
-
if (reasonedTurns === 0 && rows.length === 0)
|
|
573
|
-
return "(no reasoning)";
|
|
574
|
-
if (rows.length === 0)
|
|
575
|
-
return `0 of ${reasonedTurns} landed (unbudgeted or disabled)`;
|
|
576
|
-
if (reasonedTurns > rows.length)
|
|
577
|
-
return `${rows.length} of ${reasonedTurns} landed · ${killed} killed`;
|
|
578
|
-
return `${rows.length} landed · ${killed} killed`;
|
|
579
|
-
}
|
|
580
|
-
static waterfall(m) {
|
|
581
|
-
const lines = [];
|
|
582
|
-
lines.push(`# plurnk-service digest`);
|
|
583
|
-
lines.push("");
|
|
584
|
-
lines.push(`DB: ${m.dbPath}`);
|
|
585
|
-
lines.push(DigestRender.#storageLine(m.storage));
|
|
586
|
-
const rejectedAttempts = m.turnAttempts.filter((attempt) => attempt.accepted === 0).length;
|
|
587
|
-
const erroredCalls = m.inferenceCalls.filter((call) => call.state === "error").length;
|
|
588
|
-
const pendingCalls = m.inferenceCalls.filter((call) => call.state === "pending").length;
|
|
589
|
-
const bareCalls = m.modelCalls.filter((call) => call.kind === "bare").length;
|
|
590
|
-
const pendingRequests = m.providerRequests.filter((request) => request.state === "pending").length;
|
|
591
|
-
const packetFailures = m.turns.filter((turn) => m.evidence.packet(turn).packetFailure !== null).length;
|
|
592
|
-
lines.push(`Workspaces: ${m.workspaces.length} Workers: ${m.workers.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}`);
|
|
593
|
-
if (packetFailures > 0)
|
|
594
|
-
lines.push(`Stored packet failures: ${packetFailures}`);
|
|
595
|
-
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`);
|
|
596
|
-
const health = m.loops.map((l) => DigestRender.#loopHealth(l, m));
|
|
597
|
-
const clean = health.filter((h) => h.verdict === "CLEAN").length;
|
|
598
|
-
const degen = health.filter((h) => h.verdict === "DEGENERATE-WIN").length;
|
|
599
|
-
const failed = health.filter((h) => h.verdict.startsWith("FAILED")).length;
|
|
600
|
-
const totalErrs = health.reduce((s, h) => s + h.errors, 0);
|
|
601
|
-
const totalItems = health.reduce((s, h) => s + h.errorItems, 0);
|
|
602
|
-
lines.push(`Health: ${clean} clean · ${degen > 0 ? `⚠ ${degen} degenerate-win` : "0 degenerate-win"} · ${failed} failed (${m.loops.length} loops; ${totalErrs} error rows, ${totalItems} minted error-items total)`);
|
|
603
|
-
for (const workspace of m.workspaces) {
|
|
604
|
-
lines.push("");
|
|
605
|
-
lines.push(`## Workspace #${workspace.id} — ${workspace.name}`);
|
|
606
|
-
lines.push("");
|
|
607
|
-
lines.push(DigestRender.#cacheLine(m.requestsByWorkspace.get(workspace.id) ?? [], m));
|
|
608
|
-
const workspaceWorkers = m.workersByWorkspace.get(workspace.id) ?? [];
|
|
609
|
-
for (const worker of workspaceWorkers) {
|
|
610
|
-
lines.push("");
|
|
611
|
-
lines.push(`### Worker #${worker.id} — ${worker.name}`);
|
|
612
|
-
lines.push("");
|
|
613
|
-
lines.push("```");
|
|
614
|
-
lines.push(DigestRender.#renderWorkerShape(worker, m));
|
|
615
|
-
lines.push("```");
|
|
616
|
-
const workerLoops = m.loopsByWorker.get(worker.id) ?? [];
|
|
617
|
-
for (const loop of workerLoops) {
|
|
618
|
-
const terminal = DigestRender.#terminalResult(loop);
|
|
619
|
-
lines.push("");
|
|
620
|
-
const h = DigestRender.#loopHealth(loop, m);
|
|
621
|
-
const badge = h.verdict === "CLEAN"
|
|
622
|
-
? " — CLEAN"
|
|
623
|
-
: ` — ${h.verdict === "DEGENERATE-WIN" ? "⚠ DEGENERATE-WIN" : h.verdict} (${h.errors} errors, ${h.errorItems} error-items)`;
|
|
624
|
-
lines.push(`#### Loop ${loop.sequence} (id=${loop.id}, status=${loop.status})${badge}`);
|
|
625
|
-
lines.push("");
|
|
626
|
-
if (loop.prompt.includes("\n")) {
|
|
627
|
-
lines.push("Prompt:\n" + DigestRender.#indentLines(loop.prompt.trimEnd(), " "));
|
|
628
|
-
}
|
|
629
|
-
else {
|
|
630
|
-
lines.push(`Prompt: ${loop.prompt}`);
|
|
631
|
-
}
|
|
632
|
-
const wake = DigestRender.#wakeLatency(loop, m);
|
|
633
|
-
if (wake !== null)
|
|
634
|
-
lines.push(wake);
|
|
635
|
-
if (loop.status !== 200 && terminal?.problem?.detail !== undefined) {
|
|
636
|
-
lines.push(`Terminal${loop.terminated_by !== null ? ` (${loop.terminated_by})` : ""}: ${terminal.problem.detail.trim()}`);
|
|
637
|
-
}
|
|
638
|
-
const policy = DigestRender.parseJson(loop.policy, {});
|
|
639
|
-
const policySummary = Object.entries(policy)
|
|
640
|
-
.map(([key, value]) => `${key}=${typeof value === "string" ? value : JSON.stringify(value)}`)
|
|
641
|
-
.join(" ");
|
|
642
|
-
if (policySummary.length > 0)
|
|
643
|
-
lines.push(`Policy: ${policySummary}`);
|
|
644
|
-
lines.push("");
|
|
645
|
-
const turnLines = (m.turnsByLoop.get(loop.id) ?? []).map((t) => DigestRender.#renderTurnLine(t, m));
|
|
646
|
-
const findMaxTicks = (s) => {
|
|
647
|
-
const matches = s.match(/`+/g);
|
|
648
|
-
return matches ? Math.max(...matches.map((match) => match.length)) : 0;
|
|
649
|
-
};
|
|
650
|
-
const maxBackticks = turnLines.reduce((max, line) => Math.max(max, findMaxTicks(line)), 0);
|
|
651
|
-
const fence = "`".repeat(Math.max(3, maxBackticks + 1));
|
|
652
|
-
lines.push(fence);
|
|
653
|
-
for (const line of turnLines)
|
|
654
|
-
lines.push(line);
|
|
655
|
-
lines.push(fence);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
return lines.join("\n");
|
|
660
|
-
}
|
|
661
|
-
static reasoning(m) {
|
|
662
|
-
const lines = [];
|
|
663
|
-
lines.push(`# plurnk-service reasoning`);
|
|
664
|
-
lines.push("");
|
|
665
|
-
lines.push("Turn chronology with every provider attempt. Rejected attempts remain explicit forensic evidence.");
|
|
666
|
-
for (const t of m.turns) {
|
|
667
|
-
const loop = m.loopsById.get(t.loop_id);
|
|
668
|
-
const worker = loop ? m.workersById.get(loop.worker_id) : undefined;
|
|
669
|
-
lines.push("");
|
|
670
|
-
lines.push(`## Worker ${worker?.id ?? "?"} / Loop ${loop?.sequence ?? "?"} / Turn ${t.sequence} (id=${t.id}, producer=${t.producer}, kind=${t.kind})`);
|
|
671
|
-
if (t.kind !== "inference") {
|
|
672
|
-
lines.push("");
|
|
673
|
-
lines.push("(operation turn; no provider inference)");
|
|
674
|
-
const reasoning = m.evidence.reasoning(t);
|
|
675
|
-
if (reasoning !== null)
|
|
676
|
-
lines.push("", reasoning);
|
|
677
|
-
continue;
|
|
678
|
-
}
|
|
679
|
-
const attempts = m.attemptsByTurn.get(t.id) ?? [];
|
|
680
|
-
if (attempts.length === 0) {
|
|
681
|
-
const { packet, packetFailure } = m.evidence.packet(t);
|
|
682
|
-
const reasoning = packet !== null && StoredPacket.isAdmitted(packet)
|
|
683
|
-
? packet.assistant.reasoning
|
|
684
|
-
: null;
|
|
685
|
-
lines.push("");
|
|
686
|
-
if (packetFailure !== null)
|
|
687
|
-
lines.push("(stored provider packet is invalid; see its packet artifacts)");
|
|
688
|
-
else if (typeof reasoning === "string" && reasoning.length > 0)
|
|
689
|
-
lines.push(reasoning);
|
|
690
|
-
else
|
|
691
|
-
lines.push("(no admitted provider reasoning)");
|
|
692
|
-
continue;
|
|
693
|
-
}
|
|
694
|
-
for (const attempt of attempts) {
|
|
695
|
-
const response = DigestRender.parseJson(m.evidence.response(attempt.model_call_id), {});
|
|
696
|
-
const parseErrors = DigestRender.parseJson(attempt.parse_errors, []);
|
|
697
|
-
lines.push("");
|
|
698
|
-
const disposition = attempt.state === "error"
|
|
699
|
-
? "call error"
|
|
700
|
-
: attempt.state === "pending"
|
|
701
|
-
? "open at capture"
|
|
702
|
-
: attempt.accepted === 1
|
|
703
|
-
? "admitted"
|
|
704
|
-
: "rejected";
|
|
705
|
-
lines.push(`### Attempt ${attempt.sequence} - ${disposition}`);
|
|
706
|
-
const attributions = DigestRender.parseJson(attempt.attributions, []);
|
|
707
|
-
lines.push(`Attributions: ${attributions.length === 0 ? "(none)" : attributions.join(", ")}`);
|
|
708
|
-
if (attempt.failure !== null) {
|
|
709
|
-
lines.push(`Failure: ${JSON.stringify(DigestRender.parseJson(attempt.failure, attempt.failure))}`);
|
|
710
|
-
}
|
|
711
|
-
for (const request of m.requestsByAttempt.get(attempt.id) ?? []) {
|
|
712
|
-
lines.push(`Physical request ${request.sequence}: [${request.state === "settled" ? request.outcome : request.state} evidence](requests/${request.id}.json)`);
|
|
713
|
-
}
|
|
714
|
-
if (attempt.accepted !== 1) {
|
|
715
|
-
for (const error of parseErrors) {
|
|
716
|
-
if (typeof error.message === "string")
|
|
717
|
-
lines.push(`- ${error.message}`);
|
|
718
|
-
}
|
|
719
|
-
lines.push("");
|
|
720
|
-
}
|
|
721
|
-
const reasoning = response.assistant?.reasoning ?? null;
|
|
722
|
-
if (typeof reasoning === "string" && reasoning.length > 0)
|
|
723
|
-
lines.push(reasoning);
|
|
724
|
-
else {
|
|
725
|
-
const reasoningTokens = DigestRender.#accounting(m.requestsByAttempt.get(attempt.id) ?? [])?.usage?.outputTokenDetails?.reasoningTokens;
|
|
726
|
-
lines.push(reasoningTokens !== undefined && reasoningTokens > 0
|
|
727
|
-
? `(provider reported ${reasoningTokens} reasoning tokens; no readable reasoning content returned)`
|
|
728
|
-
: attempt.state === "error" ? "(no admitted reasoning; partial output, when available, is in physical-request evidence)"
|
|
729
|
-
: "(no reasoning content returned)");
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
return lines.join("\n");
|
|
734
|
-
}
|
|
735
|
-
// Per-turn forensic files. turnOps is the source authority; PacketWire
|
|
736
|
-
// reproduces provider request slots, and assistantRaw preserves provider bytes.
|
|
737
|
-
// {§share-packet-names}: the stem each turn's packet files carry is its log coordinate, worker, loop and
|
|
738
|
-
// turn, as `log:///<loop>/<turn>/…` addresses it; a digest spanning workspaces nests one folder per
|
|
739
|
-
// workspace. Shared with the turn lines so a reader never counts files by hand.
|
|
740
|
-
static #stemCache = new WeakMap();
|
|
741
|
-
static packetStems(m) {
|
|
742
|
-
const cached = DigestRender.#stemCache.get(m);
|
|
743
|
-
if (cached !== undefined)
|
|
744
|
-
return cached;
|
|
745
|
-
const nested = m.workspaces.length > 1;
|
|
746
|
-
const segment = (name, what) => {
|
|
747
|
-
if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/u.test(name))
|
|
748
|
-
throw new TypeError(`digest: ${what} name ${JSON.stringify(name)} cannot name a file`);
|
|
749
|
-
return name;
|
|
750
|
-
};
|
|
751
|
-
const stems = new Map();
|
|
752
|
-
const taken = new Set();
|
|
753
|
-
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)) {
|
|
754
|
-
const loop = m.loopsById.get(turn.loop_id);
|
|
755
|
-
const worker = loop === undefined ? undefined : m.workersById.get(loop.worker_id);
|
|
756
|
-
if (loop === undefined || worker === undefined)
|
|
757
|
-
throw new TypeError(`digest: turn ${turn.id} has no loop or worker in scope`);
|
|
758
|
-
const workspace = m.workspaces.find(({ id }) => id === worker.workspace_id);
|
|
759
|
-
const stem = `${nested ? `${segment(workspace?.name ?? String(worker.workspace_id), "workspace")}/` : ""}${segment(worker.name, "worker")}-${loop.sequence}-${turn.sequence}`;
|
|
760
|
-
if (taken.has(stem))
|
|
761
|
-
throw new TypeError(`digest: two turns share the packet name ${stem}`);
|
|
762
|
-
taken.add(stem);
|
|
763
|
-
stems.set(turn.id, stem);
|
|
764
|
-
}
|
|
765
|
-
DigestRender.#stemCache.set(m, stems);
|
|
766
|
-
return stems;
|
|
767
|
-
}
|
|
768
|
-
// Among its loop's model turns, this one's ordinal: the model's own count, which skips the
|
|
769
|
-
// harness turns a reader would otherwise have to subtract.
|
|
770
|
-
static #modelTurnOrdinal(turn, m) {
|
|
771
|
-
if (turn.producer !== "model")
|
|
772
|
-
return null;
|
|
773
|
-
const siblings = (m.turnsByLoop.get(turn.loop_id) ?? []).filter((t) => t.producer === "model");
|
|
774
|
-
const index = siblings.findIndex((t) => t.id === turn.id);
|
|
775
|
-
return index < 0 ? null : index + 1;
|
|
776
|
-
}
|
|
777
|
-
static packetFiles(m) {
|
|
778
|
-
const written = [];
|
|
779
|
-
const stems = DigestRender.packetStems(m);
|
|
780
|
-
const write = (file, body) => {
|
|
781
|
-
mkdirSync(dirname(join(m.digestDir, file)), { recursive: true });
|
|
782
|
-
writeFileSync(join(m.digestDir, file), body);
|
|
783
|
-
written.push(file);
|
|
784
|
-
};
|
|
785
|
-
m.turns
|
|
786
|
-
.map((turn) => ({ turn, source: turn.program }))
|
|
787
|
-
.filter(({ turn, source }) => turn.has_packet === 1 || source !== null || turn.has_reasoning === 1)
|
|
788
|
-
.toSorted((a, b) => a.turn.id - b.turn.id)
|
|
789
|
-
.forEach(({ turn, source }) => {
|
|
790
|
-
const padded = stems.get(turn.id);
|
|
791
|
-
const files = [];
|
|
792
|
-
const { packet, packetFailure } = m.evidence.packet(turn);
|
|
793
|
-
if (packetFailure !== null) {
|
|
794
|
-
files.push([`${padded}.packet.raw.txt`, packetFailure.raw], [`${padded}.packet.invalid.json`, JSON.stringify({
|
|
795
|
-
turnId: turn.id,
|
|
796
|
-
error: packetFailure.error,
|
|
797
|
-
}, null, 2)]);
|
|
798
|
-
}
|
|
799
|
-
if (packet !== null) {
|
|
800
|
-
files.push([`${padded}.system.md`, PacketWire.renderSlot(packet.sections, "system")], [`${padded}.user.md`, PacketWire.renderSlot(packet.sections, "user")]);
|
|
801
|
-
// {§packet-wire-envelope} — the exact text messages the request carried; a stored packet
|
|
802
|
-
// whose log cannot be projected is evidence of its own, never a reason to stop the digest.
|
|
803
|
-
try {
|
|
804
|
-
files.push([`${padded}.wire.json`, JSON.stringify(PacketWire.packetToWireMessages(packet, DigestRender.#emissionsOf(turn, m)), null, 2)]);
|
|
805
|
-
}
|
|
806
|
-
catch (cause) {
|
|
807
|
-
files.push([`${padded}.wire.invalid.json`, JSON.stringify({ turnId: turn.id, error: cause instanceof Error ? cause.message : String(cause) }, null, 2)]);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
if (source !== null) {
|
|
811
|
-
files.push([`${padded}.assistant.md`, source]);
|
|
812
|
-
}
|
|
813
|
-
const reasoning = m.evidence.reasoning(turn);
|
|
814
|
-
if (reasoning !== null)
|
|
815
|
-
files.push([`${padded}.reasoning.md`, reasoning]);
|
|
816
|
-
if (packet !== null && StoredPacket.isAdmitted(packet)) {
|
|
817
|
-
if (source !== null && packet.assistant.content !== source) {
|
|
818
|
-
throw new TypeError(`digest: turn ${turn.id} packet assistant differs from its turnOps source`);
|
|
819
|
-
}
|
|
820
|
-
files.push([`${padded}.assistantRaw.json`, JSON.stringify(packet.assistantRaw, null, 2)]);
|
|
821
|
-
}
|
|
822
|
-
else if (packet !== null) {
|
|
823
|
-
files.push([
|
|
824
|
-
`${padded}.response.md`,
|
|
825
|
-
`# ${padded} — request only\n\nNo provider response was admitted. Rejected attempt evidence, when present, is written separately.\n`,
|
|
826
|
-
]);
|
|
827
|
-
}
|
|
828
|
-
for (const [file, body] of files)
|
|
829
|
-
write(file, body);
|
|
830
|
-
for (const attempt of m.attemptsByTurn.get(turn.id) ?? []) {
|
|
831
|
-
if (attempt.accepted === 1)
|
|
832
|
-
continue;
|
|
833
|
-
const attemptPadded = String(attempt.sequence).padStart(3, "0");
|
|
834
|
-
if (attempt.state !== "response") {
|
|
835
|
-
write(`${padded}.attempt${attemptPadded}.${attempt.state}.json`, JSON.stringify({
|
|
836
|
-
state: attempt.state,
|
|
837
|
-
failure: DigestRender.parseJson(attempt.failure),
|
|
838
|
-
attributions: DigestRender.parseJson(attempt.attributions, []),
|
|
839
|
-
openedAt: attempt.timestamp,
|
|
840
|
-
completedAt: attempt.completed_at,
|
|
841
|
-
}, null, 2));
|
|
842
|
-
continue;
|
|
843
|
-
}
|
|
844
|
-
const response = DigestRender.parseJson(m.evidence.response(attempt.model_call_id), {});
|
|
845
|
-
const prefix = `${padded}.attempt${attemptPadded}.rejected`;
|
|
846
|
-
const attemptFiles = [
|
|
847
|
-
[
|
|
848
|
-
`${prefix}.assistant.md`,
|
|
849
|
-
typeof response.assistant?.content === "string" ? response.assistant.content : "",
|
|
850
|
-
],
|
|
851
|
-
[`${prefix}.response.json`, JSON.stringify(response, null, 2)],
|
|
852
|
-
[`${prefix}.parse-errors.json`, JSON.stringify(DigestRender.parseJson(attempt.parse_errors, []), null, 2)],
|
|
853
|
-
[`${prefix}.attributions.json`, JSON.stringify(DigestRender.parseJson(attempt.attributions, []), null, 2)],
|
|
854
|
-
];
|
|
855
|
-
for (const [file, body] of attemptFiles)
|
|
856
|
-
write(file, body);
|
|
857
|
-
}
|
|
858
|
-
});
|
|
859
|
-
for (const request of m.providerRequests) {
|
|
860
|
-
write(`requests/${request.id}.json`, JSON.stringify({
|
|
861
|
-
id: request.id,
|
|
862
|
-
inferenceCallId: request.inference_call_id,
|
|
863
|
-
sequence: request.sequence,
|
|
864
|
-
provider: request.provider,
|
|
865
|
-
model: request.model,
|
|
866
|
-
state: request.state,
|
|
867
|
-
startedAt: request.started_at,
|
|
868
|
-
completedAt: request.completed_at,
|
|
869
|
-
accounting: request.state === "settled" ? DigestRender.#requestAccounting(request) : null,
|
|
870
|
-
evidence: DigestRender.parseJson(m.evidence.request(request.id)),
|
|
871
|
-
}, null, 2));
|
|
872
|
-
}
|
|
873
|
-
return written;
|
|
874
|
-
}
|
|
875
|
-
static *json(m) {
|
|
876
|
-
const fields = {
|
|
877
|
-
dbPath: m.dbPath,
|
|
878
|
-
storage: m.storage,
|
|
879
|
-
search: m.search,
|
|
880
|
-
workspaces: m.workspaces.map((s) => ({
|
|
881
|
-
id: s.id,
|
|
882
|
-
name: s.name,
|
|
883
|
-
accounting: DigestRender.#accounting(m.requestsByWorkspace.get(s.id) ?? []),
|
|
884
|
-
})),
|
|
885
|
-
workers: m.workers.map((r) => ({
|
|
886
|
-
id: r.id,
|
|
887
|
-
workspace_id: r.workspace_id,
|
|
888
|
-
name: r.name,
|
|
889
|
-
accounting: DigestRender.#accounting(m.requestsByWorker.get(r.id) ?? []),
|
|
890
|
-
edit_census: DigestRender.#editCensus(m).byWorker.get(r.id) ?? null,
|
|
891
|
-
})),
|
|
892
|
-
loops: m.loops.map((l) => ({
|
|
893
|
-
id: l.id, worker_id: l.worker_id, sequence: l.sequence, status: l.status,
|
|
894
|
-
prompt: l.prompt, policy: DigestRender.parseJson(l.policy, {}),
|
|
895
|
-
terminated_by: l.terminated_by,
|
|
896
|
-
claimed_at: l.claimed_at,
|
|
897
|
-
terminated_at: l.terminated_at,
|
|
898
|
-
result: DigestRender.#terminalResult(l),
|
|
899
|
-
accounting: DigestRender.#accounting(m.requestsByLoop.get(l.id) ?? []),
|
|
900
|
-
})),
|
|
901
|
-
turns: projectRows(m.turns, (t) => {
|
|
902
|
-
const { packet, packetFailure } = m.evidence.packet(t);
|
|
903
|
-
return {
|
|
904
|
-
id: t.id, loop_id: t.loop_id, sequence: t.sequence,
|
|
905
|
-
// {§share-packet-names}: the stem of this turn's packet files, or null when it wrote none.
|
|
906
|
-
artifact: DigestRender.packetStems(m).get(t.id) ?? null,
|
|
907
|
-
producer: t.producer, kind: t.kind,
|
|
908
|
-
program: t.program,
|
|
909
|
-
status: t.status, completed_at: t.completed_at,
|
|
910
|
-
accounting: DigestRender.#accounting(m.requestsByTurn.get(t.id) ?? []),
|
|
911
|
-
finish_reason: t.finish_reason, model: t.model,
|
|
912
|
-
attributions: packet?.attributions ?? [],
|
|
913
|
-
attachments: packet === null ? null : packet.attachments ?? [],
|
|
914
|
-
packet_failure: packetFailure,
|
|
915
|
-
// Preserve the opaque provider and engine metadata for aggregate
|
|
916
|
-
// tooling. {§meta-passthrough}, {§operator-grammar}
|
|
917
|
-
meta: DigestRender.parseJson(t.meta ?? "null", null),
|
|
918
|
-
};
|
|
919
|
-
}),
|
|
920
|
-
inference_calls: m.inferenceCalls.map((call) => ({
|
|
921
|
-
id: call.id,
|
|
922
|
-
workspace_id: call.workspace_id,
|
|
923
|
-
turn_id: call.turn_id,
|
|
924
|
-
sequence: call.sequence,
|
|
925
|
-
kind: call.kind,
|
|
926
|
-
state: call.state,
|
|
927
|
-
attributions: DigestRender.parseJson(call.attributions, []),
|
|
928
|
-
request_model: call.request_model,
|
|
929
|
-
accounting: DigestRender.#accounting(m.requestsByInferenceCall.get(call.id) ?? []),
|
|
930
|
-
timestamp: call.timestamp,
|
|
931
|
-
completed_at: call.completed_at,
|
|
932
|
-
})),
|
|
933
|
-
model_calls: projectRows(m.modelCalls, (call) => ({
|
|
934
|
-
id: call.id,
|
|
935
|
-
turn_id: call.turn_id,
|
|
936
|
-
sequence: call.sequence,
|
|
937
|
-
kind: call.kind,
|
|
938
|
-
state: call.state,
|
|
939
|
-
response: DigestRender.parseJson(m.evidence.response(call.id)),
|
|
940
|
-
failure: DigestRender.parseJson(call.failure),
|
|
941
|
-
attributions: DigestRender.parseJson(call.attributions, []),
|
|
942
|
-
accounting: DigestRender.#accounting(m.requestsByInferenceCall.get(call.id) ?? []),
|
|
943
|
-
finish_reason: call.finish_reason,
|
|
944
|
-
model: call.model,
|
|
945
|
-
request_model: call.request_model,
|
|
946
|
-
response_model: call.response_model,
|
|
947
|
-
timestamp: call.timestamp,
|
|
948
|
-
completed_at: call.completed_at,
|
|
949
|
-
turn_attempt_id: call.turn_attempt_id,
|
|
950
|
-
accepted: call.accepted === null ? null : call.accepted === 1,
|
|
951
|
-
parse_errors: DigestRender.parseJson(call.parse_errors, []),
|
|
952
|
-
log_entry_id: call.log_entry_id,
|
|
953
|
-
})),
|
|
954
|
-
turn_attempts: projectRows(m.turnAttempts, (attempt) => ({
|
|
955
|
-
id: attempt.id,
|
|
956
|
-
turn_id: attempt.turn_id,
|
|
957
|
-
sequence: attempt.sequence,
|
|
958
|
-
state: attempt.state,
|
|
959
|
-
accepted: attempt.accepted === null ? null : attempt.accepted === 1,
|
|
960
|
-
response: DigestRender.parseJson(m.evidence.response(attempt.model_call_id)),
|
|
961
|
-
failure: DigestRender.parseJson(attempt.failure),
|
|
962
|
-
parse_errors: DigestRender.parseJson(attempt.parse_errors, []),
|
|
963
|
-
attributions: DigestRender.parseJson(attempt.attributions, []),
|
|
964
|
-
accounting: DigestRender.#accounting(m.requestsByAttempt.get(attempt.id) ?? []),
|
|
965
|
-
finish_reason: attempt.finish_reason,
|
|
966
|
-
model: attempt.model,
|
|
967
|
-
timestamp: attempt.timestamp,
|
|
968
|
-
completed_at: attempt.completed_at,
|
|
969
|
-
})),
|
|
970
|
-
provider_requests: m.providerRequests.map((request) => ({
|
|
971
|
-
id: request.id,
|
|
972
|
-
evidence: `requests/${request.id}.json`,
|
|
973
|
-
inference_call_id: request.inference_call_id,
|
|
974
|
-
turn_attempt_id: request.turn_attempt_id,
|
|
975
|
-
kind: request.kind,
|
|
976
|
-
sequence: request.sequence,
|
|
977
|
-
state: request.state,
|
|
978
|
-
accounting: request.state === "settled"
|
|
979
|
-
? DigestRender.#requestAccounting(request)
|
|
980
|
-
: null,
|
|
981
|
-
...DigestRender.cacheLedger(m).get(request.id),
|
|
982
|
-
started_at: request.started_at,
|
|
983
|
-
completed_at: request.completed_at,
|
|
984
|
-
})),
|
|
985
|
-
log_entries: m.logEntries.map((le) => ({
|
|
986
|
-
id: le.id, worker_id: le.worker_id, loop_id: le.loop_id,
|
|
987
|
-
turn_id: le.turn_id, sequence: le.sequence, origin: le.origin,
|
|
988
|
-
source: le.source, model_call_id: le.model_call_id,
|
|
989
|
-
attrs: DigestRender.parseJson(le.attrs, {}),
|
|
990
|
-
op: le.op, target: DigestRender.#renderTarget(le),
|
|
991
|
-
status_rx: le.status_rx, state: le.state, outcome: le.outcome,
|
|
992
|
-
initial_folded: DigestRender.parseJson(le.initial_folded, []),
|
|
993
|
-
projection: {
|
|
994
|
-
active: le.projection_active === 1,
|
|
995
|
-
folded: DigestRender.parseJson(le.projection_folded, []),
|
|
996
|
-
},
|
|
997
|
-
...(DigestRender.#renderStream(le) === null
|
|
998
|
-
? {}
|
|
999
|
-
: { stream: DigestRender.#renderStream(le) }),
|
|
1000
|
-
...(DigestRender.#environmentOf(le, m) === undefined ? {} : { env: DigestRender.#environmentOf(le, m) }),
|
|
1001
|
-
...(le.status_rx >= 400 ? { problem: DigestRender.#rowProblem(le) } : {}),
|
|
1002
|
-
...(DigestRender.#editCensus(m).formById.has(le.id)
|
|
1003
|
-
? { edit_form: DigestRender.#editCensus(m).formById.get(le.id), edit_revisit: DigestRender.#editCensus(m).revisitById.has(le.id) }
|
|
1004
|
-
: {}),
|
|
1005
|
-
})),
|
|
1006
|
-
log_curation_effects: m.curationEffects.map(({ active_before, active_after, folded_before, folded_after, ...effect }) => ({
|
|
1007
|
-
...effect,
|
|
1008
|
-
active_before: active_before === 1,
|
|
1009
|
-
active_after: active_after === 1,
|
|
1010
|
-
folded_before: DigestRender.parseJson(folded_before, []),
|
|
1011
|
-
folded_after: DigestRender.parseJson(folded_after, []),
|
|
1012
|
-
})),
|
|
1013
|
-
};
|
|
1014
|
-
yield "{";
|
|
1015
|
-
let fieldSeparator = "";
|
|
1016
|
-
for (const [name, value] of Object.entries(fields)) {
|
|
1017
|
-
yield `${fieldSeparator}\n ${JSON.stringify(name)}: `;
|
|
1018
|
-
if (typeof value === "object" && value !== null && Symbol.iterator in value) {
|
|
1019
|
-
yield "[";
|
|
1020
|
-
let itemSeparator = "";
|
|
1021
|
-
for (const row of value) {
|
|
1022
|
-
yield `${itemSeparator}\n${JSON.stringify(row, null, 2).replace(/^/gm, " ")}`;
|
|
1023
|
-
itemSeparator = ",";
|
|
1024
|
-
}
|
|
1025
|
-
yield itemSeparator === "" ? "]" : "\n ]";
|
|
1026
|
-
}
|
|
1027
|
-
else {
|
|
1028
|
-
yield JSON.stringify(value, null, 2);
|
|
1029
|
-
}
|
|
1030
|
-
fieldSeparator = ",";
|
|
1031
|
-
}
|
|
1032
|
-
yield "\n}\n";
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
//# sourceMappingURL=DigestRender.js.map
|