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,537 @@
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.summarizeMultiAgentOperator = summarizeMultiAgentOperator;
7
+ exports.buildMultiAgentOperatorGraph = buildMultiAgentOperatorGraph;
8
+ exports.formatMultiAgentOperatorStatus = formatMultiAgentOperatorStatus;
9
+ exports.formatMultiAgentDependencies = formatMultiAgentDependencies;
10
+ exports.formatMultiAgentFailures = formatMultiAgentFailures;
11
+ exports.formatMultiAgentEvidence = formatMultiAgentEvidence;
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ const coordinator_1 = require("./coordinator");
15
+ const multi_agent_1 = require("./multi-agent");
16
+ const topology_1 = require("./topology");
17
+ const trust_audit_1 = require("./trust-audit");
18
+ function summarizeMultiAgentOperator(run) {
19
+ const topologies = (0, topology_1.summarizeTopologies)(run);
20
+ const multiAgent = (0, multi_agent_1.summarizeMultiAgent)(run);
21
+ const blackboard = (0, coordinator_1.summarizeBlackboard)(run);
22
+ const trust = (0, trust_audit_1.summarizeTrustAudit)(run);
23
+ const dependencies = deriveDependencies(run);
24
+ const failures = deriveFailures(run, dependencies);
25
+ const evidence = deriveEvidence(run);
26
+ const missingEvidence = evidence.filter((entry) => entry.status === "missing" || entry.status === "pending" || entry.status === "conflicting");
27
+ const adoptedEvidence = evidence.filter((entry) => entry.status === "adopted");
28
+ const inspectableEvidence = missingEvidence.filter((entry) => entry.disposition === "inspectable");
29
+ const activeTopologyIds = new Set(topologies.active.map((entry) => entry.id));
30
+ const activeMultiAgentRunIds = new Set(topologies.active.map((entry) => entry.multiAgentRunId));
31
+ const state = run.multiAgent;
32
+ const nextAction = failures[0]?.nextCommand ||
33
+ topologies.nextAction ||
34
+ multiAgent.nextAction ||
35
+ blackboard.nextAction ||
36
+ readyCommitCommand(run) ||
37
+ `node scripts/cw.js multi-agent status ${run.id} --json`;
38
+ return {
39
+ schemaVersion: 1,
40
+ runId: run.id,
41
+ activeMultiAgentRunIds: [...activeMultiAgentRunIds, ...((state?.runs || []).filter((entry) => !isTerminal(entry.status)).map((entry) => entry.id))].filter(uniqueFilter),
42
+ topologyRunIds: [...activeTopologyIds],
43
+ topologyIds: topologies.active.map((entry) => entry.topologyId).filter(uniqueFilter),
44
+ groups: (state?.groups || []).map((entry) => entry.id).sort(),
45
+ roles: (state?.roles || []).map((entry) => entry.id).sort(),
46
+ memberships: (state?.memberships || []).map((entry) => entry.id).sort(),
47
+ fanouts: (state?.fanouts || []).map((entry) => entry.id).sort(),
48
+ fanins: (state?.fanins || []).map((entry) => entry.id).sort(),
49
+ blocked: failures.length > 0,
50
+ dependencies,
51
+ failures,
52
+ evidence,
53
+ missingEvidence,
54
+ adoptedEvidence,
55
+ inspectableEvidence,
56
+ nextAction,
57
+ summaries: { topologies, multiAgent, blackboard, trust }
58
+ };
59
+ }
60
+ function buildMultiAgentOperatorGraph(run) {
61
+ const nodes = new Map();
62
+ const edges = [];
63
+ const addNode = (id, kind, status, label, pathValue) => {
64
+ if (!id)
65
+ return;
66
+ nodes.set(id, { id, kind, status, label, path: pathValue });
67
+ };
68
+ const addEdge = (from, to, label) => {
69
+ if (!from || !to)
70
+ return;
71
+ edges.push({ from, to, label });
72
+ };
73
+ addNode(`${run.id}:run`, "multi-agent-run-root", run.loopStage, run.id, run.paths.state);
74
+ for (const graph of [(0, topology_1.buildTopologyGraph)(run), (0, multi_agent_1.buildMultiAgentGraph)(run), (0, coordinator_1.buildBlackboardGraph)(run)]) {
75
+ for (const node of graph.nodes)
76
+ addNode(node.id, node.kind, node.status, node.label, node.path);
77
+ for (const edge of graph.edges)
78
+ addEdge(edge.from, edge.to, relabel(edge.label));
79
+ }
80
+ for (const task of run.tasks || []) {
81
+ addNode(`${run.id}:task:${task.id}`, "task", task.status, task.id, task.taskPath);
82
+ addEdge(`${run.id}:run`, `${run.id}:task:${task.id}`, "owns");
83
+ addEdge(`${run.id}:task:${task.id}`, task.dispatchId ? `${run.id}:dispatch:${task.dispatchId}` : undefined, "dispatches");
84
+ addEdge(`${run.id}:task:${task.id}`, task.resultNodeId, "reports");
85
+ addEdge(`${run.id}:task:${task.id}`, task.verifierNodeId, "gates");
86
+ }
87
+ for (const dispatch of run.dispatches || []) {
88
+ addNode(`${run.id}:dispatch:${dispatch.id}`, "dispatch", "completed", dispatch.id, dispatch.manifestPath);
89
+ for (const workerId of dispatch.workerIds || [])
90
+ addEdge(`${run.id}:dispatch:${dispatch.id}`, `${run.id}:worker:${workerId}`, "dispatches");
91
+ }
92
+ for (const worker of run.workers || []) {
93
+ addNode(`${run.id}:worker:${worker.id}`, "worker", worker.status, worker.id, worker.inputPath);
94
+ addEdge(`${run.id}:worker:${worker.id}`, worker.resultNodeId, "reports");
95
+ addEdge(`${run.id}:worker:${worker.id}`, worker.output?.verifierNodeId, "gates");
96
+ for (const feedbackId of worker.feedbackIds || [])
97
+ addEdge(`${run.id}:worker:${worker.id}`, `${run.id}:feedback:${feedbackId}`, "blocks");
98
+ }
99
+ for (const candidate of run.candidates || []) {
100
+ const candidateId = `${run.id}:candidate:${candidate.id}`;
101
+ addNode(candidateId, "candidate", candidate.status, candidate.id, candidate.resultPath);
102
+ addEdge(candidate.workerId ? `${run.id}:worker:${candidate.workerId}` : candidate.resultNodeId, candidateId, "reports");
103
+ addEdge(candidate.verifierNodeId, candidateId, "gates");
104
+ for (const scoreId of candidate.scores || []) {
105
+ const nodeId = `${run.id}:score:${scoreId}`;
106
+ addNode(nodeId, "score", "completed", scoreId, scorePath(run, candidate.id, scoreId));
107
+ addEdge(candidateId, nodeId, "scores");
108
+ }
109
+ for (const feedbackId of candidate.feedbackIds || [])
110
+ addEdge(candidateId, `${run.id}:feedback:${feedbackId}`, "blocks");
111
+ }
112
+ for (const selection of run.candidateSelections || []) {
113
+ const nodeId = `${run.id}:selection:${selection.id}`;
114
+ addNode(nodeId, "selection", "accepted", selection.id, selection.rankingPath);
115
+ addEdge(`${run.id}:candidate:${selection.candidateId}`, nodeId, "selects");
116
+ if (selection.scoreId)
117
+ addEdge(`${run.id}:score:${selection.scoreId}`, nodeId, "selects");
118
+ addEdge(selection.verifierNodeId, nodeId, "gates");
119
+ }
120
+ for (const commit of run.commits || []) {
121
+ const nodeId = commit.stateNodeId || `${run.id}:commit:${commit.id}`;
122
+ addNode(nodeId, "commit", commit.verifierGated ? "committed" : "checkpoint", commit.id, commit.snapshotPath);
123
+ addEdge(commit.selectionId ? `${run.id}:selection:${commit.selectionId}` : undefined, nodeId, "commits");
124
+ addEdge(commit.verifierNodeId, nodeId, "gates");
125
+ }
126
+ for (const feedback of run.feedback || []) {
127
+ addNode(`${run.id}:feedback:${feedback.id}`, "feedback", feedback.status, `${feedback.severity} ${feedback.classification}`);
128
+ addEdge(feedback.nodeId, `${run.id}:feedback:${feedback.id}`, "blocks");
129
+ addEdge(feedback.taskId ? `${run.id}:task:${feedback.taskId}` : undefined, `${run.id}:feedback:${feedback.id}`, "blocks");
130
+ }
131
+ for (const dep of deriveDependencies(run))
132
+ addEdge(dep.from, dep.to, dep.label);
133
+ return {
134
+ runId: run.id,
135
+ nodes: [...nodes.values()].sort((left, right) => left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id)),
136
+ edges: uniqueEdges(edges).sort((left, right) => left.from.localeCompare(right.from) || left.to.localeCompare(right.to) || (left.label || "").localeCompare(right.label || ""))
137
+ };
138
+ }
139
+ function formatMultiAgentOperatorStatus(status) {
140
+ return [
141
+ `Multi-Agent Operator Status: ${status.runId}`,
142
+ `Active Runs: ${status.activeMultiAgentRunIds.join(", ") || "none"}`,
143
+ `Topologies: ${status.topologyIds.join(", ") || "none"} (${status.topologyRunIds.join(", ") || "none"})`,
144
+ `Blocked: ${status.blocked ? "yes" : "no"}`,
145
+ "",
146
+ "Agent Graph",
147
+ ` roles=${status.roles.length}; groups=${status.groups.length}; memberships=${status.memberships.length}; fanout=${status.fanouts.length}; fanin=${status.fanins.length}`,
148
+ "",
149
+ formatDependencies(status.dependencies),
150
+ "",
151
+ formatFailures(status.failures),
152
+ "",
153
+ formatEvidence("Adopted Evidence", status.adoptedEvidence),
154
+ "",
155
+ formatEvidence(status.inspectableEvidence.length
156
+ ? `Missing Evidence (blocking=${status.missingEvidence.length - status.inspectableEvidence.length}, inspectable=${status.inspectableEvidence.length}; a verifier-gated commit decided the selection — inspectable rows are not failures)`
157
+ : "Missing Evidence", status.missingEvidence),
158
+ "",
159
+ "Next Action",
160
+ ` ${status.nextAction}`
161
+ ].join("\n");
162
+ }
163
+ function formatMultiAgentDependencies(rows) {
164
+ return formatDependencies(rows);
165
+ }
166
+ function formatMultiAgentFailures(rows) {
167
+ return formatFailures(rows);
168
+ }
169
+ function formatMultiAgentEvidence(rows) {
170
+ return formatEvidence("Evidence Adoption", rows);
171
+ }
172
+ function deriveDependencies(run) {
173
+ const rows = [];
174
+ const add = (from, to, label, status = "known", reason, nextCommand) => {
175
+ if (!from || !to)
176
+ return;
177
+ rows.push({ id: `${from}->${to}:${label}`, from, to, label, status, reason, nextCommand });
178
+ };
179
+ const state = run.multiAgent;
180
+ for (const topology of run.topologies?.runs || []) {
181
+ add(`${run.id}:topology:${topology.id}`, `${run.id}:multi-agent:${topology.multiAgentRunId}`, "owns");
182
+ add(`${run.id}:topology:${topology.id}`, `${run.id}:blackboard:${topology.blackboardId}`, "owns");
183
+ for (const fanoutId of topology.fanoutIds)
184
+ add(`${run.id}:topology:${topology.id}`, `${run.id}:multi-agent:fanout:${fanoutId}`, "fanout");
185
+ for (const faninId of topology.faninIds)
186
+ add(`${run.id}:multi-agent:fanin:${faninId}`, `${run.id}:topology:${topology.id}`, "reports");
187
+ for (const candidateId of topology.candidateIds)
188
+ add(`${run.id}:topology:${topology.id}`, `${run.id}:candidate:${candidateId}`, "candidate");
189
+ for (const selectionId of topology.selectionIds)
190
+ add(`${run.id}:selection:${selectionId}`, `${run.id}:topology:${topology.id}`, "selects");
191
+ }
192
+ for (const group of state?.groups || []) {
193
+ add(`${run.id}:multi-agent:${group.multiAgentRunId}`, `${run.id}:multi-agent:group:${group.id}`, "owns");
194
+ for (const taskId of group.taskIds)
195
+ add(`${run.id}:multi-agent:group:${group.id}`, `${run.id}:task:${taskId}`, "depends-on");
196
+ }
197
+ for (const fanout of state?.fanouts || []) {
198
+ add(`${run.id}:multi-agent:group:${fanout.groupId}`, `${run.id}:multi-agent:fanout:${fanout.id}`, "fanout");
199
+ for (const roleId of fanout.roleIds)
200
+ add(`${run.id}:multi-agent:fanout:${fanout.id}`, `${run.id}:multi-agent:role:${roleId}`, "depends-on");
201
+ for (const dispatchId of fanout.dispatchIds)
202
+ add(`${run.id}:multi-agent:fanout:${fanout.id}`, `${run.id}:dispatch:${dispatchId}`, "dispatches");
203
+ }
204
+ for (const membership of state?.memberships || []) {
205
+ add(`${run.id}:multi-agent:role:${membership.roleId}`, `${run.id}:multi-agent:membership:${membership.id}`, "owns");
206
+ add(`${run.id}:multi-agent:membership:${membership.id}`, `${run.id}:task:${membership.taskId}`, "depends-on");
207
+ add(`${run.id}:multi-agent:membership:${membership.id}`, membership.workerId ? `${run.id}:worker:${membership.workerId}` : undefined, "dispatches");
208
+ add(membership.resultNodeId, `${run.id}:multi-agent:membership:${membership.id}`, "reports");
209
+ add(membership.verifierNodeId, `${run.id}:multi-agent:membership:${membership.id}`, "gates");
210
+ for (const artifactId of membership.blackboardArtifactRefIds || [])
211
+ add(`${run.id}:blackboard:artifact:${artifactId}`, `${run.id}:multi-agent:membership:${membership.id}`, "cites");
212
+ for (const messageId of membership.blackboardMessageIds || [])
213
+ add(`${run.id}:blackboard:message:${messageId}`, `${run.id}:multi-agent:membership:${membership.id}`, "cites");
214
+ }
215
+ for (const fanin of state?.fanins || []) {
216
+ add(fanin.fanoutId ? `${run.id}:multi-agent:fanout:${fanin.fanoutId}` : `${run.id}:multi-agent:group:${fanin.groupId}`, `${run.id}:multi-agent:fanin:${fanin.id}`, "fanin");
217
+ for (const coverage of fanin.evidenceCoverage) {
218
+ add(`${run.id}:multi-agent:membership:${coverage.membershipId}`, `${run.id}:multi-agent:fanin:${fanin.id}`, coverage.complete ? "adopted-by" : "blocks", coverage.complete ? "ready" : "blocked", coverage.complete ? undefined : "membership has not reported required evidence", `node scripts/cw.js worker manifest ${run.id} ${coverage.workerId || "<worker-id>"}`);
219
+ }
220
+ }
221
+ for (const candidate of run.candidates || []) {
222
+ add(candidate.workerId ? `${run.id}:worker:${candidate.workerId}` : candidate.resultNodeId, `${run.id}:candidate:${candidate.id}`, "reports", candidate.status);
223
+ for (const scoreId of candidate.scores || [])
224
+ add(`${run.id}:candidate:${candidate.id}`, `${run.id}:score:${scoreId}`, "scores", "completed");
225
+ }
226
+ for (const selection of run.candidateSelections || []) {
227
+ add(`${run.id}:candidate:${selection.candidateId}`, `${run.id}:selection:${selection.id}`, "selects", "accepted");
228
+ add(selection.scoreId ? `${run.id}:score:${selection.scoreId}` : undefined, `${run.id}:selection:${selection.id}`, "scores", "accepted");
229
+ }
230
+ for (const commit of run.commits || []) {
231
+ add(commit.selectionId ? `${run.id}:selection:${commit.selectionId}` : undefined, commit.stateNodeId || `${run.id}:commit:${commit.id}`, "commits", commit.verifierGated ? "committed" : "checkpoint");
232
+ }
233
+ return rows.filter(uniqueById).sort((left, right) => left.from.localeCompare(right.from) || left.to.localeCompare(right.to));
234
+ }
235
+ function deriveFailures(run, dependencies) {
236
+ const rows = [];
237
+ const add = (id, kind, status, reason, nextCommand, owner, linked) => {
238
+ rows.push({ id, kind, status, owner, linked, reason, nextCommand });
239
+ };
240
+ const state = run.multiAgent;
241
+ for (const role of state?.roles || []) {
242
+ const memberships = (state?.memberships || []).filter((entry) => entry.roleId === role.id);
243
+ if (!memberships.length && role.status !== "completed" && role.status !== "cancelled") {
244
+ add(role.id, "missing-role-coverage", role.status, `role ${role.id} has no membership`, `node scripts/cw.js multi-agent step ${run.id}`, role.id);
245
+ }
246
+ if (role.status === "blocked" || role.status === "cancelled")
247
+ add(role.id, "agent-role", role.status, `role ${role.id} is ${role.status}`, `node scripts/cw.js multi-agent status ${run.id} --json`, role.id);
248
+ }
249
+ for (const membership of state?.memberships || []) {
250
+ const worker = membership.workerId ? (run.workers || []).find((entry) => entry.id === membership.workerId) : undefined;
251
+ if (membership.status === "failed" || membership.status === "cancelled")
252
+ add(membership.id, "agent-membership", membership.status, `membership ${membership.id} is ${membership.status}`, `node scripts/cw.js multi-agent membership ${run.id} ${membership.id}`, membership.roleId, membership.workerId);
253
+ if (!membership.workerId)
254
+ add(membership.id, "missing-worker", membership.status, `membership ${membership.id} has no worker`, `node scripts/cw.js multi-agent step ${run.id}`, membership.roleId, membership.taskId);
255
+ if (worker && (worker.status === "failed" || worker.status === "rejected"))
256
+ add(worker.id, "worker", worker.status, worker.errors[0]?.message || `worker ${worker.id} is ${worker.status}`, `node scripts/cw.js worker show ${run.id} ${worker.id}`, membership.roleId, membership.id);
257
+ if (worker && (worker.status === "allocated" || worker.status === "running"))
258
+ add(worker.id, "worker-output", worker.status, `worker ${worker.id} has not reported output`, `node scripts/cw.js worker manifest ${run.id} ${worker.id}`, membership.roleId, membership.id);
259
+ }
260
+ for (const fanin of state?.fanins || []) {
261
+ for (const reason of fanin.blockedReasons)
262
+ add(fanin.id, "fanin", fanin.status, reason, `node scripts/cw.js multi-agent failures ${run.id}`, fanin.groupId, fanin.fanoutId);
263
+ for (const roleId of fanin.missingRoleIds)
264
+ add(`${fanin.id}:${roleId}`, "missing-role-evidence", "missing", `fanin ${fanin.id} is missing role ${roleId}`, `node scripts/cw.js multi-agent step ${run.id}`, roleId, fanin.id);
265
+ for (const membershipId of fanin.missingMembershipIds)
266
+ add(`${fanin.id}:${membershipId}`, "missing-membership-evidence", "missing", `fanin ${fanin.id} is missing membership ${membershipId}`, `node scripts/cw.js multi-agent membership ${run.id} ${membershipId}`, membershipId, fanin.id);
267
+ }
268
+ for (const topology of run.topologies?.runs || []) {
269
+ for (const missing of topology.missingEvidence || [])
270
+ add(`${topology.id}:${missing}`, "missing-topology-evidence", "missing", missing, topology.nextActions[0] || `node scripts/cw.js topology summary ${run.id}`, topology.id);
271
+ if (topology.status === "blocked" || topology.status === "failed")
272
+ add(topology.id, "topology", topology.status, `topology ${topology.id} is ${topology.status}`, `node scripts/cw.js topology summary ${run.id}`, topology.id);
273
+ }
274
+ for (const feedback of run.feedback || []) {
275
+ if (feedback.status === "open" || feedback.status === "tasked")
276
+ add(feedback.id, feedback.classification, feedback.status, feedback.message, `node scripts/cw.js feedback show ${run.id} ${feedback.id}`, feedback.taskId, feedback.nodeId);
277
+ }
278
+ for (const candidate of run.candidates || []) {
279
+ if (candidate.status === "rejected" || candidate.status === "failed")
280
+ add(candidate.id, "candidate", candidate.status, candidate.feedbackIds[0] || `candidate ${candidate.id} is ${candidate.status}`, `node scripts/cw.js candidate show ${run.id} ${candidate.id}`, candidate.workerId, candidate.taskId);
281
+ if (!candidate.scores.length && candidate.status !== "rejected" && candidate.status !== "failed")
282
+ add(candidate.id, "candidate-score-gap", candidate.status, `candidate ${candidate.id} has no score`, `node scripts/cw.js multi-agent score ${run.id} --candidate ${candidate.id} --evidence <path-or-ref>`, candidate.workerId, candidate.taskId);
283
+ if (!candidate.verifierNodeId)
284
+ add(`${candidate.id}:verifier`, "candidate-verifier-gap", candidate.status, `candidate ${candidate.id} has no verifier gate`, `node scripts/cw.js candidate show ${run.id} ${candidate.id}`, candidate.workerId, candidate.taskId);
285
+ }
286
+ if ((run.candidates || []).some((candidate) => candidate.scores.length) && !(run.candidateSelections || []).length) {
287
+ add("selection-gap", "selection", "missing", "scored candidates exist but no selection is recorded", `node scripts/cw.js multi-agent select ${run.id} --candidate <candidate-id> --reason "<rationale>"`);
288
+ }
289
+ for (const dep of dependencies.filter((entry) => entry.status === "blocked"))
290
+ add(dep.id, "ambiguous-dependency", dep.status, dep.reason || "dependency is blocked", dep.nextCommand || `node scripts/cw.js multi-agent status ${run.id} --json`);
291
+ const readySelection = (run.candidateSelections || []).find((selection) => !(run.commits || []).some((commit) => commit.selectionId === selection.id && commit.verifierGated));
292
+ if (readySelection)
293
+ add(readySelection.id, "commit-gate", "not-ready", `selection ${readySelection.id} has no verifier-gated commit`, `node scripts/cw.js commit ${run.id} --selection ${readySelection.id} --reason "<verified rationale>"`, readySelection.candidateId);
294
+ return rows.filter(uniqueByFailure).sort((left, right) => left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id));
295
+ }
296
+ function deriveEvidence(run) {
297
+ const rows = new Map();
298
+ const ensure = (key, patch) => {
299
+ const existing = rows.get(key);
300
+ const next = existing || {
301
+ id: key,
302
+ sourceKind: "runtime",
303
+ adoptedBy: [],
304
+ rejectedBy: [],
305
+ pendingConsumers: [],
306
+ candidateIds: [],
307
+ scoreIds: [],
308
+ selectionIds: [],
309
+ commitIds: [],
310
+ status: "pending"
311
+ };
312
+ Object.assign(next, patch);
313
+ next.adoptedBy = unique([...(next.adoptedBy || []), ...(patch.adoptedBy || [])]);
314
+ next.rejectedBy = unique([...(next.rejectedBy || []), ...(patch.rejectedBy || [])]);
315
+ next.pendingConsumers = unique([...(next.pendingConsumers || []), ...(patch.pendingConsumers || [])]);
316
+ next.candidateIds = unique([...(next.candidateIds || []), ...(patch.candidateIds || [])]);
317
+ next.scoreIds = unique([...(next.scoreIds || []), ...(patch.scoreIds || [])]);
318
+ next.selectionIds = unique([...(next.selectionIds || []), ...(patch.selectionIds || [])]);
319
+ next.commitIds = unique([...(next.commitIds || []), ...(patch.commitIds || [])]);
320
+ rows.set(key, next);
321
+ return next;
322
+ };
323
+ const addEvidence = (evidence, patch) => {
324
+ for (const item of evidence || []) {
325
+ const key = evidenceKey(item);
326
+ ensure(key, {
327
+ ref: item.summary || item.locator || item.path || item.id,
328
+ path: item.path,
329
+ locator: item.locator,
330
+ provenanceSource: item.provenance?.source,
331
+ sourceId: item.provenance?.workerId || item.provenance?.candidateId || item.provenance?.selectionId || item.provenance?.commitId || patch.sourceId,
332
+ sourceKind: sourceKindFromEvidence(item, patch.sourceKind),
333
+ ...patch
334
+ });
335
+ }
336
+ };
337
+ for (const worker of run.workers || []) {
338
+ if (worker.output?.resultPath)
339
+ ensure(worker.output.resultPath, { path: worker.output.resultPath, sourceKind: "worker", sourceId: worker.id, status: worker.status === "verified" ? "adopted" : "pending", adoptedBy: worker.status === "verified" ? [worker.id] : [], pendingConsumers: worker.status === "verified" ? [] : [worker.id] });
340
+ }
341
+ for (const membership of run.multiAgent?.memberships || []) {
342
+ for (const ref of membership.evidenceRefs || [])
343
+ ensure(ref, { ref, sourceKind: "worker", sourceId: membership.workerId || membership.id, status: membership.status === "reported" || membership.status === "verified" ? "adopted" : "pending", adoptedBy: membership.status === "reported" || membership.status === "verified" ? [membership.id] : [], pendingConsumers: membership.status === "reported" || membership.status === "verified" ? [] : [membership.id] });
344
+ for (const artifactId of membership.blackboardArtifactRefIds || [])
345
+ ensure(artifactId, { ref: artifactId, sourceKind: "blackboard", sourceId: membership.id, status: "adopted", adoptedBy: [membership.id] });
346
+ for (const messageId of membership.blackboardMessageIds || [])
347
+ ensure(messageId, { ref: messageId, sourceKind: "blackboard", sourceId: membership.id, status: "adopted", adoptedBy: [membership.id] });
348
+ }
349
+ for (const artifact of run.blackboard?.artifacts || []) {
350
+ ensure(artifact.id, { ref: artifact.locator || artifact.path || artifact.id, path: artifact.path, locator: artifact.locator, sourceKind: "blackboard", sourceId: artifact.source, provenanceSource: artifact.provenance.auditEventIds?.[0], status: artifact.status === "rejected" ? "rejected" : artifact.status === "superseded" ? "superseded" : artifact.status === "conflicting" ? "conflicting" : "pending" });
351
+ for (const ref of artifact.evidenceRefs || [])
352
+ ensure(ref, { ref, sourceKind: "blackboard", sourceId: artifact.id, status: "pending", pendingConsumers: [artifact.id] });
353
+ }
354
+ for (const message of run.blackboard?.messages || []) {
355
+ ensure(message.id, { ref: message.id, sourceKind: "blackboard", sourceId: message.author.id, status: message.status === "rejected" ? "rejected" : message.status === "superseded" ? "superseded" : "pending" });
356
+ for (const ref of message.linkedEvidenceRefs || [])
357
+ ensure(ref, { ref, sourceKind: "blackboard", sourceId: message.id, status: "pending", pendingConsumers: [message.id] });
358
+ }
359
+ for (const decision of run.blackboard?.decisions || []) {
360
+ for (const ref of [...(decision.evidenceRefs || []), ...(decision.artifactRefIds || []), ...(decision.messageIds || [])]) {
361
+ ensure(ref, { ref, sourceKind: "coordinator", sourceId: decision.id, status: evidenceStatusForDecision(decision.outcome), adoptedBy: decision.outcome === "accepted" || decision.outcome === "ready" ? [decision.id] : [], rejectedBy: decision.outcome === "rejected" ? [decision.id] : [] });
362
+ }
363
+ }
364
+ for (const fanin of run.multiAgent?.fanins || []) {
365
+ for (const coverage of fanin.evidenceCoverage) {
366
+ for (const ref of [...coverage.evidenceRefs, ...(coverage.blackboardArtifactRefIds || []), ...(coverage.blackboardMessageIds || [])])
367
+ ensure(ref, { ref, sourceKind: "worker", sourceId: coverage.workerId || coverage.membershipId, status: coverage.complete && fanin.verifierReady ? "adopted" : "pending", adoptedBy: coverage.complete ? [fanin.id] : [], pendingConsumers: coverage.complete ? [] : [fanin.id] });
368
+ }
369
+ for (const roleId of fanin.missingRoleIds)
370
+ ensure(`${fanin.id}:missing-role:${roleId}`, { ref: roleId, sourceKind: "runtime", sourceId: fanin.id, status: "missing", pendingConsumers: [fanin.id], reason: `fanin ${fanin.id} requires role ${roleId}` });
371
+ for (const membershipId of fanin.missingMembershipIds)
372
+ ensure(`${fanin.id}:missing-membership:${membershipId}`, { ref: membershipId, sourceKind: "runtime", sourceId: fanin.id, status: "missing", pendingConsumers: [fanin.id], reason: `fanin ${fanin.id} requires membership ${membershipId}` });
373
+ }
374
+ for (const candidate of run.candidates || []) {
375
+ addEvidence(candidate.evidence, { status: candidate.status === "rejected" || candidate.status === "failed" ? "rejected" : "pending", sourceKind: "worker", sourceId: candidate.workerId || candidate.id, candidateIds: [candidate.id], rejectedBy: candidate.status === "rejected" || candidate.status === "failed" ? [candidate.id] : [] });
376
+ for (const score of readScores(run, candidate.id))
377
+ addEvidence(score.evidence, { status: score.verdict === "fail" ? "rejected" : "adopted", sourceKind: "operator", sourceId: score.scorer, candidateIds: [candidate.id], scoreIds: [score.id], adoptedBy: score.verdict === "fail" ? [] : [score.id], rejectedBy: score.verdict === "fail" ? [score.id] : [] });
378
+ }
379
+ for (const selection of run.candidateSelections || []) {
380
+ addEvidence(selection.evidence, { status: "adopted", sourceKind: "verifier", sourceId: selection.verifierNodeId || selection.id, candidateIds: [selection.candidateId], selectionIds: [selection.id], scoreIds: selection.scoreId ? [selection.scoreId] : [], adoptedBy: [selection.id] });
381
+ }
382
+ for (const commit of run.commits || []) {
383
+ addEvidence(commit.evidence || [], { status: commit.verifierGated ? "adopted" : "pending", sourceKind: "runtime", sourceId: commit.id, selectionIds: commit.selectionId ? [commit.selectionId] : [], candidateIds: commit.candidateId ? [commit.candidateId] : [], commitIds: [commit.id], adoptedBy: commit.verifierGated ? [commit.id] : [], pendingConsumers: commit.verifierGated ? [] : [commit.id] });
384
+ }
385
+ for (const topology of run.topologies?.runs || []) {
386
+ for (const missing of topology.missingEvidence || [])
387
+ ensure(`${topology.id}:missing:${missing}`, { ref: missing, sourceKind: "runtime", sourceId: topology.id, status: "missing", pendingConsumers: [topology.id], reason: missing });
388
+ }
389
+ // Once any verifier-gated commit exists the selected path is decided; rows
390
+ // that still read missing/pending/conflicting are then inspectable operator
391
+ // state (e.g. sibling judge-panel roles never driven as separate workers),
392
+ // not blocking failures. Before a verifier-gated commit, those same rows
393
+ // genuinely block. `disposition` is the operator-facing reading of `status`.
394
+ const committed = (run.commits || []).some((commit) => commit.verifierGated);
395
+ const blocks = (status) => status === "missing" || status === "pending" || status === "conflicting";
396
+ const withDisposition = (row) => ({
397
+ ...row,
398
+ disposition: row.status === "adopted" ? "adopted" : blocks(row.status) && !committed ? "blocking" : "inspectable"
399
+ });
400
+ return [...rows.values()]
401
+ .map(normalizeEvidenceStatus)
402
+ .map(withDisposition)
403
+ .sort((left, right) => statusRank(left.status) - statusRank(right.status) || left.id.localeCompare(right.id));
404
+ }
405
+ function formatDependencies(rows) {
406
+ const lines = ["Dependencies"];
407
+ if (!rows.length)
408
+ return [...lines, " none"].join("\n");
409
+ for (const row of rows.slice(0, 80))
410
+ lines.push(` [${row.status}] ${row.from} -> ${row.to} (${row.label})${row.reason ? `: ${row.reason}` : ""}`);
411
+ if (rows.length > 80)
412
+ lines.push(` ... ${rows.length - 80} more`);
413
+ return lines.join("\n");
414
+ }
415
+ function formatFailures(rows) {
416
+ const lines = ["Failed / Blocked Agents"];
417
+ if (!rows.length)
418
+ return [...lines, " none"].join("\n");
419
+ for (const row of rows.slice(0, 40))
420
+ lines.push(` [${row.status}] ${row.kind} ${row.id}${row.owner ? ` owner=${row.owner}` : ""}${row.linked ? ` linked=${row.linked}` : ""}: ${row.reason}; next=${row.nextCommand}`);
421
+ if (rows.length > 40)
422
+ lines.push(` ... ${rows.length - 40} more`);
423
+ return lines.join("\n");
424
+ }
425
+ function formatEvidence(title, rows) {
426
+ const lines = [title];
427
+ if (!rows.length)
428
+ return [...lines, " none"].join("\n");
429
+ for (const row of rows.slice(0, 60)) {
430
+ const ref = row.locator || row.path || row.ref || row.id;
431
+ const adopted = row.adoptedBy.length ? ` adoptedBy=${row.adoptedBy.join(",")}` : "";
432
+ const rejected = row.rejectedBy.length ? ` rejectedBy=${row.rejectedBy.join(",")}` : "";
433
+ const pending = row.pendingConsumers.length ? ` pending=${row.pendingConsumers.join(",")}` : "";
434
+ const rationale = row.rationaleStatus ? ` rationale=${row.rationaleStatus}` : "";
435
+ const disposition = row.disposition === "inspectable" ? " disposition=inspectable" : "";
436
+ lines.push(` [${row.status}] ${row.id} ${ref} source=${row.sourceKind}:${row.sourceId || "unknown"}${rationale}${disposition}${adopted}${rejected}${pending}`);
437
+ }
438
+ if (rows.length > 60)
439
+ lines.push(` ... ${rows.length - 60} more`);
440
+ return lines.join("\n");
441
+ }
442
+ function readScores(run, candidateId) {
443
+ const dir = node_path_1.default.join(run.paths.candidatesDir || node_path_1.default.join(run.paths.runDir, "candidates"), safeFileName(candidateId), "scores");
444
+ if (!node_fs_1.default.existsSync(dir))
445
+ return [];
446
+ return node_fs_1.default
447
+ .readdirSync(dir)
448
+ .filter((file) => file.endsWith(".json"))
449
+ .sort()
450
+ .map((file) => JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(dir, file), "utf8")));
451
+ }
452
+ function scorePath(run, candidateId, scoreId) {
453
+ const file = node_path_1.default.join(run.paths.candidatesDir || node_path_1.default.join(run.paths.runDir, "candidates"), safeFileName(candidateId), "scores", `${safeFileName(scoreId)}.json`);
454
+ return node_fs_1.default.existsSync(file) ? file : undefined;
455
+ }
456
+ function readyCommitCommand(run) {
457
+ const selection = (run.candidateSelections || []).find((entry) => !(run.commits || []).some((commit) => commit.selectionId === entry.id && commit.verifierGated));
458
+ return selection ? `node scripts/cw.js commit ${run.id} --selection ${selection.id} --reason "<verified rationale>"` : undefined;
459
+ }
460
+ function normalizeEvidenceStatus(row) {
461
+ if (row.rejectedBy.length)
462
+ row.status = "rejected";
463
+ else if (row.adoptedBy.length && row.commitIds.length)
464
+ row.status = "adopted";
465
+ else if (row.adoptedBy.length && row.status !== "missing" && row.status !== "conflicting" && row.status !== "superseded")
466
+ row.status = "adopted";
467
+ return row;
468
+ }
469
+ function evidenceKey(evidence) {
470
+ return evidence.id || evidence.locator || evidence.path || evidence.summary || "evidence";
471
+ }
472
+ function sourceKindFromEvidence(evidence, fallback) {
473
+ if (fallback)
474
+ return fallback;
475
+ if (evidence.provenance?.workerId)
476
+ return "worker";
477
+ if (evidence.provenance?.verifierNodeId)
478
+ return "verifier";
479
+ if (evidence.provenance?.source === "operator-recorded")
480
+ return "operator";
481
+ return "runtime";
482
+ }
483
+ function statusRank(status) {
484
+ return { adopted: 0, pending: 1, missing: 2, conflicting: 3, rejected: 4, superseded: 5 }[status];
485
+ }
486
+ function evidenceStatusForDecision(outcome) {
487
+ if (outcome === "accepted" || outcome === "ready")
488
+ return "adopted";
489
+ if (outcome === "rejected")
490
+ return "rejected";
491
+ if (outcome === "superseded")
492
+ return "superseded";
493
+ if (outcome === "conflicting")
494
+ return "conflicting";
495
+ return "pending";
496
+ }
497
+ function safeFileName(value) {
498
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_");
499
+ }
500
+ function relabel(label) {
501
+ if (!label)
502
+ return "depends-on";
503
+ if (label === "blackboard" || label === "task")
504
+ return "depends-on";
505
+ if (label === "dispatch")
506
+ return "dispatches";
507
+ if (label === "reported" || label === "result" || label === "message")
508
+ return "reports";
509
+ if (label === "evidence")
510
+ return "cites";
511
+ return label;
512
+ }
513
+ function isTerminal(status) {
514
+ return status === "completed" || status === "failed" || status === "cancelled";
515
+ }
516
+ function unique(values) {
517
+ return values.filter(Boolean).filter(uniqueFilter).sort();
518
+ }
519
+ function uniqueFilter(value, index, values) {
520
+ return values.indexOf(value) === index;
521
+ }
522
+ function uniqueById(value, index, values) {
523
+ return values.findIndex((entry) => entry.id === value.id) === index;
524
+ }
525
+ function uniqueByFailure(value, index, values) {
526
+ return values.findIndex((entry) => entry.id === value.id && entry.kind === value.kind) === index;
527
+ }
528
+ function uniqueEdges(edges) {
529
+ const seen = new Set();
530
+ return edges.filter((edge) => {
531
+ const key = `${edge.from}\0${edge.to}\0${edge.label || ""}`;
532
+ if (seen.has(key))
533
+ return false;
534
+ seen.add(key);
535
+ return true;
536
+ });
537
+ }