cool-workflow 0.1.97 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +11 -2
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +1 -1
  6. package/apps/end-to-end-golden-path/app.json +1 -1
  7. package/apps/pr-review-fix-ci/app.json +1 -1
  8. package/apps/release-cut/app.json +1 -1
  9. package/apps/research-synthesis/app.json +1 -1
  10. package/dist/cli/dispatch.js +236 -0
  11. package/dist/cli/entry.js +120 -0
  12. package/dist/cli/io.js +21 -4
  13. package/dist/cli/parseargv.js +157 -0
  14. package/dist/cli.js +6 -33
  15. package/dist/core/capability-table.js +3534 -0
  16. package/dist/core/format/help.js +314 -0
  17. package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
  18. package/dist/core/hash.js +137 -0
  19. package/dist/core/multi-agent/candidate-scoring.js +219 -0
  20. package/dist/core/multi-agent/collaboration.js +481 -0
  21. package/dist/core/multi-agent/coordinator.js +515 -0
  22. package/dist/core/multi-agent/eval-replay.js +306 -0
  23. package/dist/core/multi-agent/runtime.js +929 -0
  24. package/dist/core/multi-agent/topology.js +298 -0
  25. package/dist/core/multi-agent/trust-policy.js +197 -0
  26. package/dist/core/pipeline/commit-gate.js +320 -0
  27. package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
  28. package/dist/core/pipeline/dispatch.js +103 -0
  29. package/dist/core/pipeline/drive-decide.js +227 -0
  30. package/dist/core/pipeline/error-feedback.js +161 -0
  31. package/dist/core/pipeline/loop-expansion.js +124 -0
  32. package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
  33. package/dist/core/pipeline/runner.js +230 -0
  34. package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
  35. package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
  36. package/dist/core/state/node-projection.js +95 -0
  37. package/dist/core/state/node-snapshot.js +230 -0
  38. package/dist/core/state/run-paths.js +93 -0
  39. package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
  40. package/dist/core/state/schema.js +50 -0
  41. package/dist/core/state/state-explosion/digest.js +243 -0
  42. package/dist/core/state/state-explosion/graph.js +527 -0
  43. package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
  44. package/dist/core/state/state-explosion/report.js +187 -0
  45. package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
  46. package/dist/{state-node.js → core/state/state-node.js} +90 -113
  47. package/dist/core/state/types.js +19 -0
  48. package/dist/{validation.js → core/state/validation.js} +64 -131
  49. package/dist/core/trust/evidence-grounding.js +134 -0
  50. package/dist/core/trust/ledger.js +199 -0
  51. package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
  52. package/dist/core/trust/telemetry-ledger.js +127 -0
  53. package/dist/core/types.js +20 -0
  54. package/dist/core/version.js +16 -0
  55. package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
  56. package/dist/mcp/dispatch.js +104 -0
  57. package/dist/mcp/server.js +144 -0
  58. package/dist/mcp-server.js +6 -84
  59. package/dist/{agent-config.js → shell/agent-config.js} +118 -71
  60. package/dist/shell/app-run-cli.js +88 -0
  61. package/dist/shell/audit-cli.js +259 -0
  62. package/dist/shell/audit-provenance.js +83 -0
  63. package/dist/shell/candidate-scoring-io.js +459 -0
  64. package/dist/shell/collaboration-io.js +264 -0
  65. package/dist/shell/commit-summary.js +104 -0
  66. package/dist/shell/commit.js +290 -0
  67. package/dist/shell/coordinator-io.js +476 -0
  68. package/dist/shell/demo-cli.js +19 -0
  69. package/dist/shell/dispatch.js +162 -0
  70. package/dist/{doctor.js → shell/doctor.js} +123 -56
  71. package/dist/shell/drive.js +873 -0
  72. package/dist/shell/error-feedback-io.js +305 -0
  73. package/dist/shell/eval-io.js +473 -0
  74. package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
  75. package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
  76. package/dist/shell/exec-backend-cli.js +88 -0
  77. package/dist/shell/execution-backend/agent.js +475 -0
  78. package/dist/shell/execution-backend/ci.js +15 -0
  79. package/dist/shell/execution-backend/container.js +69 -0
  80. package/dist/shell/execution-backend/envelopes.js +55 -0
  81. package/dist/shell/execution-backend/local.js +113 -0
  82. package/dist/shell/execution-backend/probes.js +175 -0
  83. package/dist/shell/execution-backend/registry.js +402 -0
  84. package/dist/shell/execution-backend/remote.js +128 -0
  85. package/dist/shell/execution-backend/types.js +11 -0
  86. package/dist/shell/feedback-cli.js +81 -0
  87. package/dist/shell/feedback-operations.js +48 -0
  88. package/dist/shell/fs-atomic.js +276 -0
  89. package/dist/shell/harness.js +98 -0
  90. package/dist/shell/ledger-cli.js +212 -0
  91. package/dist/shell/ledger-io.js +169 -0
  92. package/dist/shell/man-cli.js +89 -0
  93. package/dist/shell/metrics-cli.js +98 -0
  94. package/dist/shell/multi-agent-cli.js +1002 -0
  95. package/dist/shell/multi-agent-host.js +563 -0
  96. package/dist/shell/multi-agent-io.js +387 -0
  97. package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
  98. package/dist/shell/node-store.js +124 -0
  99. package/dist/{observability/format.js → shell/observability-format.js} +7 -1
  100. package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
  101. package/dist/{observability.js → shell/observability.js} +159 -332
  102. package/dist/{onramp.js → shell/onramp.js} +11 -0
  103. package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
  104. package/dist/shell/operator-ux.js +431 -0
  105. package/dist/shell/orchestrator.js +231 -0
  106. package/dist/shell/pipeline-cli.js +667 -0
  107. package/dist/shell/pipeline.js +217 -0
  108. package/dist/shell/reclamation-io.js +1366 -0
  109. package/dist/shell/registry-cli.js +329 -0
  110. package/dist/{remote-source.js → shell/remote-source.js} +12 -5
  111. package/dist/shell/report-cli.js +101 -0
  112. package/dist/shell/report-view-cli.js +117 -0
  113. package/dist/{orchestrator → shell}/report.js +289 -282
  114. package/dist/shell/reporter.js +62 -0
  115. package/dist/shell/run-export-cli.js +106 -0
  116. package/dist/shell/run-export.js +680 -0
  117. package/dist/shell/run-registry-io.js +1014 -0
  118. package/dist/shell/run-store.js +164 -0
  119. package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
  120. package/dist/{scheduler.js → shell/scheduler-io.js} +248 -48
  121. package/dist/shell/scheduling-io.js +311 -0
  122. package/dist/shell/state-cli.js +181 -0
  123. package/dist/shell/state-explosion-cli.js +197 -0
  124. package/dist/shell/telemetry-cli.js +85 -0
  125. package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
  126. package/dist/shell/telemetry-ledger-io.js +132 -0
  127. package/dist/{term.js → shell/term.js} +36 -46
  128. package/dist/shell/topology-io.js +361 -0
  129. package/dist/shell/trust-audit.js +471 -0
  130. package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
  131. package/dist/shell/verifier.js +48 -0
  132. package/dist/shell/workbench-host.js +250 -0
  133. package/dist/shell/workbench-text.js +18 -0
  134. package/dist/shell/workbench.js +175 -0
  135. package/dist/shell/worker-cli.js +124 -0
  136. package/dist/shell/worker-isolation.js +852 -0
  137. package/dist/shell/workflow-app-loader.js +650 -0
  138. package/docs/agent-delegation-drive.7.md +4 -0
  139. package/docs/cli-mcp-parity.7.md +284 -211
  140. package/docs/contract-migration-tooling.7.md +4 -0
  141. package/docs/control-plane-scheduling.7.md +4 -0
  142. package/docs/cross-agent-ledger.7.md +217 -0
  143. package/docs/designs/handoff-ledger.md +145 -0
  144. package/docs/durable-state-and-locking.7.md +4 -0
  145. package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
  146. package/docs/execution-backends.7.md +4 -0
  147. package/docs/handoff-setup.md +120 -0
  148. package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
  149. package/docs/multi-agent-eval-replay-harness.7.md +4 -0
  150. package/docs/multi-agent-operator-ux.7.md +4 -0
  151. package/docs/node-snapshot-diff-replay.7.md +4 -0
  152. package/docs/observability-cost-accounting.7.md +4 -0
  153. package/docs/project-index.md +143 -71
  154. package/docs/real-execution-backends.7.md +4 -0
  155. package/docs/release-and-migration.7.md +4 -0
  156. package/docs/release-tooling.7.md +4 -0
  157. package/docs/run-registry-control-plane.7.md +4 -0
  158. package/docs/run-retention-reclamation.7.md +25 -0
  159. package/docs/state-explosion-management.7.md +4 -0
  160. package/docs/team-collaboration.7.md +4 -0
  161. package/docs/web-desktop-workbench.7.md +4 -0
  162. package/manifest/plugin.manifest.json +1 -1
  163. package/manifest/source-context-profiles.json +9 -13
  164. package/package.json +1 -1
  165. package/scripts/agents/codex-agent.js +34 -4
  166. package/scripts/agents/cw-attest-wrap.js +2 -2
  167. package/scripts/bump-version.js +4 -3
  168. package/scripts/canonical-apps.js +4 -4
  169. package/scripts/children/batch-delegate-child.js +30 -10
  170. package/scripts/dogfood-architecture-review.js +2 -3
  171. package/scripts/dogfood-release.js +3 -3
  172. package/scripts/gen-parity-doc.js +15 -2
  173. package/scripts/golden-path.js +4 -4
  174. package/scripts/onramp-check.js +1 -1
  175. package/scripts/parity-check.js +38 -21
  176. package/scripts/release-flow.js +9 -3
  177. package/scripts/sync-project-index.js +51 -27
  178. package/scripts/validate-run-state-schema.js +2 -2
  179. package/scripts/version-sync-check.js +30 -30
  180. package/dist/candidate-scoring.js +0 -729
  181. package/dist/capability-core.js +0 -1186
  182. package/dist/capability-registry.js +0 -879
  183. package/dist/cli/command-surface.js +0 -490
  184. package/dist/cli/format.js +0 -56
  185. package/dist/cli/handlers/audit.js +0 -82
  186. package/dist/cli/handlers/blackboard.js +0 -81
  187. package/dist/cli/handlers/candidate.js +0 -40
  188. package/dist/cli/handlers/clones.js +0 -34
  189. package/dist/cli/handlers/collaboration.js +0 -61
  190. package/dist/cli/handlers/eval.js +0 -40
  191. package/dist/cli/handlers/maintenance.js +0 -107
  192. package/dist/cli/handlers/multi-agent.js +0 -165
  193. package/dist/cli/handlers/node.js +0 -41
  194. package/dist/cli/handlers/operational.js +0 -155
  195. package/dist/cli/handlers/operator.js +0 -146
  196. package/dist/cli/handlers/registry.js +0 -68
  197. package/dist/cli/handlers/run.js +0 -153
  198. package/dist/cli/handlers/scheduling.js +0 -132
  199. package/dist/cli/handlers/workbench.js +0 -41
  200. package/dist/cli/handlers/worker.js +0 -45
  201. package/dist/cli/run-summary.js +0 -45
  202. package/dist/clones.js +0 -162
  203. package/dist/collaboration.js +0 -726
  204. package/dist/commit.js +0 -592
  205. package/dist/compare.js +0 -18
  206. package/dist/coordinator/classify.js +0 -45
  207. package/dist/coordinator/paths.js +0 -42
  208. package/dist/coordinator/util.js +0 -126
  209. package/dist/coordinator.js +0 -990
  210. package/dist/daemon.js +0 -44
  211. package/dist/dispatch.js +0 -250
  212. package/dist/drive.js +0 -827
  213. package/dist/error-feedback.js +0 -419
  214. package/dist/evidence-grounding.js +0 -184
  215. package/dist/execution-backend/agent.js +0 -294
  216. package/dist/execution-backend/probes.js +0 -112
  217. package/dist/execution-backend/util.js +0 -47
  218. package/dist/execution-backend.js +0 -961
  219. package/dist/gates.js +0 -48
  220. package/dist/harness.js +0 -61
  221. package/dist/loop-expansion.js +0 -60
  222. package/dist/mcp/tool-call.js +0 -434
  223. package/dist/mcp/tool-definitions.js +0 -1040
  224. package/dist/mcp-surface.js +0 -30
  225. package/dist/multi-agent/graph.js +0 -84
  226. package/dist/multi-agent/helpers.js +0 -141
  227. package/dist/multi-agent/ids.js +0 -20
  228. package/dist/multi-agent/paths.js +0 -22
  229. package/dist/multi-agent-eval/normalize.js +0 -51
  230. package/dist/multi-agent-eval.js +0 -678
  231. package/dist/multi-agent-host.js +0 -777
  232. package/dist/multi-agent.js +0 -984
  233. package/dist/node-projection.js +0 -59
  234. package/dist/node-snapshot.js +0 -260
  235. package/dist/operator-ux.js +0 -631
  236. package/dist/orchestrator/app-operations.js +0 -211
  237. package/dist/orchestrator/audit-operations.js +0 -182
  238. package/dist/orchestrator/candidate-operations.js +0 -117
  239. package/dist/orchestrator/cli-options.js +0 -294
  240. package/dist/orchestrator/collaboration-operations.js +0 -86
  241. package/dist/orchestrator/feedback-operations.js +0 -81
  242. package/dist/orchestrator/host-operations.js +0 -78
  243. package/dist/orchestrator/lifecycle-operations.js +0 -626
  244. package/dist/orchestrator/migration-operations.js +0 -44
  245. package/dist/orchestrator/multi-agent-operations.js +0 -362
  246. package/dist/orchestrator/topology-operations.js +0 -84
  247. package/dist/orchestrator.js +0 -917
  248. package/dist/pipeline-runner.js +0 -285
  249. package/dist/reclamation/hash.js +0 -72
  250. package/dist/reclamation.js +0 -812
  251. package/dist/reporter.js +0 -67
  252. package/dist/run-export.js +0 -784
  253. package/dist/run-registry/derive.js +0 -175
  254. package/dist/run-registry/format.js +0 -124
  255. package/dist/run-registry/gc.js +0 -251
  256. package/dist/run-registry/policy.js +0 -16
  257. package/dist/run-registry/queue.js +0 -115
  258. package/dist/run-registry.js +0 -850
  259. package/dist/run-state-schema.js +0 -68
  260. package/dist/scheduling.js +0 -184
  261. package/dist/state-explosion.js +0 -1014
  262. package/dist/state.js +0 -367
  263. package/dist/telemetry-ledger.js +0 -196
  264. package/dist/topology.js +0 -565
  265. package/dist/triggers.js +0 -184
  266. package/dist/trust-audit.js +0 -644
  267. package/dist/types/blackboard.js +0 -2
  268. package/dist/types/candidate.js +0 -2
  269. package/dist/types/collaboration.js +0 -2
  270. package/dist/types/core.js +0 -2
  271. package/dist/types/drive.js +0 -10
  272. package/dist/types/error-feedback.js +0 -2
  273. package/dist/types/evidence-reasoning.js +0 -2
  274. package/dist/types/execution-backend.js +0 -2
  275. package/dist/types/multi-agent.js +0 -2
  276. package/dist/types/observability.js +0 -2
  277. package/dist/types/pipeline.js +0 -2
  278. package/dist/types/reclamation.js +0 -8
  279. package/dist/types/report-bundle.js +0 -6
  280. package/dist/types/result.js +0 -2
  281. package/dist/types/run-registry.js +0 -2
  282. package/dist/types/run.js +0 -2
  283. package/dist/types/sandbox.js +0 -2
  284. package/dist/types/schedule.js +0 -2
  285. package/dist/types/state-node.js +0 -2
  286. package/dist/types/topology.js +0 -2
  287. package/dist/types/trust.js +0 -2
  288. package/dist/types/workbench.js +0 -2
  289. package/dist/types/worker.js +0 -2
  290. package/dist/types/workflow-app.js +0 -2
  291. package/dist/types.js +0 -44
  292. package/dist/util/fingerprint.js +0 -19
  293. package/dist/util/fingerprint.test.js +0 -27
  294. package/dist/verifier.js +0 -78
  295. package/dist/version.js +0 -8
  296. package/dist/workbench-host.js +0 -182
  297. package/dist/workbench.js +0 -192
  298. package/dist/worker-accept/acceptance.js +0 -114
  299. package/dist/worker-accept/blackboard-fanout.js +0 -80
  300. package/dist/worker-accept/blackboard-linkage.js +0 -19
  301. package/dist/worker-accept/context.js +0 -2
  302. package/dist/worker-accept/telemetry-ledger.js +0 -126
  303. package/dist/worker-accept/validation.js +0 -77
  304. package/dist/worker-accept/verifier-completion.js +0 -73
  305. package/dist/worker-isolation/helpers.js +0 -51
  306. package/dist/worker-isolation/paths.js +0 -46
  307. package/dist/worker-isolation.js +0 -656
  308. package/dist/workflow-api.js +0 -131
  309. /package/dist/{types → core/types}/boundary.js +0 -0
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
- // src/term.ts — zero-dependency terminal styling.
2
+ // shell/term.ts — zero-dependency terminal styling.
3
3
  //
4
- // Provides TTY-gated ANSI formatting for human-readable output. When output is
5
- // piped (non-TTY), styled calls return plain text. This keeps data channels
6
- // (stdout) and diagnostics (stderr) clean never adds escape codes to pipes.
7
- //
8
- // Used by: doctor, help, error messages, status summaries.
4
+ // MILESTONE 5 (v2/PLAN.md build order, step 5, doctor/fix): TTY-gated
5
+ // ANSI formatting, so a piped run (every conformance case pipes with
6
+ // NO_COLOR=1) prints plain text. EXTENDED at MILESTONE 11 (reporting/
7
+ // observability) with the rest of the old build's src/term.ts: indent,
8
+ // sectionHeader, phaseProgressLine, printSuccessSummary, stripAnsi,
9
+ // visibleWidth, truncate, formatFindingsSummary, FindingRow — byte-exact
10
+ // port of plugins/cool-workflow/src/term.ts:81-210.
9
11
  Object.defineProperty(exports, "__esModule", { value: true });
10
12
  exports.bold = bold;
11
13
  exports.dim = dim;
@@ -14,9 +16,9 @@ exports.yellow = yellow;
14
16
  exports.red = red;
15
17
  exports.cyan = cyan;
16
18
  exports.doctorGlyph = doctorGlyph;
17
- exports.indent = indent;
18
- exports.nextHint = nextHint;
19
19
  exports.tryHint = tryHint;
20
+ exports.nextHint = nextHint;
21
+ exports.indent = indent;
20
22
  exports.sectionHeader = sectionHeader;
21
23
  exports.phaseProgressLine = phaseProgressLine;
22
24
  exports.printSuccessSummary = printSuccessSummary;
@@ -27,9 +29,6 @@ exports.formatFindingsSummary = formatFindingsSummary;
27
29
  function isTTY(stream = process.stderr) {
28
30
  return Boolean(stream.isTTY);
29
31
  }
30
- /** Whether to emit ANSI color on a stream, honoring the de-facto env standards:
31
- * NO_COLOR / CW_NO_COLOR (any non-empty value) disable; FORCE_COLOR (non-"0") forces on
32
- * even when piped; otherwise fall back to isTTY. The `--no-color` flag sets CW_NO_COLOR. */
33
32
  function colorEnabled(stream, env = process.env) {
34
33
  if ((env.NO_COLOR ?? "") !== "" || (env.CW_NO_COLOR ?? "") !== "")
35
34
  return false;
@@ -37,7 +36,6 @@ function colorEnabled(stream, env = process.env) {
37
36
  return true;
38
37
  return isTTY(stream);
39
38
  }
40
- // ---- ansi codes ----
41
39
  const ansi = {
42
40
  reset: "\x1b[0m",
43
41
  bold: "\x1b[1m",
@@ -47,7 +45,6 @@ const ansi = {
47
45
  red: "\x1b[31m",
48
46
  cyan: "\x1b[36m",
49
47
  };
50
- // ---- styled text ----
51
48
  function style(code, text, stream) {
52
49
  if (!colorEnabled(stream))
53
50
  return text;
@@ -71,7 +68,6 @@ function red(text, stream) {
71
68
  function cyan(text, stream) {
72
69
  return style(ansi.cyan, text, stream);
73
70
  }
74
- // ---- semantic helpers ----
75
71
  /** Returns the styled glyph + label for a doctor check severity. */
76
72
  function doctorGlyph(status, stream) {
77
73
  const glyph = { ok: "✓", warn: "!", fail: "✗" };
@@ -82,42 +78,41 @@ function doctorGlyph(status, stream) {
82
78
  };
83
79
  return color[status](`${glyph[status]}`, stream);
84
80
  }
85
- /** Render a multi-line block with consistent 2-space indentation. */
86
- function indent(text, spaces = 2) {
87
- const prefix = " ".repeat(spaces);
88
- return text.split("\n").map((line) => `${prefix}${line}`).join("\n");
81
+ /** A `Try: <cmd>` recovery hint (brew-style; the command stays plain to copy). */
82
+ function tryHint(cmd, stream) {
83
+ return `${dim("Try:", stream)} ${cmd}`;
89
84
  }
90
85
  /** A `Next: <cmd>` hint line (the command stays plain so it is copy-pasteable). */
91
86
  function nextHint(cmd, stream) {
92
87
  return `${dim("Next:", stream)} ${cmd}`;
93
88
  }
94
- /** A `Try: <cmd>` recovery hint (brew-style; the command stays plain to copy). */
95
- function tryHint(cmd, stream) {
96
- return `${dim("Try:", stream)} ${cmd}`;
89
+ /** Render a multi-line block with consistent 2-space indentation. */
90
+ function indent(text, spaces = 2) {
91
+ const prefix = " ".repeat(spaces);
92
+ return text
93
+ .split("\n")
94
+ .map((line) => `${prefix}${line}`)
95
+ .join("\n");
97
96
  }
98
97
  /** A `==> Title` section header (brew-style). */
99
98
  function sectionHeader(title, stream) {
100
99
  return `${bold("==>", stream)} ${title}`;
101
100
  }
102
- /** A phase-progress line: `==> Map ✓ (6/6)` / `==> Assess … (3/6)`. Parallel phases
103
- * use ⇉, sequential use …; a finished phase uses a green ✓. */
101
+ /** A phase-progress line: `==> Map ✓ (6/6)` / `==> Assess … (3/6)`. Parallel
102
+ * phases use ⇉, sequential use …; a finished phase uses a green ✓. */
104
103
  function phaseProgressLine(name, done, total, mode, stream) {
105
104
  const complete = total > 0 && done >= total;
106
- const glyph = complete ? green("✓", stream) : (mode === "parallel" ? "⇉" : "…");
105
+ const glyph = complete ? green("✓", stream) : mode === "parallel" ? "⇉" : "…";
107
106
  const count = total > 0 ? ` (${done}/${total})` : "";
108
107
  return `${sectionHeader(name, stream)} ${glyph}${count}`;
109
108
  }
110
- /** Print a success summary to stderr (TTY-gated). Shows the report path, a one-line
111
- * status (with N/N worker counts when known), and a copy-pasteable next/recovery
112
- * command. Pipe-friendly: silent when stderr is not a TTY, so it never pollutes
113
- * piped/`--json` stdout. A non-complete run with no agent configured gets a brew-style
114
- * `Try: cw doctor` recovery line; otherwise `Next: cw status <id>` to inspect. */
109
+ /** Print a success summary to stderr (TTY-gated). Silent when stderr is
110
+ * not a TTY, so it never pollutes piped/`--json` stdout. */
115
111
  function printSuccessSummary(fields, stream) {
116
112
  if (!isTTY(stream))
117
113
  return;
118
114
  const s = stream || process.stderr;
119
- const counts = (typeof fields.completedWorkers === "number" && typeof fields.plannedWorkers === "number")
120
- ? ` — ${fields.completedWorkers}/${fields.plannedWorkers}` : "";
115
+ const counts = typeof fields.completedWorkers === "number" && typeof fields.plannedWorkers === "number" ? ` — ${fields.completedWorkers}/${fields.plannedWorkers}` : "";
121
116
  s.write(`\n${green("✓", s)} Report: ${fields.reportPath}\n`);
122
117
  if (fields.status === "complete") {
123
118
  s.write(` ${green("✓", s)} Status: complete${counts}\n`);
@@ -125,8 +120,6 @@ function printSuccessSummary(fields, stream) {
125
120
  }
126
121
  else {
127
122
  s.write(` ${yellow("!", s)} Status: ${fields.status}${counts}\n`);
128
- // No agent backend is the #1 first-run blocker — point at the one command that
129
- // diagnoses and prints the fix, brew-style. Otherwise inspect the run's state.
130
123
  if (fields.agentConfigured === false)
131
124
  s.write(` ${tryHint("cw doctor", s)}\n`);
132
125
  else
@@ -139,13 +132,15 @@ const ANSI_RE = /\x1b\[[0-9;]*m/g;
139
132
  function stripAnsi(text) {
140
133
  return text.replace(ANSI_RE, "");
141
134
  }
142
- /** Visible width of a string, ignoring ANSI. Counts each code point as width 1 — a known
143
- * minor caveat for wide (CJK/emoji) glyphs, acceptable for one-line status truncation. */
135
+ /** Visible width of a string, ignoring ANSI. Counts each code point as
136
+ * width 1 — a known minor caveat for wide (CJK/emoji) glyphs, acceptable
137
+ * for one-line status truncation. */
144
138
  function visibleWidth(text) {
145
139
  return [...stripAnsi(text)].length;
146
140
  }
147
- /** Truncate a (possibly styled) string to `maxWidth` visible columns, appending `…` when cut.
148
- * Operates on the PLAIN text (callers truncate before styling), so no ANSI is split. */
141
+ /** Truncate a (possibly styled) string to `maxWidth` visible columns,
142
+ * appending `…` when cut. Operates on the PLAIN text (callers truncate
143
+ * before styling), so no ANSI is split. */
149
144
  function truncate(text, maxWidth) {
150
145
  if (maxWidth <= 0)
151
146
  return "";
@@ -157,12 +152,10 @@ function truncate(text, maxWidth) {
157
152
  return `${chars.slice(0, maxWidth - 1).join("")}…`;
158
153
  }
159
154
  // ---- findings summary table (end-of-run, compact) ----
160
- /** Severity order for sorting + counting (most severe first). */
161
155
  const SEVERITY_ORDER = ["P0", "P1", "P2", "P3", "none"];
162
- /** Render a compact findings summary assembled from the run's `cw:result` blocks: a one-line
163
- * count headline (e.g. `Findings: 3 2×P1, 1×P2`) plus a small id/severity/class table. This
164
- * is the end-of-run summary NOT the full prose (that stays in report.md + the transcript).
165
- * Returns "" when there are no findings (caller prints nothing). */
156
+ /** Render a compact findings summary assembled from the run's `cw:result`
157
+ * blocks: a one-line count headline plus a small id/severity/class
158
+ * table. Returns "" when there are no findings. */
166
159
  function formatFindingsSummary(findings, stream) {
167
160
  if (!findings.length)
168
161
  return "";
@@ -180,10 +173,7 @@ function formatFindingsSummary(findings, stream) {
180
173
  const rows = [...findings].sort((a, b) => order(a.severity) - order(b.severity));
181
174
  const sevW = Math.max(8, ...rows.map((r) => (r.severity || "none").length));
182
175
  const clsW = Math.max(5, ...rows.map((r) => (r.classification || "unknown").length));
183
- const lines = [
184
- `${bold("Findings:", stream)} ${findings.length} — ${counts}`,
185
- dim(` ${"SEVERITY".padEnd(sevW)} ${"CLASS".padEnd(clsW)} ID`, stream)
186
- ];
176
+ const lines = [`${bold("Findings:", stream)} ${findings.length} — ${counts}`, dim(` ${"SEVERITY".padEnd(sevW)} ${"CLASS".padEnd(clsW)} ID`, stream)];
187
177
  for (const r of rows) {
188
178
  const sev = (r.severity || "none").padEnd(sevW);
189
179
  const cls = (r.classification || "unknown").padEnd(clsW);
@@ -0,0 +1,361 @@
1
+ "use strict";
2
+ // shell/topology-io.ts — applyTopology's IO wiring: materializes the
3
+ // blackboard + multi-agent state a topology needs, then persists the
4
+ // topology-run record.
5
+ //
6
+ // MILESTONE 9. Byte-exact port of the impure half of the old build's
7
+ // src/topology.ts's applyTopology + summarizeTopologies/buildTopologyGraph/
8
+ // showTopologyRun/persistTopologyState.
9
+ //
10
+ // Evidence: SPEC/multi-agent.md section B; plugins/cool-workflow/src/
11
+ // topology.ts:139-455 (byte-exact source for the wiring sequence).
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.ensureTopologyState = ensureTopologyState;
47
+ exports.persistTopologyState = persistTopologyState;
48
+ exports.applyTopology = applyTopology;
49
+ exports.showTopologyRun = showTopologyRun;
50
+ exports.summarizeTopologies = summarizeTopologies;
51
+ exports.buildTopologyGraph = buildTopologyGraph;
52
+ exports.formatTopologySummaryText = formatTopologySummaryText;
53
+ exports.formatTopologyGraphText = formatTopologyGraphText;
54
+ const fs = __importStar(require("node:fs"));
55
+ const path = __importStar(require("node:path"));
56
+ const fs_atomic_1 = require("./fs-atomic");
57
+ const node_store_1 = require("./node-store");
58
+ const state_node_1 = require("../core/state/state-node");
59
+ const contract_1 = require("../core/pipeline/contract");
60
+ const trust_audit_1 = require("./trust-audit");
61
+ const hash_1 = require("../core/hash");
62
+ const hash_2 = require("../core/hash");
63
+ const topo = __importStar(require("../core/multi-agent/topology"));
64
+ const multi_agent_io_1 = require("./multi-agent-io");
65
+ const coordinator_io_1 = require("./coordinator-io");
66
+ function topologyRoot(run) {
67
+ return run.paths.topologiesDir || path.join(run.paths.runDir, "topologies");
68
+ }
69
+ function topologyRunPath(run, id) {
70
+ const safe = id.replace(/[^a-zA-Z0-9_.:-]+/g, "_");
71
+ return path.join(topologyRoot(run), "runs", `${safe}.json`);
72
+ }
73
+ function ensureTopologyState(run) {
74
+ run.paths.topologiesDir = topologyRoot(run);
75
+ fs.mkdirSync(run.paths.topologiesDir, { recursive: true });
76
+ fs.mkdirSync(path.join(run.paths.topologiesDir, "runs"), { recursive: true });
77
+ const existing = run.topologies;
78
+ const state = existing || { schemaVersion: topo.TOPOLOGY_SCHEMA_VERSION, runs: [] };
79
+ state.schemaVersion = topo.TOPOLOGY_SCHEMA_VERSION;
80
+ state.runs = state.runs || [];
81
+ run.topologies = state;
82
+ return state;
83
+ }
84
+ function persistTopologyState(run) {
85
+ const state = ensureTopologyState(run);
86
+ (0, fs_atomic_1.writeJson)(path.join(topologyRoot(run), "index.json"), {
87
+ schemaVersion: topo.TOPOLOGY_SCHEMA_VERSION,
88
+ runId: run.id,
89
+ counts: { runs: state.runs.length },
90
+ runs: state.runs.map((record) => ({ id: record.id, topologyId: record.topologyId, status: record.status, updatedAt: record.updatedAt })),
91
+ });
92
+ for (const record of state.runs)
93
+ (0, fs_atomic_1.writeJson)(topologyRunPath(run, record.id), record);
94
+ }
95
+ function now() {
96
+ return new Date().toISOString();
97
+ }
98
+ function compact(value) {
99
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
100
+ }
101
+ function appendTopologyNode(run, record, status) {
102
+ (0, node_store_1.appendRunNode)(run, (0, state_node_1.createStateNode)({
103
+ id: `${run.id}:topology:${record.id}`,
104
+ kind: "topology-run",
105
+ status,
106
+ loopStage: run.loopStage,
107
+ outputs: { topologyId: record.topologyId, status: record.status },
108
+ artifacts: [{ id: "topology-run", kind: "json", path: topologyRunPath(run, record.id) }],
109
+ parents: [`${run.id}:multi-agent:${record.multiAgentRunId}`, `${run.id}:blackboard:${record.blackboardId}`],
110
+ contractId: contract_1.DEFAULT_PIPELINE_CONTRACT_ID,
111
+ metadata: { topologyId: record.topologyId, topologyRunId: record.id },
112
+ }));
113
+ }
114
+ function applyTopology(run, topologyId, input = {}) {
115
+ const validation = topo.validateTopologyDefinition(topologyId);
116
+ if (!validation.valid || !validation.definition) {
117
+ throw new Error(`Invalid topology ${topologyId}: ${validation.issues.map((entry) => entry.message).join("; ")}`);
118
+ }
119
+ const definition = validation.definition;
120
+ const state = ensureTopologyState(run);
121
+ const taskIds = topo.selectedTaskIds(run.tasks, input.taskIds);
122
+ const id = input.id || topo.topologyRunId(definition, taskIds, run.id, state.runs.length, (value) => (0, hash_1.sha256)((0, hash_2.stableStringify)(value)));
123
+ if (state.runs.some((record) => record.id === id))
124
+ throw new Error(`Duplicate MultiAgentTopologyRun id: ${id}`);
125
+ const board = (0, coordinator_io_1.resolveBlackboard)(run, { id: input.blackboardId || `${id}-blackboard`, title: `${definition.title} Blackboard`, tags: ["topology", definition.id] });
126
+ const topics = definition.blackboardTopics.map((topic) => (0, coordinator_io_1.createBlackboardTopic)(run, { id: `${id}-${topic.id}`, title: topic.title, description: topic.description, blackboardId: board.id, tags: ["topology", definition.id] }));
127
+ const multiAgentRun = (0, multi_agent_io_1.createMultiAgentRun)(run, {
128
+ id: input.multiAgentRunId || `${id}-ma`,
129
+ title: input.title || definition.title,
130
+ objective: definition.summary,
131
+ blackboardId: board.id,
132
+ topicIds: topics.map((topic) => topic.id),
133
+ metadata: { topologyId: definition.id, topologyRunId: id },
134
+ });
135
+ const roleIds = [];
136
+ for (const role of topo.materializedRoles(definition, topo.withLegacyRoleCounts(input))) {
137
+ const record = (0, multi_agent_io_1.createAgentRole)(run, {
138
+ id: `${id}-${role.id}`,
139
+ multiAgentRunId: multiAgentRun.id,
140
+ title: role.title,
141
+ responsibilities: role.responsibilities,
142
+ requiredEvidence: role.requiredEvidence,
143
+ expectedArtifacts: role.expectedArtifacts,
144
+ faninObligations: role.faninObligations,
145
+ blackboardId: board.id,
146
+ topicIds: topics.map((topic) => topic.id),
147
+ metadata: { topologyId: definition.id, topologyRunId: id, topologyRoleId: role.id },
148
+ });
149
+ roleIds.push(record.id);
150
+ }
151
+ const group = (0, multi_agent_io_1.createAgentGroup)(run, { id: `${id}-group`, multiAgentRunId: multiAgentRun.id, title: `${definition.title} Group`, phase: definition.title, taskIds, blackboardId: board.id, topicIds: topics.map((topic) => topic.id), metadata: { topologyId: definition.id, topologyRunId: id } });
152
+ const fanoutRoles = topo.fanoutRoleIds(roleIds);
153
+ const fanout = (0, multi_agent_io_1.createAgentFanout)(run, {
154
+ id: `${id}-fanout`,
155
+ multiAgentRunId: multiAgentRun.id,
156
+ groupId: group.id,
157
+ reason: `${definition.id} topology fanout`,
158
+ roleIds: fanoutRoles,
159
+ taskIds,
160
+ concurrencyLimit: fanoutRoles.length,
161
+ expectedReturnShape: `${definition.title} worker output must include cw:result evidence and blackboard-indexable artifacts/messages.`,
162
+ blackboardId: board.id,
163
+ topicIds: topics.map((topic) => topic.id),
164
+ metadata: { topologyId: definition.id, topologyRunId: id, fanoutStrategy: definition.fanoutStrategy },
165
+ });
166
+ const message = (0, coordinator_io_1.postBlackboardMessage)(run, { topicId: topics[0].id, blackboardId: board.id, body: `${definition.title} topology applied. Roles=${roleIds.join(", ")} fanout=${fanout.id}.`, tags: ["topology", definition.id], metadata: { topologyRunId: id } });
167
+ const decision = (0, coordinator_io_1.recordCoordinatorDecision)(run, {
168
+ blackboardId: board.id,
169
+ topicId: topics[0].id,
170
+ kind: "context-update",
171
+ outcome: "accepted",
172
+ reason: `${definition.title} topology materialized on multi-agent runtime and blackboard.`,
173
+ subjectIds: [multiAgentRun.id, group.id, fanout.id],
174
+ messageIds: [message.id],
175
+ tags: ["topology", definition.id],
176
+ metadata: { topologyRunId: id },
177
+ });
178
+ let fanin;
179
+ if (input.collectInitialFanin) {
180
+ fanin = (0, multi_agent_io_1.collectAgentFanin)(run, { id: `${id}-fanin-initial`, multiAgentRunId: multiAgentRun.id, groupId: group.id, fanoutId: fanout.id, requiredRoleIds: fanout.roleIds, strategy: definition.faninStrategy, blackboardId: board.id, topicIds: topics.map((topic) => topic.id), metadata: { topologyId: definition.id, topologyRunId: id } });
181
+ }
182
+ const audit = (0, trust_audit_1.recordTrustAuditEvent)(run, {
183
+ kind: "topology.create",
184
+ decision: "recorded",
185
+ source: "runtime-derived",
186
+ topologyId: definition.id,
187
+ topologyRunId: id,
188
+ multiAgentRunId: multiAgentRun.id,
189
+ agentGroupId: group.id,
190
+ agentFanoutId: fanout.id,
191
+ blackboardId: board.id,
192
+ blackboardMessageId: message.id,
193
+ coordinatorDecisionId: decision.id,
194
+ metadata: { fanoutStrategy: definition.fanoutStrategy, faninStrategy: definition.faninStrategy },
195
+ });
196
+ const stamp = now();
197
+ const record = {
198
+ schemaVersion: topo.TOPOLOGY_SCHEMA_VERSION,
199
+ id,
200
+ runId: run.id,
201
+ topologyId: definition.id,
202
+ createdAt: stamp,
203
+ updatedAt: stamp,
204
+ status: fanin?.status === "blocked" ? "blocked" : "planned",
205
+ title: input.title || definition.title,
206
+ multiAgentRunId: multiAgentRun.id,
207
+ blackboardId: board.id,
208
+ topicIds: topics.map((topic) => topic.id),
209
+ roleIds,
210
+ groupIds: [group.id],
211
+ fanoutIds: [fanout.id],
212
+ faninIds: fanin ? [fanin.id] : [],
213
+ messageIds: [message.id],
214
+ artifactRefIds: [],
215
+ coordinatorDecisionIds: [decision.id],
216
+ candidateIds: [],
217
+ selectionIds: [],
218
+ commitIds: [],
219
+ missingEvidence: fanin?.blockedReasons || definition.requiredEvidence,
220
+ conflicts: [],
221
+ nextActions: topo.nextActionsFor(run.id, id, fanout.id),
222
+ links: {
223
+ workflowRunId: run.id,
224
+ multiAgentRunId: multiAgentRun.id,
225
+ blackboardId: board.id,
226
+ blackboardTopicIds: topics.map((topic) => topic.id),
227
+ agentRoleIds: roleIds,
228
+ agentGroupIds: [group.id],
229
+ agentFanoutIds: [fanout.id],
230
+ agentFaninIds: fanin ? [fanin.id] : [],
231
+ coordinatorDecisionIds: [decision.id],
232
+ candidateIds: [],
233
+ selectionIds: [],
234
+ commitIds: [],
235
+ auditEventIds: [audit.id],
236
+ },
237
+ metadata: compact({ ...(input.metadata || {}), topology: definition }),
238
+ };
239
+ state.runs.push(record);
240
+ appendTopologyNode(run, record, topo.statusToNodeStatus(record.status));
241
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
242
+ kind: "topology.verdict",
243
+ decision: record.status === "blocked" ? "failed" : "recorded",
244
+ source: "cw-validated",
245
+ topologyId: definition.id,
246
+ topologyRunId: id,
247
+ multiAgentRunId: multiAgentRun.id,
248
+ agentFanoutId: fanout.id,
249
+ agentFaninId: fanin?.id,
250
+ blackboardId: board.id,
251
+ coordinatorDecisionId: decision.id,
252
+ metadata: { status: record.status, missingEvidence: record.missingEvidence },
253
+ });
254
+ persistTopologyState(run);
255
+ return record;
256
+ }
257
+ function showTopologyRun(run, topologyRunId) {
258
+ const record = ensureTopologyState(run).runs.find((entry) => entry.id === topologyRunId);
259
+ if (!record)
260
+ throw new Error(`Unknown topology run id: ${topologyRunId}`);
261
+ return record;
262
+ }
263
+ function countBy(items, pick) {
264
+ const counts = {};
265
+ for (const item of items)
266
+ counts[pick(item)] = (counts[pick(item)] || 0) + 1;
267
+ return counts;
268
+ }
269
+ function summarizeTopologies(run) {
270
+ const state = ensureTopologyState(run);
271
+ const multi = run.multiAgent || {};
272
+ const fanins = multi.fanins || [];
273
+ const active = state.runs.map((record) => {
274
+ const inferredFanins = fanins.filter((fanin) => record.groupIds.includes(fanin.groupId) || record.fanoutIds.includes(fanin.fanoutId || ""));
275
+ const allFaninIds = topo.unique([...record.faninIds, ...inferredFanins.map((fanin) => fanin.id)]);
276
+ const blocked = inferredFanins.filter((fanin) => fanin.status === "blocked" || !fanin.verifierReady);
277
+ const ready = inferredFanins.some((fanin) => fanin.verifierReady);
278
+ const missingEvidence = topo.unique([...record.missingEvidence, ...blocked.flatMap((fanin) => fanin.blockedReasons)]);
279
+ return {
280
+ id: record.id,
281
+ topologyId: record.topologyId,
282
+ status: ready ? "ready" : blocked.length ? "blocked" : record.status,
283
+ multiAgentRunId: record.multiAgentRunId,
284
+ blackboardId: record.blackboardId,
285
+ roles: record.roleIds,
286
+ groups: record.groupIds,
287
+ topics: record.topicIds,
288
+ fanouts: record.fanoutIds,
289
+ fanins: allFaninIds,
290
+ missingEvidence,
291
+ conflicts: record.conflicts,
292
+ readiness: ready ? "fanin ready" : missingEvidence.length ? "missing evidence" : "awaiting worker output",
293
+ nextActions: ready ? [`node scripts/cw.js candidate register ${run.id} --result-node <reducer-or-panel-result>`] : record.nextActions,
294
+ };
295
+ });
296
+ return {
297
+ runId: run.id,
298
+ totalRuns: state.runs.length,
299
+ runsByStatus: countBy(active, (record) => record.status),
300
+ officialTopologies: topo.listTopologyDefinitions().map((definition) => definition.id),
301
+ active,
302
+ nextAction: active.find((record) => record.nextActions.length)?.nextActions[0] || `node scripts/cw.js topology apply ${run.id} map-reduce --task <task-id>`,
303
+ };
304
+ }
305
+ function buildTopologyGraph(run) {
306
+ const state = ensureTopologyState(run);
307
+ return topo.buildTopologyGraphFromRuns(run.id, state.runs, (id) => topologyRunPath(run, id));
308
+ }
309
+ function formatTopologyCounts(counts) {
310
+ const entries = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
311
+ if (!entries.length)
312
+ return "none";
313
+ return entries.map(([k, v]) => `${k}=${v}`).join(", ");
314
+ }
315
+ /** `cw topology summary <run>` human text — port of the old build's
316
+ * formatTopologyPanel (operator-ux/format.ts): a `Topologies` rollup with
317
+ * per-run roles/topics/fanout/fanin/readiness. */
318
+ function formatTopologySummaryText(summary) {
319
+ const lines = [
320
+ "Topologies",
321
+ ` runs=${summary.totalRuns}; status=${formatTopologyCounts(summary.runsByStatus)}; official=${summary.officialTopologies.join(", ")}`,
322
+ ];
323
+ for (const record of summary.active.slice(0, 6)) {
324
+ lines.push(` ${record.id}: ${record.topologyId}, status=${record.status}, readiness=${record.readiness}`);
325
+ lines.push(` run=${record.multiAgentRunId} board=${record.blackboardId}`);
326
+ lines.push(` roles=${record.roles.join(", ") || "none"} topics=${record.topics.join(", ") || "none"}`);
327
+ lines.push(` fanout=${record.fanouts.join(", ") || "none"} fanin=${record.fanins.join(", ") || "none"}`);
328
+ for (const missing of record.missingEvidence.slice(0, 4))
329
+ lines.push(` missing=${missing}`);
330
+ for (const conflict of record.conflicts.slice(0, 4))
331
+ lines.push(` conflict=${conflict}`);
332
+ if (record.nextActions[0])
333
+ lines.push(` next=${record.nextActions[0]}`);
334
+ }
335
+ if (summary.nextAction)
336
+ lines.push(` next=${summary.nextAction}`);
337
+ return lines.join("\n");
338
+ }
339
+ /** `cw topology graph <run>` human text — the same `Run Graph:` render
340
+ * `cw graph` uses, over the topology graph's nodes/edges (old build:
341
+ * formatOperatorGraph({ runId, nodes, edges })). */
342
+ function formatTopologyGraphText(runId, graph) {
343
+ const lines = [`Run Graph: ${runId}`, "", "Nodes"];
344
+ const groups = {};
345
+ for (const node of graph.nodes)
346
+ (groups[node.kind] ||= []).push(node);
347
+ for (const kind of Object.keys(groups).sort()) {
348
+ lines.push(` ${kind}`);
349
+ for (const node of groups[kind]) {
350
+ const suffix = node.path ? ` -> ${node.path}` : "";
351
+ lines.push(` [${node.status}] ${node.id} (${node.label})${suffix}`);
352
+ }
353
+ }
354
+ lines.push("", "Edges");
355
+ if (!graph.edges.length)
356
+ lines.push(" none");
357
+ for (const edge of graph.edges) {
358
+ lines.push(` ${edge.from} -> ${edge.to}${edge.label ? ` (${edge.label})` : ""}`);
359
+ }
360
+ return lines.join("\n");
361
+ }