cool-workflow 0.1.78

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.
Files changed (193) hide show
  1. package/.claude-plugin/plugin.json +20 -0
  2. package/.codex-plugin/mcp.json +10 -0
  3. package/.codex-plugin/plugin.json +38 -0
  4. package/.mcp.json +10 -0
  5. package/LICENSE +24 -0
  6. package/README.md +638 -0
  7. package/apps/architecture-review/app.json +51 -0
  8. package/apps/architecture-review/workflow.js +116 -0
  9. package/apps/end-to-end-golden-path/app.json +30 -0
  10. package/apps/end-to-end-golden-path/workflow.js +33 -0
  11. package/apps/pr-review-fix-ci/app.json +59 -0
  12. package/apps/pr-review-fix-ci/workflow.js +90 -0
  13. package/apps/release-cut/app.json +54 -0
  14. package/apps/release-cut/workflow.js +82 -0
  15. package/apps/research-synthesis/app.json +50 -0
  16. package/apps/research-synthesis/workflow.js +76 -0
  17. package/apps/workflow-app-framework-demo/app.json +29 -0
  18. package/apps/workflow-app-framework-demo/workflow.js +44 -0
  19. package/dist/agent-config.js +223 -0
  20. package/dist/candidate-scoring.js +715 -0
  21. package/dist/capability-core.js +630 -0
  22. package/dist/capability-dispatcher.js +86 -0
  23. package/dist/capability-registry.js +523 -0
  24. package/dist/cli.js +1276 -0
  25. package/dist/collaboration.js +727 -0
  26. package/dist/commit.js +570 -0
  27. package/dist/contract-migration.js +234 -0
  28. package/dist/coordinator.js +1163 -0
  29. package/dist/daemon.js +44 -0
  30. package/dist/dispatch.js +201 -0
  31. package/dist/drive.js +503 -0
  32. package/dist/error-feedback.js +415 -0
  33. package/dist/evidence-grounding.js +179 -0
  34. package/dist/evidence-reasoning.js +733 -0
  35. package/dist/execution-backend.js +1279 -0
  36. package/dist/harness.js +61 -0
  37. package/dist/mcp-server.js +1615 -0
  38. package/dist/multi-agent-eval.js +857 -0
  39. package/dist/multi-agent-host.js +764 -0
  40. package/dist/multi-agent-operator-ux.js +537 -0
  41. package/dist/multi-agent-trust.js +366 -0
  42. package/dist/multi-agent.js +1173 -0
  43. package/dist/node-snapshot.js +270 -0
  44. package/dist/observability.js +922 -0
  45. package/dist/operator-ux.js +971 -0
  46. package/dist/orchestrator/audit-operations.js +182 -0
  47. package/dist/orchestrator/candidate-operations.js +117 -0
  48. package/dist/orchestrator/cli-options.js +288 -0
  49. package/dist/orchestrator/collaboration-operations.js +86 -0
  50. package/dist/orchestrator/feedback-operations.js +81 -0
  51. package/dist/orchestrator/host-operations.js +78 -0
  52. package/dist/orchestrator/lifecycle-operations.js +462 -0
  53. package/dist/orchestrator/migration-operations.js +44 -0
  54. package/dist/orchestrator/multi-agent-operations.js +362 -0
  55. package/dist/orchestrator/report.js +369 -0
  56. package/dist/orchestrator/topology-operations.js +84 -0
  57. package/dist/orchestrator.js +874 -0
  58. package/dist/pipeline-contract.js +92 -0
  59. package/dist/pipeline-runner.js +285 -0
  60. package/dist/reclamation.js +882 -0
  61. package/dist/result-normalize.js +194 -0
  62. package/dist/run-export.js +64 -0
  63. package/dist/run-registry.js +1347 -0
  64. package/dist/run-state-schema.js +67 -0
  65. package/dist/sandbox-profile.js +471 -0
  66. package/dist/scheduler.js +266 -0
  67. package/dist/scheduling.js +184 -0
  68. package/dist/schema-validate.js +98 -0
  69. package/dist/state-explosion.js +1213 -0
  70. package/dist/state-migrations.js +463 -0
  71. package/dist/state-node.js +301 -0
  72. package/dist/state.js +308 -0
  73. package/dist/telemetry-attestation.js +156 -0
  74. package/dist/telemetry-ledger.js +145 -0
  75. package/dist/topology.js +527 -0
  76. package/dist/triggers.js +159 -0
  77. package/dist/trust-audit.js +475 -0
  78. package/dist/types/blackboard.js +2 -0
  79. package/dist/types/boundary.js +29 -0
  80. package/dist/types/candidate.js +2 -0
  81. package/dist/types/collaboration.js +2 -0
  82. package/dist/types/core.js +2 -0
  83. package/dist/types/drive.js +10 -0
  84. package/dist/types/error-feedback.js +2 -0
  85. package/dist/types/evidence-reasoning.js +2 -0
  86. package/dist/types/execution-backend.js +2 -0
  87. package/dist/types/multi-agent.js +2 -0
  88. package/dist/types/observability.js +2 -0
  89. package/dist/types/pipeline.js +2 -0
  90. package/dist/types/reclamation.js +8 -0
  91. package/dist/types/result.js +2 -0
  92. package/dist/types/run-registry.js +2 -0
  93. package/dist/types/run.js +2 -0
  94. package/dist/types/sandbox.js +2 -0
  95. package/dist/types/schedule.js +2 -0
  96. package/dist/types/state-node.js +2 -0
  97. package/dist/types/topology.js +2 -0
  98. package/dist/types/trust.js +2 -0
  99. package/dist/types/workbench.js +2 -0
  100. package/dist/types/worker.js +2 -0
  101. package/dist/types/workflow-app.js +2 -0
  102. package/dist/types.js +43 -0
  103. package/dist/verifier-registry.js +46 -0
  104. package/dist/verifier.js +78 -0
  105. package/dist/version.js +8 -0
  106. package/dist/workbench-host.js +172 -0
  107. package/dist/workbench.js +190 -0
  108. package/dist/worker-isolation.js +1028 -0
  109. package/dist/workflow-api.js +98 -0
  110. package/dist/workflow-app-framework.js +626 -0
  111. package/docs/agent-delegation-drive.7.md +190 -0
  112. package/docs/agent-framework.md +176 -0
  113. package/docs/candidate-scoring.7.md +106 -0
  114. package/docs/canonical-workflow-apps.7.md +137 -0
  115. package/docs/capability-topology-registry.7.md +168 -0
  116. package/docs/cli-mcp-parity.7.md +373 -0
  117. package/docs/contract-migration-tooling.7.md +123 -0
  118. package/docs/control-plane-scheduling.7.md +110 -0
  119. package/docs/coordinator-blackboard.7.md +183 -0
  120. package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
  121. package/docs/dogfood-one-real-repo.7.md +168 -0
  122. package/docs/durable-state-and-locking.7.md +107 -0
  123. package/docs/end-to-end-golden-path.7.md +117 -0
  124. package/docs/error-feedback.7.md +153 -0
  125. package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
  126. package/docs/execution-backends.7.md +300 -0
  127. package/docs/getting-started.md +99 -0
  128. package/docs/index.md +41 -0
  129. package/docs/mcp-app-surface.7.md +235 -0
  130. package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
  131. package/docs/multi-agent-eval-replay-harness.7.md +302 -0
  132. package/docs/multi-agent-operator-ux.7.md +314 -0
  133. package/docs/multi-agent-runtime-core.7.md +231 -0
  134. package/docs/multi-agent-topologies.7.md +103 -0
  135. package/docs/multi-agent-trust-policy-audit.7.md +154 -0
  136. package/docs/node-snapshot-diff-replay.7.md +135 -0
  137. package/docs/observability-cost-accounting.7.md +194 -0
  138. package/docs/operator-ux.7.md +180 -0
  139. package/docs/pipeline-runner.7.md +136 -0
  140. package/docs/project-index.md +261 -0
  141. package/docs/real-execution-backends.7.md +142 -0
  142. package/docs/release-and-migration.7.md +280 -0
  143. package/docs/release-tooling.7.md +159 -0
  144. package/docs/routines.md +48 -0
  145. package/docs/run-registry-control-plane.7.md +312 -0
  146. package/docs/run-retention-reclamation.7.md +191 -0
  147. package/docs/sandbox-profiles.7.md +137 -0
  148. package/docs/scheduled-tasks.md +80 -0
  149. package/docs/security-trust-hardening.7.md +117 -0
  150. package/docs/state-explosion-management.7.md +264 -0
  151. package/docs/state-node.7.md +96 -0
  152. package/docs/team-collaboration.7.md +207 -0
  153. package/docs/unix-principles.md +192 -0
  154. package/docs/verifier-gated-commit.7.md +140 -0
  155. package/docs/web-desktop-workbench.7.md +215 -0
  156. package/docs/worker-isolation.7.md +167 -0
  157. package/docs/workflow-app-framework.7.md +274 -0
  158. package/manifest/README.md +43 -0
  159. package/manifest/plugin.manifest.json +316 -0
  160. package/manifest/pricing.policy.json +14 -0
  161. package/package.json +79 -0
  162. package/scripts/agents/claude-p-agent.js +104 -0
  163. package/scripts/agents/claude-p-agent.sh +9 -0
  164. package/scripts/agents/cw-attest-keygen.js +55 -0
  165. package/scripts/agents/cw-attest-wrap.js +143 -0
  166. package/scripts/block-unapproved-tag.sh +39 -0
  167. package/scripts/bump-version.js +249 -0
  168. package/scripts/canonical-apps.js +171 -0
  169. package/scripts/cw.js +4 -0
  170. package/scripts/dist-drift-check.js +79 -0
  171. package/scripts/dogfood-architecture-review.js +237 -0
  172. package/scripts/dogfood-release.js +624 -0
  173. package/scripts/forward-ref-docs.js +73 -0
  174. package/scripts/gen-manifests.js +232 -0
  175. package/scripts/golden-path.js +300 -0
  176. package/scripts/mcp-server.js +4 -0
  177. package/scripts/new-feature.js +121 -0
  178. package/scripts/parity-check.js +213 -0
  179. package/scripts/release-check.js +118 -0
  180. package/scripts/release-flow.js +272 -0
  181. package/scripts/release-gate.sh +85 -0
  182. package/scripts/sync-project-index.js +387 -0
  183. package/scripts/validate-run-state-schema.js +126 -0
  184. package/scripts/verify-container-selfref.js +64 -0
  185. package/scripts/version-sync-check.js +237 -0
  186. package/skills/cool-workflow/SKILL.md +162 -0
  187. package/skills/cool-workflow/references/commands.md +282 -0
  188. package/tsconfig.json +16 -0
  189. package/ui/workbench/app.css +76 -0
  190. package/ui/workbench/app.js +159 -0
  191. package/ui/workbench/index.html +32 -0
  192. package/workflows/architecture-review.workflow.js +84 -0
  193. package/workflows/research-synthesis.workflow.js +47 -0
@@ -0,0 +1,1213 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GRAPH_VIEWS = exports.DEFAULT_STATE_EXPLOSION_THRESHOLDS = exports.STATE_EXPLOSION_SCHEMA_VERSION = void 0;
7
+ exports.computeStateSize = computeStateSize;
8
+ exports.summarizeBlackboardDigest = summarizeBlackboardDigest;
9
+ exports.buildCompactGraph = buildCompactGraph;
10
+ exports.buildOperatorDigest = buildOperatorDigest;
11
+ exports.buildStateExplosionReport = buildStateExplosionReport;
12
+ exports.maybeCompactRun = maybeCompactRun;
13
+ exports.refreshStateExplosionSummaries = refreshStateExplosionSummaries;
14
+ exports.loadStateExplosionSummaryIndex = loadStateExplosionSummaryIndex;
15
+ exports.showStateExplosionSummary = showStateExplosionSummary;
16
+ exports.formatStateExplosionReport = formatStateExplosionReport;
17
+ exports.formatCompactGraph = formatCompactGraph;
18
+ exports.formatBlackboardDigest = formatBlackboardDigest;
19
+ exports.stateExplosionReportLines = stateExplosionReportLines;
20
+ exports.normalizeStateExplosionForEval = normalizeStateExplosionForEval;
21
+ exports.fingerprintStrings = fingerprintStrings;
22
+ const node_crypto_1 = __importDefault(require("node:crypto"));
23
+ const node_fs_1 = __importDefault(require("node:fs"));
24
+ const node_path_1 = __importDefault(require("node:path"));
25
+ const state_1 = require("./state");
26
+ const coordinator_1 = require("./coordinator");
27
+ const multi_agent_operator_ux_1 = require("./multi-agent-operator-ux");
28
+ const trust_audit_1 = require("./trust-audit");
29
+ const evidence_reasoning_1 = require("./evidence-reasoning");
30
+ exports.STATE_EXPLOSION_SCHEMA_VERSION = 1;
31
+ exports.DEFAULT_STATE_EXPLOSION_THRESHOLDS = {
32
+ graphNodes: 40,
33
+ graphEdges: 60,
34
+ blackboardMessages: 25,
35
+ blackboardRecords: 40,
36
+ collapseBucket: 6,
37
+ totalRecords: 80
38
+ };
39
+ exports.GRAPH_VIEWS = [
40
+ "full",
41
+ "compact",
42
+ "critical-path",
43
+ "failures",
44
+ "evidence",
45
+ "trust",
46
+ "topology",
47
+ "blackboard",
48
+ "candidate",
49
+ "commit-gate"
50
+ ];
51
+ // ---------------------------------------------------------------------------
52
+ // State size
53
+ // ---------------------------------------------------------------------------
54
+ function computeStateSize(run, thresholds = exports.DEFAULT_STATE_EXPLOSION_THRESHOLDS) {
55
+ const ma = run.multiAgent || { runs: [], roles: [], groups: [], memberships: [], fanouts: [], fanins: [] };
56
+ const bb = run.blackboard || { topics: [], messages: [], contexts: [], artifacts: [], snapshots: [], decisions: [] };
57
+ const graph = (0, multi_agent_operator_ux_1.buildMultiAgentOperatorGraph)(run);
58
+ const counts = {
59
+ multiAgentRuns: (ma.runs || []).length,
60
+ roles: (ma.roles || []).length,
61
+ groups: (ma.groups || []).length,
62
+ memberships: (ma.memberships || []).length,
63
+ fanouts: (ma.fanouts || []).length,
64
+ fanins: (ma.fanins || []).length,
65
+ topics: (bb.topics || []).length,
66
+ messages: (bb.messages || []).length,
67
+ contexts: (bb.contexts || []).length,
68
+ artifacts: (bb.artifacts || []).length,
69
+ snapshots: (bb.snapshots || []).length,
70
+ decisions: (bb.decisions || []).length,
71
+ graphNodes: graph.nodes.length,
72
+ graphEdges: graph.edges.length
73
+ };
74
+ const total = counts.multiAgentRuns +
75
+ counts.roles +
76
+ counts.groups +
77
+ counts.memberships +
78
+ counts.fanouts +
79
+ counts.fanins +
80
+ counts.topics +
81
+ counts.messages +
82
+ counts.contexts +
83
+ counts.artifacts +
84
+ counts.snapshots +
85
+ counts.decisions;
86
+ const reasons = [];
87
+ if (counts.graphNodes > thresholds.graphNodes)
88
+ reasons.push(`graph has ${counts.graphNodes} nodes (> ${thresholds.graphNodes})`);
89
+ if (counts.graphEdges > thresholds.graphEdges)
90
+ reasons.push(`graph has ${counts.graphEdges} edges (> ${thresholds.graphEdges})`);
91
+ if (counts.messages > thresholds.blackboardMessages)
92
+ reasons.push(`blackboard has ${counts.messages} messages (> ${thresholds.blackboardMessages})`);
93
+ const bbRecords = counts.topics + counts.messages + counts.contexts + counts.artifacts + counts.snapshots + counts.decisions;
94
+ if (bbRecords > thresholds.blackboardRecords)
95
+ reasons.push(`blackboard has ${bbRecords} records (> ${thresholds.blackboardRecords})`);
96
+ if (total > thresholds.totalRecords)
97
+ reasons.push(`run has ${total} multi-agent records (> ${thresholds.totalRecords})`);
98
+ return { ...counts, total, compactionRecommended: reasons.length > 0, reasons: reasons.sort() };
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // Blackboard digest (deterministic structural summary)
102
+ // ---------------------------------------------------------------------------
103
+ function summarizeBlackboardDigest(run, blackboardId) {
104
+ const bb = run.blackboard || {
105
+ boards: [],
106
+ topics: [],
107
+ messages: [],
108
+ contexts: [],
109
+ artifacts: [],
110
+ snapshots: [],
111
+ decisions: []
112
+ };
113
+ const board = blackboardId ? (bb.boards || []).find((b) => b.id === blackboardId) : (bb.boards || [])[0];
114
+ const boardId = board?.id;
115
+ const inBoard = (items) => boardId ? items.filter((item) => item.blackboardId === boardId) : items;
116
+ const topics = inBoard(bb.topics || []);
117
+ const messages = inBoard(bb.messages || []);
118
+ const contexts = inBoard(bb.contexts || []);
119
+ const artifacts = inBoard(bb.artifacts || []);
120
+ const decisions = inBoard(bb.decisions || []);
121
+ const summary = (0, coordinator_1.summarizeBlackboard)(run, boardId);
122
+ const topicRollups = topics
123
+ .map((topic) => {
124
+ const topicMessages = messages.filter((m) => m.topicId === topic.id);
125
+ return {
126
+ id: topic.id,
127
+ label: `${topic.title} (${topicMessages.length} messages, ${topic.contextIds.length} contexts, ${topic.artifactRefIds.length} artifacts)`,
128
+ status: topic.status,
129
+ sourceIds: [topic.id, ...topicMessages.map((m) => m.id)],
130
+ evidenceRefs: unique(topicMessages.flatMap((m) => m.linkedEvidenceRefs || [])),
131
+ expansionCommand: `node scripts/cw.js blackboard message list ${run.id} --topic ${topic.id}`
132
+ };
133
+ })
134
+ .sort(byId);
135
+ const threadSummaries = topics
136
+ .map((topic) => {
137
+ const topicMessages = messages
138
+ .filter((m) => m.topicId === topic.id)
139
+ .sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
140
+ const last = topicMessages[topicMessages.length - 1];
141
+ return {
142
+ id: `thread:${topic.id}`,
143
+ label: `${topic.title}: ${topicMessages.length} messages${last ? `; latest by ${last.author.kind}:${last.author.id}` : ""}`,
144
+ status: topic.status,
145
+ sourceIds: topicMessages.map((m) => m.id),
146
+ evidenceRefs: unique(topicMessages.flatMap((m) => m.linkedEvidenceRefs || [])),
147
+ expansionCommand: `node scripts/cw.js blackboard message list ${run.id} --topic ${topic.id}`
148
+ };
149
+ })
150
+ .filter((entry) => entry.sourceIds.length)
151
+ .sort(byId);
152
+ const unresolvedQuestions = contexts
153
+ .filter((c) => c.kind === "question" && c.status === "open")
154
+ .map((c) => ({
155
+ id: c.id,
156
+ label: `${c.key}: ${truncate(c.value)}`,
157
+ status: c.status,
158
+ sourceIds: [c.id],
159
+ evidenceRefs: unique([...(c.evidenceRefs || []), ...(c.artifactRefIds || [])]),
160
+ expansionCommand: `node scripts/cw.js blackboard message post ${run.id} --topic ${c.topicId} --body "<answer with evidence>"`
161
+ }))
162
+ .sort(byId);
163
+ const conflicts = contexts
164
+ .filter((c) => c.status === "conflicting" || (c.conflictingContextIds || []).length)
165
+ .map((c) => ({
166
+ id: c.id,
167
+ label: `${c.key} conflicts with ${(c.conflictingContextIds || []).join(", ") || "another value"}`,
168
+ status: c.status,
169
+ sourceIds: [c.id, ...(c.conflictingContextIds || [])],
170
+ evidenceRefs: unique([...(c.evidenceRefs || []), ...(c.artifactRefIds || [])]),
171
+ expansionCommand: `node scripts/cw.js coordinator decision ${run.id} --kind conflict-resolution --outcome accepted --subject ${c.id} --reason "<reason>"`
172
+ }))
173
+ .sort(byId);
174
+ const decisionEntries = decisions
175
+ .map((d) => ({
176
+ id: d.id,
177
+ label: `${d.kind}:${d.outcome} ${truncate(d.reason)}`,
178
+ status: d.status,
179
+ sourceIds: [d.id, ...(d.subjectIds || [])],
180
+ evidenceRefs: unique([...(d.evidenceRefs || []), ...(d.artifactRefIds || [])]),
181
+ expansionCommand: `node scripts/cw.js node show ${run.id} ${run.id}:coordinator:decision:${d.id}`
182
+ }))
183
+ .sort(byId);
184
+ const artifactEntries = artifacts
185
+ .map((a) => ({
186
+ id: a.id,
187
+ label: `${a.kind} ${a.locator || a.path || a.id}`,
188
+ status: a.status,
189
+ sourceIds: [a.id],
190
+ evidenceRefs: unique(a.evidenceRefs || []),
191
+ expansionCommand: `node scripts/cw.js blackboard artifact list ${run.id}`
192
+ }))
193
+ .sort(byId);
194
+ const adoptedEvidence = artifacts
195
+ .filter((a) => a.status === "active")
196
+ .map((a) => ({
197
+ id: `evidence:${a.id}`,
198
+ label: `${a.kind} ${a.locator || a.path || a.id}`,
199
+ status: a.status,
200
+ sourceIds: [a.id],
201
+ evidenceRefs: unique([a.locator || a.path || a.id, ...(a.evidenceRefs || [])]),
202
+ expansionCommand: `node scripts/cw.js audit blackboard ${run.id} --json`
203
+ }))
204
+ .sort(byId);
205
+ const missingEvidence = (summary.missingEvidence || [])
206
+ .map((reason, index) => ({
207
+ id: `missing:${index}:${slug(reason)}`,
208
+ label: reason,
209
+ status: "missing",
210
+ sourceIds: [],
211
+ evidenceRefs: [],
212
+ expansionCommand: `node scripts/cw.js multi-agent failures ${run.id}`
213
+ }))
214
+ .sort(byId);
215
+ const policyViolations = decisions
216
+ .filter((d) => d.outcome === "rejected" || d.outcome === "blocked" || d.outcome === "conflicting")
217
+ .map((d) => ({
218
+ id: `policy:${d.id}`,
219
+ label: `${d.kind}:${d.outcome} ${truncate(d.reason)}`,
220
+ status: d.status,
221
+ sourceIds: [d.id],
222
+ evidenceRefs: unique(d.evidenceRefs || []),
223
+ expansionCommand: `node scripts/cw.js audit policy ${run.id} --json`
224
+ }))
225
+ .sort(byId);
226
+ const judgeRationale = messages
227
+ .filter((m) => (m.tags || []).includes("judge-rationale") || Boolean(m.metadata?.judgeRationale))
228
+ .map((m) => ({
229
+ id: `judge:${m.id}`,
230
+ label: `${m.author.kind}:${m.author.id} ${truncate(m.body)}`,
231
+ status: m.status,
232
+ sourceIds: [m.id],
233
+ evidenceRefs: unique(m.linkedEvidenceRefs || []),
234
+ expansionCommand: `node scripts/cw.js audit judge ${run.id} --json`
235
+ }))
236
+ .sort(byId);
237
+ const recentChanges = [...messages, ...contexts, ...artifacts, ...decisions]
238
+ .map((record) => ({
239
+ id: record.id,
240
+ kind: record.kind,
241
+ updatedAt: record.updatedAt,
242
+ status: record.status
243
+ }))
244
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt) || a.id.localeCompare(b.id))
245
+ .slice(0, 10)
246
+ .map((record) => ({
247
+ id: `recent:${record.id}`,
248
+ label: `${record.id} (${record.status})`,
249
+ status: record.status,
250
+ sourceIds: [record.id],
251
+ evidenceRefs: [],
252
+ expansionCommand: `node scripts/cw.js node show ${run.id} ${record.id}`
253
+ }))
254
+ .sort(byId);
255
+ const highSignal = [
256
+ ...conflicts,
257
+ ...unresolvedQuestions,
258
+ ...policyViolations,
259
+ ...missingEvidence
260
+ ].sort(byId);
261
+ const sourceRecordIds = unique([
262
+ ...topics.map((t) => t.id),
263
+ ...messages.map((m) => m.id),
264
+ ...contexts.map((c) => c.id),
265
+ ...artifacts.map((a) => a.id),
266
+ ...decisions.map((d) => d.id)
267
+ ]);
268
+ const evidenceRefs = unique([
269
+ ...messages.flatMap((m) => m.linkedEvidenceRefs || []),
270
+ ...artifacts.flatMap((a) => [a.locator || a.path || a.id, ...(a.evidenceRefs || [])]),
271
+ ...contexts.flatMap((c) => c.evidenceRefs || [])
272
+ ]);
273
+ const trustAuditEventRefs = unique([
274
+ ...messages.flatMap((m) => m.linkedAuditEventIds || []),
275
+ ...artifacts.flatMap((a) => a.trustAuditEventIds || [])
276
+ ]);
277
+ const fingerprint = fingerprintRecords([...topics, ...messages, ...contexts, ...artifacts, ...decisions]);
278
+ return {
279
+ schemaVersion: exports.STATE_EXPLOSION_SCHEMA_VERSION,
280
+ runId: run.id,
281
+ id: `blackboard-digest${boardId ? `:${boardId}` : ""}`,
282
+ scope: "blackboard",
283
+ blackboardId: boardId,
284
+ sourceRecordIds,
285
+ sourceFingerprint: fingerprint,
286
+ includedCount: topicRollups.length + conflicts.length + unresolvedQuestions.length + decisionEntries.length + artifactEntries.length,
287
+ omittedCount: Math.max(0, messages.length - threadSummaries.length),
288
+ importantRefs: unique([
289
+ ...conflicts.map((c) => c.id),
290
+ ...unresolvedQuestions.map((q) => q.id),
291
+ ...policyViolations.map((p) => p.id)
292
+ ]),
293
+ evidenceRefs,
294
+ trustAuditEventRefs,
295
+ generatedAt: new Date().toISOString(),
296
+ status: "valid",
297
+ deterministic: true,
298
+ nextAction: summary.nextAction || `node scripts/cw.js blackboard summary ${run.id}`,
299
+ topicRollups,
300
+ threadSummaries,
301
+ unresolvedQuestions,
302
+ conflicts,
303
+ decisions: decisionEntries,
304
+ artifacts: artifactEntries,
305
+ adoptedEvidence,
306
+ missingEvidence,
307
+ policyViolations,
308
+ judgeRationale,
309
+ recentChanges,
310
+ highSignal
311
+ };
312
+ }
313
+ function buildCompactGraph(run, view = "compact", options = {}) {
314
+ const thresholds = options.thresholds || exports.DEFAULT_STATE_EXPLOSION_THRESHOLDS;
315
+ const full = (0, multi_agent_operator_ux_1.buildMultiAgentOperatorGraph)(run);
316
+ const operator = (0, multi_agent_operator_ux_1.summarizeMultiAgentOperator)(run);
317
+ const critical = criticalPathNodeIds(run, operator);
318
+ const protectedIds = new Set(critical);
319
+ // Failures, blocked, rejected, conflicting nodes are always preserved.
320
+ for (const node of full.nodes) {
321
+ if (isProtectedStatus(node.status))
322
+ protectedIds.add(node.id);
323
+ }
324
+ // v0.1.26: reasoning steps are on the critical path and must never be collapsed
325
+ // into a synthetic summary node — protect every decision-gate node backing an
326
+ // adopted reasoning chain (notably score nodes, which are otherwise collapsed).
327
+ for (const id of (0, evidence_reasoning_1.reasoningCriticalNodeIds)(run))
328
+ protectedIds.add(id);
329
+ for (const failure of operator.failures) {
330
+ if (failure.linked)
331
+ protectedIds.add(failure.linked);
332
+ }
333
+ const parents = parentMap(full.edges);
334
+ const parentOf = (id) => parents.get(id);
335
+ let scopeNodes = full.nodes;
336
+ let scopeEdges = full.edges;
337
+ if (view !== "full" && view !== "compact" && view !== "critical-path") {
338
+ const filtered = filterByView(run, view, full, operator, protectedIds);
339
+ scopeNodes = filtered.nodes;
340
+ scopeEdges = filtered.edges;
341
+ }
342
+ // Focus + depth: keep nodes within BFS depth of focus; collapse the rest.
343
+ let focusKeep;
344
+ if (options.focus) {
345
+ focusKeep = bfsNeighborhood(options.focus, scopeNodes, scopeEdges, options.depth ?? 1);
346
+ for (const id of focusKeep)
347
+ protectedIds.add(id);
348
+ }
349
+ const collapseEnabled = view === "compact" || view === "critical-path" || Boolean(options.focus);
350
+ if (view === "full" || !collapseEnabled) {
351
+ // No collapse: emit scoped graph verbatim (still records provenance + critical path).
352
+ return finalizeGraphRecord(run, view, options, full, {
353
+ nodes: scopeNodes.map((node) => ({ ...node })),
354
+ edges: scopeEdges.map((edge) => ({ ...edge })),
355
+ syntheticNodes: [],
356
+ critical,
357
+ operator
358
+ });
359
+ }
360
+ // Determine collapse buckets per node.
361
+ const rule = collapseRuleFor(view);
362
+ const keep = new Set();
363
+ const buckets = new Map();
364
+ for (const node of scopeNodes) {
365
+ if (protectedIds.has(node.id) || (focusKeep && focusKeep.has(node.id))) {
366
+ keep.add(node.id);
367
+ continue;
368
+ }
369
+ if (view === "critical-path") {
370
+ // Collapse everything not on the critical path into one bucket per kind.
371
+ const key = `critical-context:${node.kind}`;
372
+ buckets.set(key, [...(buckets.get(key) || []), node.id]);
373
+ continue;
374
+ }
375
+ if (!shouldCollapseKind(node.kind, rule)) {
376
+ keep.add(node.id);
377
+ continue;
378
+ }
379
+ const key = rule.bucketBy(node, parentOf);
380
+ buckets.set(key, [...(buckets.get(key) || []), node.id]);
381
+ }
382
+ // Buckets smaller than the collapse threshold stay expanded (unless critical-path).
383
+ const synthetic = [];
384
+ const collapsedNodeIds = new Map(); // sourceNodeId -> syntheticId
385
+ for (const [key, ids] of [...buckets.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
386
+ if (view !== "critical-path" && ids.length < thresholds.collapseBucket) {
387
+ for (const id of ids)
388
+ keep.add(id);
389
+ continue;
390
+ }
391
+ const members = scopeNodes.filter((node) => ids.includes(node.id));
392
+ const internalEdges = scopeEdges.filter((edge) => ids.includes(edge.from) && ids.includes(edge.to));
393
+ const syntheticId = `${run.id}:summary:${slug(key)}`;
394
+ const dominant = dominantStatus(members.map((m) => m.status));
395
+ const blocked = members.find((m) => isProtectedStatus(m.status));
396
+ synthetic.push({
397
+ id: syntheticId,
398
+ kind: "summary",
399
+ label: `${key} (${ids.length} collapsed)`,
400
+ status: dominant,
401
+ collapsedNodeCount: ids.length,
402
+ collapsedEdgeCount: internalEdges.length,
403
+ sourceIds: [...ids].sort(),
404
+ dominantStatus: dominant,
405
+ blockedReason: blocked ? `${blocked.kind} ${blocked.id} is ${blocked.status}` : undefined,
406
+ expansionCommand: expansionCommandFor(run, view, key)
407
+ });
408
+ for (const id of ids)
409
+ collapsedNodeIds.set(id, syntheticId);
410
+ }
411
+ const redirect = (id) => collapsedNodeIds.get(id) || id;
412
+ const nodes = [];
413
+ for (const node of scopeNodes) {
414
+ if (keep.has(node.id))
415
+ nodes.push({ ...node });
416
+ }
417
+ for (const syn of synthetic) {
418
+ nodes.push({
419
+ id: syn.id,
420
+ kind: "summary",
421
+ label: syn.label,
422
+ status: syn.status,
423
+ synthetic: syn
424
+ });
425
+ }
426
+ const edgeSeen = new Set();
427
+ const edges = [];
428
+ for (const edge of scopeEdges) {
429
+ const from = redirect(edge.from);
430
+ const to = redirect(edge.to);
431
+ if (from === to)
432
+ continue; // edge fully internal to a synthetic node
433
+ const key = `${from}\0${to}\0${edge.label || ""}`;
434
+ if (edgeSeen.has(key))
435
+ continue;
436
+ edgeSeen.add(key);
437
+ edges.push({ from, to, label: edge.label });
438
+ }
439
+ return finalizeGraphRecord(run, view, options, full, {
440
+ nodes: nodes.sort((a, b) => a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id)),
441
+ edges: edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || (a.label || "").localeCompare(b.label || "")),
442
+ syntheticNodes: synthetic.sort((a, b) => a.id.localeCompare(b.id)),
443
+ critical,
444
+ operator
445
+ });
446
+ }
447
+ function finalizeGraphRecord(run, view, options, full, built) {
448
+ const collapsedNodeCount = built.syntheticNodes.reduce((acc, syn) => acc + syn.collapsedNodeCount, 0);
449
+ const collapsedEdgeCount = built.syntheticNodes.reduce((acc, syn) => acc + syn.collapsedEdgeCount, 0);
450
+ const blockedReasons = unique([
451
+ ...built.operator.failures.map((f) => `${f.kind} ${f.id}: ${f.reason}`),
452
+ ...built.syntheticNodes.filter((s) => s.blockedReason).map((s) => s.blockedReason)
453
+ ]);
454
+ return {
455
+ schemaVersion: exports.STATE_EXPLOSION_SCHEMA_VERSION,
456
+ runId: run.id,
457
+ id: `graph-${view}${options.focus ? `:focus:${slug(options.focus)}` : ""}`,
458
+ scope: "run",
459
+ view,
460
+ focus: options.focus,
461
+ depth: options.depth,
462
+ fullNodeCount: full.nodes.length,
463
+ fullEdgeCount: full.edges.length,
464
+ compactNodeCount: built.nodes.length,
465
+ compactEdgeCount: built.edges.length,
466
+ collapsedNodeCount,
467
+ collapsedEdgeCount,
468
+ syntheticNodes: built.syntheticNodes,
469
+ criticalPath: built.critical,
470
+ blockedReasons,
471
+ nodes: built.nodes,
472
+ edges: built.edges,
473
+ sourceRecordIds: full.nodes.map((n) => n.id).sort(),
474
+ sourceFingerprint: fingerprintStrings(full.nodes.map((n) => `${n.id}:${n.status}`)),
475
+ includedCount: built.nodes.length,
476
+ omittedCount: collapsedNodeCount,
477
+ importantRefs: built.critical,
478
+ evidenceRefs: [],
479
+ trustAuditEventRefs: [],
480
+ generatedAt: new Date().toISOString(),
481
+ status: "valid",
482
+ deterministic: true,
483
+ nextAction: collapsedNodeCount > 0
484
+ ? `node scripts/cw.js multi-agent graph ${run.id} --view full --json`
485
+ : `node scripts/cw.js multi-agent graph ${run.id} --view ${view} --json`
486
+ };
487
+ }
488
+ function collapseRuleFor(view) {
489
+ return {
490
+ collapse: true,
491
+ bucketBy: (node, parentOf) => {
492
+ switch (node.kind) {
493
+ case "blackboard-message":
494
+ return "messages";
495
+ case "blackboard-context":
496
+ return "contexts";
497
+ case "agent-membership": {
498
+ const parent = parentOf(node.id);
499
+ return `memberships:${parent ? parent.split(":").pop() : "unscoped"}`;
500
+ }
501
+ case "worker":
502
+ return "workers";
503
+ case "score":
504
+ return "scores";
505
+ case "blackboard-snapshot":
506
+ return "snapshots";
507
+ default:
508
+ return `${node.kind}`;
509
+ }
510
+ }
511
+ };
512
+ }
513
+ function shouldCollapseKind(kind, _rule) {
514
+ // Collapsible kinds (high-volume, low-individual-signal). Decisions, artifacts,
515
+ // fanins, candidates, selections, commits and feedback are NEVER collapsed so
516
+ // failures, evidence, policy and judge rationale stay visible.
517
+ return [
518
+ "blackboard-message",
519
+ "blackboard-context",
520
+ "agent-membership",
521
+ "worker",
522
+ "score",
523
+ "blackboard-snapshot",
524
+ "agent-role"
525
+ ].includes(kind);
526
+ }
527
+ function filterByView(run, view, full, operator, protectedIds) {
528
+ const keepKinds = (kinds) => {
529
+ const ids = new Set();
530
+ for (const node of full.nodes) {
531
+ if (kinds.includes(node.kind) || protectedIds.has(node.id))
532
+ ids.add(node.id);
533
+ }
534
+ return ids;
535
+ };
536
+ let ids;
537
+ switch (view) {
538
+ case "failures": {
539
+ ids = new Set();
540
+ for (const failure of operator.failures) {
541
+ if (failure.linked)
542
+ ids.add(failure.linked);
543
+ }
544
+ for (const node of full.nodes)
545
+ if (isProtectedStatus(node.status))
546
+ ids.add(node.id);
547
+ ids.add(`${run.id}:run`);
548
+ break;
549
+ }
550
+ case "evidence":
551
+ ids = keepKinds([
552
+ "multi-agent-run-root",
553
+ "blackboard",
554
+ "blackboard-topic",
555
+ "blackboard-artifact",
556
+ "blackboard-message",
557
+ "agent-membership",
558
+ "agent-fanin",
559
+ "candidate",
560
+ "selection",
561
+ "commit"
562
+ ]);
563
+ break;
564
+ case "trust":
565
+ ids = keepKinds([
566
+ "multi-agent-run-root",
567
+ "blackboard",
568
+ "coordinator-decision",
569
+ "agent-fanin",
570
+ "candidate",
571
+ "selection",
572
+ "commit"
573
+ ]);
574
+ break;
575
+ case "topology":
576
+ ids = keepKinds([
577
+ "multi-agent-run-root",
578
+ "topology",
579
+ "multi-agent-run",
580
+ "agent-group",
581
+ "agent-role",
582
+ "agent-fanout",
583
+ "agent-fanin"
584
+ ]);
585
+ break;
586
+ case "blackboard":
587
+ ids = keepKinds([
588
+ "multi-agent-run-root",
589
+ "blackboard",
590
+ "blackboard-topic",
591
+ "blackboard-message",
592
+ "blackboard-context",
593
+ "blackboard-artifact",
594
+ "blackboard-snapshot",
595
+ "coordinator-decision"
596
+ ]);
597
+ break;
598
+ case "candidate":
599
+ ids = keepKinds(["multi-agent-run-root", "candidate", "score", "selection", "worker", "agent-fanin"]);
600
+ break;
601
+ case "commit-gate":
602
+ ids = keepKinds(["multi-agent-run-root", "selection", "commit", "candidate", "agent-fanin"]);
603
+ break;
604
+ default:
605
+ ids = new Set(full.nodes.map((n) => n.id));
606
+ }
607
+ const nodes = full.nodes.filter((node) => ids.has(node.id));
608
+ const edges = full.edges.filter((edge) => ids.has(edge.from) && ids.has(edge.to));
609
+ return { nodes, edges };
610
+ }
611
+ function criticalPathNodeIds(run, operator) {
612
+ const ids = [`${run.id}:run`];
613
+ const ma = run.multiAgent;
614
+ for (const record of ma?.runs || [])
615
+ ids.push(`${run.id}:multi-agent:${record.id}`);
616
+ for (const group of ma?.groups || [])
617
+ ids.push(`${run.id}:multi-agent:group:${group.id}`);
618
+ for (const fanout of ma?.fanouts || [])
619
+ ids.push(`${run.id}:multi-agent:fanout:${fanout.id}`);
620
+ for (const fanin of ma?.fanins || [])
621
+ ids.push(`${run.id}:multi-agent:fanin:${fanin.id}`);
622
+ for (const selection of run.candidateSelections || []) {
623
+ ids.push(`${run.id}:selection:${selection.id}`);
624
+ ids.push(`${run.id}:candidate:${selection.candidateId}`);
625
+ }
626
+ for (const commit of run.commits || []) {
627
+ if (commit.verifierGated)
628
+ ids.push(commit.stateNodeId || `${run.id}:commit:${commit.id}`);
629
+ }
630
+ // Blocked dependencies live on the critical path because they gate completion.
631
+ for (const failure of operator.failures) {
632
+ if (failure.linked)
633
+ ids.push(failure.linked);
634
+ }
635
+ return unique(ids);
636
+ }
637
+ function bfsNeighborhood(focus, nodes, edges, depth) {
638
+ const adjacency = new Map();
639
+ for (const edge of edges) {
640
+ if (!adjacency.has(edge.from))
641
+ adjacency.set(edge.from, new Set());
642
+ if (!adjacency.has(edge.to))
643
+ adjacency.set(edge.to, new Set());
644
+ adjacency.get(edge.from).add(edge.to);
645
+ adjacency.get(edge.to).add(edge.from);
646
+ }
647
+ const keep = new Set([focus]);
648
+ let frontier = new Set([focus]);
649
+ for (let level = 0; level < Math.max(0, depth); level += 1) {
650
+ const next = new Set();
651
+ for (const id of frontier) {
652
+ for (const neighbor of adjacency.get(id) || []) {
653
+ if (!keep.has(neighbor)) {
654
+ keep.add(neighbor);
655
+ next.add(neighbor);
656
+ }
657
+ }
658
+ }
659
+ frontier = next;
660
+ }
661
+ return keep;
662
+ }
663
+ function expansionCommandFor(run, view, key) {
664
+ if (key === "messages" || key.startsWith("thread"))
665
+ return `node scripts/cw.js blackboard message list ${run.id}`;
666
+ if (key.startsWith("memberships"))
667
+ return `node scripts/cw.js multi-agent graph ${run.id} --view full --json`;
668
+ return `node scripts/cw.js multi-agent graph ${run.id} --view full --focus ${key} --json`;
669
+ }
670
+ // ---------------------------------------------------------------------------
671
+ // Operator digest
672
+ // ---------------------------------------------------------------------------
673
+ function buildOperatorDigest(run, thresholds = exports.DEFAULT_STATE_EXPLOSION_THRESHOLDS) {
674
+ const stateSize = computeStateSize(run, thresholds);
675
+ const operator = (0, multi_agent_operator_ux_1.summarizeMultiAgentOperator)(run);
676
+ const compact = buildCompactGraph(run, "compact", { thresholds });
677
+ const blackboard = summarizeBlackboardDigest(run);
678
+ const evidence = operator.evidence;
679
+ const adopted = evidence.filter((e) => e.status === "adopted");
680
+ const missing = evidence.filter((e) => e.status === "missing" || e.status === "pending" || e.status === "conflicting");
681
+ const rejected = evidence.filter((e) => e.status === "rejected");
682
+ const trust = operator.summaries.trust;
683
+ const hiddenSourceRecords = compact.syntheticNodes.map((syn) => ({
684
+ kind: syn.id.split(":summary:")[1] || syn.kind,
685
+ count: syn.collapsedNodeCount,
686
+ expansionCommand: syn.expansionCommand
687
+ }));
688
+ const expansionCommands = unique([
689
+ `node scripts/cw.js multi-agent graph ${run.id} --view full --json`,
690
+ `node scripts/cw.js blackboard message list ${run.id} --topic <topic-id>`,
691
+ `node scripts/cw.js multi-agent graph ${run.id} --view critical-path`,
692
+ `node scripts/cw.js multi-agent failures ${run.id} --json`,
693
+ ...compact.syntheticNodes.map((syn) => syn.expansionCommand)
694
+ ]);
695
+ return {
696
+ schemaVersion: exports.STATE_EXPLOSION_SCHEMA_VERSION,
697
+ runId: run.id,
698
+ id: "operator-digest",
699
+ scope: "run",
700
+ sourceRecordIds: compact.sourceRecordIds,
701
+ sourceFingerprint: fingerprintStrings([
702
+ compact.sourceFingerprint,
703
+ blackboard.sourceFingerprint,
704
+ String(stateSize.total)
705
+ ]),
706
+ includedCount: compact.compactNodeCount,
707
+ omittedCount: compact.collapsedNodeCount,
708
+ importantRefs: compact.criticalPath,
709
+ evidenceRefs: unique(adopted.map((e) => e.ref || e.id)),
710
+ trustAuditEventRefs: [],
711
+ generatedAt: new Date().toISOString(),
712
+ status: "valid",
713
+ deterministic: true,
714
+ nextAction: operator.nextAction,
715
+ stateSize,
716
+ compactGraphRef: compact.id,
717
+ blackboardDigestRef: blackboard.id,
718
+ criticalPath: compact.criticalPath,
719
+ failures: operator.failures.map((f) => ({
720
+ id: f.id,
721
+ kind: f.kind,
722
+ status: f.status,
723
+ reason: f.reason,
724
+ nextCommand: f.nextCommand
725
+ })),
726
+ evidenceDigest: {
727
+ adopted: adopted.length,
728
+ missing: missing.length,
729
+ rejected: rejected.length,
730
+ entries: [...adopted, ...missing].slice(0, 40).map((e) => ({
731
+ id: e.id,
732
+ label: `${e.ref || e.id} (${e.status})`,
733
+ status: e.status,
734
+ sourceIds: [e.sourceId || e.id].filter(Boolean),
735
+ evidenceRefs: [e.ref || e.id].filter(Boolean),
736
+ expansionCommand: `node scripts/cw.js multi-agent evidence ${run.id} --json`
737
+ }))
738
+ },
739
+ trustDigest: {
740
+ events: trust?.totalEvents || 0,
741
+ policyViolations: blackboard.policyViolations.length,
742
+ judgeRationales: blackboard.judgeRationale.length,
743
+ entries: unique([
744
+ ...blackboard.policyViolations.map((p) => p.id),
745
+ ...blackboard.judgeRationale.map((j) => j.id)
746
+ ])
747
+ },
748
+ hiddenSourceRecords,
749
+ expansionCommands
750
+ };
751
+ }
752
+ // ---------------------------------------------------------------------------
753
+ // State explosion report (combines all derived indexes)
754
+ // ---------------------------------------------------------------------------
755
+ function buildStateExplosionReport(run, options = {}) {
756
+ const thresholds = options.thresholds || exports.DEFAULT_STATE_EXPLOSION_THRESHOLDS;
757
+ const stateSize = computeStateSize(run, thresholds);
758
+ const compactGraph = buildCompactGraph(run, "compact", { thresholds });
759
+ const criticalPathGraph = buildCompactGraph(run, "critical-path", { thresholds });
760
+ const blackboardDigest = summarizeBlackboardDigest(run);
761
+ const operatorDigest = buildOperatorDigest(run, thresholds);
762
+ const currentFingerprint = fingerprintStrings([
763
+ compactGraph.sourceFingerprint,
764
+ blackboardDigest.sourceFingerprint,
765
+ operatorDigest.sourceFingerprint,
766
+ String(stateSize.total)
767
+ ]);
768
+ const persisted = options.index;
769
+ const staleScopes = [];
770
+ let status = persisted ? "valid" : "absent";
771
+ if (persisted) {
772
+ if (persisted.sourceFingerprint !== currentFingerprint)
773
+ status = "stale";
774
+ for (const entry of persisted.entries) {
775
+ const current = currentEntryFingerprint(run, entry, { compactGraph, blackboardDigest, operatorDigest });
776
+ if (current && current !== entry.sourceFingerprint)
777
+ staleScopes.push(`${entry.scope}:${entry.id}`);
778
+ }
779
+ if (staleScopes.length)
780
+ status = "stale";
781
+ }
782
+ const nextAction = status === "stale" || status === "absent"
783
+ ? `node scripts/cw.js summary refresh ${run.id}`
784
+ : operatorDigest.nextAction;
785
+ return {
786
+ schemaVersion: exports.STATE_EXPLOSION_SCHEMA_VERSION,
787
+ runId: run.id,
788
+ generatedAt: new Date().toISOString(),
789
+ stateSize,
790
+ freshness: {
791
+ status,
792
+ persistedFingerprint: persisted?.sourceFingerprint,
793
+ currentFingerprint,
794
+ staleScopes: staleScopes.sort()
795
+ },
796
+ index: persisted,
797
+ compactGraph,
798
+ criticalPathGraph,
799
+ blackboardDigest,
800
+ operatorDigest,
801
+ hiddenSourceRecords: operatorDigest.hiddenSourceRecords,
802
+ expansionCommands: operatorDigest.expansionCommands,
803
+ nextAction
804
+ };
805
+ }
806
+ function currentEntryFingerprint(run, entry, records) {
807
+ if (entry.scope === "blackboard")
808
+ return records.blackboardDigest.sourceFingerprint;
809
+ if (entry.id.startsWith("graph-")) {
810
+ if (entry.id === records.compactGraph.id)
811
+ return records.compactGraph.sourceFingerprint;
812
+ return undefined;
813
+ }
814
+ if (entry.id === "operator-digest")
815
+ return records.operatorDigest.sourceFingerprint;
816
+ return undefined;
817
+ }
818
+ // ---------------------------------------------------------------------------
819
+ // Persistence + refresh
820
+ // ---------------------------------------------------------------------------
821
+ /** Check state size and auto-compact if thresholds exceeded. Best-effort —
822
+ * errors are silently caught; never fail a state mutation for compaction.
823
+ * BSD: mechanism (check + refresh); policy (when to call) is at the call site. */
824
+ function maybeCompactRun(run) {
825
+ try {
826
+ const size = computeStateSize(run);
827
+ if (size.compactionRecommended) {
828
+ refreshStateExplosionSummaries(run);
829
+ }
830
+ }
831
+ catch {
832
+ // Best-effort optimization only.
833
+ }
834
+ }
835
+ function summariesDir(run) {
836
+ return node_path_1.default.join(run.paths.runDir, "summaries");
837
+ }
838
+ function refreshStateExplosionSummaries(run, options = {}) {
839
+ const thresholds = options.thresholds || exports.DEFAULT_STATE_EXPLOSION_THRESHOLDS;
840
+ const dir = summariesDir(run);
841
+ node_fs_1.default.mkdirSync(dir, { recursive: true });
842
+ const views = options.views || ["full", "compact", "critical-path", "failures", "evidence", "trust", "topology", "blackboard", "candidate", "commit-gate"];
843
+ const blackboardDigest = summarizeBlackboardDigest(run);
844
+ const operatorDigest = buildOperatorDigest(run, thresholds);
845
+ const graphRecords = views.map((view) => buildCompactGraph(run, view, { thresholds }));
846
+ const entries = [];
847
+ const writeRecord = (id, record, scope, fingerprint, included, omitted) => {
848
+ const file = node_path_1.default.join(dir, `${(0, state_1.safeFileName)(id)}.json`);
849
+ (0, state_1.writeJson)(file, record);
850
+ entries.push({ scope, id, path: file, sourceFingerprint: fingerprint, includedCount: included, omittedCount: omitted, status: "valid" });
851
+ };
852
+ writeRecord(blackboardDigest.id, blackboardDigest, "blackboard", blackboardDigest.sourceFingerprint, blackboardDigest.includedCount, blackboardDigest.omittedCount);
853
+ writeRecord(operatorDigest.id, operatorDigest, "run", operatorDigest.sourceFingerprint, operatorDigest.includedCount, operatorDigest.omittedCount);
854
+ for (const record of graphRecords) {
855
+ writeRecord(record.id, record, "run", record.sourceFingerprint, record.compactNodeCount, record.collapsedNodeCount);
856
+ }
857
+ const stateSize = computeStateSize(run, thresholds);
858
+ const indexFingerprint = fingerprintStrings([
859
+ operatorDigest.sourceFingerprint,
860
+ blackboardDigest.sourceFingerprint,
861
+ ...graphRecords.map((r) => r.sourceFingerprint),
862
+ String(stateSize.total)
863
+ ]);
864
+ const reportPath = node_path_1.default.join(dir, "state-explosion-report.json");
865
+ const index = {
866
+ schemaVersion: exports.STATE_EXPLOSION_SCHEMA_VERSION,
867
+ runId: run.id,
868
+ id: "multi-agent-summary-index",
869
+ scope: "run",
870
+ sourceRecordIds: unique([...blackboardDigest.sourceRecordIds, ...operatorDigest.sourceRecordIds]),
871
+ sourceFingerprint: fingerprintStrings([
872
+ buildCompactGraph(run, "compact", { thresholds }).sourceFingerprint,
873
+ blackboardDigest.sourceFingerprint,
874
+ operatorDigest.sourceFingerprint,
875
+ String(stateSize.total)
876
+ ]),
877
+ includedCount: entries.reduce((acc, e) => acc + e.includedCount, 0),
878
+ omittedCount: entries.reduce((acc, e) => acc + e.omittedCount, 0),
879
+ importantRefs: operatorDigest.criticalPath,
880
+ evidenceRefs: operatorDigest.evidenceRefs,
881
+ trustAuditEventRefs: blackboardDigest.trustAuditEventRefs,
882
+ generatedAt: new Date().toISOString(),
883
+ status: "valid",
884
+ deterministic: true,
885
+ nextAction: `node scripts/cw.js summary show ${run.id}`,
886
+ entries: entries.sort((a, b) => a.id.localeCompare(b.id)),
887
+ views,
888
+ paths: {
889
+ summariesDir: dir,
890
+ indexPath: node_path_1.default.join(dir, "index.json"),
891
+ reportPath
892
+ }
893
+ };
894
+ void indexFingerprint;
895
+ (0, state_1.writeJson)(index.paths.indexPath, index);
896
+ const report = buildStateExplosionReport(run, { thresholds, index });
897
+ (0, state_1.writeJson)(reportPath, report);
898
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
899
+ kind: "summary.refresh",
900
+ decision: "recorded",
901
+ source: "runtime-derived",
902
+ actor: "cw",
903
+ metadata: {
904
+ deterministic: true,
905
+ scopes: entries.map((e) => `${e.scope}:${e.id}`),
906
+ includedRecords: index.includedCount,
907
+ omittedRecords: index.omittedCount,
908
+ compactionRecommended: stateSize.compactionRecommended,
909
+ sourceFingerprint: index.sourceFingerprint,
910
+ stale: false
911
+ }
912
+ });
913
+ return index;
914
+ }
915
+ function loadStateExplosionSummaryIndex(run) {
916
+ const indexPath = node_path_1.default.join(summariesDir(run), "index.json");
917
+ if (!node_fs_1.default.existsSync(indexPath))
918
+ return undefined;
919
+ try {
920
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(indexPath, "utf8"));
921
+ if (!parsed || parsed.id !== "multi-agent-summary-index")
922
+ return undefined;
923
+ return parsed;
924
+ }
925
+ catch {
926
+ return undefined;
927
+ }
928
+ }
929
+ function showStateExplosionSummary(run, options = {}) {
930
+ const index = loadStateExplosionSummaryIndex(run);
931
+ const report = buildStateExplosionReport(run, { thresholds: options.thresholds, index });
932
+ if (index && report.freshness.status === "stale") {
933
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
934
+ kind: "summary.stale",
935
+ decision: "failed",
936
+ source: "runtime-derived",
937
+ actor: "cw",
938
+ metadata: {
939
+ persistedFingerprint: report.freshness.persistedFingerprint,
940
+ currentFingerprint: report.freshness.currentFingerprint,
941
+ staleScopes: report.freshness.staleScopes
942
+ }
943
+ });
944
+ }
945
+ return report;
946
+ }
947
+ // ---------------------------------------------------------------------------
948
+ // Human formatting
949
+ // ---------------------------------------------------------------------------
950
+ function formatStateExplosionReport(report) {
951
+ const lines = [];
952
+ const size = report.stateSize;
953
+ lines.push(`State Explosion Report: ${report.runId}`);
954
+ lines.push(`Freshness: ${report.freshness.status}${report.freshness.staleScopes.length ? ` (stale: ${report.freshness.staleScopes.join(", ")})` : ""}`);
955
+ lines.push("");
956
+ lines.push("State Size");
957
+ lines.push(` records=${size.total}; graph nodes=${size.graphNodes}; graph edges=${size.graphEdges}; messages=${size.messages}; compaction=${size.compactionRecommended ? "recommended" : "not needed"}`);
958
+ for (const reason of size.reasons)
959
+ lines.push(` - ${reason}`);
960
+ lines.push("");
961
+ lines.push("Compact Graph");
962
+ lines.push(` full=${report.compactGraph.fullNodeCount} nodes/${report.compactGraph.fullEdgeCount} edges -> compact=${report.compactGraph.compactNodeCount} nodes/${report.compactGraph.compactEdgeCount} edges`);
963
+ if (report.compactGraph.collapsedNodeCount > 0) {
964
+ lines.push(` Graph compacted: ${report.compactGraph.collapsedNodeCount} nodes collapsed into ${report.compactGraph.syntheticNodes.length} summary nodes`);
965
+ }
966
+ for (const syn of report.compactGraph.syntheticNodes) {
967
+ lines.push(` [${syn.dominantStatus}] ${syn.id} collapses ${syn.collapsedNodeCount} nodes/${syn.collapsedEdgeCount} edges${syn.blockedReason ? ` blocked=${syn.blockedReason}` : ""}; expand: ${syn.expansionCommand}`);
968
+ }
969
+ lines.push("");
970
+ lines.push("Blackboard Digest");
971
+ lines.push(` topics=${report.blackboardDigest.topicRollups.length}; threads=${report.blackboardDigest.threadSummaries.length}; unresolved=${report.blackboardDigest.unresolvedQuestions.length}; conflicts=${report.blackboardDigest.conflicts.length}; decisions=${report.blackboardDigest.decisions.length}; artifacts=${report.blackboardDigest.artifacts.length}`);
972
+ for (const topic of report.blackboardDigest.topicRollups.slice(0, 20))
973
+ lines.push(` - ${topic.label}; expand: ${topic.expansionCommand}`);
974
+ lines.push("");
975
+ lines.push("Critical Path");
976
+ if (!report.criticalPathGraph.criticalPath.length)
977
+ lines.push(" none");
978
+ for (const id of report.criticalPathGraph.criticalPath.slice(0, 40))
979
+ lines.push(` -> ${id}`);
980
+ lines.push("");
981
+ lines.push("Failures / Blockers");
982
+ if (!report.operatorDigest.failures.length)
983
+ lines.push(" none");
984
+ for (const failure of report.operatorDigest.failures.slice(0, 30))
985
+ lines.push(` [${failure.status}] ${failure.kind} ${failure.id}: ${failure.reason}; next=${failure.nextCommand}`);
986
+ lines.push("");
987
+ lines.push("Evidence Digest");
988
+ lines.push(` adopted=${report.operatorDigest.evidenceDigest.adopted}; missing=${report.operatorDigest.evidenceDigest.missing}; rejected=${report.operatorDigest.evidenceDigest.rejected}`);
989
+ lines.push("");
990
+ lines.push("Trust / Policy Digest");
991
+ lines.push(` events=${report.operatorDigest.trustDigest.events}; policyViolations=${report.operatorDigest.trustDigest.policyViolations}; judgeRationales=${report.operatorDigest.trustDigest.judgeRationales}`);
992
+ for (const violation of report.blackboardDigest.policyViolations.slice(0, 20))
993
+ lines.push(` [policy] ${violation.label}; expand: ${violation.expansionCommand}`);
994
+ lines.push("");
995
+ lines.push("Hidden Source Records");
996
+ if (!report.hiddenSourceRecords.length)
997
+ lines.push(" none (all records shown)");
998
+ for (const hidden of report.hiddenSourceRecords)
999
+ lines.push(` ${hidden.kind}: ${hidden.count} records hidden; expand: ${hidden.expansionCommand}`);
1000
+ lines.push("");
1001
+ lines.push("Expansion Commands");
1002
+ for (const command of report.expansionCommands)
1003
+ lines.push(` ${command}`);
1004
+ lines.push("");
1005
+ lines.push("Next Action");
1006
+ lines.push(` ${report.nextAction}`);
1007
+ return lines.join("\n");
1008
+ }
1009
+ function formatCompactGraph(graph) {
1010
+ const lines = [];
1011
+ lines.push(`Compact Graph (${graph.view}): ${graph.runId}`);
1012
+ lines.push(` full=${graph.fullNodeCount} nodes/${graph.fullEdgeCount} edges -> view=${graph.compactNodeCount} nodes/${graph.compactEdgeCount} edges`);
1013
+ if (graph.collapsedNodeCount > 0) {
1014
+ lines.push(` Graph compacted: ${graph.collapsedNodeCount} nodes collapsed into ${graph.syntheticNodes.length} summary nodes`);
1015
+ }
1016
+ lines.push("");
1017
+ lines.push("Critical Path");
1018
+ if (!graph.criticalPath.length)
1019
+ lines.push(" none");
1020
+ for (const id of graph.criticalPath.slice(0, 40))
1021
+ lines.push(` -> ${id}`);
1022
+ lines.push("");
1023
+ lines.push("Summary Nodes");
1024
+ if (!graph.syntheticNodes.length)
1025
+ lines.push(" none");
1026
+ for (const syn of graph.syntheticNodes) {
1027
+ lines.push(` [${syn.dominantStatus}] ${syn.id}: ${syn.collapsedNodeCount} nodes / ${syn.collapsedEdgeCount} edges${syn.blockedReason ? ` blocked=${syn.blockedReason}` : ""}`);
1028
+ lines.push(` expand: ${syn.expansionCommand}`);
1029
+ }
1030
+ lines.push("");
1031
+ lines.push("Blockers");
1032
+ if (!graph.blockedReasons.length)
1033
+ lines.push(" none");
1034
+ for (const reason of graph.blockedReasons.slice(0, 20))
1035
+ lines.push(` ${reason}`);
1036
+ lines.push("");
1037
+ lines.push("Nodes");
1038
+ for (const node of graph.nodes.slice(0, 80)) {
1039
+ lines.push(` [${node.status}] ${node.kind} ${node.id}${node.synthetic ? ` (summary of ${node.synthetic.collapsedNodeCount})` : ""}`);
1040
+ }
1041
+ if (graph.nodes.length > 80)
1042
+ lines.push(` ... ${graph.nodes.length - 80} more`);
1043
+ lines.push("");
1044
+ lines.push("Next Action");
1045
+ lines.push(` ${graph.nextAction}`);
1046
+ return lines.join("\n");
1047
+ }
1048
+ function formatBlackboardDigest(record) {
1049
+ const lines = [];
1050
+ lines.push(`Blackboard Digest: ${record.runId}${record.blackboardId ? ` (${record.blackboardId})` : ""}`);
1051
+ lines.push(` freshness=${record.status}; included=${record.includedCount}; omitted=${record.omittedCount}`);
1052
+ const section = (title, entries) => {
1053
+ lines.push("");
1054
+ lines.push(title);
1055
+ if (!entries.length) {
1056
+ lines.push(" none");
1057
+ return;
1058
+ }
1059
+ for (const entry of entries.slice(0, 25))
1060
+ lines.push(` [${entry.status}] ${entry.label}; expand: ${entry.expansionCommand}`);
1061
+ if (entries.length > 25)
1062
+ lines.push(` ... ${entries.length - 25} more`);
1063
+ };
1064
+ section("Topic Rollups", record.topicRollups);
1065
+ section("Thread Summaries", record.threadSummaries);
1066
+ section("Unresolved Questions", record.unresolvedQuestions);
1067
+ section("Conflicts", record.conflicts);
1068
+ section("Decisions", record.decisions);
1069
+ section("Artifacts", record.artifacts);
1070
+ section("Adopted Evidence", record.adoptedEvidence);
1071
+ section("Missing Evidence", record.missingEvidence);
1072
+ section("Policy Violations", record.policyViolations);
1073
+ section("Judge Rationale", record.judgeRationale);
1074
+ section("Recent Changes", record.recentChanges);
1075
+ section("High-Signal Records", record.highSignal);
1076
+ lines.push("");
1077
+ lines.push("Next Action");
1078
+ lines.push(` ${record.nextAction}`);
1079
+ return lines.join("\n");
1080
+ }
1081
+ function stateExplosionReportLines(report) {
1082
+ // Markdown lines for inclusion in the run report.md State Size section.
1083
+ const size = report.stateSize;
1084
+ const lines = [
1085
+ `- Records: ${size.total}; graph nodes: ${size.graphNodes}; graph edges: ${size.graphEdges}; messages: ${size.messages}`,
1086
+ `- Compaction: ${size.compactionRecommended ? "recommended" : "not needed"}`,
1087
+ `- Summary freshness: ${report.freshness.status}`
1088
+ ];
1089
+ for (const reason of size.reasons)
1090
+ lines.push(` - ${reason}`);
1091
+ if (report.compactGraph.collapsedNodeCount > 0) {
1092
+ lines.push(`- Graph compacted: ${report.compactGraph.collapsedNodeCount} nodes collapsed into ${report.compactGraph.syntheticNodes.length} summary nodes`);
1093
+ lines.push(` - Use: \`node scripts/cw.js multi-agent graph ${report.runId} --view full --json\``);
1094
+ }
1095
+ if (report.hiddenSourceRecords.length) {
1096
+ for (const hidden of report.hiddenSourceRecords) {
1097
+ lines.push(`- Hidden ${hidden.kind}: ${hidden.count} records; expand: \`${hidden.expansionCommand}\``);
1098
+ }
1099
+ }
1100
+ lines.push(`- Next: \`${report.nextAction}\``);
1101
+ return lines;
1102
+ }
1103
+ function normalizeStateExplosionForEval(run) {
1104
+ const report = buildStateExplosionReport(run);
1105
+ const graph = report.compactGraph;
1106
+ return {
1107
+ summaryFreshness: [
1108
+ stableLine({
1109
+ compactionRecommended: report.stateSize.compactionRecommended,
1110
+ total: report.stateSize.total,
1111
+ deterministic: graph.deterministic
1112
+ })
1113
+ ],
1114
+ compactGraphShape: [
1115
+ stableLine({
1116
+ view: graph.view,
1117
+ fullNodeCount: graph.fullNodeCount,
1118
+ fullEdgeCount: graph.fullEdgeCount,
1119
+ compactNodeCount: graph.compactNodeCount,
1120
+ compactEdgeCount: graph.compactEdgeCount,
1121
+ collapsedNodeCount: graph.collapsedNodeCount,
1122
+ syntheticNodes: graph.syntheticNodes.map((s) => ({
1123
+ kind: s.id.split(":summary:")[1] || s.kind,
1124
+ collapsedNodeCount: s.collapsedNodeCount,
1125
+ collapsedEdgeCount: s.collapsedEdgeCount,
1126
+ dominantStatus: s.dominantStatus
1127
+ }))
1128
+ })
1129
+ ],
1130
+ blackboardDigest: [
1131
+ stableLine({
1132
+ topics: report.blackboardDigest.topicRollups.length,
1133
+ threads: report.blackboardDigest.threadSummaries.length,
1134
+ unresolved: report.blackboardDigest.unresolvedQuestions.map((q) => q.id),
1135
+ conflicts: report.blackboardDigest.conflicts.map((c) => c.id),
1136
+ decisions: report.blackboardDigest.decisions.length,
1137
+ artifacts: report.blackboardDigest.artifacts.length,
1138
+ policyViolations: report.blackboardDigest.policyViolations.map((p) => p.id),
1139
+ judgeRationale: report.blackboardDigest.judgeRationale.map((j) => j.id),
1140
+ missingEvidence: report.blackboardDigest.missingEvidence.map((m) => m.label)
1141
+ })
1142
+ ],
1143
+ criticalPath: graph.criticalPath.map((id) => stripRunId(run, id)).sort(),
1144
+ evidenceDigest: [
1145
+ stableLine({
1146
+ adopted: report.operatorDigest.evidenceDigest.adopted,
1147
+ missing: report.operatorDigest.evidenceDigest.missing,
1148
+ rejected: report.operatorDigest.evidenceDigest.rejected
1149
+ })
1150
+ ],
1151
+ expansionRefs: report.hiddenSourceRecords.map((h) => `${h.kind}=${h.count}`).sort()
1152
+ };
1153
+ }
1154
+ // ---------------------------------------------------------------------------
1155
+ // Helpers
1156
+ // ---------------------------------------------------------------------------
1157
+ function isProtectedStatus(status) {
1158
+ return ["failed", "blocked", "rejected", "conflicting"].includes(status);
1159
+ }
1160
+ function dominantStatus(statuses) {
1161
+ for (const priority of ["failed", "blocked", "rejected", "conflicting", "running", "pending"]) {
1162
+ if (statuses.includes(priority))
1163
+ return priority;
1164
+ }
1165
+ return statuses[0] || "completed";
1166
+ }
1167
+ function parentMap(edges) {
1168
+ const parents = new Map();
1169
+ for (const edge of edges) {
1170
+ if (!parents.has(edge.to))
1171
+ parents.set(edge.to, edge.from);
1172
+ }
1173
+ return parents;
1174
+ }
1175
+ function fingerprintRecords(records) {
1176
+ return fingerprintStrings(records.map((r) => `${r.id}:${r.status || ""}`).sort());
1177
+ }
1178
+ function fingerprintStrings(values) {
1179
+ const hash = node_crypto_1.default.createHash("sha256");
1180
+ hash.update(JSON.stringify([...values].sort()));
1181
+ return `sha256:${hash.digest("hex").slice(0, 32)}`;
1182
+ }
1183
+ function stableLine(value) {
1184
+ return JSON.stringify(sortKeys(value));
1185
+ }
1186
+ function sortKeys(value) {
1187
+ if (Array.isArray(value))
1188
+ return value.map(sortKeys);
1189
+ if (value && typeof value === "object") {
1190
+ const record = value;
1191
+ const result = {};
1192
+ for (const key of Object.keys(record).sort())
1193
+ result[key] = sortKeys(record[key]);
1194
+ return result;
1195
+ }
1196
+ return value;
1197
+ }
1198
+ function stripRunId(run, id) {
1199
+ return id.startsWith(`${run.id}:`) ? id.slice(run.id.length + 1) : id;
1200
+ }
1201
+ function unique(values) {
1202
+ return Array.from(new Set(values.filter(Boolean))).sort();
1203
+ }
1204
+ function byId(a, b) {
1205
+ return a.id.localeCompare(b.id);
1206
+ }
1207
+ function truncate(value) {
1208
+ const single = value.replace(/\s+/g, " ").trim();
1209
+ return single.length > 80 ? `${single.slice(0, 77)}...` : single;
1210
+ }
1211
+ function slug(value) {
1212
+ return value.replace(/[^a-zA-Z0-9._:-]/g, "-");
1213
+ }