@crouton-kit/crouter 0.3.27 → 0.3.29

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 (343) hide show
  1. package/dist/build-root.js +4 -2
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
  5. package/dist/builtin-memory/crouter-development/personas.md +1 -1
  6. package/dist/builtin-memory/crouter-development/plugins.md +20 -18
  7. package/dist/builtin-memory/design.md +1 -1
  8. package/dist/builtin-memory/development.md +1 -1
  9. package/dist/builtin-memory/internal/INDEX.md +1 -1
  10. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
  12. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  13. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  14. package/dist/builtin-memory/planning.md +1 -1
  15. package/dist/builtin-memory/spec.md +6 -4
  16. package/dist/builtin-personas/orchestration-kernel.md +4 -5
  17. package/dist/builtin-personas/runtime-base.md +8 -4
  18. package/dist/builtin-personas/spec/PERSONA.md +8 -4
  19. package/dist/builtin-personas/spec/orchestrator.md +5 -3
  20. package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
  21. package/dist/builtin-views/canvas/core.mjs +586 -0
  22. package/dist/builtin-views/canvas/text.mjs +58 -0
  23. package/dist/builtin-views/canvas/tui.mjs +165 -0
  24. package/dist/builtin-views/canvas/web.jsx +120 -0
  25. package/dist/builtin-views/git-pr/core.mjs +673 -0
  26. package/dist/builtin-views/git-pr/text.mjs +84 -0
  27. package/dist/builtin-views/git-pr/tui.mjs +302 -0
  28. package/dist/builtin-views/git-pr/web.jsx +216 -0
  29. package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
  30. package/dist/builtin-views/inbox/core.mjs +1273 -0
  31. package/dist/builtin-views/inbox/text.mjs +73 -0
  32. package/dist/builtin-views/inbox/tui.mjs +312 -0
  33. package/dist/builtin-views/inbox/web.jsx +188 -0
  34. package/dist/builtin-views/linkedin/core.mjs +906 -0
  35. package/dist/builtin-views/linkedin/text.mjs +69 -0
  36. package/dist/builtin-views/linkedin/tui.mjs +427 -0
  37. package/dist/builtin-views/linkedin/web.jsx +206 -0
  38. package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
  39. package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
  40. package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
  41. package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
  42. package/dist/cli.js +3 -3
  43. package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
  44. package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
  45. package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
  46. package/dist/clients/attach/__tests__/git-info.test.js +31 -0
  47. package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
  48. package/dist/clients/attach/__tests__/reconnect-giveup.test.js +30 -0
  49. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
  50. package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
  51. package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
  52. package/dist/clients/attach/attach-cmd.js +445 -41
  53. package/dist/clients/attach/auth-pickers.d.ts +9 -0
  54. package/dist/clients/attach/auth-pickers.js +194 -0
  55. package/dist/clients/attach/canvas-panels.d.ts +12 -0
  56. package/dist/clients/attach/canvas-panels.js +78 -0
  57. package/dist/clients/attach/chat-view.d.ts +27 -2
  58. package/dist/clients/attach/chat-view.js +98 -9
  59. package/dist/clients/attach/clipboard-image.d.ts +47 -8
  60. package/dist/clients/attach/clipboard-image.js +150 -26
  61. package/dist/clients/attach/clipboard-text.d.ts +3 -0
  62. package/dist/clients/attach/clipboard-text.js +39 -0
  63. package/dist/clients/attach/config-load.d.ts +54 -7
  64. package/dist/clients/attach/config-load.js +99 -3
  65. package/dist/clients/attach/context-message.d.ts +12 -0
  66. package/dist/clients/attach/context-message.js +72 -0
  67. package/dist/clients/attach/git-info.d.ts +20 -0
  68. package/dist/clients/attach/git-info.js +44 -0
  69. package/dist/clients/attach/graph-overlay.d.ts +43 -0
  70. package/dist/clients/attach/graph-overlay.js +306 -0
  71. package/dist/clients/attach/input-controller.d.ts +81 -13
  72. package/dist/clients/attach/input-controller.js +307 -67
  73. package/dist/clients/attach/pickers.d.ts +58 -0
  74. package/dist/clients/attach/pickers.js +171 -0
  75. package/dist/clients/attach/slash-commands.d.ts +44 -1
  76. package/dist/clients/attach/slash-commands.js +250 -27
  77. package/dist/clients/attach/titled-editor.d.ts +33 -0
  78. package/dist/clients/attach/titled-editor.js +84 -0
  79. package/dist/clients/attach/view-socket.d.ts +37 -2
  80. package/dist/clients/attach/view-socket.js +131 -7
  81. package/dist/clients/web/dev-server.d.ts +7 -0
  82. package/dist/clients/web/dev-server.js +59 -0
  83. package/dist/clients/web/events.d.ts +14 -0
  84. package/dist/clients/web/events.js +151 -0
  85. package/dist/clients/web/server.d.ts +18 -0
  86. package/dist/clients/web/server.js +450 -0
  87. package/dist/clients/web/web-cmd.d.ts +2 -0
  88. package/dist/clients/web/web-cmd.js +120 -0
  89. package/dist/commands/canvas.js +1 -2
  90. package/dist/commands/chord.js +1 -1
  91. package/dist/commands/dashboard.js +5 -1
  92. package/dist/commands/human/prompts.js +3 -3
  93. package/dist/commands/human/queue.d.ts +17 -0
  94. package/dist/commands/human/queue.js +111 -4
  95. package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
  96. package/dist/commands/memory/lint.d.ts +5 -4
  97. package/dist/commands/memory/lint.js +23 -7
  98. package/dist/commands/memory/read.js +2 -2
  99. package/dist/commands/memory/shared.d.ts +7 -1
  100. package/dist/commands/memory/shared.js +18 -3
  101. package/dist/commands/memory/write.js +20 -7
  102. package/dist/commands/memory.js +4 -4
  103. package/dist/commands/node.d.ts +1 -1
  104. package/dist/commands/node.js +64 -65
  105. package/dist/commands/pkg.js +1 -2
  106. package/dist/commands/revive.js +11 -3
  107. package/dist/commands/sys/feedback.d.ts +1 -0
  108. package/dist/commands/sys/feedback.js +163 -0
  109. package/dist/commands/sys/sync.d.ts +1 -0
  110. package/dist/commands/sys/sync.js +187 -0
  111. package/dist/commands/sys.js +4 -2
  112. package/dist/commands/view-cycle.js +2 -2
  113. package/dist/commands/view-list.js +8 -8
  114. package/dist/commands/view-new.js +21 -17
  115. package/dist/commands/view-pick.js +1 -1
  116. package/dist/commands/view-run.js +35 -14
  117. package/dist/commands/view.js +8 -6
  118. package/dist/commands/workspace.d.ts +2 -0
  119. package/dist/commands/workspace.js +161 -0
  120. package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
  121. package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
  122. package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
  123. package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
  124. package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
  125. package/dist/core/__tests__/broker-snapshot-history.test.d.ts +1 -0
  126. package/dist/core/__tests__/broker-snapshot-history.test.js +105 -0
  127. package/dist/core/__tests__/child-death-wake.test.js +56 -111
  128. package/dist/core/__tests__/close.test.js +14 -20
  129. package/dist/core/__tests__/context-intro.test.js +19 -19
  130. package/dist/core/__tests__/daemon-boot.test.js +30 -23
  131. package/dist/core/__tests__/draw-style.test.js +1 -1
  132. package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
  133. package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
  134. package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -1
  135. package/dist/core/__tests__/fixtures/fake-engine.js +32 -2
  136. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  137. package/dist/core/__tests__/focuses.test.js +25 -13
  138. package/dist/core/__tests__/fork.test.js +22 -5
  139. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
  140. package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
  141. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
  142. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
  143. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
  144. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
  145. package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
  146. package/dist/core/__tests__/full/cascade-close.test.js +22 -8
  147. package/dist/core/__tests__/full/detach-focus.test.js +66 -169
  148. package/dist/core/__tests__/helpers/harness.d.ts +6 -9
  149. package/dist/core/__tests__/helpers/harness.js +19 -36
  150. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
  151. package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
  152. package/dist/core/__tests__/human-stranded-deliver.test.d.ts +1 -0
  153. package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
  154. package/dist/core/__tests__/lifecycle.test.js +1 -19
  155. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
  156. package/dist/core/__tests__/memory-resolver.test.js +1 -1
  157. package/dist/core/__tests__/on-read-dedup-resume.test.d.ts +1 -0
  158. package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
  159. package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
  160. package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
  161. package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
  162. package/dist/core/__tests__/relaunch-root.test.js +114 -0
  163. package/dist/core/__tests__/reset.test.js +19 -79
  164. package/dist/core/__tests__/steer-note.test.js +9 -7
  165. package/dist/core/__tests__/tmux-surface.test.js +13 -7
  166. package/dist/core/bootstrap.d.ts +0 -3
  167. package/dist/core/bootstrap.js +1 -143
  168. package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
  169. package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
  170. package/dist/core/canvas/browse/app.js +131 -8
  171. package/dist/core/canvas/browse/model.d.ts +23 -5
  172. package/dist/core/canvas/browse/model.js +68 -13
  173. package/dist/core/canvas/browse/render.d.ts +7 -1
  174. package/dist/core/canvas/browse/render.js +178 -47
  175. package/dist/core/canvas/canvas.js +1 -1
  176. package/dist/core/canvas/focuses.d.ts +4 -4
  177. package/dist/core/canvas/focuses.js +7 -7
  178. package/dist/core/canvas/nav-model.d.ts +162 -0
  179. package/dist/core/canvas/nav-model.js +487 -0
  180. package/dist/core/canvas/paths.d.ts +7 -0
  181. package/dist/core/canvas/paths.js +9 -0
  182. package/dist/core/canvas/render.d.ts +68 -9
  183. package/dist/core/canvas/render.js +195 -61
  184. package/dist/core/canvas/types.d.ts +16 -23
  185. package/dist/core/config.js +1 -4
  186. package/dist/core/memory-resolver.js +43 -5
  187. package/dist/core/runtime/bearings.d.ts +1 -1
  188. package/dist/core/runtime/bearings.js +8 -8
  189. package/dist/core/runtime/branded-host.d.ts +16 -0
  190. package/dist/core/runtime/branded-host.js +127 -0
  191. package/dist/core/runtime/broker-protocol.d.ts +212 -6
  192. package/dist/core/runtime/broker-sdk.d.ts +0 -12
  193. package/dist/core/runtime/broker-sdk.js +77 -6
  194. package/dist/core/runtime/broker.d.ts +5 -1
  195. package/dist/core/runtime/broker.js +520 -59
  196. package/dist/core/runtime/close.d.ts +12 -2
  197. package/dist/core/runtime/close.js +37 -16
  198. package/dist/core/runtime/front-door.js +14 -7
  199. package/dist/core/runtime/host.d.ts +14 -34
  200. package/dist/core/runtime/host.js +14 -50
  201. package/dist/core/runtime/launch.d.ts +12 -9
  202. package/dist/core/runtime/launch.js +27 -15
  203. package/dist/core/runtime/lifecycle.d.ts +1 -1
  204. package/dist/core/runtime/lifecycle.js +15 -19
  205. package/dist/core/runtime/nodes.d.ts +0 -55
  206. package/dist/core/runtime/nodes.js +6 -74
  207. package/dist/core/runtime/placement.d.ts +91 -334
  208. package/dist/core/runtime/placement.js +262 -828
  209. package/dist/core/runtime/promote.d.ts +2 -0
  210. package/dist/core/runtime/promote.js +24 -6
  211. package/dist/core/runtime/recap.d.ts +8 -0
  212. package/dist/core/runtime/recap.js +107 -0
  213. package/dist/core/runtime/recycle.js +25 -61
  214. package/dist/core/runtime/reset.d.ts +43 -37
  215. package/dist/core/runtime/reset.js +131 -218
  216. package/dist/core/runtime/revive.d.ts +9 -29
  217. package/dist/core/runtime/revive.js +45 -135
  218. package/dist/core/runtime/spawn.d.ts +15 -11
  219. package/dist/core/runtime/spawn.js +168 -136
  220. package/dist/core/runtime/surface-bg.d.ts +11 -0
  221. package/dist/core/runtime/surface-bg.js +68 -0
  222. package/dist/core/runtime/tmux.d.ts +15 -43
  223. package/dist/core/runtime/tmux.js +38 -80
  224. package/dist/core/scope.d.ts +1 -2
  225. package/dist/core/scope.js +1 -5
  226. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
  227. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
  228. package/dist/core/skill-sync/builtins.d.ts +42 -0
  229. package/dist/core/skill-sync/builtins.js +112 -0
  230. package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
  231. package/dist/core/skill-sync/claude-plugins.js +71 -0
  232. package/dist/core/skill-sync/engine.d.ts +42 -0
  233. package/dist/core/skill-sync/engine.js +633 -0
  234. package/dist/core/skill-sync/export.d.ts +23 -0
  235. package/dist/core/skill-sync/export.js +86 -0
  236. package/dist/core/skill-sync/manifest.d.ts +64 -0
  237. package/dist/core/skill-sync/manifest.js +181 -0
  238. package/dist/core/skill-sync/profile.d.ts +76 -0
  239. package/dist/core/skill-sync/profile.js +173 -0
  240. package/dist/core/skill-sync/snapshot.d.ts +57 -0
  241. package/dist/core/skill-sync/snapshot.js +120 -0
  242. package/dist/core/spawn.d.ts +14 -0
  243. package/dist/core/spawn.js +29 -9
  244. package/dist/core/substrate/index.d.ts +2 -2
  245. package/dist/core/substrate/index.js +7 -7
  246. package/dist/core/substrate/injected-store.d.ts +10 -0
  247. package/dist/core/substrate/injected-store.js +55 -0
  248. package/dist/core/substrate/on-read.js +68 -6
  249. package/dist/core/substrate/render.d.ts +8 -11
  250. package/dist/core/substrate/render.js +29 -43
  251. package/dist/core/substrate/schema.d.ts +16 -11
  252. package/dist/core/substrate/schema.js +32 -31
  253. package/dist/core/tui/host.d.ts +12 -4
  254. package/dist/core/tui/host.js +280 -149
  255. package/dist/core/view/bridge.d.ts +10 -0
  256. package/dist/core/view/bridge.js +31 -0
  257. package/dist/core/view/chrome.d.ts +9 -0
  258. package/dist/core/view/chrome.js +22 -0
  259. package/dist/core/view/contract.d.ts +171 -0
  260. package/dist/core/view/contract.js +23 -0
  261. package/dist/core/view/loader.d.ts +31 -0
  262. package/dist/core/view/loader.js +188 -0
  263. package/dist/core/view/transport-local.d.ts +7 -0
  264. package/dist/core/view/transport-local.js +70 -0
  265. package/dist/core/view/transport.d.ts +4 -0
  266. package/dist/core/view/transport.js +15 -0
  267. package/dist/daemon/crtrd.d.ts +38 -27
  268. package/dist/daemon/crtrd.js +303 -376
  269. package/dist/daemon/manage.js +6 -1
  270. package/dist/index.js +1 -1
  271. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
  272. package/dist/pi-extensions/canvas-context-intro.js +4 -4
  273. package/dist/pi-extensions/canvas-doc-substrate.js +29 -20
  274. package/dist/pi-extensions/canvas-nav.js +35 -385
  275. package/dist/pi-extensions/canvas-recap.d.ts +37 -0
  276. package/dist/pi-extensions/canvas-recap.js +236 -0
  277. package/dist/pi-extensions/canvas-resume.js +2 -1
  278. package/dist/pi-extensions/canvas-stophook.d.ts +15 -13
  279. package/dist/pi-extensions/canvas-stophook.js +119 -103
  280. package/dist/pi-extensions/canvas-view.js +2 -1
  281. package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
  282. package/dist/pi-extensions/widget-order-bus.js +34 -0
  283. package/dist/prompts/view.d.ts +2 -2
  284. package/dist/prompts/view.js +145 -73
  285. package/dist/types.d.ts +1 -6
  286. package/dist/types.js +1 -3
  287. package/dist/web/ViewChrome.d.ts +7 -0
  288. package/dist/web/ViewChrome.js +28 -0
  289. package/dist/web/ViewPane.d.ts +39 -0
  290. package/dist/web/ViewPane.js +48 -0
  291. package/dist/web/index.d.ts +6 -0
  292. package/dist/web/index.js +16 -0
  293. package/dist/web/runtime.d.ts +39 -0
  294. package/dist/web/runtime.js +133 -0
  295. package/dist/web/states.d.ts +24 -0
  296. package/dist/web/states.js +24 -0
  297. package/dist/web/transport-http.d.ts +5 -0
  298. package/dist/web/transport-http.js +28 -0
  299. package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
  300. package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
  301. package/dist/web-client/index.html +13 -0
  302. package/package.json +20 -6
  303. package/dist/builtin-views/canvas/client.mjs +0 -303
  304. package/dist/builtin-views/canvas/view.mjs +0 -576
  305. package/dist/builtin-views/git-pr/client.mjs +0 -440
  306. package/dist/builtin-views/git-pr/view.mjs +0 -675
  307. package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
  308. package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
  309. package/dist/builtin-views/inbox/view.mjs +0 -889
  310. package/dist/builtin-views/linkedin/client.mjs +0 -610
  311. package/dist/builtin-views/linkedin/view.mjs +0 -1171
  312. package/dist/commands/pkg/bridge.d.ts +0 -1
  313. package/dist/commands/pkg/bridge.js +0 -137
  314. package/dist/commands/skill/author.d.ts +0 -3
  315. package/dist/commands/skill/author.js +0 -140
  316. package/dist/commands/skill/shared.d.ts +0 -3
  317. package/dist/commands/skill/shared.js +0 -19
  318. package/dist/commands/skill.d.ts +0 -2
  319. package/dist/commands/skill.js +0 -21
  320. package/dist/commands/tmux-spread.d.ts +0 -2
  321. package/dist/commands/tmux-spread.js +0 -144
  322. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
  323. package/dist/core/__tests__/full/placement-focus.test.js +0 -309
  324. package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
  325. package/dist/core/__tests__/full/placement-revive.test.js +0 -238
  326. package/dist/core/__tests__/full/placement-teardown.test.js +0 -221
  327. package/dist/core/__tests__/home-session.test.js +0 -186
  328. package/dist/core/__tests__/relaunch.test.js +0 -335
  329. package/dist/core/bridge-map.d.ts +0 -19
  330. package/dist/core/bridge-map.js +0 -73
  331. package/dist/core/tui/contract.d.ts +0 -83
  332. package/dist/core/tui/contract.js +0 -8
  333. package/dist/core/tui/loader.d.ts +0 -16
  334. package/dist/core/tui/loader.js +0 -94
  335. package/dist/prompts/skill.d.ts +0 -2
  336. package/dist/prompts/skill.js +0 -650
  337. /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
  338. /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
  339. /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
  340. /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
  341. /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
  342. /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/reconnect-giveup.test.d.ts} +0 -0
  343. /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
@@ -1,576 +0,0 @@
1
- // @ts-check
2
- /**
3
- * Canvas Dashboard — the crtr `canvas` view (the monitor archetype).
4
- *
5
- * Self-contained ESM. Imports its data layer from `./client.mjs` (which shells
6
- * the `crtr` binary) and the shared state helpers from `../_lib/states.mjs`. It
7
- * imports NOTHING from crtr — the host injects the `Draw` + `ViewHost` API and
8
- * dynamically `import()`s this module's DEFAULT EXPORT.
9
- *
10
- * A READ-ONLY monitor of the live agent graph: it rebuilds the forest from each
11
- * node's `parent` edge, keeps only trees that contain live (active/idle) or
12
- * human-blocked work, and renders them as a scrollable ASCII tree. It auto-polls
13
- * (refreshMs) — no node-focus / close / swap (views are sessionless). j/k move a
14
- * read cursor; g forces a refresh; q quits.
15
- *
16
- * VISUAL LANGUAGE (crtr-views-visual-design §2/§3/§4): hierarchy is carried by
17
- * weight + hue + position, never boxes. The status glyph hues match `canvas
18
- * browse`'s authoritative palette (active=green, idle=yellow, done=cyan,
19
- * dead=red, canceled=grey; asks=bright-yellow) — "not a recolor." The chrome's
20
- * one state chip is host-derived from data freshness (busy→working, an error
21
- * banner→blocked, an action banner→attention, else ready/idle), driven here by
22
- * toggling banners + the busy lane. Per-row metadata (the relative age) is
23
- * right-flushed via `ListItemRow.right`; secondary text recedes via grey+dim so
24
- * it survives NO_COLOR. The four standard states come from `_lib/states.mjs`.
25
- *
26
- * @module canvas/view
27
- */
28
-
29
- import { fetchNodes, fetchAttention } from './client.mjs';
30
- import { loadingState, emptyState, notReadyState } from '../_lib/states.mjs';
31
-
32
- /** @typedef {import('./client.mjs').CanvasNode} CanvasNode */
33
- /** @typedef {import('./client.mjs').ClientError} ClientError */
34
-
35
- /**
36
- * One rendered tree line (a flattened forest node). Width-independent, so it is
37
- * built once per refresh and re-rendered on resize without a re-fetch.
38
- * @typedef {Object} TreeRow
39
- * @property {string} nodeId
40
- * @property {string} prefix Tree-branch art (e.g. "│ └─ "); '' for a root.
41
- * @property {string} glyph Status glyph.
42
- * @property {string} status
43
- * @property {string} name
44
- * @property {string} kind
45
- * @property {string} mode
46
- * @property {string} lifecycle
47
- * @property {string} shortId
48
- * @property {string} created ISO 8601 birth timestamp (drives the right-flush age).
49
- * @property {boolean} blocked True ⇒ this node has pending human asks.
50
- * @property {number} askCount
51
- */
52
-
53
- /**
54
- * The view's single mutable state object. The view owns it; hooks mutate it in
55
- * place.
56
- * @typedef {Object} CanvasState
57
- * @property {TreeRow[]} rows Flattened active-tree forest (render source).
58
- * @property {number} cursor Read cursor into rows (j/k).
59
- * @property {number} scroll draw.list scroll, stored back each frame.
60
- * @property {number} shownRoots How many trees survived the active filter.
61
- * @property {number} totalNodes Total nodes on the canvas (all statuses).
62
- * @property {number} activeCount Nodes with status 'active' (canvas-wide).
63
- * @property {number} attnTotal Total pending human asks (canvas-wide).
64
- * @property {number} lastFetch Epoch ms of the last successful refresh.
65
- * @property {string|null} sourceError Non-null ⇒ the data source failed; the
66
- * string is the cause (drives the not-ready takeover when there is no data, and
67
- * is the dump fallback). Cleared on a successful refresh.
68
- */
69
-
70
- // ── Status vocabulary (mirrors core/canvas/browse/render.ts — single source) ──
71
-
72
- const LIVE_STATUS = new Set(['active', 'idle']);
73
-
74
- /** @type {Record<string,string>} */
75
- const STATUS_GLYPH = {
76
- active: '●',
77
- idle: '○',
78
- done: '✓',
79
- dead: '✗',
80
- canceled: '⊘',
81
- };
82
-
83
- /**
84
- * Load-bearing status hue — NUMERIC SGR codes, matching `canvas browse`'s
85
- * STATUS_COLOR exactly (active=green, idle=yellow, done=cyan, dead=red,
86
- * canceled=grey). The design is explicit: keep these, "this is not a recolor."
87
- * @type {Record<string,string>}
88
- */
89
- const STATUS_FG = {
90
- active: '32', // green
91
- idle: '33', // yellow
92
- done: '36', // cyan
93
- dead: '31', // red
94
- canceled: '90', // grey
95
- };
96
-
97
- /** @param {string} status @returns {import('../../core/tui/draw.js').Style|undefined} */
98
- function glyphStyle(status) {
99
- const fg = STATUS_FG[status];
100
- return fg ? { fg } : undefined; // hue only — the glyph SHAPE is the mono carrier
101
- }
102
-
103
- /**
104
- * Name weight = hierarchy (design §2 "weight creates hierarchy"): live work
105
- * (active) LEADS in bold; terminal nodes (done/dead/canceled) recede dim; idle
106
- * stays default weight (readable, not shouting). Mono-safe (weight, not hue).
107
- * @param {string} status @returns {import('../../core/tui/draw.js').Style|undefined}
108
- */
109
- function nameStyle(status) {
110
- if (status === 'active') return { bold: true };
111
- if (status === 'done' || status === 'dead' || status === 'canceled') return { dim: true };
112
- return undefined;
113
- }
114
-
115
- /** @param {string} lifecycle @returns {string} */
116
- function lifeAbbr(lifecycle) {
117
- if (lifecycle === 'resident') return 'res';
118
- if (lifecycle === 'terminal') return 'term';
119
- return lifecycle || '?';
120
- }
121
-
122
- /** @param {string} id @returns {string} */
123
- function shortId(id) {
124
- const s = String(id || '');
125
- const dash = s.indexOf('-');
126
- return dash > 0 ? s.slice(0, dash) : s.slice(0, 8);
127
- }
128
-
129
- const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
130
-
131
- /**
132
- * Relative-age ladder (design §5): `now` (<60s), `{m}m` (<60m), `{h}h` (<24h),
133
- * `{d}d` (<7d), else a calendar date `Mon D` (`Mar 4`), prior-year `Mon ʼYY`.
134
- * Max ~5 cols. Used for the right-flushed per-row age (the node's birth time).
135
- * @param {string} createdIso @param {number} now @returns {string}
136
- */
137
- function relAge(createdIso, now) {
138
- const t = Date.parse(createdIso);
139
- if (Number.isNaN(t)) return '';
140
- const s = Math.max(0, Math.floor((now - t) / 1000));
141
- if (s < 60) return 'now';
142
- const m = Math.floor(s / 60);
143
- if (m < 60) return `${m}m`;
144
- const h = Math.floor(m / 60);
145
- if (h < 24) return `${h}h`;
146
- const d = Math.floor(h / 24);
147
- if (d < 7) return `${d}d`;
148
- const date = new Date(t);
149
- const mon = MONTHS[date.getMonth()] || '?';
150
- if (date.getFullYear() === new Date(now).getFullYear()) return `${mon} ${date.getDate()}`;
151
- return `${mon} ʼ${String(date.getFullYear()).slice(-2)}`;
152
- }
153
-
154
- /** @param {number} n @param {string} w @returns {string} */
155
- function plural(n, w) {
156
- return `${n} ${w}${n === 1 ? '' : 's'}`;
157
- }
158
-
159
- // ── Error → guidance text ─────────────────────────────────────────────────────
160
-
161
- /**
162
- * Map a typed {@link ClientError} to guidance text (the host error banner +
163
- * dump fallback).
164
- * @param {ClientError} error
165
- * @returns {string}
166
- */
167
- function bannerFor(error) {
168
- switch (error && error.kind) {
169
- case 'crtr-missing':
170
- return error.message;
171
- case 'crtr-failed':
172
- return 'crtr command failed: ' + (error.message || 'unknown error');
173
- case 'parse':
174
- return error.message || 'could not parse crtr output';
175
- case 'error':
176
- return (error && error.message) || 'Unknown error.';
177
- default:
178
- return (error && /** @type {any} */ (error).message) || 'Unknown error.';
179
- }
180
- }
181
-
182
- // ── Forest builder ───────────────────────────────────────────────────────────
183
-
184
- /**
185
- * Build the flattened active-tree forest from a flat node list + the
186
- * human-blocked id→count map.
187
- *
188
- * A tree is shown iff its subtree contains any LIVE node (status active|idle) OR
189
- * any human-blocked node — so a fully-finished tree drops out, but a blocked one
190
- * always surfaces. Shown trees are rendered IN FULL (finished children included)
191
- * to preserve context, mirroring `crtr canvas dashboard`.
192
- *
193
- * @param {CanvasNode[]} nodes
194
- * @param {Map<string,number>} blockedById
195
- * @returns {{rows: TreeRow[], shownRoots: number}}
196
- */
197
- function buildForest(nodes, blockedById) {
198
- /** @type {Map<string, CanvasNode>} */
199
- const byId = new Map();
200
- for (const n of nodes) byId.set(n.nodeId, n);
201
-
202
- /** @type {Map<string, CanvasNode[]>} */
203
- const children = new Map();
204
- /** @type {CanvasNode[]} */
205
- const roots = [];
206
- for (const n of nodes) {
207
- const parent = n.parent && byId.has(n.parent) ? n.parent : null;
208
- if (parent) {
209
- const arr = children.get(parent);
210
- if (arr) arr.push(n);
211
- else children.set(parent, [n]);
212
- } else {
213
- roots.push(n);
214
- }
215
- }
216
- // Order siblings + roots by birth time (matches spawn order).
217
- const cmp = (/** @type {CanvasNode} */ a, /** @type {CanvasNode} */ b) =>
218
- a.created < b.created ? -1 : a.created > b.created ? 1 : 0;
219
- for (const arr of children.values()) arr.sort(cmp);
220
- roots.sort(cmp);
221
-
222
- // Subtree liveness, memoised + cycle-guarded (the graph is a forest, but the
223
- // db is mutable — guard defensively).
224
- /** @type {Map<string, boolean>} */
225
- const liveMemo = new Map();
226
- /** @param {string} id @param {Set<string>} guard @returns {boolean} */
227
- function subtreeLive(id, guard) {
228
- const memo = liveMemo.get(id);
229
- if (memo !== undefined) return memo;
230
- if (guard.has(id)) return false;
231
- guard.add(id);
232
- const node = byId.get(id);
233
- let res = !!node && (LIVE_STATUS.has(node.status) || blockedById.has(id));
234
- if (!res) {
235
- for (const c of children.get(id) || []) {
236
- if (subtreeLive(c.nodeId, guard)) {
237
- res = true;
238
- break;
239
- }
240
- }
241
- }
242
- guard.delete(id);
243
- liveMemo.set(id, res);
244
- return res;
245
- }
246
-
247
- /** @param {CanvasNode} node @param {string} prefix @returns {TreeRow} */
248
- function makeRow(node, prefix) {
249
- const askCount = blockedById.get(node.nodeId) || 0;
250
- return {
251
- nodeId: node.nodeId,
252
- prefix,
253
- glyph: STATUS_GLYPH[node.status] || '?',
254
- status: node.status,
255
- name: node.name,
256
- kind: node.kind,
257
- mode: node.mode,
258
- lifecycle: node.lifecycle,
259
- shortId: shortId(node.nodeId),
260
- created: node.created,
261
- blocked: askCount > 0,
262
- askCount,
263
- };
264
- }
265
-
266
- /** @type {TreeRow[]} */
267
- const rows = [];
268
- /** @type {Set<string>} */
269
- const visited = new Set();
270
- /** @param {CanvasNode} node @param {string} indent @param {boolean} isLast @param {boolean} isRoot */
271
- function walk(node, indent, isLast, isRoot) {
272
- if (visited.has(node.nodeId)) return; // cycle guard
273
- visited.add(node.nodeId);
274
- const prefix = isRoot ? '' : indent + (isLast ? '└─ ' : '├─ ');
275
- rows.push(makeRow(node, prefix));
276
- const kids = children.get(node.nodeId) || [];
277
- const childIndent = isRoot ? '' : indent + (isLast ? ' ' : '│ ');
278
- for (let i = 0; i < kids.length; i++) {
279
- walk(kids[i], childIndent, i === kids.length - 1, false);
280
- }
281
- }
282
-
283
- let shownRoots = 0;
284
- for (const r of roots) {
285
- if (!subtreeLive(r.nodeId, new Set())) continue;
286
- shownRoots++;
287
- walk(r, '', true, true);
288
- }
289
- return { rows, shownRoots };
290
- }
291
-
292
- // ── Chrome copy (subtitle / footer / banner / dump) ───────────────────────────
293
-
294
- /**
295
- * Live title subtitle — canvas-wide health (design §3 "a dim ` · <subtitle>`").
296
- * `null` ⇒ no subtitle (the title leads alone) on an empty canvas.
297
- * @param {CanvasState} state @returns {string|null}
298
- */
299
- function subtitleFor(state) {
300
- if (state.totalNodes === 0) return null;
301
- return `${state.activeCount} active · ${plural(state.totalNodes, 'node')}`;
302
- }
303
-
304
- /**
305
- * Footer status (left, transient) — the RENDERED forest scope, distinct from the
306
- * canvas-wide subtitle. `null` ⇒ nothing (the empty/loading state speaks).
307
- * @param {CanvasState} state @returns {string|null}
308
- */
309
- function footerSummary(state) {
310
- if (state.rows.length === 0) return null;
311
- return `${plural(state.shownRoots, 'live tree')} · ${state.rows.length} shown`;
312
- }
313
-
314
- /** @param {CanvasState} state @returns {string} */
315
- function dumpSummary(state) {
316
- return (
317
- `${plural(state.shownRoots, 'tree')} · ${state.rows.length} shown · ` +
318
- `${state.activeCount} active · ${plural(state.attnTotal, 'ask')} · ` +
319
- `${state.totalNodes} total`
320
- );
321
- }
322
-
323
- // ── Row → ListItemRow (left spans + right-flushed age) ─────────────────────────
324
-
325
- /**
326
- * Build one list row: a 1-cell left gutter (§2), the tree prefix (dim), the
327
- * status glyph (hue), the name (weight = status), the dim `[kind/mode]` cue, and
328
- * a bright-yellow `⚑N` attention flag when blocked — with the relative age
329
- * RIGHT-FLUSHED into a clean scannable column via `ListItemRow.right`.
330
- * @param {TreeRow} r @param {number} now
331
- * @returns {import('../../core/tui/draw.js').ListItemRow}
332
- */
333
- function rowToItem(r, now) {
334
- /** @type {import('../../core/tui/draw.js').Span[]} */
335
- const spans = [{ text: ' ', style: undefined }]; // 1-cell gutter (rides the cursor bg)
336
- if (r.prefix) spans.push({ text: r.prefix, style: { dim: true } });
337
- spans.push({ text: r.glyph, style: glyphStyle(r.status) });
338
- spans.push({ text: ' ', style: undefined });
339
- spans.push({ text: r.name, style: nameStyle(r.status) });
340
- spans.push({ text: ` [${r.kind}/${r.mode}]`, style: { fg: '90', dim: true } }); // muted: grey + dim (mono-safe)
341
- if (r.blocked) spans.push({ text: ` ⚑${r.askCount}`, style: { fg: '93', bold: true } }); // attention
342
-
343
- const age = relAge(r.created, now);
344
- if (age) {
345
- return { spans, right: [{ text: age, style: { fg: '90', dim: true } }] };
346
- }
347
- return { spans };
348
- }
349
-
350
- // ── Refresh (data lane) ──────────────────────────────────────────────────────
351
-
352
- /**
353
- * Pull the node graph + attention, rebuild the active-tree forest. Runs in the
354
- * host's single-flight lane (launch, refreshMs, and `{type:'refresh'}`). Maps any
355
- * fetch failure to guidance (a banner + the data-freshness chip) instead of
356
- * crashing, and KEEPS the last-known forest on a transient failure.
357
- * @param {CanvasState} state
358
- * @param {import('../../core/tui/contract.js').ViewHost} host
359
- * @returns {Promise<void>}
360
- */
361
- async function refresh(state, host) {
362
- host.setStatus('Loading the canvas…');
363
-
364
- const rn = await fetchNodes();
365
- if (!rn.ok) {
366
- // Data source down. KEEP the last-known forest; raise the cause as the error
367
- // banner → the host derives the BLOCKED (red) chip. The not-ready takeover
368
- // only owns the screen when there is nothing to keep (see render()).
369
- state.sourceError = bannerFor(rn.error);
370
- host.setStatus(null);
371
- host.setError(state.sourceError);
372
- return;
373
- }
374
- state.sourceError = null;
375
- const nodes = rn.data;
376
-
377
- // Attention is best-effort: a failure here still renders the graph (just
378
- // without the blocked flags), so the monitor degrades gracefully.
379
- /** @type {Map<string,number>} */
380
- const blockedById = new Map();
381
- let attnTotal = 0;
382
- const ra = await fetchAttention();
383
- if (ra.ok) {
384
- for (const it of ra.data.items) {
385
- if (it.nodeId) blockedById.set(it.nodeId, it.count);
386
- }
387
- attnTotal = ra.data.total;
388
- }
389
-
390
- const built = buildForest(nodes, blockedById);
391
- state.rows = built.rows;
392
- state.shownRoots = built.shownRoots;
393
- state.totalNodes = nodes.length;
394
- state.activeCount = nodes.filter((n) => n.status === 'active').length;
395
- state.attnTotal = attnTotal;
396
- if (state.cursor >= state.rows.length) state.cursor = Math.max(0, state.rows.length - 1);
397
- state.lastFetch = Date.now();
398
-
399
- host.setSubtitle(subtitleFor(state));
400
-
401
- // Data-freshness → state chip. Pending human asks are the one thing that wants
402
- // a human: raise an ACTION banner → the host derives the ATTENTION (yellow)
403
- // chip. Otherwise clear → READY (green). (busy→working is automatic.)
404
- if (attnTotal > 0) {
405
- host.setBanner(`${plural(attnTotal, 'ask')} waiting on a human — see the ⚑ rows`, 'action');
406
- } else {
407
- host.setError(null);
408
- }
409
-
410
- host.setStatus(footerSummary(state));
411
- }
412
-
413
- // ── ViewModule ───────────────────────────────────────────────────────────────
414
-
415
- /** @type {import('../../core/tui/contract.js').ViewModule<CanvasState>} */
416
- const view = {
417
- manifest: {
418
- id: 'canvas',
419
- title: 'Canvas',
420
- description: 'Live agent graph — who is working, who is blocked',
421
- refreshMs: 3000,
422
- keymap: [
423
- { keys: 'j/k', label: 'move' },
424
- { keys: 'g', label: 'refresh' },
425
- { keys: 'q', label: 'quit' },
426
- ],
427
- },
428
-
429
- /**
430
- * Cheap + synchronous initial state — NO slow fetch (the host paints a loading
431
- * frame, then calls refresh()).
432
- * @returns {CanvasState}
433
- */
434
- init() {
435
- return {
436
- rows: [],
437
- cursor: 0,
438
- scroll: 0,
439
- shownRoots: 0,
440
- totalNodes: 0,
441
- activeCount: 0,
442
- attnTotal: 0,
443
- lastFetch: 0,
444
- sourceError: null,
445
- };
446
- },
447
-
448
- refresh,
449
-
450
- /**
451
- * Paint the forest, or one of the four standard states. Pure (reads state,
452
- * calls draw.*); the only state write is storing draw.list's adjusted scroll
453
- * back, per the Draw contract.
454
- * @param {CanvasState} state
455
- * @param {import('../../core/tui/draw.js').Draw} draw
456
- * @param {import('../../core/tui/draw.js').Rect} content
457
- */
458
- render(state, draw, content) {
459
- if (content.width <= 0 || content.height <= 0) return;
460
-
461
- if (state.rows.length === 0) {
462
- // Hard not-ready: the data source is down and there is nothing to keep —
463
- // a guided takeover owns the whole content rect (design §4/§5). The
464
- // specific cause rides the host error banner (full-width); this names the
465
- // state + the next action so the view never dead-ends.
466
- if (state.sourceError) {
467
- notReadyState(draw, content, {
468
- glyph: '⚠',
469
- glyphFg: '31', // red — pairs with the blocked chip
470
- headline: 'Canvas unavailable',
471
- explanation: 'crtr could not read the canvas graph.',
472
- nextStep: 'Press g to retry.',
473
- });
474
- return;
475
- }
476
- // First load in flight — a skeleton, not a blank screen.
477
- if (state.lastFetch === 0) {
478
- loadingState(draw, content, { rows: Math.min(5, content.height), label: 'Loading the canvas…' });
479
- return;
480
- }
481
- // Loaded, nothing to render — the reward state, two flavors.
482
- if (state.totalNodes === 0) {
483
- emptyState(draw, content, {
484
- headline: 'No nodes on the canvas',
485
- secondary: ['Spawn one with `crtr node new`.', 'Press g to refresh.'],
486
- });
487
- } else {
488
- emptyState(draw, content, {
489
- headline: 'All caught up',
490
- secondary: [`${plural(state.totalNodes, 'node')} finished — none active.`, 'Press g to refresh.'],
491
- });
492
- }
493
- return;
494
- }
495
-
496
- // The forest. A 1-row section gap below the header (§2 rhythm) when there is
497
- // room; full-width list so the cursor highlight + age column reach the edges.
498
- const now = Date.now();
499
- const gap = content.height > 4 ? 1 : 0;
500
- const listRect = {
501
- row: content.row + gap,
502
- col: content.col,
503
- width: content.width,
504
- height: content.height - gap,
505
- };
506
- const items = state.rows.map((r) => rowToItem(r, now));
507
- const res = draw.list(listRect, items, state.cursor, state.scroll);
508
- state.scroll = res.scroll; // store adjusted scroll back (Draw.list contract)
509
- },
510
-
511
- /**
512
- * Read-only navigation: j/k move the cursor, g refreshes, q quits. No async
513
- * actions — this is a monitor, not a controller.
514
- * @param {import('../../core/tui/contract.js').ViewKey} k
515
- * @param {CanvasState} state
516
- * @returns {import('../../core/tui/contract.js').ViewAction}
517
- */
518
- onKey(k, state) {
519
- const key = k.key;
520
- const ch = k.input;
521
- if (ch === 'q') return { type: 'quit' };
522
- if (ch === 'g') return { type: 'refresh' };
523
- if (key.downArrow || ch === 'j') {
524
- if (state.rows.length) state.cursor = Math.min(state.rows.length - 1, state.cursor + 1);
525
- return { type: 'render' };
526
- }
527
- if (key.upArrow || ch === 'k') {
528
- state.cursor = Math.max(0, state.cursor - 1);
529
- return { type: 'render' };
530
- }
531
- return { type: 'none' };
532
- },
533
-
534
- /**
535
- * Plain-text snapshot for the non-TTY / piped path. No ANSI. The host threads
536
- * its current banner via `ctx` so guidance (a source error / a pending-ask
537
- * action) surfaces without the view mirroring it into state.
538
- * @param {CanvasState} state
539
- * @param {import('../../core/tui/contract.js').DumpContext} [ctx]
540
- * @returns {string}
541
- */
542
- dump(state, ctx) {
543
- /** @type {string[]} */
544
- const lines = ['Canvas — live agent graph'];
545
- const banner = ctx && ctx.banner ? ctx.banner : null;
546
- if (banner) lines.push('', `[${banner.level}] ${banner.msg}`);
547
- else if (state.sourceError) lines.push('', `[error] ${state.sourceError}`);
548
-
549
- lines.push('', dumpSummary(state), '');
550
-
551
- if (state.rows.length === 0) {
552
- lines.push(
553
- state.sourceError
554
- ? '(canvas unavailable)'
555
- : state.lastFetch === 0
556
- ? '(loading…)'
557
- : state.totalNodes === 0
558
- ? '(no nodes on the canvas)'
559
- : '(no active trees)',
560
- );
561
- return lines.join('\n');
562
- }
563
-
564
- const now = Date.now();
565
- for (const r of state.rows) {
566
- const blk = r.blocked ? ` ⚑${r.askCount}` : '';
567
- const age = relAge(r.created, now);
568
- lines.push(
569
- `${r.prefix}${r.glyph} ${r.name} [${r.kind}/${r.mode}] ${lifeAbbr(r.lifecycle)} ${r.shortId} ${age}${blk}`,
570
- );
571
- }
572
- return lines.join('\n');
573
- },
574
- };
575
-
576
- export default view;