@north-light/crouter 0.3.173 → 0.3.174

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 (673) hide show
  1. package/bin/runtime-selector.mjs +8 -1
  2. package/dist/api/__tests__/{client.test.js → serial/client.test.js} +2 -135
  3. package/dist/api/client.d.ts +12 -21
  4. package/dist/api/client.js +22 -31
  5. package/dist/api/dto/broker-ops.d.ts +158 -0
  6. package/dist/api/dto/broker-ops.js +6 -0
  7. package/dist/api/dto/crons.d.ts +6 -0
  8. package/dist/api/dto/human.d.ts +7 -1
  9. package/dist/api/dto/inbox.d.ts +1 -71
  10. package/dist/api/dto/inbox.js +1 -9
  11. package/dist/api/dto/nodes.d.ts +6 -7
  12. package/dist/api/dto/reviews.d.ts +1 -11
  13. package/dist/api/routes.d.ts +7 -4
  14. package/dist/api/routes.js +8 -5
  15. package/dist/build-root.js +3 -3
  16. package/dist/builtin-memory/04-base-worker.md +1 -4
  17. package/dist/builtin-memory/05-kinds/advisor/00-base.md +1 -5
  18. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +2 -7
  19. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -6
  20. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -6
  21. package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -4
  22. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +1 -2
  23. package/dist/builtin-memory/05-kinds/general/00-base.md +1 -3
  24. package/dist/builtin-memory/05-kinds/plan/00-base.md +1 -6
  25. package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +1 -2
  26. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +1 -3
  27. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +1 -3
  28. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +1 -3
  29. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -3
  30. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -4
  31. package/dist/builtin-memory/05-kinds/review/companion/00-base.md +2 -3
  32. package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -2
  33. package/dist/builtin-memory/advisor/council.md +1 -6
  34. package/dist/builtin-memory/design.md +2 -7
  35. package/dist/builtin-memory/development.md +9 -14
  36. package/dist/builtin-memory/internal/marketplaces.md +2 -6
  37. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  38. package/dist/builtin-memory/internal/plugins.md +2 -8
  39. package/dist/builtin-memory/internal/storage-tiers.md +1 -2
  40. package/dist/builtin-memory/testing.md +41 -0
  41. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{provider-rotation.test.ts → serial/provider-rotation.test.ts} +27 -179
  42. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +5 -15
  43. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +1 -28
  44. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +43 -88
  45. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +7 -1
  46. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +9 -1
  47. package/dist/cli.js +2 -2
  48. package/dist/clients/attach/__tests__/attach-remote-readonly.test.js +37 -131
  49. package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +71 -152
  50. package/dist/clients/attach/__tests__/context-message.test.js +16 -174
  51. package/dist/clients/attach/__tests__/crtr-output.test.js +36 -2069
  52. package/dist/clients/attach/__tests__/diagram.test.js +2 -2
  53. package/dist/clients/attach/__tests__/edit-diff.test.js +0 -4
  54. package/dist/clients/attach/__tests__/file-review-focus.test.js +2 -1
  55. package/dist/clients/attach/__tests__/group-activity.test.js +40 -229
  56. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +26 -76
  57. package/dist/clients/attach/__tests__/prompt-preview.test.js +4 -70
  58. package/dist/clients/attach/__tests__/ref-autocomplete.test.js +11 -125
  59. package/dist/clients/attach/__tests__/titled-editor-preview.test.js +14 -205
  60. package/dist/clients/attach/command.d.ts +2 -1
  61. package/dist/clients/attach/command.js +81 -76
  62. package/dist/clients/attach/input/capabilities.d.ts +27 -0
  63. package/dist/clients/attach/input/capabilities.js +96 -0
  64. package/dist/clients/attach/input/controller.d.ts +20 -182
  65. package/dist/clients/attach/input/controller.js +128 -537
  66. package/dist/clients/attach/input/overlay-owner.d.ts +41 -0
  67. package/dist/clients/attach/input/overlay-owner.js +199 -0
  68. package/dist/clients/attach/overlays/file-review.d.ts +0 -4
  69. package/dist/clients/attach/overlays/file-review.js +13 -5
  70. package/dist/clients/attach/overlays/graph.js +2 -10
  71. package/dist/clients/attach/render/chat-view.d.ts +4 -3
  72. package/dist/clients/attach/render/chat-view.js +39 -33
  73. package/dist/clients/attach/render/group-activity.d.ts +1 -0
  74. package/dist/clients/attach/render/group-activity.js +20 -13
  75. package/dist/clients/attach/render/group-recap.js +12 -3
  76. package/dist/clients/attach/session/connection.js +9 -15
  77. package/dist/clients/attach/session/editor-inventory.d.ts +6 -5
  78. package/dist/clients/attach/session/editor-inventory.js +7 -8
  79. package/dist/clients/attach/session/file-links.d.ts +13 -0
  80. package/dist/clients/attach/session/file-links.js +107 -0
  81. package/dist/clients/attach/session/frame.d.ts +3 -0
  82. package/dist/clients/attach/session/frame.js +10 -1
  83. package/dist/clients/attach/session/frames.js +2 -3
  84. package/dist/clients/attach/session/input-wiring.d.ts +4 -1
  85. package/dist/clients/attach/session/input-wiring.js +15 -14
  86. package/dist/clients/attach/session/mouse.d.ts +6 -1
  87. package/dist/clients/attach/session/mouse.js +8 -5
  88. package/dist/clients/attach/session/profile-files.js +18 -12
  89. package/dist/clients/attach/session/reconnect.js +1 -1
  90. package/dist/clients/attach/session/state-sync.js +1 -2
  91. package/dist/clients/attach/session/surface-host.d.ts +3 -2
  92. package/dist/clients/attach/session/surface-host.js +4 -4
  93. package/dist/clients/attach/slash/dispatch.d.ts +8 -31
  94. package/dist/clients/attach/slash/dispatch.js +34 -81
  95. package/dist/clients/attach/viewer.js +820 -860
  96. package/dist/clients/conversation/projection.d.ts +79 -0
  97. package/dist/clients/conversation/projection.js +341 -0
  98. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +159 -0
  99. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +151 -0
  100. package/dist/clients/inbox/controller.d.ts +0 -4
  101. package/dist/clients/inbox/controller.js +0 -2
  102. package/dist/clients/inbox/review/__tests__/review-core.test.js +4 -293
  103. package/dist/clients/inbox/review/__tests__/teardown-notice.test.js +100 -0
  104. package/dist/clients/inbox/review/comments-client.d.ts +3 -2
  105. package/dist/clients/inbox/review/comments-client.js +12 -12
  106. package/dist/clients/inbox/review/companion-pane.d.ts +30 -0
  107. package/dist/clients/inbox/review/companion-pane.js +86 -0
  108. package/dist/clients/inbox/review/document-surface.d.ts +182 -0
  109. package/dist/clients/inbox/review/{document-region.js → document-surface.js} +252 -48
  110. package/dist/clients/inbox/review/frame.js +5 -4
  111. package/dist/clients/inbox/review/launch.d.ts +8 -8
  112. package/dist/clients/inbox/review/launch.js +52 -92
  113. package/dist/clients/inbox/review/nvim.d.ts +2 -0
  114. package/dist/clients/inbox/review/nvim.js +14 -12
  115. package/dist/clients/inbox/review/review-client.d.ts +12 -8
  116. package/dist/clients/inbox/review/review-client.js +46 -35
  117. package/dist/clients/inbox/review-adapter.d.ts +3 -10
  118. package/dist/clients/inbox/review-adapter.js +27 -19
  119. package/dist/clients/inbox/surface.d.ts +0 -4
  120. package/dist/clients/inbox/surface.js +0 -2
  121. package/dist/clients/surfaces/host.d.ts +11 -3
  122. package/dist/commands/__tests__/api-client-cold-start-diagnostic.test.js +7 -34
  123. package/dist/commands/__tests__/human.test.js +11 -322
  124. package/dist/commands/api-client.d.ts +5 -5
  125. package/dist/commands/api-client.js +6 -85
  126. package/dist/commands/canvas-browse.js +3 -29
  127. package/dist/commands/canvas-config.js +1 -1
  128. package/dist/commands/canvas-use.js +3 -3
  129. package/dist/commands/canvas.js +1 -1
  130. package/dist/commands/cron.js +5 -1
  131. package/dist/commands/human/queue.d.ts +0 -4
  132. package/dist/commands/human/queue.js +7 -40
  133. package/dist/commands/memory/list.d.ts +14 -0
  134. package/dist/commands/memory/list.js +35 -31
  135. package/dist/commands/memory/read.d.ts +7 -0
  136. package/dist/commands/memory/read.js +14 -8
  137. package/dist/commands/node/bash.d.ts +1 -0
  138. package/dist/commands/node/bash.js +198 -0
  139. package/dist/commands/node/create.d.ts +23 -0
  140. package/dist/commands/node/create.js +207 -0
  141. package/dist/commands/node/inspect.d.ts +2 -0
  142. package/dist/commands/node/inspect.js +302 -0
  143. package/dist/commands/node/lifecycle.d.ts +4 -0
  144. package/dist/commands/node/lifecycle.js +415 -0
  145. package/dist/commands/node/message.d.ts +1 -0
  146. package/dist/commands/node/message.js +164 -0
  147. package/dist/commands/node/pyramid.d.ts +2 -0
  148. package/dist/commands/node/pyramid.js +79 -0
  149. package/dist/commands/node/subscription.d.ts +1 -0
  150. package/dist/commands/node/subscription.js +82 -0
  151. package/dist/commands/node/wait.d.ts +1 -0
  152. package/dist/commands/node/wait.js +159 -0
  153. package/dist/commands/node-lifecycle-revive.js +10 -57
  154. package/dist/commands/node.d.ts +0 -42
  155. package/dist/commands/node.js +11 -2065
  156. package/dist/commands/search.js +1 -1
  157. package/dist/commands/surface/node/cycle.d.ts +1 -0
  158. package/dist/commands/surface/node/cycle.js +83 -0
  159. package/dist/commands/surface/node/focus.d.ts +1 -0
  160. package/dist/commands/surface/node/focus.js +67 -0
  161. package/dist/commands/surface/node/id.d.ts +1 -0
  162. package/dist/commands/surface/node/id.js +38 -0
  163. package/dist/commands/surface/node/pin.d.ts +1 -0
  164. package/dist/commands/surface/node/pin.js +42 -0
  165. package/dist/commands/surface/node/placement.d.ts +46 -0
  166. package/dist/commands/surface/node/placement.js +124 -0
  167. package/dist/commands/surface-node.js +4 -1
  168. package/dist/commands/surface-reload.js +1 -2
  169. package/dist/commands/surface-tmux-spread.js +1 -1
  170. package/dist/commands/surface.js +5 -20
  171. package/dist/commands/sys/__tests__/setup-core.test.js +18 -271
  172. package/dist/commands/sys/config.js +6 -6
  173. package/dist/commands/sys/logs.js +1 -1
  174. package/dist/commands/sys/panels/broker-limits-panel.d.ts +17 -0
  175. package/dist/commands/sys/panels/broker-limits-panel.js +104 -0
  176. package/dist/commands/sys/panels/dependencies-panel.d.ts +14 -0
  177. package/dist/commands/sys/panels/dependencies-panel.js +80 -0
  178. package/dist/commands/sys/panels/keybindings-panel.d.ts +56 -0
  179. package/dist/commands/sys/panels/keybindings-panel.js +703 -0
  180. package/dist/commands/sys/panels/keys-panel.d.ts +16 -0
  181. package/dist/commands/sys/panels/keys-panel.js +60 -0
  182. package/dist/commands/sys/panels/models-panel.d.ts +77 -0
  183. package/dist/commands/sys/panels/models-panel.js +647 -0
  184. package/dist/commands/sys/panels/panel.d.ts +45 -0
  185. package/dist/commands/sys/panels/panel.js +137 -0
  186. package/dist/commands/sys/panels/plugins-panel.d.ts +16 -0
  187. package/dist/commands/sys/panels/plugins-panel.js +80 -0
  188. package/dist/commands/sys/panels/prefs-panel.d.ts +21 -0
  189. package/dist/commands/sys/panels/prefs-panel.js +85 -0
  190. package/dist/commands/sys/panels/provider-panel.d.ts +62 -0
  191. package/dist/commands/sys/panels/provider-panel.js +538 -0
  192. package/dist/commands/sys/settings-shell.d.ts +55 -0
  193. package/dist/commands/sys/settings-shell.js +400 -0
  194. package/dist/commands/sys/settings.d.ts +2 -0
  195. package/dist/commands/sys/settings.js +31 -0
  196. package/dist/commands/sys/setup-core.d.ts +3 -33
  197. package/dist/commands/sys/setup-core.js +27 -78
  198. package/dist/commands/sys/setup-settings-runner.d.ts +24 -0
  199. package/dist/commands/sys/setup-settings-runner.js +63 -0
  200. package/dist/commands/sys/setup-wizard.d.ts +26 -125
  201. package/dist/commands/sys/setup-wizard.js +209 -2473
  202. package/dist/commands/sys/setup.js +5 -7
  203. package/dist/commands/sys.js +3 -2
  204. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +50 -0
  205. package/dist/core/__tests__/broker-launch-candidates.test.js +20 -9
  206. package/dist/core/__tests__/broker-preflight.test.js +1 -1
  207. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +108 -27
  208. package/dist/core/__tests__/canvas-inbox-watcher.test.js +14 -0
  209. package/dist/core/__tests__/canvas.test.js +2 -36
  210. package/dist/core/__tests__/close.test.js +14 -3
  211. package/dist/core/__tests__/context-intro.test.js +32 -143
  212. package/dist/core/__tests__/cron-broker-capacity.test.js +22 -3
  213. package/dist/core/__tests__/daemon-boot.test.js +2 -20
  214. package/dist/core/__tests__/daemon-wedge.test.js +1 -1
  215. package/dist/core/__tests__/dead-node-policy-table.test.js +1 -18
  216. package/dist/core/__tests__/fixtures/fake-engine.d.ts +1 -1
  217. package/dist/core/__tests__/fixtures/fake-engine.js +2 -2
  218. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  219. package/dist/core/__tests__/focuses.test.js +4 -98
  220. package/dist/core/__tests__/helpers/broker-clients.d.ts +0 -3
  221. package/dist/core/__tests__/helpers/broker-clients.js +0 -14
  222. package/dist/core/__tests__/helpers/harness.d.ts +0 -8
  223. package/dist/core/__tests__/helpers/harness.js +7 -87
  224. package/dist/core/__tests__/helpers/viewer-panes.js +1 -1
  225. package/dist/core/__tests__/inline-memory-refs.test.js +5 -134
  226. package/dist/core/__tests__/kickoff.test.js +1 -1
  227. package/dist/core/__tests__/memory-resolver-precedence.test.js +29 -1
  228. package/dist/core/__tests__/migration.test.js +35 -48
  229. package/dist/core/__tests__/on-read-dedup-resume.test.js +22 -12
  230. package/dist/core/__tests__/passive-subscription.test.js +26 -49
  231. package/dist/core/__tests__/preview-registry-sync.test.js +1 -2
  232. package/dist/core/__tests__/prune.test.js +94 -1
  233. package/dist/core/__tests__/review-model-floor.test.js +0 -32
  234. package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +255 -0
  235. package/dist/core/__tests__/{full → seam}/broker-attach-stream.test.js +39 -7
  236. package/dist/core/__tests__/{full → seam}/broker-crash-teardown.test.js +30 -29
  237. package/dist/core/__tests__/{full → seam}/broker-pane-resolution.test.js +54 -5
  238. package/dist/core/__tests__/{full → seam}/broker-provider-retry.test.js +134 -82
  239. package/dist/core/__tests__/{full → seam}/broker-whip-interrupt.test.js +15 -4
  240. package/dist/core/__tests__/{full/idle-release-settlement.test.js → seam/dormancy-release.test.js} +69 -17
  241. package/dist/core/__tests__/{full/yield-message-transaction.test.js → seam/yield-refresh-transaction.test.js} +31 -9
  242. package/dist/core/__tests__/secrets.test.js +1 -7
  243. package/dist/core/__tests__/{broker-fork-seam.test.js → serial/broker-fork-seam.test.js} +58 -7
  244. package/dist/core/__tests__/{broker-sdk-wiring.test.js → serial/broker-sdk-wiring.test.js} +18 -162
  245. package/dist/core/__tests__/{broker-snapshot-history.test.js → serial/broker-snapshot-history.test.js} +9 -9
  246. package/dist/core/__tests__/{command-plugins.test.js → serial/command-plugins.test.js} +105 -15
  247. package/dist/core/__tests__/{deferred-no-wake.test.js → serial/deferred-no-wake.test.js} +23 -20
  248. package/dist/core/__tests__/{flagship-lifecycle.test.js → serial/flagship-lifecycle.test.js} +7 -7
  249. package/dist/core/__tests__/{host-teardown-process-group.test.js → serial/host-teardown-process-group.test.js} +6 -6
  250. package/dist/core/__tests__/{human-deliver-e2e.test.js → serial/human-deliver-e2e.test.js} +12 -12
  251. package/dist/core/__tests__/{live-mutation.test.js → serial/live-mutation.test.js} +5 -5
  252. package/dist/core/__tests__/{refresh-stall-recycle.test.js → serial/refresh-stall-recycle.test.js} +5 -14
  253. package/dist/core/__tests__/{revive.test.js → serial/revive.test.js} +15 -100
  254. package/dist/core/__tests__/{spawn-root.test.js → serial/spawn-root.test.js} +32 -15
  255. package/dist/core/__tests__/serial/subscription-delivery.test.js +126 -0
  256. package/dist/core/__tests__/serial/tmux-surface.test.js +256 -0
  257. package/dist/core/__tests__/{worktree.test.js → serial/worktree.test.js} +15 -15
  258. package/dist/core/__tests__/session-model.test.js +0 -18
  259. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +9 -0
  260. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +50 -0
  261. package/dist/core/broker-client/__tests__/transport-relay.test.js +15 -16
  262. package/dist/core/broker-client/__tests__/transport-socket.test.js +28 -0
  263. package/dist/core/broker-client/client.d.ts +2 -2
  264. package/dist/core/broker-client/client.js +4 -7
  265. package/dist/core/broker-client/transport-relay.d.ts +13 -19
  266. package/dist/core/broker-client/transport-relay.js +19 -34
  267. package/dist/core/broker-client/transport-socket.d.ts +2 -2
  268. package/dist/core/broker-client/transport-socket.js +9 -2
  269. package/dist/core/broker-client/transport.d.ts +7 -3
  270. package/dist/core/broker-client/transport.js +4 -4
  271. package/dist/core/canvas/__tests__/attention.test.js +1 -18
  272. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +7 -64
  273. package/dist/core/canvas/__tests__/remote-transport.test.js +2 -3
  274. package/dist/core/canvas/__tests__/render-remote.test.js +7 -52
  275. package/dist/core/canvas/browse/app.d.ts +2 -17
  276. package/dist/core/canvas/browse/app.js +16 -88
  277. package/dist/core/canvas/browse/model.d.ts +0 -4
  278. package/dist/core/canvas/browse/model.js +1 -12
  279. package/dist/core/canvas/browse/render.js +6 -3
  280. package/dist/core/canvas/crons.d.ts +8 -5
  281. package/dist/core/canvas/crons.js +7 -6
  282. package/dist/core/canvas/extensions.d.ts +7 -3
  283. package/dist/core/canvas/extensions.js +16 -4
  284. package/dist/core/canvas/nav-model.d.ts +0 -4
  285. package/dist/core/canvas/nav-model.js +2 -14
  286. package/dist/core/canvas/node-order.d.ts +3 -0
  287. package/dist/core/canvas/node-order.js +12 -0
  288. package/dist/core/canvas/remote-canvas-source.d.ts +3 -6
  289. package/dist/core/canvas/remote-canvas-source.js +8 -15
  290. package/dist/core/command.js +11 -4
  291. package/dist/core/config.js +3 -1
  292. package/dist/core/events/envelope.js +1 -1
  293. package/dist/core/events/read.js +7 -21
  294. package/dist/core/events/source.d.ts +0 -2
  295. package/dist/core/events/source.js +0 -16
  296. package/dist/core/events/types.d.ts +1 -1
  297. package/dist/core/fault-classifier.d.ts +1 -1
  298. package/dist/core/feed/passive.d.ts +0 -11
  299. package/dist/core/feed/passive.js +1 -62
  300. package/dist/core/human/__tests__/{inbox-core.test.js → serial/inbox-core.test.js} +6 -6
  301. package/dist/core/keybindings/__tests__/resolve.test.js +1 -96
  302. package/dist/core/keybindings/background-bash.js +3 -4
  303. package/dist/core/keybindings/catalog.js +1 -1
  304. package/dist/core/memory/inline-ref-grammar.js +3 -3
  305. package/dist/core/memory-resolver.d.ts +11 -25
  306. package/dist/core/memory-resolver.js +51 -33
  307. package/dist/core/model-routes.d.ts +15 -0
  308. package/dist/core/model-routes.js +12 -0
  309. package/dist/core/pool-credential-store.js +1 -1
  310. package/dist/core/preview-registry.js +8 -9
  311. package/dist/core/profiles/__tests__/root-profile.test.js +4 -14
  312. package/dist/core/review/realize.d.ts +0 -13
  313. package/dist/core/review/realize.js +0 -40
  314. package/dist/core/review/types.d.ts +1 -1
  315. package/dist/core/runtime/__tests__/engine-route.test.js +2 -2
  316. package/dist/core/runtime/bearings.js +1 -11
  317. package/dist/core/runtime/broker/client-registry.d.ts +33 -0
  318. package/dist/core/runtime/broker/client-registry.js +164 -0
  319. package/dist/core/runtime/broker/daemon-ops.d.ts +19 -0
  320. package/dist/core/runtime/broker/daemon-ops.js +49 -0
  321. package/dist/core/runtime/broker/event-projection.d.ts +42 -0
  322. package/dist/core/runtime/broker/event-projection.js +178 -0
  323. package/dist/core/runtime/broker/fault-retry.d.ts +39 -0
  324. package/dist/core/runtime/broker/fault-retry.js +277 -0
  325. package/dist/core/runtime/broker/frame-dispatch.d.ts +238 -0
  326. package/dist/core/runtime/broker/frame-dispatch.js +1537 -0
  327. package/dist/core/runtime/broker/inbox.d.ts +22 -0
  328. package/dist/core/runtime/broker/inbox.js +167 -0
  329. package/dist/core/runtime/broker/passive.d.ts +16 -0
  330. package/dist/core/runtime/broker/passive.js +44 -0
  331. package/dist/core/runtime/broker/read-ops.d.ts +23 -0
  332. package/dist/core/runtime/broker/read-ops.js +150 -0
  333. package/dist/core/runtime/broker/rebind.d.ts +74 -0
  334. package/dist/core/runtime/broker/rebind.js +211 -0
  335. package/dist/core/runtime/broker/tool-groups.d.ts +39 -0
  336. package/dist/core/runtime/broker/tool-groups.js +244 -0
  337. package/dist/core/runtime/broker-extension-render.d.ts +11 -0
  338. package/dist/core/runtime/broker-extension-render.js +181 -0
  339. package/dist/core/runtime/broker-persona-guidance.d.ts +13 -0
  340. package/dist/core/runtime/broker-persona-guidance.js +58 -0
  341. package/dist/core/runtime/broker-protocol.d.ts +1 -1
  342. package/dist/core/runtime/broker-protocol.js +1 -1
  343. package/dist/core/runtime/broker.d.ts +5 -196
  344. package/dist/core/runtime/broker.js +276 -3059
  345. package/dist/core/runtime/canvas-extensions.js +11 -2
  346. package/dist/core/runtime/fault.js +1 -1
  347. package/dist/core/runtime/model-selection.d.ts +9 -0
  348. package/dist/core/runtime/model-selection.js +12 -0
  349. package/dist/core/runtime/model-swap.d.ts +3 -3
  350. package/dist/core/runtime/model-swap.js +33 -25
  351. package/dist/core/runtime/placement-tmux.d.ts +3 -14
  352. package/dist/core/runtime/placement-tmux.js +11 -80
  353. package/dist/core/runtime/placement.d.ts +4 -2
  354. package/dist/core/runtime/placement.js +7 -12
  355. package/dist/core/runtime/promote.d.ts +14 -1
  356. package/dist/core/runtime/promote.js +1 -1
  357. package/dist/core/runtime/reset.d.ts +1 -1
  358. package/dist/core/runtime/roadmap.js +1 -1
  359. package/dist/core/runtime/spawn.d.ts +8 -0
  360. package/dist/core/runtime/spawn.js +36 -6
  361. package/dist/core/runtime/stop-guard.d.ts +4 -3
  362. package/dist/core/runtime/stop-guard.js +36 -18
  363. package/dist/core/runtime/stop-signals.d.ts +4 -1
  364. package/dist/core/runtime/stream-watchdog.js +4 -5
  365. package/dist/core/runtime/structured-output.d.ts +15 -2
  366. package/dist/core/runtime/structured-output.js +35 -12
  367. package/dist/core/runtime/tmux-bindings.d.ts +25 -0
  368. package/dist/core/runtime/tmux-bindings.js +682 -0
  369. package/dist/core/runtime/tmux-chrome.d.ts +2 -1
  370. package/dist/core/runtime/tmux-chrome.js +3 -2
  371. package/dist/core/runtime/{tmux.d.ts → tmux-driver.d.ts} +18 -26
  372. package/dist/core/runtime/tmux-driver.js +598 -0
  373. package/dist/core/runtime/view-socket.d.ts +14 -0
  374. package/dist/core/runtime/view-socket.js +83 -0
  375. package/dist/core/session-model/session-state.d.ts +1 -2
  376. package/dist/core/session-model/session-state.js +5 -8
  377. package/dist/core/spawn.d.ts +1 -1
  378. package/dist/core/spawn.js +2 -2
  379. package/dist/core/substrate/gate.d.ts +1 -1
  380. package/dist/core/substrate/on-read-pure.d.ts +7 -0
  381. package/dist/core/substrate/on-read-pure.js +272 -0
  382. package/dist/core/termrender/termrender.js +32 -2
  383. package/dist/core/user-settings.d.ts +23 -1
  384. package/dist/core/user-settings.js +18 -1
  385. package/dist/daemon/api/__tests__/node-model-validation.test.js +134 -0
  386. package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +98 -0
  387. package/dist/daemon/api/__tests__/{full → seam}/api-server.test.js +6 -48
  388. package/dist/daemon/api/__tests__/{full → seam}/b10-attach-modelauth.test.js +17 -44
  389. package/dist/daemon/api/__tests__/{full/push-parity.test.js → seam/leaf-api-parity.test.js} +61 -42
  390. package/dist/daemon/api/bridge.js +40 -81
  391. package/dist/daemon/api/handlers/broker-ops.d.ts +2 -0
  392. package/dist/daemon/api/handlers/broker-ops.js +383 -0
  393. package/dist/daemon/api/handlers/crons.js +9 -4
  394. package/dist/daemon/api/handlers/files.js +6 -7
  395. package/dist/daemon/api/handlers/inbox.js +2 -340
  396. package/dist/daemon/api/handlers/nodes.d.ts +10 -1
  397. package/dist/daemon/api/handlers/nodes.js +74 -39
  398. package/dist/daemon/api/handlers/review-comments.js +5 -1
  399. package/dist/daemon/api/handlers/reviews.js +0 -19
  400. package/dist/daemon/api/map.js +25 -4
  401. package/dist/daemon/api/router.js +2 -3
  402. package/dist/daemon/api/server.js +5 -7
  403. package/dist/daemon/cron-run.d.ts +4 -0
  404. package/dist/daemon/cron-run.js +30 -13
  405. package/dist/daemon/crtrd.d.ts +3 -58
  406. package/dist/daemon/crtrd.js +28 -1244
  407. package/dist/daemon/manage.js +0 -1
  408. package/dist/daemon/reconcilers/broker-supervision.d.ts +50 -0
  409. package/dist/daemon/reconcilers/broker-supervision.js +549 -0
  410. package/dist/daemon/reconcilers/cron-lane.d.ts +10 -0
  411. package/dist/daemon/reconcilers/cron-lane.js +32 -0
  412. package/dist/daemon/reconcilers/dormant-inbox.d.ts +18 -0
  413. package/dist/daemon/reconcilers/dormant-inbox.js +143 -0
  414. package/dist/daemon/reconcilers/storage-maintenance.d.ts +22 -0
  415. package/dist/daemon/reconcilers/storage-maintenance.js +213 -0
  416. package/dist/daemon/review/finish.js +2 -5
  417. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +34 -53
  418. package/dist/pi-extensions/__tests__/canvas-recap.test.js +16 -113
  419. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +72 -99
  420. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +29 -12
  421. package/dist/pi-extensions/broker-local.d.ts +20 -0
  422. package/dist/pi-extensions/broker-local.js +89 -0
  423. package/dist/pi-extensions/canvas-context-intro.d.ts +3 -3
  424. package/dist/pi-extensions/canvas-context-intro.js +22 -11
  425. package/dist/pi-extensions/canvas-doc-substrate.js +17 -34
  426. package/dist/pi-extensions/canvas-goal-capture.js +7 -12
  427. package/dist/pi-extensions/canvas-inbox-watcher.js +124 -139
  428. package/dist/pi-extensions/canvas-passive-context.d.ts +3 -3
  429. package/dist/pi-extensions/canvas-passive-context.js +22 -18
  430. package/dist/pi-extensions/canvas-recap.d.ts +2 -2
  431. package/dist/pi-extensions/canvas-recap.js +48 -21
  432. package/dist/pi-extensions/canvas-review-boundary.js +10 -4
  433. package/dist/pi-extensions/canvas-stophook.d.ts +0 -5
  434. package/dist/pi-extensions/canvas-stophook.js +149 -431
  435. package/dist/pi-extensions/canvas-structured-output.js +52 -30
  436. package/dist/pi-extensions/envelope-merge.d.ts +13 -0
  437. package/dist/pi-extensions/envelope-merge.js +29 -0
  438. package/dist/shared/working-activity.d.ts +3 -2
  439. package/dist/shared/working-activity.js +3 -2
  440. package/dist/types.d.ts +6 -5
  441. package/dist/types.js +2 -2
  442. package/docs/compat/hearth-crtr-v1.md +4 -12
  443. package/docs/compat/hearth-crtr-v2.md +3 -11
  444. package/docs/compat/hearth-crtr-v3.md +4 -12
  445. package/docs/compat/hearth-crtr-v4.md +3 -11
  446. package/docs/compat/hearth-crtr-v5.md +3 -11
  447. package/docs/public-api.md +3 -7
  448. package/package.json +7 -49
  449. package/runtime.lock.json +2766 -14533
  450. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/claude-plugin-commands.test.ts +0 -49
  451. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/memory-slash-commands.test.ts +0 -194
  452. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/statusline.test.ts +0 -68
  453. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/test.ts +0 -119
  454. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +0 -439
  455. package/dist/clients/attach/__tests__/attach-keybindings.test.js +0 -170
  456. package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +0 -57
  457. package/dist/clients/attach/__tests__/editor-frame-title.test.js +0 -19
  458. package/dist/clients/attach/__tests__/input-controller-extension-command.test.js +0 -32
  459. package/dist/clients/attach/__tests__/markdown-headings.test.js +0 -35
  460. package/dist/clients/attach/__tests__/session-identity-refresh.test.js +0 -32
  461. package/dist/clients/conversation/conversation.d.ts +0 -65
  462. package/dist/clients/conversation/conversation.js +0 -318
  463. package/dist/clients/conversation/frames.d.ts +0 -30
  464. package/dist/clients/conversation/frames.js +0 -52
  465. package/dist/clients/conversation/region.d.ts +0 -51
  466. package/dist/clients/conversation/region.js +0 -265
  467. package/dist/clients/inbox/__tests__/inbox-controller.test.js +0 -247
  468. package/dist/clients/inbox/__tests__/mount-panel.test.js +0 -776
  469. package/dist/clients/inbox/review/__tests__/remap.test.js +0 -196
  470. package/dist/clients/inbox/review/columns.d.ts +0 -22
  471. package/dist/clients/inbox/review/columns.js +0 -40
  472. package/dist/clients/inbox/review/composition.d.ts +0 -60
  473. package/dist/clients/inbox/review/composition.js +0 -278
  474. package/dist/clients/inbox/review/document-region.d.ts +0 -120
  475. package/dist/clients/surfaces/region.d.ts +0 -16
  476. package/dist/clients/web/__tests__/push-engine.test.js +0 -158
  477. package/dist/clients/web/__tests__/push-registry.test.d.ts +0 -1
  478. package/dist/clients/web/__tests__/push-registry.test.js +0 -177
  479. package/dist/clients/web/dev-server.d.ts +0 -7
  480. package/dist/clients/web/dev-server.js +0 -46
  481. package/dist/clients/web/events.d.ts +0 -19
  482. package/dist/clients/web/events.js +0 -163
  483. package/dist/clients/web/push-engine.d.ts +0 -44
  484. package/dist/clients/web/push-engine.js +0 -125
  485. package/dist/clients/web/push-registry.d.ts +0 -85
  486. package/dist/clients/web/push-registry.js +0 -373
  487. package/dist/clients/web/server.d.ts +0 -19
  488. package/dist/clients/web/server.js +0 -661
  489. package/dist/clients/web/web-client/shared/protocol.d.ts +0 -214
  490. package/dist/clients/web/web-client/shared/protocol.js +0 -8
  491. package/dist/clients/web/web-cmd.d.ts +0 -2
  492. package/dist/clients/web/web-cmd.js +0 -141
  493. package/dist/commands/__tests__/api-canvas-source.test.d.ts +0 -1
  494. package/dist/commands/__tests__/api-canvas-source.test.js +0 -37
  495. package/dist/commands/__tests__/canvas-config.test.d.ts +0 -1
  496. package/dist/commands/__tests__/canvas-config.test.js +0 -107
  497. package/dist/commands/__tests__/node-no-kickoff-flag.test.d.ts +0 -1
  498. package/dist/commands/__tests__/node-no-kickoff-flag.test.js +0 -73
  499. package/dist/commands/__tests__/node-worktree-flag.test.d.ts +0 -1
  500. package/dist/commands/__tests__/node-worktree-flag.test.js +0 -49
  501. package/dist/commands/__tests__/search-contents.test.d.ts +0 -1
  502. package/dist/commands/__tests__/search-contents.test.js +0 -52
  503. package/dist/commands/memory/__tests__/lint.test.d.ts +0 -1
  504. package/dist/commands/memory/__tests__/lint.test.js +0 -15
  505. package/dist/commands/sys/__tests__/config-broker-thresholds.test.d.ts +0 -1
  506. package/dist/commands/sys/__tests__/config-broker-thresholds.test.js +0 -95
  507. package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +0 -1
  508. package/dist/commands/sys/__tests__/config-keybindings.test.js +0 -55
  509. package/dist/commands/sys/__tests__/helpers/setup-runtime.d.ts +0 -5
  510. package/dist/commands/sys/__tests__/helpers/setup-runtime.js +0 -19
  511. package/dist/commands/sys/__tests__/setup-front-door.test.d.ts +0 -1
  512. package/dist/commands/sys/__tests__/setup-front-door.test.js +0 -102
  513. package/dist/commands/sys/__tests__/setup-models.test.d.ts +0 -1
  514. package/dist/commands/sys/__tests__/setup-models.test.js +0 -205
  515. package/dist/commands/sys/__tests__/setup-providers-viewport.test.d.ts +0 -1
  516. package/dist/commands/sys/__tests__/setup-providers-viewport.test.js +0 -155
  517. package/dist/commands/sys/__tests__/sync-deps.test.d.ts +0 -1
  518. package/dist/commands/sys/__tests__/sync-deps.test.js +0 -179
  519. package/dist/commands/sys/__tests__/sync-import.test.d.ts +0 -1
  520. package/dist/commands/sys/__tests__/sync-import.test.js +0 -315
  521. package/dist/core/__tests__/base-worker-prompt.test.d.ts +0 -1
  522. package/dist/core/__tests__/base-worker-prompt.test.js +0 -57
  523. package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +0 -1
  524. package/dist/core/__tests__/command-plugins-surfaces.test.js +0 -359
  525. package/dist/core/__tests__/connection-reconnect-resume.test.d.ts +0 -1
  526. package/dist/core/__tests__/connection-reconnect-resume.test.js +0 -36
  527. package/dist/core/__tests__/finalization-migration.test.d.ts +0 -1
  528. package/dist/core/__tests__/finalization-migration.test.js +0 -38
  529. package/dist/core/__tests__/fixtures/extra-extension.d.ts +0 -1
  530. package/dist/core/__tests__/fixtures/extra-extension.js +0 -3
  531. package/dist/core/__tests__/fixtures/spawn-proof-target.test.d.ts +0 -1
  532. package/dist/core/__tests__/fixtures/spawn-proof-target.test.js +0 -15
  533. package/dist/core/__tests__/fork.test.d.ts +0 -1
  534. package/dist/core/__tests__/fork.test.js +0 -108
  535. package/dist/core/__tests__/full/broker-attach-limits.test.d.ts +0 -1
  536. package/dist/core/__tests__/full/broker-attach-limits.test.js +0 -147
  537. package/dist/core/__tests__/full/broker-deliver-ack.test.d.ts +0 -1
  538. package/dist/core/__tests__/full/broker-deliver-ack.test.js +0 -73
  539. package/dist/core/__tests__/full/broker-dialogs.test.d.ts +0 -1
  540. package/dist/core/__tests__/full/broker-dialogs.test.js +0 -180
  541. package/dist/core/__tests__/full/broker-display-snapshot.test.d.ts +0 -1
  542. package/dist/core/__tests__/full/broker-display-snapshot.test.js +0 -75
  543. package/dist/core/__tests__/full/broker-dormant-wake.test.d.ts +0 -1
  544. package/dist/core/__tests__/full/broker-dormant-wake.test.js +0 -130
  545. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +0 -1
  546. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +0 -57
  547. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +0 -1
  548. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +0 -80
  549. package/dist/core/__tests__/full/cascade-close.test.d.ts +0 -1
  550. package/dist/core/__tests__/full/cascade-close.test.js +0 -240
  551. package/dist/core/__tests__/full/detach-focus.test.d.ts +0 -1
  552. package/dist/core/__tests__/full/detach-focus.test.js +0 -126
  553. package/dist/core/__tests__/full/idle-release-settlement.test.d.ts +0 -1
  554. package/dist/core/__tests__/full/issue-146-yield-spawn-focus.test.d.ts +0 -1
  555. package/dist/core/__tests__/full/issue-146-yield-spawn-focus.test.js +0 -107
  556. package/dist/core/__tests__/full/park-clock-release.test.d.ts +0 -1
  557. package/dist/core/__tests__/full/park-clock-release.test.js +0 -56
  558. package/dist/core/__tests__/full/yield-message-transaction.test.d.ts +0 -1
  559. package/dist/core/__tests__/memory-index-render.test.d.ts +0 -1
  560. package/dist/core/__tests__/memory-index-render.test.js +0 -60
  561. package/dist/core/__tests__/model-routes.test.d.ts +0 -1
  562. package/dist/core/__tests__/model-routes.test.js +0 -41
  563. package/dist/core/__tests__/on-read-identity.test.d.ts +0 -1
  564. package/dist/core/__tests__/on-read-identity.test.js +0 -61
  565. package/dist/core/__tests__/phase4-review-migration.test.d.ts +0 -1
  566. package/dist/core/__tests__/phase4-review-migration.test.js +0 -100
  567. package/dist/core/__tests__/prune-to-limit.test.d.ts +0 -1
  568. package/dist/core/__tests__/prune-to-limit.test.js +0 -126
  569. package/dist/core/__tests__/reset.test.d.ts +0 -1
  570. package/dist/core/__tests__/reset.test.js +0 -69
  571. package/dist/core/__tests__/spawn-no-kickoff.test.d.ts +0 -1
  572. package/dist/core/__tests__/spawn-no-kickoff.test.js +0 -115
  573. package/dist/core/__tests__/steer-note.test.d.ts +0 -1
  574. package/dist/core/__tests__/steer-note.test.js +0 -41
  575. package/dist/core/__tests__/subcommand-tier.test.d.ts +0 -1
  576. package/dist/core/__tests__/subcommand-tier.test.js +0 -99
  577. package/dist/core/__tests__/subscription-delivery.test.js +0 -211
  578. package/dist/core/__tests__/test-runner-cheap-sharding.test.d.ts +0 -1
  579. package/dist/core/__tests__/test-runner-cheap-sharding.test.js +0 -97
  580. package/dist/core/__tests__/tmux-surface.test.js +0 -484
  581. package/dist/core/__tests__/wake-bearings.test.d.ts +0 -1
  582. package/dist/core/__tests__/wake-bearings.test.js +0 -102
  583. package/dist/core/canvas/__tests__/remote-event-stream.test.d.ts +0 -1
  584. package/dist/core/canvas/__tests__/remote-event-stream.test.js +0 -146
  585. package/dist/core/canvas/__tests__/source-resolve.test.d.ts +0 -1
  586. package/dist/core/canvas/__tests__/source-resolve.test.js +0 -77
  587. package/dist/core/canvas/browse/__tests__/model.test.d.ts +0 -1
  588. package/dist/core/canvas/browse/__tests__/model.test.js +0 -290
  589. package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.d.ts +0 -1
  590. package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.js +0 -134
  591. package/dist/core/canvas/browse/__tests__/render.test.d.ts +0 -1
  592. package/dist/core/canvas/browse/__tests__/render.test.js +0 -111
  593. package/dist/core/canvas/browse/rebuild-coalescer.d.ts +0 -7
  594. package/dist/core/canvas/browse/rebuild-coalescer.js +0 -61
  595. package/dist/core/canvas/remote-event-stream.d.ts +0 -24
  596. package/dist/core/canvas/remote-event-stream.js +0 -94
  597. package/dist/core/feed/__tests__/coalesce-human.test.d.ts +0 -1
  598. package/dist/core/feed/__tests__/coalesce-human.test.js +0 -36
  599. package/dist/core/feed/__tests__/empty-node-guard.test.d.ts +0 -1
  600. package/dist/core/feed/__tests__/empty-node-guard.test.js +0 -45
  601. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +0 -1
  602. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +0 -84
  603. package/dist/core/human/__tests__/visible-paths.test.d.ts +0 -1
  604. package/dist/core/human/__tests__/visible-paths.test.js +0 -28
  605. package/dist/core/keybindings/__tests__/inbox-affordance.test.d.ts +0 -1
  606. package/dist/core/keybindings/__tests__/inbox-affordance.test.js +0 -38
  607. package/dist/core/review/__tests__/project.test.d.ts +0 -1
  608. package/dist/core/review/__tests__/project.test.js +0 -71
  609. package/dist/core/runtime/__tests__/command-expansion.test.d.ts +0 -1
  610. package/dist/core/runtime/__tests__/command-expansion.test.js +0 -25
  611. package/dist/core/runtime/__tests__/session-visibility.test.d.ts +0 -1
  612. package/dist/core/runtime/__tests__/session-visibility.test.js +0 -119
  613. package/dist/core/runtime/__tests__/spawn-at-given-id.test.d.ts +0 -1
  614. package/dist/core/runtime/__tests__/spawn-at-given-id.test.js +0 -34
  615. package/dist/core/runtime/tmux.js +0 -1314
  616. package/dist/daemon/api/__tests__/full/push-parity.test.d.ts +0 -1
  617. package/dist/daemon/api/__tests__/full/worktree-parity.test.d.ts +0 -1
  618. package/dist/daemon/api/__tests__/full/worktree-parity.test.js +0 -160
  619. package/dist/daemon/api/__tests__/inbox.test.d.ts +0 -1
  620. package/dist/daemon/api/__tests__/inbox.test.js +0 -489
  621. package/dist/web-client/apple-touch-icon.png +0 -0
  622. package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
  623. package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
  624. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
  625. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
  626. package/dist/web-client/assets/index-CCPaLeW7.css +0 -2
  627. package/dist/web-client/assets/index-kyOWsnZK.js +0 -86
  628. package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
  629. package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
  630. package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
  631. package/dist/web-client/assets/workbox-window.prod.es5-Bd17z0YL.js +0 -2
  632. package/dist/web-client/icon-192.png +0 -0
  633. package/dist/web-client/icon-512.png +0 -0
  634. package/dist/web-client/icon-maskable-512.png +0 -0
  635. package/dist/web-client/index.html +0 -17
  636. package/dist/web-client/manifest.webmanifest +0 -1
  637. package/dist/web-client/sw.js +0 -1
  638. /package/dist/api/__tests__/{client.test.d.ts → serial/client.test.d.ts} +0 -0
  639. /package/dist/clients/inbox/__tests__/{inbox-controller.test.d.ts → serial/inbox-controller.test.d.ts} +0 -0
  640. /package/dist/clients/inbox/__tests__/{mount-panel.test.d.ts → serial/mount-panel.test.d.ts} +0 -0
  641. /package/dist/clients/{attach/__tests__/attach-chrome-remote.test.d.ts → inbox/review/__tests__/teardown-notice.test.d.ts} +0 -0
  642. /package/dist/{clients/attach/__tests__/attach-keybindings.test.d.ts → core/__tests__/broker-extension-canvas-db-boundary.test.d.ts} +0 -0
  643. /package/dist/{clients/attach/__tests__/crtr-output-coverage.test.d.ts → core/__tests__/seam/broker-attach-multiclient.test.d.ts} +0 -0
  644. /package/dist/core/__tests__/{full → seam}/broker-attach-stream.test.d.ts +0 -0
  645. /package/dist/core/__tests__/{full → seam}/broker-crash-teardown.test.d.ts +0 -0
  646. /package/dist/core/__tests__/{full → seam}/broker-pane-resolution.test.d.ts +0 -0
  647. /package/dist/core/__tests__/{full → seam}/broker-provider-retry.test.d.ts +0 -0
  648. /package/dist/core/__tests__/{full → seam}/broker-whip-interrupt.test.d.ts +0 -0
  649. /package/dist/{clients/attach/__tests__/editor-frame-title.test.d.ts → core/__tests__/seam/dormancy-release.test.d.ts} +0 -0
  650. /package/dist/{clients/attach/__tests__/input-controller-extension-command.test.d.ts → core/__tests__/seam/yield-refresh-transaction.test.d.ts} +0 -0
  651. /package/dist/core/__tests__/{broker-fork-seam.test.d.ts → serial/broker-fork-seam.test.d.ts} +0 -0
  652. /package/dist/core/__tests__/{broker-sdk-wiring.test.d.ts → serial/broker-sdk-wiring.test.d.ts} +0 -0
  653. /package/dist/core/__tests__/{broker-snapshot-history.test.d.ts → serial/broker-snapshot-history.test.d.ts} +0 -0
  654. /package/dist/core/__tests__/{command-plugins.test.d.ts → serial/command-plugins.test.d.ts} +0 -0
  655. /package/dist/core/__tests__/{deferred-no-wake.test.d.ts → serial/deferred-no-wake.test.d.ts} +0 -0
  656. /package/dist/core/__tests__/{flagship-lifecycle.test.d.ts → serial/flagship-lifecycle.test.d.ts} +0 -0
  657. /package/dist/core/__tests__/{host-teardown-process-group.test.d.ts → serial/host-teardown-process-group.test.d.ts} +0 -0
  658. /package/dist/core/__tests__/{human-deliver-e2e.test.d.ts → serial/human-deliver-e2e.test.d.ts} +0 -0
  659. /package/dist/core/__tests__/{live-mutation.test.d.ts → serial/live-mutation.test.d.ts} +0 -0
  660. /package/dist/core/__tests__/{refresh-stall-recycle.test.d.ts → serial/refresh-stall-recycle.test.d.ts} +0 -0
  661. /package/dist/core/__tests__/{revive.test.d.ts → serial/revive.test.d.ts} +0 -0
  662. /package/dist/core/__tests__/{spawn-root.test.d.ts → serial/spawn-root.test.d.ts} +0 -0
  663. /package/dist/core/__tests__/{subscription-delivery.test.d.ts → serial/subscription-delivery.test.d.ts} +0 -0
  664. /package/dist/core/__tests__/{tmux-surface.test.d.ts → serial/tmux-surface.test.d.ts} +0 -0
  665. /package/dist/core/__tests__/{worktree.test.d.ts → serial/worktree.test.d.ts} +0 -0
  666. /package/dist/{clients/attach/__tests__/markdown-headings.test.d.ts → core/__tests__/watchdog-abort-arms-retry.test.d.ts} +0 -0
  667. /package/dist/{clients/attach/__tests__/session-identity-refresh.test.d.ts → core/broker-client/__tests__/transport-socket.test.d.ts} +0 -0
  668. /package/dist/core/human/__tests__/{inbox-core.test.d.ts → serial/inbox-core.test.d.ts} +0 -0
  669. /package/dist/{clients/inbox/review/__tests__/remap.test.d.ts → daemon/api/__tests__/node-model-validation.test.d.ts} +0 -0
  670. /package/dist/{clients/surfaces/region.js → daemon/api/__tests__/review-terminal-conflict.test.d.ts} +0 -0
  671. /package/dist/daemon/api/__tests__/{full → seam}/api-server.test.d.ts +0 -0
  672. /package/dist/daemon/api/__tests__/{full → seam}/b10-attach-modelauth.test.d.ts +0 -0
  673. /package/dist/{clients/web/__tests__/push-engine.test.d.ts → daemon/api/__tests__/seam/leaf-api-parity.test.d.ts} +0 -0
@@ -1,2101 +1,68 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import test from 'node:test';
3
3
  import { visibleWidth } from '@earendil-works/pi-tui';
4
- import { createCrtrBashToolDefinition, isCrtrBashCommand, isCrtrHelpInvocation, renderCrtrOutputText, } from '../render/crtr-output.js';
5
- import { HELP_ICON, iconForPath } from '../../../core/preview-registry.js';
4
+ import { createCrtrBashToolDefinition, isCrtrBashCommand, isCrtrHelpInvocation, } from '../render/crtr-output.js';
6
5
  const theme = {
7
6
  // `text` and `dim` are the preview's base hierarchy roles; leave them
8
7
  // transparent here so assertions can still match the underlying receipt.
9
- // Semantic colors remain tagged and are asserted where state matters.
10
8
  fg(name, text) { return name === 'text' || name === 'dim' ? text : `<${name}>${text}</${name}>`; },
11
9
  bg(_name, text) { return text; },
12
10
  bold(text) { return `<b>${text}</b>`; },
13
11
  };
14
- const plainTheme = {
15
- fg(_name, text) { return text; },
16
- bg(_name, text) { return text; },
17
- bold(text) { return text; },
18
- };
19
12
  /** pi calls tool renderers with a shared state bag; a normal (unfolded) call has an empty one. */
20
- function renderCrtrCall(definition, args) {
21
- const definitionWithContext = definition;
22
- return definitionWithContext.renderCall(args, theme, { state: {} });
13
+ function renderCall(command) {
14
+ const definition = createCrtrBashToolDefinition({ command });
15
+ return definition.renderCall({ command }, theme, { state: {} }).render(80).join('\n');
23
16
  }
24
- function stripAnsi(text) {
25
- return text.replace(/\x1b\[[0-9;]*m/g, '');
17
+ function renderResult(command, text, expanded) {
18
+ const definition = createCrtrBashToolDefinition({ command });
19
+ return definition.renderResult({ content: [{ type: 'text', text }] }, { expanded }, theme, {}).render(70);
26
20
  }
27
21
  test('detects crtr in bash command position, not heredoc body prose', () => {
28
22
  assert.equal(isCrtrBashCommand('crtr node new -h'), true);
29
23
  assert.equal(isCrtrBashCommand('crtr node inspect snapshot --node abc'), true);
30
- assert.equal(isCrtrBashCommand('crtr sys daemon status'), true);
31
- assert.equal(isCrtrBashCommand('npm test && /usr/local/bin/crouter surface web serve'), true);
24
+ assert.equal(isCrtrBashCommand('npm test && /usr/local/bin/crouter surface attach to node-1'), true);
32
25
  assert.equal(isCrtrBashCommand("cat <<'EOF'\nask about crtr node new\nEOF\necho done"), false);
33
26
  assert.equal(isCrtrBashCommand('echo crtr node new'), false);
34
27
  });
35
- test('returns a viewer tool definition only for crtr bash commands', () => {
36
- assert.equal(createCrtrBashToolDefinition({ command: 'echo hi' }), undefined);
37
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
38
- assert.equal(typeof def.renderCall, 'function');
39
- const rendered = renderCrtrCall(def, { command: 'crtr canvas dashboard' }).render(80).join('\n');
40
- assert.equal(rendered.trimEnd().split('\n').length, 1);
41
- assert.match(rendered, new RegExp(iconForPath(['canvas', 'dashboard'])));
42
- assert.match(rendered, /crtr/);
43
- assert.match(rendered, /canvas dashboard/);
44
- assert.doesNotMatch(rendered, /◆/);
45
- assert.match(def.renderResult({ content: [{ type: 'text', text: '<command name="canvas">' }], details: undefined }, { expanded: true }, theme, {}).render(80).join('\n'), /<accent><b><command/);
46
- });
47
- test('styles crtr structural output and status glyphs', () => {
48
- const rendered = renderCrtrOutputText('<command name="node">\nInput\n● active\nCommand exited with code 1', theme);
49
- assert.match(rendered, /<accent><b><command/);
50
- assert.match(rendered, /<toolTitle><b>Input/);
51
- assert.match(rendered, /<success>● active/);
52
- assert.match(rendered, /<error>Command exited/);
53
- });
54
- test('crtr results do not invalidate their owning tool component', () => {
55
- let ownerInvalidations = 0;
56
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
57
- const component = def.renderResult({ content: [{ type: 'text', text: 'dashboard' }] }, { expanded: false }, theme, { invalidate: () => { ownerInvalidations++; } });
58
- component.invalidate?.();
59
- assert.equal(ownerInvalidations, 0);
60
- });
61
- // Help output is structural schema text, never a command result, so the
62
- // viewer detects it from the invocation itself and gives it help-specific
63
- // collapsed chrome rather than treating it as a normal result preview.
64
- test('detects a bare -h/--help token in a crtr invocation', () => {
28
+ // Help output is structural schema text, never a command result, so the viewer
29
+ // detects it from the invocation itself. Detection is scoped to the extracted
30
+ // crtr segment's own tokens, with quote boundaries respected and outer shell
31
+ // redirects treated as plumbing — so unrelated siblings, quoted prompt text,
32
+ // and redirect targets neither trip help chrome nor leak into the shown path.
33
+ test('scopes -h/--help detection and the displayed path to the crtr invocation\'s own argv', () => {
65
34
  assert.equal(isCrtrHelpInvocation('crtr node new -h'), true);
66
35
  assert.equal(isCrtrHelpInvocation('crtr node inspect snapshot --node abc --help'), true);
67
36
  assert.equal(isCrtrHelpInvocation('crtr canvas dashboard'), false);
68
37
  assert.equal(isCrtrHelpInvocation('echo crtr node new -h'), false);
69
- });
70
- // Help-token detection is scoped to the extracted crtr segment's own tokens,
71
- // with quote boundaries respected, so unrelated shell siblings and quoted
72
- // prompt text do not affect the crtr preview.
73
- test('does not misclassify -h/--help outside the crtr invocation\'s own argv as help', () => {
74
38
  assert.equal(isCrtrHelpInvocation('echo -h && crtr canvas snapshot'), false);
75
39
  assert.equal(isCrtrHelpInvocation('crtr canvas snapshot && echo --help'), false);
76
40
  assert.equal(isCrtrHelpInvocation('crtr node new "write about -h flags"'), false);
77
- // Sanity: the real thing one token over still trips it.
78
- assert.equal(isCrtrHelpInvocation('crtr node new -h'), true);
79
- assert.equal(isCrtrHelpInvocation('crtr node new --help'), true);
80
- });
81
- // Realistic leaf schema (renderLeafArgv shape: `name: summary.` then an
82
- // Input-headed param block) — a stand-in for e.g. `crtr canvas dashboard -h`.
83
- const LEAF_SCHEMA = 'canvas dashboard: render the canvas as an ASCII subscription tree.\n\n' +
84
- 'Input\n' +
85
- ' --root ROOT string, optional. Node id to use as the tree root.\n\n' +
86
- 'Output (fields carried in the rendered result)\n' +
87
- ' tree string. Multi-line ASCII tree.\n\n' +
88
- 'Effects\n' +
89
- ' Read-only: queries canvas.db.';
90
- // Realistic branch schema (renderBranch shape: one <command description=...>
91
- // card with self-closing <subcommand .../> rows) — a stand-in for e.g.
92
- // `crtr node inspect -h`.
93
- const BRANCH_SCHEMA = '<command name="node inspect" description="read the canvas graph">\n' +
94
- '<subcommand name="list" description="list nodes on the canvas" whenToUse="a very long rubric that is not part of the preview at all"/>\n\n' +
95
- '<subcommand name="show" description="show a node + its spine neighbors" whenToUse="another long rubric"/>\n' +
96
- '</command>';
97
- test('collapsed help preview never parrots the call header or dumps the raw schema, and stays within the line cap', () => {
98
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard -h' });
99
- const collapsed = def
100
- .renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: false }, theme, {})
101
- .render(80)
102
- .join('\n');
103
- // The call header already carries the help glyph header; the collapsed body is
104
- // JUST the synthesized preview content, no repeated status/help label.
105
- assert.doesNotMatch(collapsed, new RegExp(`${HELP_ICON} crtr help`));
106
- assert.match(collapsed, /ctrl\+o to expand/);
107
- assert.doesNotMatch(collapsed, /<success>✓ crtr done/);
108
- // No raw schema dump leaking into the body.
109
- assert.doesNotMatch(collapsed, /<command/);
110
- // A useful synthesized slice IS present: the leaf's own summary line.
111
- assert.match(collapsed, /render the canvas as an ASCII subscription/);
112
- assert.match(collapsed, /Params: --root/);
113
- assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
114
- // Full schema remains readable on demand.
115
- const expanded = def
116
- .renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: true }, theme, {})
117
- .render(80)
118
- .join('\n');
119
- assert.match(expanded, /Node id to use as the tree root/);
120
- assert.match(expanded, /Read-only: queries canvas\.db/);
121
- });
122
- test('collapsed branch/root help synthesizes a subcommand/description preview, not the raw <subcommand> tags', () => {
123
- const def = createCrtrBashToolDefinition({ command: 'crtr node inspect -h' });
124
- const collapsed = def
125
- .renderResult({ content: [{ type: 'text', text: BRANCH_SCHEMA }] }, { expanded: false }, theme, {})
126
- .render(80)
127
- .join('\n');
128
- assert.doesNotMatch(collapsed, /<command/);
129
- assert.doesNotMatch(collapsed, /<subcommand/);
130
- assert.doesNotMatch(collapsed, /whenToUse/);
131
- // Useful preview: branch description plus name — description subcommand rows.
132
- assert.match(collapsed, /read the canvas graph/);
133
- assert.match(collapsed, /list.*—.*list nodes on the canvas/s);
134
- assert.match(collapsed, /show.*—.*show a node \+ its spine neighbors/s);
135
- assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
136
- });
137
- // Realistic root schema (renderRoot shape: a tagline, then one
138
- // `<command name="X">\n<concept>\nuse when ...` block per subtree, NO
139
- // description attribute on the tag) — a stand-in for `crtr -h`.
140
- const ROOT_SCHEMA = 'crtr: agentic runtime.\n\n' +
141
- '<command name="memory">\n' +
142
- 'a memory document you read on demand\n' +
143
- 'use when a task matches stored knowledge or a preference\n' +
144
- '</command>\n\n' +
145
- '<command name="node">\n' +
146
- 'a unit of the agent runtime\n' +
147
- 'use when delegating work\n' +
148
- '</command>\n\n' +
149
- 'Globals\n' +
150
- ' -h print help\n\n' +
151
- 'I/O contract: ...';
152
- test('collapsed root help synthesizes a tagline + name/concept preview, not the raw <command> blocks', () => {
153
- const def = createCrtrBashToolDefinition({ command: 'crtr -h' });
154
- const collapsed = def
155
- .renderResult({ content: [{ type: 'text', text: ROOT_SCHEMA }] }, { expanded: false }, theme, {})
156
- .render(80)
157
- .join('\n');
158
- assert.doesNotMatch(collapsed, /<command/);
159
- assert.match(collapsed, /crtr: agentic runtime/);
160
- assert.match(collapsed, /memory.*—.*a memory document you read on demand/s);
161
- assert.match(collapsed, /node.*—.*a unit of the agent runtime/s);
162
- assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
163
- });
164
- test('a branch help preview with more subcommands than the line cap truncates with an expand hint, never mid-tag', () => {
165
- const def = createCrtrBashToolDefinition({ command: 'crtr node -h' });
166
- const manySubs = Array.from({ length: 30 }, (_, i) => `<subcommand name="sub${i}" description="description for subcommand ${i}" whenToUse="long rubric ${i}"/>`).join('\n\n');
167
- const bigBranchSchema = `<command name="node" description="spawn and navigate canvas nodes">\n${manySubs}\n</command>`;
168
- const collapsed = def
169
- .renderResult({ content: [{ type: 'text', text: bigBranchSchema }] }, { expanded: false }, theme, {})
170
- .render(80)
171
- .join('\n');
172
- assert.doesNotMatch(collapsed, /<subcommand/);
173
- assert.match(collapsed, /ctrl\+o to expand/);
174
- assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
175
- });
176
- // Contract: outer bash redirects are shell plumbing, not crtr argv, so the
177
- // displayed crtr path and help detection include only the command segment.
178
- test('a shell redirect on the outer bash call never leaks into the displayed crtr path or help detection', () => {
179
- const def = createCrtrBashToolDefinition({ command: 'crtr push -h 2>&1' });
180
- const rendered = renderCrtrCall(def, { command: 'crtr push -h 2>&1' }).render(80).join('\n');
181
- assert.match(rendered, new RegExp(HELP_ICON));
182
- assert.match(rendered, /crtr help/);
183
- assert.match(rendered, /<b>push<\/b>/);
184
- assert.doesNotMatch(rendered, /2>/);
185
- assert.doesNotMatch(rendered, />&1/);
186
41
  assert.equal(isCrtrHelpInvocation('crtr push -h 2>&1'), true);
187
- // Other redirect shapes on the outer call, and mid-argv redirects, must
188
- // never survive into the path either.
189
- const other = createCrtrBashToolDefinition({ command: 'crtr node new --name foo > /tmp/log 2>&1' });
190
- const otherRendered = renderCrtrCall(other, { command: 'crtr node new --name foo > /tmp/log 2>&1' }).render(80).join('\n');
191
- assert.match(otherRendered, /node new --name foo/);
192
- assert.doesNotMatch(otherRendered, /tmp\/log/);
193
- // Redirect operators glued to their target with NO space (`2>/tmp/out`,
194
- // `>/tmp/log`) are still shell plumbing and never displayed as path text.
195
- const noSpace = createCrtrBashToolDefinition({ command: 'crtr push -h 2>/tmp/out' });
196
- const noSpaceRendered = renderCrtrCall(noSpace, { command: 'crtr push -h 2>/tmp/out' }).render(80).join('\n');
197
- assert.match(noSpaceRendered, /<b>push<\/b>/);
198
- assert.doesNotMatch(noSpaceRendered, /2>/);
199
- assert.doesNotMatch(noSpaceRendered, /tmp\/out/);
200
- const noSpaceMid = createCrtrBashToolDefinition({ command: 'crtr node new --name foo >/tmp/log' });
201
- const noSpaceMidRendered = renderCrtrCall(noSpaceMid, { command: 'crtr node new --name foo >/tmp/log' }).render(80).join('\n');
202
- assert.match(noSpaceMidRendered, /node new --name foo/);
203
- assert.doesNotMatch(noSpaceMidRendered, /tmp\/log/);
204
- });
205
- // Contract: the call header owns the help glyph label; the collapsed result
206
- // body contains only synthesized preview content, never a repeated header.
207
- test('a collapsed help card never shows the help label twice (header + body)', () => {
208
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard -h' });
209
- const header = renderCrtrCall(def, { command: 'crtr canvas dashboard -h' }).render(80).join('\n');
210
- const body = def
211
- .renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: false }, theme, {})
212
- .render(80)
213
- .join('\n');
214
- const helpLabel = new RegExp(`${HELP_ICON} crtr help`, 'g');
215
- const headerLabelCount = (header.match(helpLabel) ?? []).length;
216
- const bodyLabelCount = (body.match(helpLabel) ?? []).length;
217
- assert.equal(headerLabelCount, 1);
218
- assert.equal(bodyLabelCount, 0);
219
- });
220
- // REQUIREMENT: expanded (ctrl+o) is raw truth — the exact invoked command
221
- // (redirects and all) plus the exact, unsummarized output, for both help and
222
- // normal commands.
223
- test('expanded view shows the original raw command (redirects included) plus the raw output', () => {
224
- const def = createCrtrBashToolDefinition({ command: 'crtr push -h 2>&1' });
225
- const expanded = def
226
- .renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: true }, theme, {})
227
- .render(80)
228
- .join('\n');
229
- assert.match(expanded, /\$ crtr push -h 2>&1/);
230
- assert.match(expanded, /render the canvas as an ASCII subscription/);
231
- assert.match(expanded, /Read-only: queries canvas\.db/);
232
- // Normal (non-help) command: expanded still carries the raw invocation.
233
- const normalDef = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
234
- const normalExpanded = normalDef
235
- .renderResult({ content: [{ type: 'text', text: 'a normal-sized dashboard body' }] }, { expanded: true }, theme, {})
236
- .render(80)
237
- .join('\n');
238
- assert.match(normalExpanded, /\$ crtr canvas dashboard/);
239
- assert.match(normalExpanded, /a normal-sized dashboard body/);
240
- });
241
- test('a running or failed crtr -h invocation never gets the muted help status', () => {
242
- const def = createCrtrBashToolDefinition({ command: 'crtr node new -h' });
243
- const running = def
244
- .renderResult({ content: [{ type: 'text', text: '' }] }, { expanded: false, isPartial: true }, theme, {})
245
- .render(80)
246
- .join('\n');
247
- assert.match(running, /<warning>⟳ crtr running<\/warning>/);
248
- });
249
- // BUG regression (Silas, 2026-07-01): a SUCCESSFUL normal crtr call showed a
250
- // "✓ crtr done" body status line — pure noise, since the header already names
251
- // the command and the output is right there. Success (help or normal) carries
252
- // no body status; only running/failed do.
253
- test('a successful crtr call shows no "crtr done" body status line', () => {
254
- const def = createCrtrBashToolDefinition({ command: 'crtr push update' });
255
- const collapsed = def
256
- .renderResult({ content: [{ type: 'text', text: 'some push output' }] }, { expanded: false }, theme, {})
257
- .render(80)
258
- .join('\n');
259
- assert.doesNotMatch(collapsed, /crtr done/);
260
- assert.match(collapsed, /some push output/);
261
- const expanded = def
262
- .renderResult({ content: [{ type: 'text', text: 'some push output' }] }, { expanded: true }, theme, {})
263
- .render(80)
264
- .join('\n');
265
- assert.doesNotMatch(expanded, /crtr done/);
266
- });
267
- // Command-owned preview: a leaf declares `help.preview.suppressOutput` next
268
- // to its own definition (e.g. canvas snapshot, node inspect snapshot —
269
- // machine-readable dumps, not agent glances). The viewer reads this
270
- // synchronously from the bundle-safe static registry (preview-registry.ts,
271
- // kept honest by preview-registry-sync.test.ts) keyed by the real leaf path.
272
- test('command-owned suppressOutput collapses a successful result, never on failure', () => {
273
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas snapshot' });
274
- const component = def.renderResult({ content: [{ type: 'text', text: '{"nodes":[/* huge */]}' }] }, { expanded: false }, theme, {});
275
- const collapsed = component.render(80).join('\n');
276
- // A successful call shows no body status line at all (✓ crtr done is noise).
277
- assert.doesNotMatch(collapsed, /crtr done/);
278
- assert.doesNotMatch(collapsed, /huge/);
279
- // A failed run is never collapsed, even when the leaf opts into suppressOutput.
280
- const failedComponent = def.renderResult({ content: [{ type: 'text', text: 'huge dump on failure too' }] }, { expanded: false }, theme, { isError: true });
281
- assert.match(failedComponent.render(80).join('\n'), /huge dump on failure too/);
282
- });
283
- test('command-owned suppressOutput matches the leaf name even with positional args after it', () => {
284
- // node inspect snapshot takes a required positional id; the lookup must
285
- // match the leaf-name prefix and ignore the argument that follows it.
286
- const def = createCrtrBashToolDefinition({ command: 'crtr node inspect snapshot abc' });
287
- const component = def.renderResult({ content: [{ type: 'text', text: '{"snapshot":{/* huge */}}' }] }, { expanded: false }, theme, {});
288
- const collapsed = component.render(80).join('\n');
289
- assert.doesNotMatch(collapsed, /crtr done/);
290
- assert.doesNotMatch(collapsed, /huge/);
291
- });
292
- // Command-owned preview (Silas, 2026-07-01, option C): a `crtr memory read`
293
- // collapsed card is a tidy frontmatter glance — `name · kind · scope` identity
294
- // line (path dropped as noise, follow_up hint dropped) + a short content
295
- // teaser — not the full doc body, which ctrl+o reveals.
296
- const MEMORY_READ_OUTPUT = [
297
- '- name: no-flag-and-defer',
298
- '- kind: preference',
299
- '- scope: user',
300
- '- path: /Users/silasrhyneer/.crouter/memory/no-flag-and-defer.md',
301
- '',
302
- '**content:** Fix issues in the same pass you find them — never "flagged for later".',
303
- '',
304
- '',
305
- '**follow_up:** Use --frontmatter on this same command to inspect the YAML frontmatter, or edit `path` directly for a body-only tweak. Browse the inventory with `crtr memory list`.',
306
- ].join('\n');
307
- test('a crtr memory read card previews a tidy frontmatter glance, not the raw block or body dump', () => {
308
- const def = createCrtrBashToolDefinition({ command: 'crtr memory read no-flag-and-defer' });
309
- const collapsed = def
310
- .renderResult({ content: [{ type: 'text', text: MEMORY_READ_OUTPUT }] }, { expanded: false }, theme, {})
311
- .render(120)
312
- .join('\n');
313
- // Curated frontmatter identity line: name · kind · scope, path/follow_up gone.
314
- assert.match(collapsed, /no-flag-and-defer · preference · user/);
315
- assert.doesNotMatch(collapsed, /\/Users\//);
316
- assert.doesNotMatch(collapsed, /follow_up/);
317
- assert.doesNotMatch(collapsed, /Use --frontmatter/);
318
- // A slice of the content is teased, and the raw `**content:**` marker is gone.
319
- assert.match(collapsed, /Fix issues in the same pass/);
320
- assert.doesNotMatch(collapsed, /\*\*content:\*\*/);
321
- // No spurious success status line.
322
- assert.doesNotMatch(collapsed, /crtr done/);
323
- assert.match(collapsed, /ctrl\+o to expand/);
324
- // A long doc body is teased, then clipped with an ellipsis — never dumped whole.
325
- const longOutput = [
326
- '- name: pi-frontmatter-rules',
327
- '- kind: knowledge',
328
- '- scope: user',
329
- '- path: /x/pi-frontmatter-rules.md',
330
- '',
331
- '**content:** # Title',
332
- 'line one',
333
- 'line two',
334
- 'line three',
335
- 'line four',
336
- 'line five',
337
- ].join('\n');
338
- const longCollapsed = def
339
- .renderResult({ content: [{ type: 'text', text: longOutput }] }, { expanded: false }, theme, {})
340
- .render(120)
341
- .join('\n');
342
- assert.match(longCollapsed, /pi-frontmatter-rules · knowledge · user/);
343
- assert.match(longCollapsed, /…/);
344
- assert.doesNotMatch(longCollapsed, /line five/);
345
- });
346
- // REQUIREMENT: the memory read summarizer must return null (not a blank
347
- // summary) when its expected metadata/content markers are absent — an error
348
- // result (missing/ambiguous doc name, etc.) has none of the `- name:`/
349
- // `**content:**` shape, so it must fall back to the raw output.
350
- test('a crtr memory read error output falls back to raw, never a blank summary card', () => {
351
- const def = createCrtrBashToolDefinition({ command: 'crtr memory read nonexistent-doc' });
352
- const collapsed = def
353
- .renderResult({ content: [{ type: 'text', text: 'crtr: memory read: no document found matching "nonexistent-doc"' }] }, { expanded: false }, theme, { isError: true })
354
- .render(120)
355
- .join('\n');
356
- assert.match(collapsed, /no document found matching/);
357
- });
358
- test('an expanded crtr memory read shows the full raw output, not the frontmatter glance', () => {
359
- const def = createCrtrBashToolDefinition({ command: 'crtr memory read no-flag-and-defer' });
360
- const expanded = def
361
- .renderResult({ content: [{ type: 'text', text: MEMORY_READ_OUTPUT }] }, { expanded: true }, theme, {})
362
- .render(120)
363
- .join('\n');
364
- // Raw truth: the path, the content marker, and the follow_up hint are all present.
365
- assert.match(expanded, /\$ crtr memory read no-flag-and-defer/);
366
- assert.match(expanded, /\.crouter\/memory\/no-flag-and-defer\.md/);
367
- assert.match(expanded, /\*\*follow_up:\*\*/);
368
- });
369
- const MEMORY_LINT_CLEAN_OUTPUT = [
370
- '- checked: 261',
371
- '- corpora: {"memory_stores":261}',
372
- '',
373
- 'Corpus green — zero invalid frontmatter docs.',
374
- '',
375
- '0 findings.',
376
- ].join('\n');
377
- const MEMORY_LINT_FINDINGS_OUTPUT = [
378
- 'crtr: memory lint: /tmp/lint-test/.crouter/memory/bad-doc.md: invalid kind: "bogus" (expected knowledge|preference)',
379
- 'crtr: memory lint: /tmp/lint-test/.crouter/memory/bad-doc2.md: missing `when-and-why-to-read`: one read-routing line',
380
- '<error code="error">',
381
- 'memory lint: 2 finding(s) across 229 files',
382
- 'Next: Fix each doc (quote YAML values containing `: `; use a valid kind/rung/gate), then re-run `crtr memory lint`.',
383
- '</error>',
384
- ].join('\n');
385
- test('a clean crtr memory lint collapses to PASS and docs checked, with semantic tones', () => {
386
- const def = createCrtrBashToolDefinition({ command: 'crtr memory lint' });
387
- const collapsed = def
388
- .renderResult({ content: [{ type: 'text', text: MEMORY_LINT_CLEAN_OUTPUT }] }, { expanded: false }, theme, {})
389
- .render(120)
390
- .join('\n');
391
- assert.match(collapsed, /<success>PASS<\/success>/);
392
- assert.match(collapsed, /<muted>261 docs checked<\/muted>/);
393
- assert.doesNotMatch(collapsed, /- checked:/);
394
- assert.doesNotMatch(collapsed, /- corpora:/);
395
- assert.doesNotMatch(collapsed, /0 findings\./);
396
- assert.match(collapsed, /ctrl\+o to expand/);
397
- const expanded = def
398
- .renderResult({ content: [{ type: 'text', text: MEMORY_LINT_CLEAN_OUTPUT }] }, { expanded: true }, theme, {})
399
- .render(120)
400
- .join('\n');
401
- assert.match(expanded, /checked: 261/);
402
- assert.match(expanded, /corpora: \{"memory_stores":261\}/);
403
- assert.match(expanded, /0 findings\./);
404
- });
405
- // REQUIREMENT: a real failed `crtr memory lint` (delivered to the renderer as
406
- // `{ isError: true }` — see render/chat-view.ts) must still collapse to the
407
- // command-owned findings summary, not fall through to the raw failed output;
408
- // only a command with no summarizer (or a shape neither summarizer branch
409
- // recognizes) stays raw on failure.
410
- test('a crtr memory lint with findings collapses to a findings count and warnings even on the real failed-command path', () => {
411
- const def = createCrtrBashToolDefinition({ command: 'crtr memory lint' });
412
- const collapsed = def
413
- .renderResult({ content: [{ type: 'text', text: MEMORY_LINT_FINDINGS_OUTPUT }] }, { expanded: false }, theme, { isError: true })
414
- .render(120)
415
- .join('\n');
416
- assert.match(collapsed, /<error>2 findings<\/error>/);
417
- assert.match(collapsed, /<warning>[^<]*invalid kind: "bogus"[^<]*<\/warning>/);
418
- assert.match(collapsed, /<warning>[^<]*missing `when-and-why-to-read`[^<]*<\/warning>/);
419
- assert.match(collapsed, /<muted>229 docs checked<\/muted>/);
420
- assert.doesNotMatch(collapsed, /PASS/);
421
- // No raw error block/XML leaks into the collapsed summary.
422
- assert.doesNotMatch(collapsed, /code="error"/);
423
- assert.doesNotMatch(collapsed, /Next: Fix each doc/);
424
- });
425
- // Help must look like help at the call header itself, not only once the
426
- // (possibly collapsed) result renders.
427
- test('renderCall shows a help-specific header for a -h/--help invocation, no bash chrome', () => {
428
- const def = createCrtrBashToolDefinition({ command: 'crtr node config -h' });
429
- const rendered = renderCrtrCall(def, { command: 'crtr node config -h' }).render(80).join('\n');
430
- assert.equal(rendered.trimEnd().split('\n').length, 1);
431
- assert.match(rendered, new RegExp(HELP_ICON));
432
- assert.match(rendered, /crtr help/);
433
- assert.doesNotMatch(rendered, /via bash/);
434
- assert.doesNotMatch(rendered, /\$ crtr/);
435
- assert.doesNotMatch(rendered, /-h/);
436
- assert.match(rendered, /node config/);
437
- });
438
- // Table-driven header coverage across every mapped root family (rule 12: real
439
- // Nerd Font glyphs, never the generic ◆) — includes `memory lint` (the
440
- // motivating case for the memory-lint summarizer above), not just `canvas`/
441
- // `node`.
442
- test('renderCall uses a mapped Nerd Font glyph and one line for every mapped root family, never the generic ◆', () => {
443
- const cases = [
444
- { command: 'crtr memory lint', iconTokens: ['memory', 'lint'], path: 'memory lint' },
445
- { command: 'crtr canvas dashboard', iconTokens: ['canvas', 'dashboard'], path: 'canvas dashboard' },
446
- { command: 'crtr node yield', iconTokens: ['node', 'yield'], path: 'node yield' },
447
- { command: 'crtr human ask', iconTokens: ['human', 'ask'], path: 'human ask' },
448
- { command: 'crtr node config set foo bar', iconTokens: ['node', 'config'], path: 'node config set foo bar' },
449
- { command: 'crtr pkg plugin list', iconTokens: ['pkg', 'plugin', 'list'], path: 'pkg plugin list' },
450
- { command: 'crtr push update', iconTokens: ['push', 'update'], path: 'push update' },
451
- { command: 'crtr search web', iconTokens: ['search', 'web'], path: 'search web' },
452
- { command: 'crtr surface attach to demo', iconTokens: ['surface', 'attach', 'to'], path: 'surface attach to demo' },
453
- { command: 'crtr sys doctor', iconTokens: ['sys', 'doctor'], path: 'sys doctor' },
454
- ];
455
- for (const { command, iconTokens, path } of cases) {
456
- const def = createCrtrBashToolDefinition({ command });
457
- // Width 200 (not 80): this test's fake theme wraps each styled span in
458
- // literal `<tag>...</tag>` text (not real ANSI escapes the wrapper would
459
- // strip for width purposes), so the visible-width accounting used by the
460
- // real Text component's word-wrap counts that markup as content width —
461
- // a generous width keeps every case here well clear of an unrelated wrap.
462
- const rendered = renderCrtrCall(def, { command }).render(200).join('\n');
463
- assert.equal(rendered.trimEnd().split('\n').length, 1, `expected one line for ${command}`);
464
- assert.match(rendered, new RegExp(iconForPath(iconTokens)), `expected the ${iconTokens.join(' ')} glyph in ${command}`);
465
- assert.match(rendered, /crtr/, `expected "crtr" in ${command}`);
466
- assert.match(rendered, new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `expected path "${path}" on the same line`);
467
- assert.doesNotMatch(rendered, /◆/, `unexpected generic diamond for ${command}`);
468
- }
469
- });
470
- test('renderCall for a normal invocation previews the crtr path, not a raw bash echo', () => {
471
- const def = createCrtrBashToolDefinition({ command: 'crtr node config set foo bar' });
472
- const rendered = renderCrtrCall(def, { command: 'crtr node config set foo bar' }).render(200).join('\n');
473
- assert.equal(rendered.trimEnd().split('\n').length, 1);
474
- assert.match(rendered, new RegExp(iconForPath(['node', 'config'])));
475
- assert.match(rendered, /crtr/);
476
- assert.match(rendered, /node config set foo bar/);
477
- assert.doesNotMatch(rendered, /◆/);
478
- assert.doesNotMatch(rendered, /via bash/);
479
- assert.doesNotMatch(rendered, /\$ crtr/);
480
- });
481
- test('a leaf that does not declare suppressOutput is never collapsed', () => {
482
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
483
- const component = def.renderResult({ content: [{ type: 'text', text: 'a normal-sized dashboard body' }] }, { expanded: false }, theme, {});
484
- assert.match(component.render(80).join('\n'), /a normal-sized dashboard body/);
485
- });
486
- test('expanded crtr output clamps long single lines to the terminal width', () => {
487
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
488
- const longLine = 'x'.repeat(442);
489
- const lines = def
490
- .renderResult({ content: [{ type: 'text', text: longLine }] }, { expanded: true }, theme, {})
491
- .render(70);
492
- assert.ok(lines.length > 0);
493
- for (const line of lines) {
494
- assert.ok(visibleWidth(line) <= 70, `line exceeded width: ${visibleWidth(line)} > 70`);
495
- }
496
- });
497
- test('collapsed crtr output clamps all rendered chrome to the terminal width', () => {
498
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
499
- const longLine = 'y'.repeat(442);
500
- const lines = def
501
- .renderResult({ content: [{ type: 'text', text: longLine }] }, { expanded: false }, theme, {})
502
- .render(70);
503
- assert.ok(lines.length > 0);
504
- for (const line of lines) {
505
- assert.ok(visibleWidth(line) <= 70, `line exceeded width: ${visibleWidth(line)} > 70`);
506
- }
507
- });
508
- const WIDE_GENERIC_TABLE_OUTPUT = [
509
- '24 plugins:',
510
- '',
511
- '| name | version | description | status |',
512
- '| --- | --- | --- | --- |',
513
- ...Array.from({ length: 24 }, (_, i) => `| plugin-${String(i).padStart(2, '0')} | 1.0.${i} | This is a deliberately over-wide description cell that used to wrap the collapsed card mid-cell for row ${i} | ${i % 2 === 0 ? 'enabled' : 'disabled'} |`),
514
- ].join('\n');
515
- test('generic collapsed fallback renders wide GFM tables aligned, clipped, and row-capped instead of raw pipe rows', () => {
516
- const def = createCrtrBashToolDefinition({ command: 'crtr surface web serve' });
517
- const rendered = def
518
- .renderResult({ content: [{ type: 'text', text: WIDE_GENERIC_TABLE_OUTPUT }] }, { expanded: false }, plainTheme, {})
519
- .render(58)
520
- .join('\n');
521
- assert.doesNotMatch(rendered, /\|/);
522
- assert.match(rendered, /plugin-00\s+1\.0\.0\s+.*…/);
523
- assert.match(rendered, /more lines/);
524
- assert.doesNotMatch(rendered, /plugin-23/);
525
- assert.ok(rendered.split('\n').length <= 20, `expected <=20 lines, got ${rendered.split('\n').length}`);
526
- const row0 = rendered.split('\n').find((line) => line.includes('plugin-00')) ?? '';
527
- const row1 = rendered.split('\n').find((line) => line.includes('plugin-01')) ?? '';
528
- assert.notEqual(row0, '');
529
- assert.notEqual(row1, '');
530
- assert.equal(row0.indexOf('1.0.0'), row1.indexOf('1.0.1'));
531
- for (const line of rendered.split('\n')) {
532
- assert.ok(visibleWidth(line) <= 58, `line exceeded width: ${visibleWidth(line)} > 58: ${line}`);
533
- }
534
- });
535
- const HEADERLESS_TABLE_TAIL_OUTPUT = [
536
- '| mr2iy5tw-a1f5e1f0 | batch-a-implementation | /Users/silasrhyneer/Code/cli/crouter | active |',
537
- '| mr147q9d-c7681b04 | initial-session-setup | /Users/silasrhyneer/Code/cli/crouter | idle |',
538
- '| mr2ejzaw-6f70e858 | preview-roadmap | /Users/silasrhyneer/Code/cli/crouter | done |',
539
- ].join('\n');
540
- test('generic collapsed fallback aligns a header-less truncated table tail by column count', () => {
541
- const def = createCrtrBashToolDefinition({ command: 'crtr surface web serve' });
542
- const rendered = def
543
- .renderResult({ content: [{ type: 'text', text: HEADERLESS_TABLE_TAIL_OUTPUT }] }, { expanded: false }, plainTheme, {})
544
- .render(64)
545
- .join('\n');
546
- assert.doesNotMatch(rendered, /\|/);
547
- assert.match(rendered, /batch-a-implemen/);
548
- const rows = rendered.split('\n').filter((line) => line.includes('mr')).map(stripAnsi);
549
- assert.equal(rows.length, 3);
550
- assert.equal(rows[0].indexOf('/Users'), rows[1].indexOf('/Users'));
551
- assert.equal(rows[1].indexOf('/Users'), rows[2].indexOf('/Users'));
552
- });
553
- test('generic collapsed fallback renders the pi truncation marker as one muted line', () => {
554
- const def = createCrtrBashToolDefinition({ command: 'crtr surface web serve' });
555
- const marker = '[Showing lines 951-1000 of 1000 (50.0KB limit). Full output: /tmp/pi-bash-output.log]';
556
- const rendered = def
557
- .renderResult({ content: [{ type: 'text', text: `last retained line\n${marker}` }] }, { expanded: false }, theme, {})
558
- .render(240)
559
- .join('\n');
560
- assert.match(rendered, /<muted>\[Showing lines 951-1000 of 1000 \(50\.0KB limit\)\. Full output: \/tmp\/pi-bash-output\.log\]<\/muted>/);
561
- assert.equal((rendered.match(/Showing lines/g) ?? []).length, 1);
562
- });
563
- const NO_EDGE_PIPE_TABLE_OUTPUT = [
564
- 'name | version | status',
565
- '--- | --- | ---',
566
- 'alpha | 1.0.0 | enabled',
567
- 'beta | 2.1.0 | disabled',
568
- ].join('\n');
569
- test('generic collapsed fallback aligns a GFM table whose rows omit both outer pipes', () => {
570
- const def = createCrtrBashToolDefinition({ command: 'crtr surface web serve' });
571
- const rendered = def
572
- .renderResult({ content: [{ type: 'text', text: NO_EDGE_PIPE_TABLE_OUTPUT }] }, { expanded: false }, plainTheme, {})
573
- .render(60)
574
- .join('\n');
575
- assert.doesNotMatch(rendered, /\|/);
576
- assert.doesNotMatch(rendered, /---/);
577
- const rows = rendered.split('\n').filter((line) => line.trim() !== '');
578
- const alphaRow = rows.find((line) => line.includes('alpha')) ?? '';
579
- const betaRow = rows.find((line) => line.includes('beta')) ?? '';
580
- assert.notEqual(alphaRow, '');
581
- assert.notEqual(betaRow, '');
582
- assert.equal(alphaRow.indexOf('1.0.0'), betaRow.indexOf('2.1.0'));
42
+ const redirected = renderCall('crtr push -h 2>&1');
43
+ assert.match(redirected, /<b>push<\/b>/);
44
+ assert.doesNotMatch(redirected, /2>|>&1/);
45
+ // Redirect operators glued to their target with no space are plumbing too,
46
+ // mid-argv as well as trailing.
47
+ const midArgv = renderCall('crtr node new --name foo >/tmp/log 2>&1');
48
+ assert.match(midArgv, /node new --name foo/);
49
+ assert.doesNotMatch(midArgv, /tmp\/log|2>/);
583
50
  });
584
- test('a stray line containing a single literal pipe is not mistaken for a table row', () => {
585
- const def = createCrtrBashToolDefinition({ command: 'crtr surface web serve' });
586
- const rendered = def
587
- .renderResult({ content: [{ type: 'text', text: 'warning: use x | y instead' }] }, { expanded: false }, plainTheme, {})
588
- .render(60)
589
- .join('\n');
590
- assert.match(rendered, /warning: use x \| y instead/);
591
- });
592
- test('icon lookup uses per-leaf glyphs before family glyphs, with help unchanged', () => {
593
- assert.equal(iconForPath(['canvas', 'dashboard']), String.fromCodePoint(0xf0e4));
594
- assert.notEqual(iconForPath(['canvas', 'dashboard']), iconForPath(['canvas']));
595
- assert.equal(iconForPath(['node', 'inspect', 'list']), String.fromCodePoint(0xf03a));
596
- assert.notEqual(iconForPath(['node', 'inspect', 'list']), iconForPath(['node']));
597
- assert.equal(iconForPath(['sys', 'doctor']), String.fromCodePoint(0xf0f1));
598
- assert.equal(HELP_ICON, String.fromCodePoint(0xf059));
599
- });
600
- // ---------------------------------------------------------------------------
601
- // canvas/node runtime cockpit summaries (first non-memory batch, Silas's
602
- // 2026-07-01 reframe: collapsed cards are computed status digests, not
603
- // decorated stdout). Fixtures below are literal renderResult() output shapes
604
- // (core/render.ts: a `- field: N` scalar bullet, an `N <label>:` count
605
- // lead-in + GFM table for an array of objects) — not the live command tree,
606
- // so these stay independent of what actually runs on a real canvas.
607
- // ---------------------------------------------------------------------------
608
- const DASHBOARD_OUTPUT = [
609
- '- nodes: 4',
610
- '',
611
- '● root-node [developer/orchestrator] ctx 5k',
612
- '├─ ✓ child-a ...',
613
- '└─ ○ child-b ...',
614
- '',
615
- '4 rows:',
616
- '',
617
- '| node_id | name | status | kind | mode | ctx_tokens | asks | cwd | created |',
618
- '| --- | --- | --- | --- | --- | --- | --- | --- | --- |',
619
- '| n1 | root-node | active | developer | orchestrator | 5000 | 0 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
620
- '| n2 | child-a | active | developer | base | 12000 | 3 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
621
- '| n3 | child-b | done | advisor | base | 8000 | 0 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
622
- '| n4 | child-c | idle | reviewer | base | 3000 | 0 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
623
- ].join('\n');
624
- test('a crtr canvas dashboard card collapses to a status-count digest, not the raw tree/table', () => {
51
+ test('crtr results do not invalidate their owning tool component', () => {
52
+ let ownerInvalidations = 0;
625
53
  const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
626
- const collapsed = def
627
- .renderResult({ content: [{ type: 'text', text: DASHBOARD_OUTPUT }] }, { expanded: false }, theme, {})
628
- .render(120)
629
- .join('\n');
630
- assert.match(collapsed, /2 active/);
631
- assert.match(collapsed, /1 idle/);
632
- assert.match(collapsed, /1 done/);
633
- assert.match(collapsed, /<muted>largest ctx: 12k tokens<\/muted>/);
634
- assert.match(collapsed, /<warning>3 pending asks<\/warning>/);
635
- assert.doesNotMatch(collapsed, /\| ---/);
636
- assert.doesNotMatch(collapsed, /node_id/);
637
- assert.doesNotMatch(collapsed, /crtr done/);
638
- assert.match(collapsed, /ctrl\+o to expand/);
639
- const expanded = def
640
- .renderResult({ content: [{ type: 'text', text: DASHBOARD_OUTPUT }] }, { expanded: true }, theme, {})
641
- .render(120)
642
- .join('\n');
643
- assert.match(expanded, /\$ crtr canvas dashboard/);
644
- assert.match(expanded, /\| node_id \| name \|/);
645
- assert.match(expanded, /root-node \[developer\/orchestrator\]/);
646
- });
647
- const NODE_LIST_OUTPUT = [
648
- '4 nodes:',
649
- '',
650
- '| node_id | name | status | kind | mode | lifecycle | cwd | created | hanging |',
651
- '| --- | --- | --- | --- | --- | --- | --- | --- | --- |',
652
- '| n1 | alpha | active | developer | orchestrator | terminal | /a | 2026-01-01T00:00:00.000Z | none |',
653
- '| n2 | beta | active | developer | base | terminal | /a | 2026-01-01T00:00:00.000Z | none |',
654
- '| n3 | gamma | done | advisor | base | terminal | /a | 2026-01-01T00:00:00.000Z | none |',
655
- '| n4 | delta | idle | reviewer | base | resident | /a | 2026-01-01T00:00:00.000Z | none |',
656
- ].join('\n');
657
- test('a crtr node inspect list card collapses to status + kind digests, not the raw table', () => {
658
- const def = createCrtrBashToolDefinition({ command: 'crtr node inspect list' });
659
- const collapsed = def
660
- .renderResult({ content: [{ type: 'text', text: NODE_LIST_OUTPUT }] }, { expanded: false }, theme, {})
661
- .render(120)
662
- .join('\n');
663
- assert.match(collapsed, /2 active/);
664
- assert.match(collapsed, /1 idle/);
665
- assert.match(collapsed, /1 done/);
666
- assert.match(collapsed, /<muted>kinds: developer×2, advisor×1, reviewer×1<\/muted>/);
667
- assert.doesNotMatch(collapsed, /\| ---/);
668
- assert.doesNotMatch(collapsed, /node_id/);
669
- assert.doesNotMatch(collapsed, /crtr done/);
670
- assert.match(collapsed, /ctrl\+o to expand/);
671
- });
672
- test('a crtr canvas attention count card renders a computed health tone, error when pending, success when clear', () => {
673
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas attention count' });
674
- const pending = def
675
- .renderResult({ content: [{ type: 'text', text: '- count: 14' }] }, { expanded: false }, theme, {})
676
- .render(120)
677
- .join('\n');
678
- assert.match(pending, /<error>14 pending asks<\/error>/);
679
- assert.match(pending, /ctrl\+o to expand/);
680
- const clear = def
681
- .renderResult({ content: [{ type: 'text', text: '- count: 0' }] }, { expanded: false }, theme, {})
682
- .render(120)
683
- .join('\n');
684
- assert.match(clear, /<success>PASS · no pending asks<\/success>/);
685
- assert.doesNotMatch(clear, /0 pending asks/);
686
- });
687
- const ATTENTION_LIST_OUTPUT = [
688
- '- total: 14',
689
- '',
690
- '5 items:',
691
- '',
692
- '| node_id | name | cwd | count |',
693
- '| --- | --- | --- | --- |',
694
- '| mq3geac4-9b947921 | general | /Users/silasrhyneer/Code/alaria-map-site | 7 |',
695
- '| mqa7c632-24f0e384 | general | /Users/silasrhyneer/Code/cli/crouter | 3 |',
696
- '| mqa9w9qy-e1cafda1 | general | /Users/silasrhyneer/Code/godot/StarTheory2 | 2 |',
697
- '| mqt6lz47-09daf181 | developer | /Users/silasrhyneer/Code/godot/StarTheory2-worktrees/escape-pod-selection | 1 |',
698
- '| mr0v8vxi-580c7572 | general | /Users/silasrhyneer/Code/northlight | 1 |',
699
- ].join('\n');
700
- test('a crtr canvas attention list card collapses to total/hottest/digest, not the raw table', () => {
701
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas attention list' });
702
- const collapsed = def
703
- .renderResult({ content: [{ type: 'text', text: ATTENTION_LIST_OUTPUT }] }, { expanded: false }, theme, {})
704
- .render(120)
705
- .join('\n');
706
- assert.match(collapsed, /14 total · 5 cwds/);
707
- assert.match(collapsed, /<warning>hottest: alaria-map-site \(7\)<\/warning>/);
708
- assert.match(collapsed, /<muted>other cwds: [^<]*crouter[^<]*<\/muted>/);
709
- assert.doesNotMatch(collapsed, /\| ---/);
710
- assert.doesNotMatch(collapsed, /node_id/);
711
- assert.match(collapsed, /ctrl\+o to expand/);
712
- const empty = def
713
- .renderResult({ content: [{ type: 'text', text: ['- total: 0', '', '0 items.'].join('\n') }] }, { expanded: false }, theme, {})
714
- .render(120)
715
- .join('\n');
716
- assert.match(empty, /<success>PASS · no pending asks<\/success>/);
717
- });
718
- // Command-owned preview: `node inspect show` inlines a full node meta object
719
- // (system prompt + every telemetry field) with no useful human-glance slice —
720
- // same rationale as canvas snapshot / node inspect snapshot, mirrored in the
721
- // bundle-safe SUPPRESS_OUTPUT_LEAVES registry.
722
- test('node inspect show is command-owned suppressOutput, never on failure', () => {
723
- const def = createCrtrBashToolDefinition({ command: 'crtr node inspect show abc' });
724
- const collapsed = def.renderResult({ content: [{ type: 'text', text: '{"node":{/* huge */}}' }] }, { expanded: false }, theme, {});
725
- const rendered = collapsed.render(80).join('\n');
726
- assert.doesNotMatch(rendered, /crtr done/);
727
- assert.doesNotMatch(rendered, /huge/);
728
- assert.match(rendered, /suppressed — ctrl\+o to expand/);
729
- const failed = def.renderResult({ content: [{ type: 'text', text: 'huge dump on failure too' }] }, { expanded: false }, theme, { isError: true });
730
- assert.match(failed.render(80).join('\n'), /huge dump on failure too/);
731
- });
732
- // ---------------------------------------------------------------------------
733
- // Batch B: canvas/node/push runtime cockpit summaries (Silas's
734
- // 2026-07-01 reframe continued). Fixtures below are literal render() output
735
- // shapes copied from the real command source (see preview-registry.ts's
736
- // per-summarizer doc comments for the exact file:line), not the live command
737
- // tree — these stay independent of what actually runs on a real canvas.
738
- // ---------------------------------------------------------------------------
739
- test('a crtr canvas attention map card collapses to a hottest-id digest, PASS when all zero', () => {
740
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas attention map --view mq1' });
741
- const collapsed = def
742
- .renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":3,"n3":5}' }] }, { expanded: false }, theme, {})
743
- .render(120)
744
- .join('\n');
745
- assert.match(collapsed, /8 asks across 2 nodes/);
746
- assert.match(collapsed, /<warning>hottest: n3 \(5\)<\/warning>/);
747
- const zero = def
748
- .renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":0}' }] }, { expanded: false }, theme, {})
749
- .render(120)
750
- .join('\n');
751
- assert.match(zero, /<success>PASS · no pending asks<\/success>/);
752
- const zeroExpanded = def
753
- .renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":0}' }] }, { expanded: true }, theme, {})
754
- .render(120)
755
- .join('\n');
756
- assert.match(zeroExpanded, /counts: \{"n1":0,"n2":0\}/);
757
- const expanded = def
758
- .renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":3,"n3":5}' }] }, { expanded: true }, theme, {})
759
- .render(120)
760
- .join('\n');
761
- assert.match(expanded, /counts: \{"n1":0,"n2":3,"n3":5\}/);
762
- });
763
- test('a crtr node lifecycle revive card collapses to a kicked/revived status line, argv-recovered id', () => {
764
- const kickedDef = createCrtrBashToolDefinition({ command: 'crtr node lifecycle revive mq2jpqz5-87fa626e --now' });
765
- const kicked = kickedDef
766
- .renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- kicked: true', '', 'Sent SIGTERM to worker-a (mq2jpqz5-87fa626e, pid 456).'].join('\n') }] }, { expanded: false }, theme, {})
767
- .render(120)
768
- .join('\n');
769
- assert.match(kicked, /<warning>kicked — daemon resumes in ~20s<\/warning>/);
770
- const kickedExpanded = kickedDef
771
- .renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- kicked: true', '', 'Sent SIGTERM to worker-a (mq2jpqz5-87fa626e, pid 456).'].join('\n') }] }, { expanded: true }, theme, {})
772
- .render(120)
773
- .join('\n');
774
- assert.match(kickedExpanded, /Sent SIGTERM to worker-a \(mq2jpqz5-87fa626e, pid 456\)\./);
775
- const revivedDef = createCrtrBashToolDefinition({ command: 'crtr node lifecycle revive mq2jpqz5-87fa626e' });
776
- const revived = revivedDef
777
- .renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- resumed: true', '- ready: true'].join('\n') }] }, { expanded: false }, theme, {})
778
- .render(120)
779
- .join('\n');
780
- assert.match(revived, /<success>revived mq2jpqz5-87fa626e<\/success>/);
781
- assert.doesNotMatch(revived, /not yet attachable/);
782
- const revivedExpanded = revivedDef
783
- .renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- resumed: true', '- ready: true'].join('\n') }] }, { expanded: true }, theme, {})
784
- .render(120)
785
- .join('\n');
786
- assert.match(revivedExpanded, /session: mysession/);
787
- const notReady = revivedDef
788
- .renderResult({ content: [{ type: 'text', text: ['- resumed: false', '- ready: false'].join('\n') }] }, { expanded: false }, theme, {})
789
- .render(120)
790
- .join('\n');
791
- assert.match(notReady, /<warning>not yet attachable \(socket not ready\)<\/warning>/);
792
- const notReadyExpanded = revivedDef
793
- .renderResult({ content: [{ type: 'text', text: ['- resumed: false', '- ready: false'].join('\n') }] }, { expanded: true }, theme, {})
794
- .render(120)
795
- .join('\n');
796
- assert.match(notReadyExpanded, /resumed: false/);
797
- assert.match(notReadyExpanded, /ready: false/);
798
- // `canvas revive --all` carries a `- mode:` bullet; no summarizer is
799
- // registered for it (rare admin path) — falls through to generic
800
- // truncation, raw `mode` text survives.
801
- const allDef = createCrtrBashToolDefinition({ command: 'crtr canvas revive --all' });
802
- const allPreview = allDef
803
- .renderResult({ content: [{ type: 'text', text: '- mode: preview\n- candidates: mq1' }] }, { expanded: false }, theme, {})
804
- .render(120)
805
- .join('\n');
806
- assert.match(allPreview, /mode: preview/);
807
- const allPreviewExpanded = revivedDef
808
- .renderResult({ content: [{ type: 'text', text: '- mode: preview\n- candidates: mq1' }] }, { expanded: true }, theme, {})
809
- .render(120)
810
- .join('\n');
811
- assert.match(allPreviewExpanded, /candidates: mq1/);
812
- });
813
- const HISTORY_SEARCH_HITS_OUTPUT = [
814
- '3 of 10 hits:',
815
- '',
816
- '| ref | node | kind | source | ts | snippet |',
817
- '| --- | --- | --- | --- | --- | --- |',
818
- '| n1:reports/a.md | alpha (n1) | developer | report:final | 2026-01-01T00:00:00.000Z | did a thing |',
819
- '| n2:reports/b.md | beta (n2) | developer | doc | 2026-01-02T00:00:00.000Z | did another |',
820
- '| n3:reports/c.md | gamma (n3) | advisor | report:update | 2026-01-03T00:00:00.000Z | did a third |',
821
- '',
822
- '- next_cursor: abc123',
823
- '- total: 10',
824
- '',
825
- 'Read a hit with `canvas history read <ref>`; narrow with `canvas history search -h`.',
826
- ].join('\n');
827
- test('a crtr canvas history search card collapses to a hit count + top rows, not the raw table', () => {
828
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas history search foo' });
829
- const collapsed = def
830
- .renderResult({ content: [{ type: 'text', text: HISTORY_SEARCH_HITS_OUTPUT }] }, { expanded: false }, theme, {})
831
- .render(140)
832
- .join('\n');
833
- assert.match(collapsed, /10 hits/);
834
- assert.match(collapsed, /<muted>alpha \(n1\) · developer · 2026-01-01T00:00:00\.000Z<\/muted>/);
835
- assert.doesNotMatch(collapsed, /\| ref \|/);
836
- const expanded = def
837
- .renderResult({ content: [{ type: 'text', text: HISTORY_SEARCH_HITS_OUTPUT }] }, { expanded: true }, theme, {})
838
- .render(140)
839
- .join('\n');
840
- assert.match(expanded, /\| ref \| node \| kind \| source \| ts \| snippet \|/);
841
- assert.match(expanded, /did a thing/);
842
- const zero = def
843
- .renderResult({ content: [{ type: 'text', text: ['0 hits.', '', '- next_cursor: null\n- total: 0', 'Read a hit…'].join('\n\n') }] }, { expanded: false }, theme, {})
844
- .render(120)
845
- .join('\n');
846
- assert.match(zero, /<muted>0 hits<\/muted>/);
847
- const zeroExpanded = def
848
- .renderResult({ content: [{ type: 'text', text: ['0 hits.', '', '- next_cursor: null\n- total: 0', 'Read a hit…'].join('\n\n') }] }, { expanded: true }, theme, {})
849
- .render(120)
850
- .join('\n');
851
- assert.match(zeroExpanded, /total: 0/);
852
- // --full mode shares the same `N of M hits:` lead-in but has no table —
853
- // deliberately skipped (null), raw survives.
854
- const full = def
855
- .renderResult({ content: [{ type: 'text', text: '1 of 1 hits:\n\n### n1:reports/a.md\n- node: alpha (n1) | kind: developer | source: doc | ts: 2026-01-01\n\nfull body here' }] }, { expanded: false }, theme, {})
856
- .render(120)
857
- .join('\n');
858
- assert.match(full, /full body here/);
859
- const fullExpanded = def
860
- .renderResult({ content: [{ type: 'text', text: '1 of 1 hits:\n\n### n1:reports/a.md\n- node: alpha (n1) | kind: developer | source: doc | ts: 2026-01-01\n\nfull body here' }] }, { expanded: true }, theme, {})
861
- .render(120)
862
- .join('\n');
863
- assert.match(fullExpanded, /full body here/);
864
- // Lead-in says 3 displayed hits, but the table only has 2 rows — a
865
- // truncated/mismatched render is a shape mismatch, not a partial summary:
866
- // skipped (null), raw survives even collapsed.
867
- const truncated = def
868
- .renderResult({
869
- content: [
870
- {
871
- type: 'text',
872
- text: [
873
- '3 of 10 hits:',
874
- '',
875
- '| ref | node | kind | source | ts | snippet |',
876
- '| --- | --- | --- | --- | --- | --- |',
877
- '| n1:reports/a.md | alpha (n1) | developer | report:final | 2026-01-01T00:00:00.000Z | did a thing |',
878
- '| n2:reports/b.md | beta (n2) | developer | doc | 2026-01-02T00:00:00.000Z | did another |',
879
- '',
880
- '- next_cursor: abc123',
881
- '- total: 10',
882
- ].join('\n'),
883
- },
884
- ],
885
- }, { expanded: false }, theme, {})
886
- .render(140)
887
- .join('\n');
888
- assert.match(truncated, /3 of 10 hits:/);
889
- assert.doesNotMatch(truncated, /<muted>alpha \(n1\) · developer/);
890
- });
891
- const HISTORY_READ_OUTPUT = [
892
- 'alpha (n1) | source: report:final | ts: 2026-01-01T00:00:00.000Z | ref: n1:reports/a.md',
893
- '',
894
- 'Line one of body.',
895
- 'Line two of body.',
896
- 'Line three of body.',
897
- 'Line four of body.',
898
- ].join('\n');
899
- test('a crtr canvas history read card previews an identity line + content teaser', () => {
900
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas history read n1:reports/a.md' });
901
- const collapsed = def
902
- .renderResult({ content: [{ type: 'text', text: HISTORY_READ_OUTPUT }] }, { expanded: false }, theme, {})
903
- .render(120)
904
- .join('\n');
905
- assert.match(collapsed, /alpha \(n1\) · report:final · 2026-01-01T00:00:00\.000Z/);
906
- assert.match(collapsed, /Line one of body\./);
907
- assert.match(collapsed, /…/);
908
- assert.doesNotMatch(collapsed, /Line four of body\./);
909
- const expanded = def
910
- .renderResult({ content: [{ type: 'text', text: HISTORY_READ_OUTPUT }] }, { expanded: true }, theme, {})
911
- .render(120)
912
- .join('\n');
913
- assert.match(expanded, /Line four of body\./);
914
- });
915
- const HISTORY_SHOW_OUTPUT = [
916
- 'node: alpha (n1)',
917
- '',
918
- '4 artifacts:',
919
- '',
920
- '| ref | source | ts | detail |',
921
- '| --- | --- | --- | --- |',
922
- '| n1:reports/a.md | report:final | 2026-01-01T00:00:00.000Z | Final report (120B) |',
923
- '| n1:reports/b.md | report:update | 2026-01-02T00:00:00.000Z | Update (80B) |',
924
- '| n1:docs/design.md | doc | 2026-01-03T00:00:00.000Z | Design doc |',
925
- '| n1:roadmap.md | roadmap | 2026-01-04T00:00:00.000Z | Roadmap |',
926
- '',
927
- 'Read one with `canvas history read <ref>`; reopen the node with `node lifecycle revive n1`.',
928
- ].join('\n');
929
- test('a crtr node inspect artifacts card collapses to a node line + type-count digest, not the raw table', () => {
930
- const def = createCrtrBashToolDefinition({ command: 'crtr node inspect artifacts n1' });
931
- const collapsed = def
932
- .renderResult({ content: [{ type: 'text', text: HISTORY_SHOW_OUTPUT }] }, { expanded: false }, theme, {})
933
- .render(120)
934
- .join('\n');
935
- assert.match(collapsed, /4 artifacts/);
936
- assert.match(collapsed, /<muted>reports×2, docs×1, roadmaps×1<\/muted>/);
937
- assert.doesNotMatch(collapsed, /\| ref \|/);
938
- const expanded = def
939
- .renderResult({ content: [{ type: 'text', text: HISTORY_SHOW_OUTPUT }] }, { expanded: true }, theme, {})
940
- .render(120)
941
- .join('\n');
942
- assert.match(expanded, /\| ref \| source \| ts \| detail \|/);
943
- assert.match(expanded, /Final report {2}\(120B\)/);
944
- const empty = def
945
- .renderResult({ content: [{ type: 'text', text: ['node: alpha (n1)', '', '0 artifacts.', '', 'Read one…'].join('\n\n') }] }, { expanded: false }, theme, {})
946
- .render(120)
947
- .join('\n');
948
- assert.match(empty, /<muted>alpha \(n1\) · 0 artifacts<\/muted>/);
949
- // Lead-in says 4 artifacts, but the table only has 3 rows — a
950
- // truncated/mismatched render is a shape mismatch, not a partial summary:
951
- // skipped (null), raw survives even collapsed.
952
- const truncated = def
953
- .renderResult({
954
- content: [
955
- {
956
- type: 'text',
957
- text: [
958
- 'node: alpha (n1)',
959
- '',
960
- '4 artifacts:',
961
- '',
962
- '| ref | source | ts | detail |',
963
- '| --- | --- | --- | --- |',
964
- '| n1:reports/a.md | report:final | 2026-01-01T00:00:00.000Z | Final report (120B) |',
965
- '| n1:reports/b.md | report:update | 2026-01-02T00:00:00.000Z | Update (80B) |',
966
- '| n1:docs/design.md | doc | 2026-01-03T00:00:00.000Z | Design doc |',
967
- ].join('\n'),
968
- },
969
- ],
970
- }, { expanded: false }, theme, {})
971
- .render(120)
972
- .join('\n');
973
- assert.match(truncated, /4 artifacts:/);
974
- assert.doesNotMatch(truncated, /<muted>reports×/);
975
- const emptyExpanded = def
976
- .renderResult({ content: [{ type: 'text', text: ['node: alpha (n1)', '', '0 artifacts.', '', 'Read one…'].join('\n\n') }] }, { expanded: true }, theme, {})
977
- .render(120)
978
- .join('\n');
979
- assert.match(emptyExpanded, /0 artifacts\./);
980
- });
981
- test('crtr sys daemon start/status/stop cards collapse to a one-line pid status', () => {
982
- const start = createCrtrBashToolDefinition({ command: 'crtr sys daemon start' });
983
- assert.match(start.renderResult({ content: [{ type: 'text', text: '- started: true\n- pid: 4242' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<success>started · pid 4242<\/success>/);
984
- assert.match(start.renderResult({ content: [{ type: 'text', text: '- started: false\n- existing_pid: 99' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<muted>already running · pid 99<\/muted>/);
985
- assert.match(start.renderResult({ content: [{ type: 'text', text: '- started: false\n- existing_pid: 99' }] }, { expanded: true }, theme, {}).render(120).join('\n'), /existing_pid: 99/);
986
- const startExpanded = start
987
- .renderResult({ content: [{ type: 'text', text: '- started: true\n- pid: 4242' }] }, { expanded: true }, theme, {})
988
- .render(120)
989
- .join('\n');
990
- assert.match(startExpanded, /started: true/);
991
- assert.match(startExpanded, /pid: 4242/);
992
- const status = createCrtrBashToolDefinition({ command: 'crtr sys daemon status' });
993
- assert.match(status.renderResult({ content: [{ type: 'text', text: '- running: true\n- pid: 4242' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<success>running · pid 4242<\/success>/);
994
- assert.match(status.renderResult({ content: [{ type: 'text', text: '- running: false' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<error>not running<\/error>/);
995
- assert.match(status.renderResult({ content: [{ type: 'text', text: '- running: false' }] }, { expanded: true }, theme, {}).render(120).join('\n'), /running: false/);
996
- const statusExpanded = status
997
- .renderResult({ content: [{ type: 'text', text: '- running: true\n- pid: 4242' }] }, { expanded: true }, theme, {})
998
- .render(120)
999
- .join('\n');
1000
- assert.match(statusExpanded, /running: true/);
1001
- assert.match(statusExpanded, /pid: 4242/);
1002
- const stop = createCrtrBashToolDefinition({ command: 'crtr sys daemon stop' });
1003
- assert.match(stop.renderResult({ content: [{ type: 'text', text: '- stopped: true\n- pid: 4242' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /stopped · pid 4242/);
1004
- assert.match(stop.renderResult({ content: [{ type: 'text', text: '- stopped: false' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<muted>was not running<\/muted>/);
1005
- assert.match(stop.renderResult({ content: [{ type: 'text', text: '- stopped: false' }] }, { expanded: true }, theme, {}).render(120).join('\n'), /stopped: false/);
1006
- const stopExpanded = stop
1007
- .renderResult({ content: [{ type: 'text', text: '- stopped: true\n- pid: 4242' }] }, { expanded: true }, theme, {})
1008
- .render(120)
1009
- .join('\n');
1010
- assert.match(stopExpanded, /stopped: true/);
1011
- assert.match(stopExpanded, /pid: 4242/);
1012
- });
1013
- test('a crtr canvas issue new card collapses to an issue/note confirmation, not the raw bullets', () => {
1014
- const def = createCrtrBashToolDefinition({ command: 'crtr canvas issue new --type bug' });
1015
- const github = def
1016
- .renderResult({
1017
- content: [
1018
- {
1019
- type: 'text',
1020
- text: '- created: true\n- target: github\n- type: bug\n- title: Fix the thing\n- url: https://github.com/acme/repo/issues/42',
1021
- },
1022
- ],
1023
- }, { expanded: false }, theme, {})
1024
- .render(120)
1025
- .join('\n');
1026
- assert.match(github, /<success>issue #42<\/success>/);
1027
- assert.match(github, /<muted>https:\/\/github\.com\/acme\/repo\/issues\/42<\/muted>/);
1028
- const githubExpanded = def
1029
- .renderResult({
1030
- content: [
1031
- {
1032
- type: 'text',
1033
- text: '- created: true\n- target: github\n- type: bug\n- title: Fix the thing\n- url: https://github.com/acme/repo/issues/42',
1034
- },
1035
- ],
1036
- }, { expanded: true }, theme, {})
1037
- .render(120)
1038
- .join('\n');
1039
- assert.match(githubExpanded, /url: https:\/\/github\.com\/acme\/repo\/issues\/42/);
1040
- const file = def
1041
- .renderResult({
1042
- content: [
1043
- {
1044
- type: 'text',
1045
- text: '- created: true\n- target: file\n- type: idea\n- title: Add the thing\n- path: /Users/x/proj/.todos/20260101-add-the-thing.md',
1046
- },
1047
- ],
1048
- }, { expanded: false }, theme, {})
1049
- .render(120)
1050
- .join('\n');
1051
- assert.match(file, /<success>note saved<\/success>/);
1052
- assert.match(file, /<muted>20260101-add-the-thing\.md<\/muted>/);
1053
- const fileExpanded = def
1054
- .renderResult({
1055
- content: [
1056
- {
1057
- type: 'text',
1058
- text: '- created: true\n- target: file\n- type: idea\n- title: Add the thing\n- path: /Users/x/proj/.todos/20260101-add-the-thing.md',
1059
- },
1060
- ],
1061
- }, { expanded: true }, theme, {})
1062
- .render(120)
1063
- .join('\n');
1064
- assert.match(fileExpanded, /path: \/Users\/x\/proj\/\.todos\/20260101-add-the-thing\.md/);
1065
- const empty = def
1066
- .renderResult({ content: [{ type: 'text', text: '- created: false' }] }, { expanded: false }, theme, {})
1067
- .render(120)
1068
- .join('\n');
1069
- assert.match(empty, /<muted>nothing captured<\/muted>/);
1070
- const emptyExpanded = def
1071
- .renderResult({ content: [{ type: 'text', text: '- created: false' }] }, { expanded: true }, theme, {})
1072
- .render(120)
1073
- .join('\n');
1074
- assert.match(emptyExpanded, /created: false/);
1075
- });
1076
- test('a crtr node new card collapses to a spawned status line, not the raw follow_up prose', () => {
1077
- const def = createCrtrBashToolDefinition({ command: 'crtr node new --kind developer' });
1078
- const spawned = def
1079
- .renderResult({ content: [{ type: 'text', text: ['Spawned "worker-a" (mq2jpqz5-87fa626e) — active.', '', 'Independent root spawned — hand it off and move on.'].join('\n') }] }, { expanded: false }, theme, {})
1080
- .render(120)
1081
- .join('\n');
1082
- assert.match(spawned, /<success>spawned worker-a \(mq2jpqz5-87fa626e\) · active<\/success>/);
1083
- const spawnedExpanded = def
1084
- .renderResult({ content: [{ type: 'text', text: ['Spawned "worker-a" (mq2jpqz5-87fa626e) — active.', '', 'Independent root spawned — hand it off and move on.'].join('\n') }] }, { expanded: true }, theme, {})
1085
- .render(120)
1086
- .join('\n');
1087
- assert.match(spawnedExpanded, /Independent root spawned — hand it off and move on\./);
1088
- });
1089
- test('a crtr node message send card collapses to a delivered status line, not the raw guidance prose', () => {
1090
- const def = createCrtrBashToolDefinition({ command: 'crtr node message send --to mq123' });
1091
- const delivered = def
1092
- .renderResult({ content: [{ type: 'text', text: 'Delivered.' }] }, { expanded: false }, theme, {})
1093
- .render(120)
1094
- .join('\n');
1095
- assert.match(delivered, /<success>delivered<\/success>/);
1096
- const deliveredExpanded = def
1097
- .renderResult({ content: [{ type: 'text', text: 'Delivered.' }] }, { expanded: true }, theme, {})
1098
- .render(120)
1099
- .join('\n');
1100
- assert.match(deliveredExpanded, /Delivered\./);
1101
- const revived = def
1102
- .renderResult({ content: [{ type: 'text', text: 'Delivered — the dormant target was revived to receive it.' }] }, { expanded: false }, theme, {})
1103
- .render(120)
1104
- .join('\n');
1105
- assert.match(revived, /<success>delivered · revived<\/success>/);
1106
- const revivedExpanded = def
1107
- .renderResult({ content: [{ type: 'text', text: 'Delivered — the dormant target was revived to receive it.' }] }, { expanded: true }, theme, {})
1108
- .render(120)
1109
- .join('\n');
1110
- assert.match(revivedExpanded, /Delivered — the dormant target was revived to receive it\./);
1111
- const deferredHeld = def
1112
- .renderResult({ content: [{ type: 'text', text: "Delivered — held for the target's next natural cycle; not revived (deferred)." }] }, { expanded: false }, theme, {})
1113
- .render(120)
1114
- .join('\n');
1115
- assert.match(deferredHeld, /<accent>delivered · held \(deferred\)<\/accent>/);
1116
- const deferredHeldExpanded = def
1117
- .renderResult({ content: [{ type: 'text', text: "Delivered — held for the target's next natural cycle; not revived (deferred)." }] }, { expanded: true }, theme, {})
1118
- .render(120)
1119
- .join('\n');
1120
- assert.match(deferredHeldExpanded, /Delivered — held for the target's next natural cycle; not revived \(deferred\)\./);
1121
- });
1122
- test('a crtr node yield card collapses to a refresh-armed status line, not the raw bullets', () => {
1123
- const def = createCrtrBashToolDefinition({ command: 'crtr node yield' });
1124
- const withExtras = def
1125
- .renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- promoted: true\n- model: strong\n- will_refresh: true' }] }, { expanded: false }, theme, {})
1126
- .render(120)
1127
- .join('\n');
1128
- assert.match(withExtras, /<accent>yield armed — refreshes on next wake<\/accent>/);
1129
- assert.match(withExtras, /<muted>promoted → orchestrator · model: strong<\/muted>/);
1130
- const withExtrasExpanded = def
1131
- .renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- promoted: true\n- model: strong\n- will_refresh: true' }] }, { expanded: true }, theme, {})
1132
- .render(120)
1133
- .join('\n');
1134
- assert.match(withExtrasExpanded, /promoted: true/);
1135
- assert.match(withExtrasExpanded, /model: strong/);
1136
- const plain = def
1137
- .renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- will_refresh: true' }] }, { expanded: false }, theme, {})
1138
- .render(120)
1139
- .join('\n');
1140
- assert.match(plain, /<accent>yield armed — refreshes on next wake<\/accent>/);
1141
- assert.doesNotMatch(plain, /promoted/);
1142
- const plainExpanded = def
1143
- .renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- will_refresh: true' }] }, { expanded: true }, theme, {})
1144
- .render(120)
1145
- .join('\n');
1146
- assert.match(plainExpanded, /node_id: mq123/);
1147
- });
1148
- test('a crtr node lifecycle close card collapses to a cascade digest, not the raw prose sentence', () => {
1149
- const def = createCrtrBashToolDefinition({ command: 'crtr node lifecycle close --node mq123' });
1150
- const cascade = def
1151
- .renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 3 node(s) closed, 1 spared (still managed from outside the subtree).' }] }, { expanded: false }, theme, {})
1152
- .render(120)
1153
- .join('\n');
1154
- assert.match(cascade, /<success>closed mq123<\/success>/);
1155
- assert.match(cascade, /<warning>2 descendants canceled<\/warning>/);
1156
- assert.match(cascade, /<muted>1 spared \(managed elsewhere\)<\/muted>/);
1157
- const cascadeExpanded = def
1158
- .renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 3 node(s) closed, 1 spared (still managed from outside the subtree).' }] }, { expanded: true }, theme, {})
1159
- .render(120)
1160
- // Expanded lines WRAP rather than clip, so rejoin the wrap to assert the
1161
- // full sentence survived intact.
1162
- .join(' ')
1163
- .replace(/\s+/g, ' ');
1164
- assert.match(cascadeExpanded, /Closed mq123 and its exclusive subtree — 3 node\(s\) closed, 1 spared \(still managed from outside the subtree\)/);
1165
- const solo = def
1166
- .renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 1 node(s) closed.' }] }, { expanded: false }, theme, {})
1167
- .render(120)
1168
- .join('\n');
1169
- assert.match(solo, /<success>closed mq123<\/success>/);
1170
- assert.doesNotMatch(solo, /descendants? canceled/);
1171
- const soloExpanded = def
1172
- .renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 1 node(s) closed.' }] }, { expanded: true }, theme, {})
1173
- .render(120)
1174
- .join('\n');
1175
- assert.match(soloExpanded, /Closed mq123 and its exclusive subtree — 1 node\(s\) closed\./);
1176
- });
1177
- test('crtr push urgent/final cards collapse to a one-line outcome + report basename', () => {
1178
- const urgentDef = createCrtrBashToolDefinition({ command: 'crtr push urgent' });
1179
- const urgent = urgentDef
1180
- .renderResult({ content: [{ type: 'text', text: 'Urgent report fanned to 3 subscriber(s) — they are force-woken.\n\n- report: /Users/x/nodes/mq1/reports/20260101-urgent.md' }] }, { expanded: false }, theme, {})
1181
- .render(120)
1182
- .join('\n');
1183
- assert.match(urgent, /<warning>urgent → 3 subscribers woken<\/warning>/);
1184
- const urgentExpanded = urgentDef
1185
- .renderResult({ content: [{ type: 'text', text: 'Urgent report fanned to 3 subscriber(s) — they are force-woken.\n\n- report: /Users/x/nodes/mq1/reports/20260101-urgent.md' }] }, { expanded: true }, theme, {})
1186
- .render(120)
1187
- .join('\n');
1188
- assert.match(urgentExpanded, /report: \/Users\/x\/nodes\/mq1\/reports\/20260101-urgent\.md/);
1189
- const finalDef = createCrtrBashToolDefinition({ command: 'crtr push final' });
1190
- const final = finalDef
1191
- .renderResult({
1192
- content: [
1193
- {
1194
- type: 'text',
1195
- text: 'Result recorded — node finished; its engine shuts down on next stop. Nothing more to do here: STOP your turn immediately. Reply with just "Done." and nothing else.\n\n- report: /Users/x/nodes/mq1/reports/20260101-final.md',
1196
- },
1197
- ],
1198
- }, { expanded: false }, theme, {})
1199
- .render(120)
1200
- .join('\n');
1201
- assert.match(final, /<success>final pushed · node done<\/success>/);
1202
- assert.match(final, /<muted>20260101-final\.md<\/muted>/);
1203
- const finalExpanded = finalDef
1204
- .renderResult({
1205
- content: [
1206
- {
1207
- type: 'text',
1208
- text: 'Result recorded — node finished; its engine shuts down on next stop. Nothing more to do here: STOP your turn immediately. Reply with just "Done." and nothing else.\n\n- report: /Users/x/nodes/mq1/reports/20260101-final.md',
1209
- },
1210
- ],
1211
- }, { expanded: true }, theme, {})
1212
- .render(120)
1213
- .join('\n');
1214
- assert.match(finalExpanded, /report: \/Users\/x\/nodes\/mq1\/reports\/20260101-final\.md/);
1215
- });
1216
- // ---------------------------------------------------------------------------
1217
- // Batch C — human/memory/pkg summarizers (preview-registry.ts "Batch C")
1218
- // ---------------------------------------------------------------------------
1219
- // Ask and review share the queuedFollowUp road-sign (prompts.ts): arrival is
1220
- // always just durable queue state, so the card collapses to one posted line.
1221
- // The summarizer keys on the stable `Queued for the human` prefix; the inbox-
1222
- // affordance tail resolves per call, so this fixture pins one resolved form.
1223
- const QUEUED_FOLLOW_UP = 'Queued for the human. Their answer is pushed to your inbox and wakes you when they respond — never poll, verify it opened, or block. To see the queue, press ⌥I in a node viewer (or run `crtr human list` for the queue).';
1224
- const HUMAN_ASK_QUEUED_OUTPUT = [
1225
- '- job_id: mrASK0001',
1226
- '',
1227
- '**dir:** /Users/silas/Code/cli/crouter',
1228
- '',
1229
- `**follow_up:** ${QUEUED_FOLLOW_UP}`,
1230
- ].join('\n');
1231
- test('a crtr human ask card collapses to a posted status + question teaser, not the raw fields', () => {
1232
- const def = createCrtrBashToolDefinition({ command: 'crtr human ask "Should we ship the preview rollout now?"' });
1233
- const collapsed = def
1234
- .renderResult({ content: [{ type: 'text', text: HUMAN_ASK_QUEUED_OUTPUT }] }, { expanded: false }, theme, {})
1235
- .render(140)
1236
- .join('\n');
1237
- assert.match(collapsed, /<accent>ask posted → waiting on human<\/accent>/);
1238
- assert.match(collapsed, /<muted>Should we ship the preview rollout now\?<\/muted>/);
1239
- assert.doesNotMatch(collapsed, /- job_id:/);
1240
- const expanded = def
1241
- .renderResult({ content: [{ type: 'text', text: HUMAN_ASK_QUEUED_OUTPUT }] }, { expanded: true }, theme, {})
1242
- .render(140)
1243
- .join('\n');
1244
- assert.match(expanded, /job_id: mrASK0001/);
1245
- assert.match(expanded, /wakes you when they respond/);
1246
- });
1247
- const HUMAN_REVIEW_NEW_OUTPUT = [
1248
- '- job_id: mrREV0001',
1249
- '',
1250
- '**dir:** /Users/silas/Code/cli/crouter',
1251
- '',
1252
- '**output:** /Users/silas/Code/cli/crouter/feedback.json',
1253
- '',
1254
- '- review_id: review-0001',
1255
- '',
1256
- '**follow_up:** The review is live for the person in the attach viewer with its companion agent. Approval is pushed to the origin inbox when submitted.',
1257
- ].join('\n');
1258
- test('a crtr human review new card collapses to its live companion status + file basename, not the raw fields', () => {
1259
- const def = createCrtrBashToolDefinition({ command: 'crtr human review new /Users/silas/Code/cli/crouter/spec.md --subtitle "Review the implementation."' });
1260
- const collapsed = def
1261
- .renderResult({ content: [{ type: 'text', text: HUMAN_REVIEW_NEW_OUTPUT }] }, { expanded: false }, theme, {})
1262
- .render(140)
1263
- .join('\n');
1264
- assert.match(collapsed, /<accent>review posted: spec\.md → live with companion<\/accent>/);
1265
- assert.doesNotMatch(collapsed, /- job_id:/);
1266
- const expanded = def
1267
- .renderResult({ content: [{ type: 'text', text: HUMAN_REVIEW_NEW_OUTPUT }] }, { expanded: true }, theme, {})
1268
- .render(140)
1269
- .join('\n');
1270
- assert.match(expanded, /job_id: mrREV0001/);
1271
- assert.match(expanded, /review_id: review-0001/);
1272
- assert.match(expanded, /Approval is pushed to the origin inbox when\s+submitted/);
1273
- });
1274
- const HUMAN_LIST_OUTPUT = [
1275
- '- total: 2',
1276
- '',
1277
- '2 items:',
1278
- '',
1279
- '| id | job_id | dir | title | kind | blocked_since | asking_node_id | asking_node_name | conversation_id | conversation_title | interaction_count |',
1280
- '| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |',
1281
- '| h1 | mrJOB1 | /Users/silas/Code/cli/crouter | Ship the rollout? | decision | 2026-06-30T12:00:00.000Z | mrNODE1 | orchestrator | c1 | Rollout plan | 3 |',
1282
- '| h2 | mrJOB2 | /Users/silas/Code/cli/crouter | Quick check | | 2026-06-30T12:05:00.000Z | mrNODE2 | reviewer | c2 | Spec review | 1 |',
1283
- ].join('\n');
1284
- test('a crtr human list card collapses to a pending count + kind digest, not the raw table', () => {
1285
- const def = createCrtrBashToolDefinition({ command: 'crtr human list' });
1286
- const collapsed = def
1287
- .renderResult({ content: [{ type: 'text', text: HUMAN_LIST_OUTPUT }] }, { expanded: false }, theme, {})
1288
- .render(140)
1289
- .join('\n');
1290
- assert.match(collapsed, /2 pending/);
1291
- assert.match(collapsed, /<muted>decision×1<\/muted>/);
1292
- assert.doesNotMatch(collapsed, /\| id \|/);
1293
- const expanded = def
1294
- .renderResult({ content: [{ type: 'text', text: HUMAN_LIST_OUTPUT }] }, { expanded: true }, theme, {})
1295
- .render(140)
1296
- .join('\n');
1297
- assert.match(expanded, /\| id \| job_id \| dir \|/);
1298
- const none = def
1299
- .renderResult({ content: [{ type: 'text', text: '- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
1300
- .render(140)
1301
- .join('\n');
1302
- assert.match(none, /<success>none pending<\/success>/);
1303
- // REQUIREMENT: total is nonzero but the page is empty (exhausted
1304
- // pagination) — must NOT collapse to "none pending" (that's only correct
1305
- // for a true zero total); fall back to the generic renderer instead.
1306
- const exhausted = def
1307
- .renderResult({ content: [{ type: 'text', text: '- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
1308
- .render(140)
1309
- .join('\n');
1310
- assert.doesNotMatch(exhausted, /none pending/);
1311
- });
1312
- const HUMAN_DECK_OUTPUT = [
1313
- '- id: h1',
1314
- '- title: Ship the rollout?',
1315
- '- kind: decision',
1316
- '- interaction_count: 1',
1317
- '',
1318
- '1 interactions:',
1319
- '',
1320
- '| id | kind | prompt | options | allow_freetext |',
1321
- '| --- | --- | --- | --- | --- |',
1322
- '| i1 | decision | Ship it? | {"id":"yes","label":"Yes"}, {"id":"no","label":"No"} | false |',
1323
- ].join('\n');
1324
- const HUMAN_DECK_MULTI_OUTPUT = [
1325
- '- id: h1',
1326
- '- title: Ship the rollout?',
1327
- '- kind: decision',
1328
- '- interaction_count: 2',
1329
- '',
1330
- '2 interactions:',
1331
- '',
1332
- '| id | kind | prompt | options | allow_freetext |',
1333
- '| --- | --- | --- | --- | --- |',
1334
- '| i1 | decision | Ship it? | {"id":"yes","label":"Yes"}, {"id":"no","label":"No"} | true |',
1335
- '| i2 | question | Any concerns? | | true |',
1336
- ].join('\n');
1337
- test('a crtr human deck card collapses to a prompt teaser + option/freetext digest, not the raw table', () => {
1338
- const def = createCrtrBashToolDefinition({ command: 'crtr human deck h1' });
1339
- const collapsed = def
1340
- .renderResult({ content: [{ type: 'text', text: HUMAN_DECK_OUTPUT }] }, { expanded: false }, theme, {})
1341
- .render(140)
1342
- .join('\n');
1343
- assert.match(collapsed, /Ship it\?/);
1344
- assert.match(collapsed, /<muted>2 options<\/muted>/);
1345
- assert.doesNotMatch(collapsed, /\| id \| kind \|/);
1346
- const expanded = def
1347
- .renderResult({ content: [{ type: 'text', text: HUMAN_DECK_OUTPUT }] }, { expanded: true }, theme, {})
1348
- .render(140)
1349
- .join('\n');
1350
- assert.match(expanded, /\| id \| kind \| prompt \| options \| allow_freetext \|/);
1351
- const multi = def
1352
- .renderResult({ content: [{ type: 'text', text: HUMAN_DECK_MULTI_OUTPUT }] }, { expanded: false }, theme, {})
1353
- .render(140)
1354
- .join('\n');
1355
- assert.match(multi, /Ship it\?/);
1356
- assert.match(multi, /<muted>2 options · freetext · 2 interactions<\/muted>/);
1357
- // REQUIREMENT: a malformed/truncated `allow_freetext` cell (neither
1358
- // `true` nor `false`) must not silently default to "no freetext" — it
1359
- // must fall back to the generic renderer instead.
1360
- const malformedFreetext = [
1361
- '- id: h1',
1362
- '- title: Ship the rollout?',
1363
- '- kind: decision',
1364
- '- interaction_count: 1',
1365
- '',
1366
- '1 interactions:',
1367
- '',
1368
- '| id | kind | prompt | options | allow_freetext |',
1369
- '| --- | --- | --- | --- | --- |',
1370
- '| i1 | decision | Ship it? | {"id":"yes","label":"Yes"}, {"id":"no","label":"No"} | |',
1371
- ].join('\n');
1372
- const malformed = def
1373
- .renderResult({ content: [{ type: 'text', text: malformedFreetext }] }, { expanded: false }, theme, {})
1374
- .render(140)
1375
- .join('\n');
1376
- assert.doesNotMatch(malformed, /<muted>2 options<\/muted>/);
1377
- });
1378
- const MEMORY_LIST_OUTPUT = [
1379
- '- total: 3',
1380
- '',
1381
- 'Read one in full with `crtr memory read <name>`, or pass --paths for its file path to edit directly. Narrow with --kind / --scope, page with --cursor, or search a topic with `crtr memory find <query>`.',
1382
- '',
1383
- '3 items:',
1384
- '',
1385
- '| name | short_form | kind | scope | is_dir |',
1386
- '| --- | --- | --- | --- | --- |',
1387
- '| ai/x | AI helpers reference | knowledge | user | false |',
1388
- '| taste/y | Taste doc | preference | project | false |',
1389
- '| todos/z | Todo doc | knowledge | node | false |',
1390
- ].join('\n');
1391
- test('a crtr memory list card collapses to a doc count + kind/scope digest, not the raw table', () => {
1392
- const def = createCrtrBashToolDefinition({ command: 'crtr memory list' });
1393
- const collapsed = def
1394
- .renderResult({ content: [{ type: 'text', text: MEMORY_LIST_OUTPUT }] }, { expanded: false }, theme, {})
1395
- .render(140)
1396
- .join('\n');
1397
- assert.match(collapsed, /3 docs/);
1398
- assert.match(collapsed, /<muted>knowledge×2, preference×1 \/ node×1, project×1, user×1<\/muted>/);
1399
- assert.doesNotMatch(collapsed, /\| name \|/);
1400
- const expanded = def
1401
- .renderResult({ content: [{ type: 'text', text: MEMORY_LIST_OUTPUT }] }, { expanded: true }, theme, {})
1402
- .render(140)
1403
- .join('\n');
1404
- assert.match(expanded, /\| name \| short_form \| kind \|/);
1405
- });
1406
- const MEMORY_FIND_OUTPUT = [
1407
- '- query: batch c fixtures',
1408
- '- matches: 12',
1409
- '',
1410
- 'Matches ranked by relevance.',
1411
- '',
1412
- '2 hits:',
1413
- '',
1414
- '| name | kind | scope | score | short_form |',
1415
- '| --- | --- | --- | --- | --- |',
1416
- '| ai/x | knowledge | user | 18 | AI helpers reference |',
1417
- '| taste/y | preference | project | 11 | Taste doc |',
1418
- ].join('\n');
1419
- const MEMORY_FIND_EMPTY_OUTPUT = ['- query: nonexistent thing', '', 'Matches ranked by relevance.', '', '0 hits.'].join('\n');
1420
- const MEMORY_FIND_GREP_OUTPUT = [
1421
- '- query: TODO',
1422
- '- matches: 1',
1423
- '',
1424
- 'Matches ranked by relevance.',
1425
- '',
1426
- '1 hits:',
1427
- '',
1428
- '| name | scope | line | text |',
1429
- '| --- | --- | --- | --- |',
1430
- '| notes/a | project | 12 | TODO: fix this |',
1431
- ].join('\n');
1432
- test('a crtr memory find card collapses to a hit count + name digest, not the raw table', () => {
1433
- const def = createCrtrBashToolDefinition({ command: 'crtr memory find "batch c fixtures"' });
1434
- const collapsed = def
1435
- .renderResult({ content: [{ type: 'text', text: MEMORY_FIND_OUTPUT }] }, { expanded: false }, theme, {})
1436
- .render(140)
1437
- .join('\n');
1438
- assert.match(collapsed, /2 hits for "batch c fixtures"/);
1439
- assert.match(collapsed, /<muted>ai\/x, taste\/y<\/muted>/);
1440
- assert.doesNotMatch(collapsed, /\| name \|/);
1441
- const expanded = def
1442
- .renderResult({ content: [{ type: 'text', text: MEMORY_FIND_OUTPUT }] }, { expanded: true }, theme, {})
1443
- .render(140)
1444
- .join('\n');
1445
- assert.match(expanded, /\| name \| kind \| scope \|/);
1446
- assert.doesNotMatch(expanded, /\| path \|/);
1447
- const empty = def
1448
- .renderResult({ content: [{ type: 'text', text: MEMORY_FIND_EMPTY_OUTPUT }] }, { expanded: false }, theme, {})
1449
- .render(140)
1450
- .join('\n');
1451
- assert.match(empty, /<muted>0 hits for "nonexistent thing"<\/muted>/);
1452
- const grepDef = createCrtrBashToolDefinition({ command: 'crtr memory find TODO --grep' });
1453
- const grepCollapsed = grepDef
1454
- .renderResult({ content: [{ type: 'text', text: MEMORY_FIND_GREP_OUTPUT }] }, { expanded: false }, theme, {})
1455
- .render(140)
1456
- .join('\n');
1457
- assert.doesNotMatch(grepCollapsed, /hits for "TODO"/);
1458
- assert.match(grepCollapsed, /TODO: fix this/);
1459
- });
1460
- const MEMORY_WRITE_CREATED_OUTPUT = [
1461
- '- name: taste/batch-c-fixtures',
1462
- '- kind: knowledge',
1463
- '- scope: node',
1464
- '- created: true',
1465
- '',
1466
- '**path:** /Users/silas/Code/cli/crouter/.crouter/memory/taste/batch-c-fixtures.md',
1467
- '',
1468
- '**follow_up:** Written. Read it back with `crtr memory read taste/batch-c-fixtures`.',
1469
- '',
1470
- '4 frontmatter:',
1471
- '',
1472
- '| key | value |',
1473
- '| --- | --- |',
1474
- '| kind | knowledge |',
1475
- '| when-and-why-to-read | When changing batch C fixtures, read this because the stable contract prevents rework. |',
1476
- '| system-prompt-visibility | preview |',
1477
- '| file-read-visibility | none |',
1478
- ].join('\n');
1479
- const MEMORY_WRITE_UPDATED_OUTPUT = [
1480
- '- name: taste/batch-c-fixtures',
1481
- '- kind: knowledge',
1482
- '- scope: node',
1483
- '- created: false',
1484
- '',
1485
- '**path:** /Users/silas/Code/cli/crouter/.crouter/memory/taste/batch-c-fixtures.md',
1486
- '',
1487
- '**follow_up:** Updated. Read it back with `crtr memory read taste/batch-c-fixtures`.',
1488
- '',
1489
- '1 frontmatter:',
1490
- '',
1491
- '| key | value |',
1492
- '| --- | --- |',
1493
- '| kind | knowledge |',
1494
- ].join('\n');
1495
- test('a crtr memory write card shows selected frontmatter as aligned key/value pairs', () => {
1496
- const def = createCrtrBashToolDefinition({ command: 'crtr memory write taste/batch-c-fixtures \\\n --kind knowledge \\\n --system-prompt-visibility preview' });
1497
- const collapsed = def
1498
- .renderResult({ content: [{ type: 'text', text: MEMORY_WRITE_CREATED_OUTPUT }] }, { expanded: false }, theme, {})
1499
- .render(140)
1500
- .join('\n');
1501
- assert.match(collapsed, /<success>wrote taste\/batch-c-fixtures<\/success>/);
1502
- assert.match(collapsed, /kind\s*: knowledge/);
1503
- assert.match(collapsed, /when-and-why-to-read\s*: When changing batch C fixtures/);
1504
- assert.match(collapsed, /system-prompt-visibility\s*: preview/);
1505
- assert.match(collapsed, /file-read-visibility\s*: none/);
1506
- assert.doesNotMatch(collapsed, /\/Users\/silas/);
1507
- assert.doesNotMatch(collapsed, /- created:/);
1508
- const expanded = def
1509
- .renderResult({ content: [{ type: 'text', text: MEMORY_WRITE_CREATED_OUTPUT }] }, { expanded: true }, theme, {})
1510
- .render(140)
1511
- .join('\n');
1512
- assert.match(expanded, /created: true/);
1513
- assert.match(expanded, /4 frontmatter:/);
1514
- const updated = def
1515
- .renderResult({ content: [{ type: 'text', text: MEMORY_WRITE_UPDATED_OUTPUT }] }, { expanded: false }, theme, {})
1516
- .render(140)
1517
- .join('\n');
1518
- assert.match(updated, /<success>updated taste\/batch-c-fixtures<\/success>/);
1519
- assert.match(updated, /kind\s*: knowledge/);
1520
- });
1521
- test('configuration mutations share an outcome-first aligned key/value preview', () => {
1522
- const cases = [
1523
- {
1524
- command: 'crtr node config --node n1 --model strong --mode orchestrator',
1525
- output: 'Configured n1 — model=anthropic/claude-opus-4-6, mode=orchestrator',
1526
- expected: [/configured n1/, /model\s*: anthropic\/claude-opus-4-6/, /mode\s*: orchestrator/],
1527
- },
1528
- {
1529
- command: 'crtr sys config set auto_update.crtr --value apply',
1530
- output: '- key: auto_update.crtr\n- value: apply\n- scope: user',
1531
- expected: [/updated config/, /auto_update\.crtr\s*: apply/, /scope\s*: user/],
1532
- },
1533
- {
1534
- command: 'crtr canvas config add prod --preview https://prod.example --relay-token -',
1535
- output: "Registered remote canvas target 'prod' → https://prod.example (token stored).",
1536
- expected: [/registered prod/, /preview\s*: https:\/\/prod\.example/, /relay-token\s*: stored/],
1537
- },
1538
- {
1539
- command: 'crtr canvas config remove old',
1540
- output: "Removed remote canvas target 'old'.",
1541
- expected: [/removed old/],
1542
- },
1543
- {
1544
- command: 'crtr canvas use prod',
1545
- output: 'Active canvas: prod.',
1546
- expected: [/active canvas: prod/],
1547
- },
1548
- ];
1549
- for (const item of cases) {
1550
- const def = createCrtrBashToolDefinition({ command: item.command });
1551
- const collapsed = def
1552
- .renderResult({ content: [{ type: 'text', text: item.output }] }, { expanded: false }, theme, {})
1553
- .render(140)
1554
- .join('\n');
1555
- for (const expected of item.expected)
1556
- assert.match(collapsed, expected);
1557
- }
54
+ const component = def.renderResult({ content: [{ type: 'text', text: 'dashboard' }] }, { expanded: false }, theme, { invalidate: () => { ownerInvalidations++; } });
55
+ component.invalidate?.();
56
+ assert.equal(ownerInvalidations, 0);
1558
57
  });
1559
- test('node and package mutations share compact outcome-first receipts', () => {
1560
- const cases = [
1561
- ['crtr node subscription add pub --subscriber sub --passive', 'Subscribed sub → pub (passive).', [/subscribed/, /subscriber\s*: sub/, /publisher\s*: pub/, /mode\s*: passive/]],
1562
- ['crtr node subscription remove pub --subscriber sub', 'Unsubscribed sub from pub.', [/unsubscribed/, /subscriber\s*: sub/, /publisher\s*: pub/]],
1563
- ['crtr node promote --kind plan --resident', '- node_id: n1\n- kind: plan\n- mode: orchestrator\n- lifecycle: resident\n- roadmap_written: true\n\n**roadmap_path:** /tmp/roadmap.md\n\n**goal_path:** /tmp/goal.md', [/promoted n1/, /kind\s*: plan/, /lifecycle\s*: resident/, /roadmap\s*: seeded/]],
1564
- ['crtr node lifecycle demote --node n1 --detach', 'Demoted n1 — lifecycle now terminal (in place), viewer closed (broker still running off-screen).', [/demoted n1/, /lifecycle\s*: terminal/, /viewer\s*: detached/]],
1565
- ['crtr node lifecycle recycle --node n1', 'Recycled the pane — finished n1.\n- finalized: true\n- delivered: 2\n- new root: n2', [/recycled n1/, /finalized\s*: true/, /delivered\s*: 2/, /new root\s*: n2/]],
1566
- ['crtr pkg plugin enable ai', '- name: ai\n- scope: user\n- enabled: true', [/enabled ai/, /scope\s*: user/]],
1567
- ['crtr pkg plugin disable ai', '- name: ai\n- scope: project\n- enabled: false', [/disabled ai/, /scope\s*: project/]],
1568
- ['crtr pkg plugin remove ai', '- removed: true\n\n1 scopes:\n- user', [/removed ai/, /scopes\s*: user/]],
1569
- ['crtr pkg market add https://example.test/m.git', '- name: official\n- scope: user\n\n/Users/silas/.crouter/marketplaces/official', [/added marketplace official/, /scope\s*: user/]],
1570
- ['crtr pkg market remove official', '- name: official\n- removed: true', [/removed marketplace official/]],
1571
- ['crtr pkg market update --name official', '1 updated:\n\n| name | updated | sha |\n| --- | --- | --- |\n| official | true | abcdef123 |', [/updated official/, /abcdef1/]],
1572
- ];
1573
- for (const [command, output, expected] of cases) {
1574
- const def = createCrtrBashToolDefinition({ command });
1575
- const collapsed = def.renderResult({ content: [{ type: 'text', text: output }] }, { expanded: false }, theme, {}).render(140).join('\n');
1576
- for (const pattern of expected)
1577
- assert.match(collapsed, pattern, command);
1578
- assert.match(collapsed, /ctrl\+o to expand/, command);
58
+ // Pane-overflow invariant: a rendered line wider than the pane breaks the
59
+ // viewer's frame-length guarantee, expanded or collapsed.
60
+ test('crtr output clamps long lines to the terminal width, expanded and collapsed', () => {
61
+ for (const expanded of [true, false]) {
62
+ const lines = renderResult('crtr canvas dashboard', 'x'.repeat(442), expanded);
63
+ assert.ok(lines.length > 0);
64
+ for (const line of lines) {
65
+ assert.ok(visibleWidth(line) <= 70, `line exceeded width (expanded=${expanded}): ${visibleWidth(line)} > 70`);
66
+ }
1579
67
  }
1580
68
  });
1581
- const PKG_PLUGIN_INSTALL_OUTPUT = [
1582
- '- name: ai',
1583
- '- scope: user',
1584
- '- transport: exec',
1585
- '- commands: {"mounts":["app"],"issues":[]}',
1586
- '',
1587
- '/Users/silas/.crouter/plugins/ai',
1588
- ].join('\n');
1589
- const PKG_PLUGIN_INSTALL_SHORT_PATH_OUTPUT = ['- name: ai', '- scope: user', '- transport: http', '- path: /a'].join('\n');
1590
- test('a crtr pkg plugin install card collapses to an installed status + scope/path digest, not the raw fields', () => {
1591
- const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin install ai' });
1592
- const collapsed = def
1593
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_INSTALL_OUTPUT }] }, { expanded: false }, theme, {})
1594
- .render(140)
1595
- .join('\n');
1596
- assert.match(collapsed, /<success>installed ai · exec<\/success>/);
1597
- assert.match(collapsed, /<muted>user · \/Users\/silas\/\.crouter\/plugins\/ai<\/muted>/);
1598
- assert.doesNotMatch(collapsed, /- name:/);
1599
- const expanded = def
1600
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_INSTALL_OUTPUT }] }, { expanded: true }, theme, {})
1601
- .render(140)
1602
- .join('\n');
1603
- assert.match(expanded, /\/Users\/silas\/\.crouter\/plugins\/ai/);
1604
- const shortPath = def
1605
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_INSTALL_SHORT_PATH_OUTPUT }] }, { expanded: false }, theme, {})
1606
- .render(140)
1607
- .join('\n');
1608
- assert.match(shortPath, /<success>installed ai · http<\/success>/);
1609
- assert.match(shortPath, /<muted>user · \/a<\/muted>/);
1610
- });
1611
- const PKG_PLUGIN_UPDATE_UPDATED_OUTPUT = ['1 updated:', '', '| name | transport | updated | sha |', '| --- | --- | --- | --- |', '| ai | http | true | |'].join('\n');
1612
- const PKG_PLUGIN_UPDATE_CURRENT_OUTPUT = ['1 updated:', '', '| name | transport | updated | sha |', '| --- | --- | --- | --- |', '| ai | exec | false | abc1234 |'].join('\n');
1613
- const PKG_PLUGIN_UPDATE_MULTI_OUTPUT = [
1614
- '3 updated:',
1615
- '',
1616
- '| name | transport | updated | sha |',
1617
- '| --- | --- | --- | --- |',
1618
- '| ai | exec | true | aaa1111 |',
1619
- '| beta | exec | false | bbb2222 |',
1620
- '| gamma | http | true | |',
1621
- ].join('\n');
1622
- test('a crtr pkg plugin update card collapses to an updated/current transport-aware status + sha, not the raw table', () => {
1623
- const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin update --name ai' });
1624
- const collapsed = def
1625
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_UPDATED_OUTPUT }] }, { expanded: false }, theme, {})
1626
- .render(140)
1627
- .join('\n');
1628
- assert.match(collapsed, /<success>refreshed ai<\/success>/);
1629
- assert.match(collapsed, /<muted>http<\/muted>/);
1630
- assert.doesNotMatch(collapsed, /\| name \| transport \| updated \|/);
1631
- const expanded = def
1632
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_UPDATED_OUTPUT }] }, { expanded: true }, theme, {})
1633
- .render(140)
1634
- .join('\n');
1635
- assert.match(expanded, /\| name \| transport \| updated \| sha \|/);
1636
- const current = def
1637
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_CURRENT_OUTPUT }] }, { expanded: false }, theme, {})
1638
- .render(140)
1639
- .join('\n');
1640
- assert.match(current, /<muted>ai already current<\/muted>/);
1641
- assert.match(current, /<muted>abc1234<\/muted>/);
1642
- const multiDef = createCrtrBashToolDefinition({ command: 'crtr pkg plugin update' });
1643
- const multi = multiDef
1644
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_MULTI_OUTPUT }] }, { expanded: false }, theme, {})
1645
- .render(140)
1646
- .join('\n');
1647
- assert.match(multi, /2 updated · 1 already current · 1 HTTP/);
1648
- const empty = multiDef
1649
- .renderResult({ content: [{ type: 'text', text: '0 updated.' }] }, { expanded: false }, theme, {})
1650
- .render(140)
1651
- .join('\n');
1652
- assert.match(empty, /<muted>nothing to update<\/muted>/);
1653
- // REQUIREMENT: a malformed `updated` cell (neither `true` nor `false`) in
1654
- // a multi-row table must not be silently counted as "already current" —
1655
- // the multi-row loop must reject exactly like the single-row path does.
1656
- const malformedUpdate = [
1657
- '3 updated:',
1658
- '',
1659
- '| name | transport | updated | sha |',
1660
- '| --- | --- | --- | --- |',
1661
- '| ai | exec | true | aaa1111 |',
1662
- '| beta | exec | maybe | bbb2222 |',
1663
- '| gamma | http | false | |',
1664
- ].join('\n');
1665
- const malformed = multiDef
1666
- .renderResult({ content: [{ type: 'text', text: malformedUpdate }] }, { expanded: false }, theme, {})
1667
- .render(140)
1668
- .join('\n');
1669
- assert.doesNotMatch(malformed, /already current/);
1670
- });
1671
- const PKG_PLUGIN_LIST_OUTPUT = [
1672
- '- total: 3',
1673
- '',
1674
- '3 items:',
1675
- '',
1676
- '| name | scope | version | enabled | transport | source_marketplace | path |',
1677
- '| --- | --- | --- | --- | --- | --- | --- |',
1678
- '| ai | user | 0.7.1 | true | exec | crouter-official-marketplace | /Users/silas/.crouter/plugins/ai |',
1679
- '| beta | user | 1.0.0 | false | | crouter-official-marketplace | /Users/silas/.crouter/plugins/beta |',
1680
- '| gamma | project | 2.3.0 | true | http | custom-market | /Users/silas/Code/cli/crouter/.crouter/plugins/gamma |',
1681
- ].join('\n');
1682
- test('a crtr pkg plugin list card collapses to a plugin/enabled count + name digest, not the raw table', () => {
1683
- const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin list' });
1684
- const collapsed = def
1685
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_LIST_OUTPUT }] }, { expanded: false }, theme, {})
1686
- .render(140)
1687
- .join('\n');
1688
- assert.match(collapsed, /3 plugins · 2 enabled/);
1689
- assert.match(collapsed, /<muted>ai \(exec\), beta, gamma \(http\)<\/muted>/);
1690
- assert.doesNotMatch(collapsed, /\| name \| scope \|/);
1691
- const expanded = def
1692
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_LIST_OUTPUT }] }, { expanded: true }, theme, {})
1693
- .render(140)
1694
- .join('\n');
1695
- assert.match(expanded, /\| name \| scope \| version \| enabled \| transport \|/);
1696
- const empty = def
1697
- .renderResult({ content: [{ type: 'text', text: '- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
1698
- .render(140)
1699
- .join('\n');
1700
- assert.match(empty, /<muted>0 plugins<\/muted>/);
1701
- // REQUIREMENT: nonzero total with an empty page (exhausted pagination)
1702
- // must NOT collapse to "0 plugins" — fall back to the generic renderer.
1703
- const exhausted = def
1704
- .renderResult({ content: [{ type: 'text', text: '- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
1705
- .render(140)
1706
- .join('\n');
1707
- assert.doesNotMatch(exhausted, /0 plugins/);
1708
- });
1709
- const PKG_PLUGIN_SHOW_OUTPUT = [
1710
- '- name: ai',
1711
- '- scope: user',
1712
- '- path: /Users/silas/.crouter/plugins/ai',
1713
- '- enabled: true',
1714
- '- manifest: {"name":"ai","version":"0.7.1","description":"AI helpers","commands":"commands.json","transport":{"kind":"exec","executable":"bin/ai.js"}}',
1715
- '- commands: {"manifestPath":"/Users/silas/.crouter/plugins/ai/commands.json","mounts":["ai"],"issues":[]}',
1716
- '',
1717
- '2 docs:',
1718
- '',
1719
- '| name | path |',
1720
- '| --- | --- |',
1721
- '| ai/x | /Users/silas/.crouter/plugins/ai/memory/ai/x.md |',
1722
- '| ai/y | /Users/silas/.crouter/plugins/ai/memory/ai/y.md |',
1723
- ].join('\n');
1724
- const PKG_PLUGIN_SHOW_DISABLED_OUTPUT = [
1725
- '- name: beta',
1726
- '- scope: user',
1727
- '- path: /Users/silas/.crouter/plugins/beta',
1728
- '- enabled: false',
1729
- '- manifest: {"name":"beta","version":"1.0.0","description":"Beta helpers"}',
1730
- '',
1731
- '0 docs.',
1732
- ].join('\n');
1733
- test('a crtr pkg plugin show card collapses to a name@version + enabled status + doc count, not the raw fields', () => {
1734
- const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin show ai' });
1735
- const collapsed = def
1736
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_SHOW_OUTPUT }] }, { expanded: false }, theme, {})
1737
- .render(140)
1738
- .join('\n');
1739
- assert.match(collapsed, /<success>ai@0\.7\.1 · exec · enabled<\/success>/);
1740
- assert.match(collapsed, /<muted>2 substrate docs<\/muted>/);
1741
- assert.doesNotMatch(collapsed, /\| name \| path \|/);
1742
- const expanded = def
1743
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_SHOW_OUTPUT }] }, { expanded: true }, theme, {})
1744
- .render(140)
1745
- .join('\n');
1746
- assert.match(expanded, /\| name \| path \|/);
1747
- const disabledDef = createCrtrBashToolDefinition({ command: 'crtr pkg plugin show beta' });
1748
- const disabled = disabledDef
1749
- .renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_SHOW_DISABLED_OUTPUT }] }, { expanded: false }, theme, {})
1750
- .render(140)
1751
- .join('\n');
1752
- assert.match(disabled, /<muted>beta@1\.0\.0 · disabled<\/muted>/);
1753
- assert.match(disabled, /<muted>0 substrate docs<\/muted>/);
1754
- });
1755
- const PKG_MARKET_LIST_OUTPUT = [
1756
- '- total: 2',
1757
- '',
1758
- '2 items:',
1759
- '',
1760
- '| name | scope | url | ref | path |',
1761
- '| --- | --- | --- | --- | --- |',
1762
- '| crouter-official-marketplace | user | https://github.com/crouton-labs/crouter-official-marketplace | main | /Users/silas/.crouter/marketplaces/crouter-official-marketplace |',
1763
- '| custom-market | project | https://github.com/example/custom-market | main | /Users/silas/Code/cli/crouter/.crouter/marketplaces/custom-market |',
1764
- ].join('\n');
1765
- test('a crtr pkg market list card collapses to a marketplace count + name digest, not the raw table', () => {
1766
- const def = createCrtrBashToolDefinition({ command: 'crtr pkg market list' });
1767
- const collapsed = def
1768
- .renderResult({ content: [{ type: 'text', text: PKG_MARKET_LIST_OUTPUT }] }, { expanded: false }, theme, {})
1769
- .render(140)
1770
- .join('\n');
1771
- assert.match(collapsed, /2 marketplaces/);
1772
- assert.match(collapsed, /<muted>crouter-official-marketplace, custom-market<\/muted>/);
1773
- assert.doesNotMatch(collapsed, /\| name \| scope \|/);
1774
- const expanded = def
1775
- .renderResult({ content: [{ type: 'text', text: PKG_MARKET_LIST_OUTPUT }] }, { expanded: true }, theme, {})
1776
- .render(140)
1777
- .join('\n');
1778
- assert.match(expanded, /\| name \| scope \| url \|/);
1779
- const empty = def
1780
- .renderResult({ content: [{ type: 'text', text: '- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
1781
- .render(140)
1782
- .join('\n');
1783
- assert.match(empty, /<muted>0 marketplaces<\/muted>/);
1784
- // REQUIREMENT: nonzero total with an empty page (exhausted pagination)
1785
- // must NOT collapse to "0 marketplaces" — fall back to the generic renderer.
1786
- const exhausted = def
1787
- .renderResult({ content: [{ type: 'text', text: '- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
1788
- .render(140)
1789
- .join('\n');
1790
- assert.doesNotMatch(exhausted, /0 marketplaces/);
1791
- });
1792
- const PKG_MARKET_BROWSE_ROWS = Array.from({ length: 11 }, (_, i) => {
1793
- const n = i + 1;
1794
- return `| p${n} | main | github | 0.${n}.0 | Plugin ${n} | tag${n} | false | |`;
1795
- });
1796
- const PKG_MARKET_BROWSE_OUTPUT = [
1797
- '- marketplace: crouter-official-marketplace',
1798
- '- total: 11',
1799
- '',
1800
- '11 items:',
1801
- '',
1802
- '| name | ref | source | version | description | keywords | installed | installed_scope |',
1803
- '| --- | --- | --- | --- | --- | --- | --- | --- |',
1804
- ...PKG_MARKET_BROWSE_ROWS,
1805
- ].join('\n');
1806
- test('a crtr pkg market browse card collapses to a plugin count in <market> + capped name digest, not the raw table', () => {
1807
- const def = createCrtrBashToolDefinition({ command: 'crtr pkg market browse crouter-official-marketplace' });
1808
- const collapsed = def
1809
- .renderResult({ content: [{ type: 'text', text: PKG_MARKET_BROWSE_OUTPUT }] }, { expanded: false }, theme, {})
1810
- .render(140)
1811
- .join('\n');
1812
- assert.match(collapsed, /11 plugins in crouter-official-marketplace/);
1813
- assert.match(collapsed, /<muted>p1, p2, p3, p4, p5, p6, p7, p8, … 3 more<\/muted>/);
1814
- assert.doesNotMatch(collapsed, /\| name \| ref \|/);
1815
- const expanded = def
1816
- .renderResult({ content: [{ type: 'text', text: PKG_MARKET_BROWSE_OUTPUT }] }, { expanded: true }, theme, {})
1817
- .render(140)
1818
- .join('\n');
1819
- assert.match(expanded, /\| name \| ref \| source \|/);
1820
- const empty = def
1821
- .renderResult({ content: [{ type: 'text', text: '- marketplace: crouter-official-marketplace\n- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
1822
- .render(140)
1823
- .join('\n');
1824
- assert.match(empty, /<muted>0 plugins in crouter-official-marketplace<\/muted>/);
1825
- // REQUIREMENT: nonzero total with an empty page (exhausted pagination)
1826
- // must NOT collapse to "0 plugins in <market>" — fall back to generic.
1827
- const exhausted = def
1828
- .renderResult({ content: [{ type: 'text', text: '- marketplace: crouter-official-marketplace\n- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
1829
- .render(140)
1830
- .join('\n');
1831
- assert.doesNotMatch(exhausted, /0 plugins in crouter-official-marketplace/);
1832
- });
1833
- // ---------------------------------------------------------------------------
1834
- // Batch D — search/sys/surface summarizers + sys sysprompt suppression.
1835
- // ---------------------------------------------------------------------------
1836
- test('sys sysprompt is command-owned suppressOutput, never on failure', () => {
1837
- const def = createCrtrBashToolDefinition({ command: 'crtr sys sysprompt abc' });
1838
- const component = def.renderResult({ content: [{ type: 'text', text: '- node_id: abc\n- captured_at: 2026-07-01T00:00:00.000Z\n- chars: 40000\n\n# System prompt\n...(huge prompt dump)...' }] }, { expanded: false }, theme, {});
1839
- const collapsed = component.render(80).join('\n');
1840
- assert.doesNotMatch(collapsed, /huge prompt dump/);
1841
- assert.match(collapsed, /suppressed — ctrl\+o to expand/);
1842
- const failedComponent = def.renderResult({ content: [{ type: 'text', text: 'huge dump on failure too' }] }, { expanded: false }, theme, { isError: true });
1843
- assert.match(failedComponent.render(80).join('\n'), /huge dump on failure too/);
1844
- });
1845
- const SEARCH_WEB_OUTPUT = [
1846
- '2 results for "test query":',
1847
- '',
1848
- '### 1. First Result Title',
1849
- 'https://example.com/page1 · 2024-01-15 · Jane Doe',
1850
- '- highlight one text here',
1851
- '- highlight two text here',
1852
- '',
1853
- '### 2. Second Result Title',
1854
- 'https://other.org/page2',
1855
- '- another highlight',
1856
- '',
1857
- 'Fetch full content for selected result URLs with the contents leaf after checking its schema. No good hits? Broaden the query, drop domain filters, or try --type deep.',
1858
- ].join('\n');
1859
- test('a crtr search web card collapses to a result count + title — domain digest, not the raw sections', () => {
1860
- const def = createCrtrBashToolDefinition({ command: 'crtr search web "test query"' });
1861
- const collapsed = def
1862
- .renderResult({ content: [{ type: 'text', text: SEARCH_WEB_OUTPUT }] }, { expanded: false }, theme, {})
1863
- .render(140)
1864
- .join('\n');
1865
- assert.match(collapsed, /2 results/);
1866
- assert.match(collapsed, /<muted>First Result Title — example\.com<\/muted>/);
1867
- assert.match(collapsed, /<muted>Second Result Title — other\.org<\/muted>/);
1868
- assert.doesNotMatch(collapsed, /### 1\./);
1869
- const expanded = def
1870
- .renderResult({ content: [{ type: 'text', text: SEARCH_WEB_OUTPUT }] }, { expanded: true }, theme, {})
1871
- .render(140)
1872
- .join('\n');
1873
- assert.match(expanded, /### 1\. First Result Title/);
1874
- assert.match(expanded, /### 2\. Second Result Title/);
1875
- const empty = def
1876
- .renderResult({ content: [{ type: 'text', text: 'No results for "empty query".\n\nFetch full content for selected result URLs with the contents leaf after checking its schema. No good hits? Broaden the query, drop domain filters, or try --type deep.' }] }, { expanded: false }, theme, {})
1877
- .render(140)
1878
- .join('\n');
1879
- assert.match(empty, /<muted>0 results for "empty query"<\/muted>/);
1880
- });
1881
- const SEARCH_ANSWER_OUTPUT = [
1882
- 'The answer is 42, according to the sources below.',
1883
- '',
1884
- 'Sources (2):',
1885
- '1. Deep Thought Paper — https://example.com/paper',
1886
- '2. Hitchhiker Wiki — https://wiki.example.org/guide',
1887
- '',
1888
- 'Read any cited source in full with the contents leaf after checking its schema. Want raw ranked results instead of a synthesized answer? Use the web leaf.',
1889
- ].join('\n');
1890
- test('a crtr search answer card collapses to the answer teaser + citation count, not the raw sources list', () => {
1891
- const def = createCrtrBashToolDefinition({ command: 'crtr search answer "what is the answer"' });
1892
- const collapsed = def
1893
- .renderResult({ content: [{ type: 'text', text: SEARCH_ANSWER_OUTPUT }] }, { expanded: false }, theme, {})
1894
- .render(140)
1895
- .join('\n');
1896
- assert.match(collapsed, /The answer is 42, according to the sources below\./);
1897
- assert.match(collapsed, /<muted>2 citations<\/muted>/);
1898
- assert.doesNotMatch(collapsed, /Sources \(2\):/);
1899
- const expanded = def
1900
- .renderResult({ content: [{ type: 'text', text: SEARCH_ANSWER_OUTPUT }] }, { expanded: true }, theme, {})
1901
- .render(140)
1902
- .join('\n');
1903
- assert.match(expanded, /Sources \(2\):/);
1904
- assert.match(expanded, /Deep Thought Paper/);
1905
- const noAnswer = def
1906
- .renderResult({ content: [{ type: 'text', text: 'No answer was returned.\n\nRead any cited source in full with the contents leaf after checking its schema. Want raw ranked results instead of a synthesized answer? Use the web leaf.' }] }, { expanded: false }, theme, {})
1907
- .render(140)
1908
- .join('\n');
1909
- assert.match(noAnswer, /<muted>no answer returned<\/muted>/);
1910
- });
1911
- const SEARCH_CONTENTS_OUTPUT = [
1912
- 'Extracted 2 URLs:',
1913
- '',
1914
- '### 1. Example Page',
1915
- 'https://example.com/a',
1916
- 'Some highlight text about the page contents that is reasonably long for testing chars count.',
1917
- '',
1918
- '### 2. Another Page',
1919
- 'https://example.org/b',
1920
- '- bullet one',
1921
- '- bullet two',
1922
- '',
1923
- 'Stale or empty content? Re-run with --max-age-hours 0 to force a fresh crawl. Need to find more pages? Use the web leaf after checking its schema.',
1924
- ].join('\n');
1925
- test('a crtr search contents card collapses to a per-URL title + char-count digest, not the raw sections', () => {
1926
- const def = createCrtrBashToolDefinition({ command: 'crtr search contents https://example.com/a,https://example.org/b' });
1927
- const collapsed = def
1928
- .renderResult({ content: [{ type: 'text', text: SEARCH_CONTENTS_OUTPUT }] }, { expanded: false }, theme, {})
1929
- .render(140)
1930
- .join('\n');
1931
- assert.match(collapsed, /extracted 2 URLs/);
1932
- assert.match(collapsed, /<muted>Example Page · \d+ chars<\/muted>/);
1933
- assert.match(collapsed, /<muted>Another Page · \d+ chars<\/muted>/);
1934
- assert.doesNotMatch(collapsed, /### 1\./);
1935
- const expanded = def
1936
- .renderResult({ content: [{ type: 'text', text: SEARCH_CONTENTS_OUTPUT }] }, { expanded: true }, theme, {})
1937
- .render(140)
1938
- .join('\n');
1939
- assert.match(expanded, /### 1\. Example Page/);
1940
- const failed = def
1941
- .renderResult({
1942
- content: [{
1943
- type: 'text',
1944
- text: [
1945
- 'No content extracted.',
1946
- 'Failed (2):',
1947
- '- https://dead.example.com — 404 Not Found',
1948
- '- https://slow.example.com — timeout',
1949
- '',
1950
- 'Stale or empty content? Re-run with --max-age-hours 0 to force a fresh crawl. Need to find more pages? Use the web leaf after checking its schema.',
1951
- ].join('\n'),
1952
- }],
1953
- }, { expanded: false }, theme, {})
1954
- .render(140)
1955
- .join('\n');
1956
- assert.match(failed, /<error>0 extracted · 2 failed<\/error>/);
1957
- const nothingAtAll = def
1958
- .renderResult({ content: [{ type: 'text', text: 'No content extracted.\n\nStale or empty content? Re-run with --max-age-hours 0 to force a fresh crawl. Need to find more pages? Use the web leaf after checking its schema.' }] }, { expanded: false }, theme, {})
1959
- .render(140)
1960
- .join('\n');
1961
- assert.match(nothingAtAll, /<muted>no content extracted<\/muted>/);
1962
- });
1963
- const SYS_SETUP_OUTPUT = [
1964
- 'crtr sys setup (non-interactive)',
1965
- '',
1966
- 'Packages:',
1967
- '- pi-crtr-extensions — crtr companion extensions. (installed)',
1968
- '- pi-claude-oauth-adapter — OAuth adapter. (not installed)',
1969
- '',
1970
- 'System deps:',
1971
- '- tmux: ✓ installed',
1972
- '- nvim: brew install neovim',
1973
- '',
1974
- '- Exa key: write to /Users/example/.crouter/exa.key or set EXA_API_KEY',
1975
- 'Run `crtr sys setup` in a TTY to open the wizard.',
1976
- ].join('\n');
1977
- test('a crtr sys setup card collapses to an ok/pending step digest, not the raw sections', () => {
1978
- const def = createCrtrBashToolDefinition({ command: 'crtr sys setup' });
1979
- const collapsed = def
1980
- .renderResult({ content: [{ type: 'text', text: SYS_SETUP_OUTPUT }] }, { expanded: false }, theme, {})
1981
- .render(140)
1982
- .join('\n');
1983
- assert.match(collapsed, /<warning>2 ok · 2 pending<\/warning>/);
1984
- assert.match(collapsed, /<muted>pi-claude-oauth-adapter, nvim<\/muted>/);
1985
- assert.doesNotMatch(collapsed, /Packages:/);
1986
- const expanded = def
1987
- .renderResult({ content: [{ type: 'text', text: SYS_SETUP_OUTPUT }] }, { expanded: true }, theme, {})
1988
- .render(140)
1989
- .join('\n');
1990
- assert.match(expanded, /Packages:/);
1991
- assert.match(expanded, /System deps:/);
1992
- const allOk = def
1993
- .renderResult({ content: [{ type: 'text', text: SYS_SETUP_OUTPUT.replace('(not installed)', '(installed)').replace('brew install neovim', '✓ installed') }] }, { expanded: false }, theme, {})
1994
- .render(140)
1995
- .join('\n');
1996
- assert.match(allOk, /<success>4 steps ok<\/success>/);
1997
- });
1998
- const SYS_DOCTOR_PASS_OUTPUT = [
1999
- '- ok: true',
2000
- '- failed: 0',
2001
- '',
2002
- '2 checks:',
2003
- '',
2004
- '| scope | name | status | message | remediation |',
2005
- '| --- | --- | --- | --- | --- |',
2006
- '| user | plugin:ai:dir | pass | directory exists | |',
2007
- '| user | plugin:ai:manifest | pass | manifest valid | |',
2008
- ].join('\n');
2009
- const SYS_DOCTOR_FAIL_OUTPUT = [
2010
- '- ok: false',
2011
- '- failed: 1',
2012
- '',
2013
- '2 checks:',
2014
- '',
2015
- '| scope | name | status | message | remediation |',
2016
- '| --- | --- | --- | --- | --- |',
2017
- '| user | plugin:ai:dir | pass | directory exists | |',
2018
- '| user | plugin:beta:dir | fail | directory missing: /path/to/beta | |',
2019
- ].join('\n');
2020
- test('a crtr sys doctor card collapses to PASS or an issue digest, not the raw checks table', () => {
2021
- const def = createCrtrBashToolDefinition({ command: 'crtr sys doctor' });
2022
- const pass = def
2023
- .renderResult({ content: [{ type: 'text', text: SYS_DOCTOR_PASS_OUTPUT }] }, { expanded: false }, theme, {})
2024
- .render(140)
2025
- .join('\n');
2026
- assert.match(pass, /<success>PASS · 2 checks<\/success>/);
2027
- assert.doesNotMatch(pass, /\| scope \| name \|/);
2028
- const fail = def
2029
- .renderResult({ content: [{ type: 'text', text: SYS_DOCTOR_FAIL_OUTPUT }] }, { expanded: false }, theme, {})
2030
- .render(140)
2031
- .join('\n');
2032
- assert.match(fail, /<error>1 issue<\/error>/);
2033
- assert.match(fail, /<warning>plugin:beta:dir: directory missing: \/path\/to\/beta<\/warning>/);
2034
- const expanded = def
2035
- .renderResult({ content: [{ type: 'text', text: SYS_DOCTOR_FAIL_OUTPUT }] }, { expanded: true }, theme, {})
2036
- .render(140)
2037
- .join('\n');
2038
- assert.match(expanded, /\| scope \| name \| status \|/);
2039
- });
2040
- const SYS_UPDATE_CHECK_OUTPUT = [
2041
- '- up_to_date: false',
2042
- '',
2043
- '2 updates:',
2044
- '',
2045
- '| name | kind | current | latest | up_to_date | unreachable |',
2046
- '| --- | --- | --- | --- | --- | --- |',
2047
- '| @north-light/crouter | self | 0.3.34 | 0.3.35 | false | false |',
2048
- '| some-marketplace | marketplace | abc123 | abc123 | true | false |',
2049
- ].join('\n');
2050
- test('a crtr sys update --check card collapses to an outdated/checked digest, not the raw table', () => {
2051
- const def = createCrtrBashToolDefinition({ command: 'crtr sys update --check' });
2052
- const collapsed = def
2053
- .renderResult({ content: [{ type: 'text', text: SYS_UPDATE_CHECK_OUTPUT }] }, { expanded: false }, theme, {})
2054
- .render(140)
2055
- .join('\n');
2056
- assert.match(collapsed, /<warning>1 outdated · 2 checked<\/warning>/);
2057
- assert.match(collapsed, /<muted>@north-light\/crouter: 0\.3\.34 → 0\.3\.35<\/muted>/);
2058
- assert.doesNotMatch(collapsed, /\| name \| kind \|/);
2059
- const expanded = def
2060
- .renderResult({ content: [{ type: 'text', text: SYS_UPDATE_CHECK_OUTPUT }] }, { expanded: true }, theme, {})
2061
- .render(140)
2062
- .join('\n');
2063
- assert.match(expanded, /\| name \| kind \| current \|/);
2064
- const upToDate = def
2065
- .renderResult({ content: [{ type: 'text', text: '- up_to_date: true\n\n1 updates:\n\n| name | kind | current | latest | up_to_date | unreachable |\n| --- | --- | --- | --- | --- | --- |\n| a | self | 1.0.0 | 1.0.0 | true | false |' }] }, { expanded: false }, theme, {})
2066
- .render(140)
2067
- .join('\n');
2068
- assert.match(upToDate, /<success>PASS · 1 up to date<\/success>/);
2069
- const applyDone = def
2070
- .renderResult({ content: [{ type: 'text', text: '- target: all\n- status: done' }] }, { expanded: false }, theme, {})
2071
- .render(140)
2072
- .join('\n');
2073
- assert.match(applyDone, /<success>updated · all<\/success>/);
2074
- const applyFailed = def
2075
- .renderResult({ content: [{ type: 'text', text: '- target: self\n- status: failed\n- error: network unreachable' }] }, { expanded: false }, theme, {})
2076
- .render(140)
2077
- .join('\n');
2078
- assert.match(applyFailed, /<error>update failed · self<\/error>/);
2079
- assert.match(applyFailed, /<muted>network unreachable<\/muted>/);
2080
- });
2081
- test('a crtr sys feedback card collapses to a filed/saved-locally pointer, not the raw fields', () => {
2082
- const def = createCrtrBashToolDefinition({ command: 'crtr sys feedback "crtr crashed"' });
2083
- const filed = def
2084
- .renderResult({ content: [{ type: 'text', text: '- status: filed\n- url: https://github.com/crouton-labs/crouter/issues/123' }] }, { expanded: false }, theme, {})
2085
- .render(140)
2086
- .join('\n');
2087
- assert.match(filed, /<success>issue filed<\/success>/);
2088
- assert.match(filed, /<muted>https:\/\/github\.com\/crouton-labs\/crouter\/issues\/123<\/muted>/);
2089
- const savedText = '- status: saved-locally\n- path: /Users/example/.crouter/feedback/2026-07-01T00-00-00-000Z.md\n- error: gh not available: not found';
2090
- const saved = def
2091
- .renderResult({ content: [{ type: 'text', text: savedText }] }, { expanded: false }, theme, {})
2092
- .render(140)
2093
- .join('\n');
2094
- assert.match(saved, /<warning>saved locally — not filed<\/warning>/);
2095
- assert.match(saved, /<muted>2026-07-01T00-00-00-000Z\.md<\/muted>/);
2096
- const expanded = def
2097
- .renderResult({ content: [{ type: 'text', text: savedText }] }, { expanded: true }, theme, {})
2098
- .render(140)
2099
- .join('\n');
2100
- assert.match(expanded, /gh not available: not found/);
2101
- });