@crouton-kit/crouter 0.3.32 → 0.3.33

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 (440) hide show
  1. package/README.md +8 -0
  2. package/dist/build-root.js +1 -0
  3. package/dist/builtin-memory/crouter-development/marketplaces.md +2 -2
  4. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +2 -2
  5. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +5 -5
  6. package/dist/builtin-memory/crouter-development/personas.md +3 -4
  7. package/dist/builtin-memory/crouter-development/plugins.md +18 -18
  8. package/dist/builtin-memory/design.md +4 -1
  9. package/dist/builtin-memory/development.md +4 -1
  10. package/dist/builtin-memory/internal/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/nodes-and-canvas.md +6 -6
  12. package/dist/builtin-memory/internal/storage-tiers.md +5 -5
  13. package/dist/builtin-memory/planning.md +4 -1
  14. package/dist/builtin-memory/product.md +80 -0
  15. package/dist/builtin-memory/spec.md +4 -1
  16. package/dist/builtin-personas/advisor/PERSONA.md +10 -0
  17. package/dist/builtin-personas/design/PERSONA.md +2 -2
  18. package/dist/builtin-personas/design/orchestrator.md +3 -3
  19. package/dist/builtin-personas/developer/PERSONA.md +1 -1
  20. package/dist/builtin-personas/developer/orchestrator.md +2 -2
  21. package/dist/builtin-personas/explore/PERSONA.md +3 -3
  22. package/dist/builtin-personas/explore/orchestrator.md +4 -2
  23. package/dist/builtin-personas/general/PERSONA.md +1 -1
  24. package/dist/builtin-personas/general/orchestrator.md +1 -1
  25. package/dist/builtin-personas/orchestration-kernel.md +7 -14
  26. package/dist/builtin-personas/plan/PERSONA.md +1 -1
  27. package/dist/builtin-personas/plan/orchestrator.md +2 -2
  28. package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -1
  29. package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -1
  30. package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -1
  31. package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -1
  32. package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -1
  33. package/dist/builtin-personas/product/PERSONA.md +18 -0
  34. package/dist/builtin-personas/product/orchestrator.md +14 -0
  35. package/dist/builtin-personas/product/teardown/PERSONA.md +13 -0
  36. package/dist/builtin-personas/review/PERSONA.md +1 -1
  37. package/dist/builtin-personas/review/orchestrator.md +1 -1
  38. package/dist/builtin-personas/runtime-base.md +5 -0
  39. package/dist/builtin-personas/spec/PERSONA.md +1 -1
  40. package/dist/builtin-personas/spec/orchestrator.md +2 -2
  41. package/dist/builtin-personas/spec/requirements/PERSONA.md +1 -1
  42. package/dist/builtin-views/canvas/core.mjs +82 -1
  43. package/dist/builtin-views/canvas/tui.mjs +9 -5
  44. package/dist/builtin-views/canvas/web.jsx +3 -2
  45. package/dist/builtin-views/chat/core.mjs +725 -0
  46. package/dist/builtin-views/chat/text.mjs +101 -0
  47. package/dist/builtin-views/chat/tui.mjs +368 -0
  48. package/dist/builtin-views/chat/web.jsx +367 -0
  49. package/dist/builtin-views/prompt-review/core.mjs +863 -0
  50. package/dist/builtin-views/prompt-review/text.mjs +15 -0
  51. package/dist/builtin-views/prompt-review/tui.mjs +196 -0
  52. package/dist/builtin-views/prompt-review/web.jsx +484 -0
  53. package/dist/builtin-views/settings/core.mjs +397 -0
  54. package/dist/builtin-views/settings/text.mjs +40 -0
  55. package/dist/builtin-views/settings/tui.mjs +95 -0
  56. package/dist/builtin-views/settings/web.jsx +167 -0
  57. package/dist/cli.js +16 -3
  58. package/dist/clients/attach/__tests__/autocomplete-and-bash-mode.test.js +96 -0
  59. package/dist/clients/attach/__tests__/bash-bang-routing.test.js +58 -0
  60. package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.d.ts +1 -0
  61. package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.js +56 -0
  62. package/dist/clients/attach/__tests__/titled-editor.test.js +22 -1
  63. package/dist/clients/attach/attach-cmd.d.ts +18 -0
  64. package/dist/clients/attach/attach-cmd.js +1723 -795
  65. package/dist/clients/attach/canvas-panels.js +2 -2
  66. package/dist/clients/attach/chat-view.d.ts +10 -0
  67. package/dist/clients/attach/chat-view.js +23 -0
  68. package/dist/clients/attach/config-load.d.ts +8 -4
  69. package/dist/clients/attach/config-load.js +2 -0
  70. package/dist/clients/attach/extension-dialogs.d.ts +2 -3
  71. package/dist/clients/attach/extension-dialogs.js +7 -9
  72. package/dist/clients/attach/graph-overlay.js +3 -2
  73. package/dist/clients/attach/input-controller.d.ts +4 -3
  74. package/dist/clients/attach/input-controller.js +24 -3
  75. package/dist/clients/attach/slash-commands.d.ts +7 -7
  76. package/dist/clients/attach/slash-commands.js +38 -14
  77. package/dist/clients/attach/titled-editor.js +18 -14
  78. package/dist/clients/attach/view-socket.d.ts +2 -1
  79. package/dist/clients/attach/view-socket.js +27 -8
  80. package/dist/clients/web/__tests__/source-cache.test.d.ts +1 -0
  81. package/dist/clients/web/__tests__/source-cache.test.js +32 -0
  82. package/dist/clients/web/dev-server.js +1 -0
  83. package/dist/clients/web/events.js +9 -11
  84. package/dist/clients/web/server.d.ts +4 -3
  85. package/dist/clients/web/server.js +138 -31
  86. package/dist/clients/web/source-cache.d.ts +10 -0
  87. package/dist/clients/web/source-cache.js +57 -0
  88. package/dist/clients/web/web-cmd.js +28 -9
  89. package/dist/commands/__tests__/human.test.js +30 -28
  90. package/dist/commands/canvas-history/search.js +1 -1
  91. package/dist/commands/canvas-history.js +5 -8
  92. package/dist/commands/canvas-issue.d.ts +2 -0
  93. package/dist/commands/canvas-issue.js +148 -0
  94. package/dist/commands/canvas-prune.js +19 -0
  95. package/dist/commands/canvas-rebuild-index.d.ts +2 -0
  96. package/dist/commands/canvas-rebuild-index.js +57 -0
  97. package/dist/commands/canvas-snapshot.d.ts +2 -0
  98. package/dist/commands/canvas-snapshot.js +48 -0
  99. package/dist/commands/canvas-tmux-spread.d.ts +2 -0
  100. package/dist/commands/canvas-tmux-spread.js +188 -0
  101. package/dist/commands/canvas.d.ts +1 -0
  102. package/dist/commands/canvas.js +16 -1
  103. package/dist/commands/chord.js +143 -48
  104. package/dist/commands/daemon.js +3 -3
  105. package/dist/commands/human/prompts.d.ts +0 -1
  106. package/dist/commands/human/prompts.js +39 -54
  107. package/dist/commands/human/queue.d.ts +12 -1
  108. package/dist/commands/human/queue.js +468 -73
  109. package/dist/commands/human/shared.d.ts +3 -4
  110. package/dist/commands/human/shared.js +3 -3
  111. package/dist/commands/human.js +11 -10
  112. package/dist/commands/memory/find.js +3 -2
  113. package/dist/commands/memory/list.js +4 -3
  114. package/dist/commands/memory/read.js +3 -3
  115. package/dist/commands/memory/shared.d.ts +1 -4
  116. package/dist/commands/memory/shared.js +6 -9
  117. package/dist/commands/memory/write.js +3 -3
  118. package/dist/commands/memory.js +4 -7
  119. package/dist/commands/node-context.d.ts +2 -0
  120. package/dist/commands/node-context.js +172 -0
  121. package/dist/commands/node-snapshot.d.ts +2 -0
  122. package/dist/commands/node-snapshot.js +123 -0
  123. package/dist/commands/node.js +228 -52
  124. package/dist/commands/pkg/plugin-inspect.js +6 -6
  125. package/dist/commands/pkg/plugin-manage.js +1 -1
  126. package/dist/commands/pkg/plugin.js +2 -2
  127. package/dist/commands/pkg.js +2 -2
  128. package/dist/commands/push.d.ts +2 -0
  129. package/dist/commands/push.js +79 -11
  130. package/dist/commands/revive.js +74 -1
  131. package/dist/commands/search/answer.d.ts +1 -0
  132. package/dist/commands/search/answer.js +50 -0
  133. package/dist/commands/search/contents.d.ts +1 -0
  134. package/dist/commands/search/contents.js +96 -0
  135. package/dist/commands/search/exa.d.ts +53 -0
  136. package/dist/commands/search/exa.js +155 -0
  137. package/dist/commands/search/puremd.d.ts +11 -0
  138. package/dist/commands/search/puremd.js +53 -0
  139. package/dist/commands/search/web.d.ts +1 -0
  140. package/dist/commands/search/web.js +65 -0
  141. package/dist/commands/search.d.ts +2 -0
  142. package/dist/commands/search.js +24 -0
  143. package/dist/commands/sys/__tests__/sync-import.test.d.ts +1 -0
  144. package/dist/commands/sys/__tests__/sync-import.test.js +72 -0
  145. package/dist/commands/sys/config.js +59 -10
  146. package/dist/commands/sys/doctor.js +56 -4
  147. package/dist/commands/sys/prompt-review.d.ts +1 -0
  148. package/dist/commands/sys/prompt-review.js +178 -0
  149. package/dist/commands/sys/promptstudio.d.ts +2 -0
  150. package/dist/commands/sys/promptstudio.js +65 -0
  151. package/dist/commands/sys/settings.d.ts +2 -0
  152. package/dist/commands/sys/settings.js +16 -0
  153. package/dist/commands/sys/sync.js +311 -159
  154. package/dist/commands/sys/sysprompt.d.ts +1 -0
  155. package/dist/commands/sys/sysprompt.js +86 -0
  156. package/dist/commands/sys.js +9 -5
  157. package/dist/commands/view-new.js +2 -2
  158. package/dist/core/__tests__/artifact-paths.test.d.ts +1 -0
  159. package/dist/core/__tests__/artifact-paths.test.js +44 -0
  160. package/dist/core/__tests__/broker-preflight.test.d.ts +1 -0
  161. package/dist/core/__tests__/broker-preflight.test.js +85 -0
  162. package/dist/core/__tests__/broker-sdk-wiring.test.js +15 -0
  163. package/dist/core/__tests__/canvas.test.js +37 -1
  164. package/dist/core/__tests__/chat-view-reconnect.test.d.ts +1 -0
  165. package/dist/core/__tests__/chat-view-reconnect.test.js +70 -0
  166. package/dist/core/__tests__/child-death-wake.test.js +11 -2
  167. package/dist/core/__tests__/close.test.js +39 -1
  168. package/dist/core/__tests__/connection-reconnect-resume.test.d.ts +1 -0
  169. package/dist/core/__tests__/connection-reconnect-resume.test.js +37 -0
  170. package/dist/core/__tests__/context-intro.test.js +48 -14
  171. package/dist/core/__tests__/daemon-boot.test.js +182 -9
  172. package/dist/core/__tests__/editor-label.test.d.ts +1 -0
  173. package/dist/core/__tests__/editor-label.test.js +26 -0
  174. package/dist/core/__tests__/fault-marker.test.d.ts +1 -0
  175. package/dist/core/__tests__/fault-marker.test.js +112 -0
  176. package/dist/core/__tests__/fault-retry-rewind.test.d.ts +1 -0
  177. package/dist/core/__tests__/fault-retry-rewind.test.js +123 -0
  178. package/dist/core/__tests__/fixtures/fake-engine.d.ts +30 -4
  179. package/dist/core/__tests__/fixtures/fake-engine.js +156 -17
  180. package/dist/core/__tests__/fixtures/fake-pi-host.js +10 -1
  181. package/dist/core/__tests__/full/human-new-window-regression.test.js +1 -1
  182. package/dist/core/__tests__/helpers/harness.d.ts +1 -1
  183. package/dist/core/__tests__/helpers/harness.js +2 -2
  184. package/dist/core/__tests__/human-stranded-deliver.test.js +38 -1
  185. package/dist/core/__tests__/human-surface-target.test.js +1 -1
  186. package/dist/core/__tests__/kickoff.test.js +18 -19
  187. package/dist/core/__tests__/memory-resolver.test.js +12 -1
  188. package/dist/core/__tests__/migration.test.js +49 -14
  189. package/dist/core/__tests__/model-ladders.test.d.ts +1 -0
  190. package/dist/core/__tests__/model-ladders.test.js +160 -0
  191. package/dist/core/__tests__/node-env.test.d.ts +1 -0
  192. package/dist/core/__tests__/node-env.test.js +26 -0
  193. package/dist/core/__tests__/push-final-guard.test.js +7 -1
  194. package/dist/core/__tests__/relaunch-root.test.js +9 -9
  195. package/dist/core/__tests__/revive.test.js +36 -6
  196. package/dist/core/__tests__/spawn-root.test.js +24 -1
  197. package/dist/core/__tests__/stop-guard.test.js +10 -0
  198. package/dist/core/__tests__/stranded-relaunch.test.d.ts +1 -0
  199. package/dist/core/__tests__/stranded-relaunch.test.js +72 -0
  200. package/dist/core/__tests__/tmux-surface.test.js +5 -1
  201. package/dist/core/__tests__/unknown-path.test.js +19 -19
  202. package/dist/core/__tests__/yield-ensures-daemon.test.d.ts +1 -0
  203. package/dist/core/__tests__/yield-ensures-daemon.test.js +54 -0
  204. package/dist/core/artifact.d.ts +2 -33
  205. package/dist/core/artifact.js +27 -87
  206. package/dist/core/bootstrap.js +2 -0
  207. package/dist/core/canvas/browse/__tests__/model.test.js +35 -1
  208. package/dist/core/canvas/browse/app.js +87 -17
  209. package/dist/core/canvas/browse/model.d.ts +10 -1
  210. package/dist/core/canvas/browse/model.js +37 -1
  211. package/dist/core/canvas/browse/render.js +35 -33
  212. package/dist/core/canvas/canvas.d.ts +7 -0
  213. package/dist/core/canvas/canvas.js +54 -5
  214. package/dist/core/canvas/db.js +35 -8
  215. package/dist/core/canvas/labels.d.ts +6 -8
  216. package/dist/core/canvas/labels.js +8 -11
  217. package/dist/core/canvas/nav-model.d.ts +5 -4
  218. package/dist/core/canvas/nav-model.js +31 -17
  219. package/dist/core/canvas/paths.d.ts +18 -1
  220. package/dist/core/canvas/paths.js +31 -2
  221. package/dist/core/canvas/render.d.ts +25 -0
  222. package/dist/core/canvas/render.js +96 -13
  223. package/dist/core/canvas/status-glyph.d.ts +35 -0
  224. package/dist/core/canvas/status-glyph.js +104 -0
  225. package/dist/core/canvas/types.d.ts +2 -2
  226. package/dist/core/config.js +114 -16
  227. package/dist/core/fault-classifier.d.ts +41 -0
  228. package/dist/core/fault-classifier.js +110 -0
  229. package/dist/core/feed/inbox.d.ts +15 -0
  230. package/dist/core/feed/inbox.js +40 -14
  231. package/dist/core/frontmatter.d.ts +1 -11
  232. package/dist/core/frontmatter.js +1 -55
  233. package/dist/core/hearth/config.d.ts +2 -0
  234. package/dist/core/hearth/config.js +75 -0
  235. package/dist/core/hearth/guest-env.d.ts +2 -0
  236. package/dist/core/hearth/guest-env.js +11 -0
  237. package/dist/core/hearth/index.d.ts +5 -0
  238. package/dist/core/hearth/index.js +5 -0
  239. package/dist/core/hearth/provider.d.ts +21 -0
  240. package/dist/core/hearth/provider.js +10 -0
  241. package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +10 -0
  242. package/dist/core/hearth/providers/blaxel-bootstrap.js +39 -0
  243. package/dist/core/hearth/providers/blaxel-home.d.ts +11 -0
  244. package/dist/core/hearth/providers/blaxel-home.js +126 -0
  245. package/dist/core/hearth/providers/blaxel.d.ts +26 -0
  246. package/dist/core/hearth/providers/blaxel.js +208 -0
  247. package/dist/core/hearth/providers/types.d.ts +72 -0
  248. package/dist/core/hearth/providers/types.js +1 -0
  249. package/dist/core/hearth/registry.d.ts +15 -0
  250. package/dist/core/hearth/registry.js +179 -0
  251. package/dist/core/hearth/types.d.ts +121 -0
  252. package/dist/core/hearth/types.js +1 -0
  253. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +1 -0
  254. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +79 -0
  255. package/dist/core/{skill-sync → host-exports}/builtins.d.ts +21 -13
  256. package/dist/core/host-exports/builtins.js +68 -0
  257. package/dist/core/{skill-sync → host-exports}/export.d.ts +9 -6
  258. package/dist/core/{skill-sync → host-exports}/export.js +37 -14
  259. package/dist/core/io.js +1 -1
  260. package/dist/core/log.d.ts +9 -0
  261. package/dist/core/log.js +113 -0
  262. package/dist/core/memory-resolver.d.ts +8 -8
  263. package/dist/core/memory-resolver.js +15 -28
  264. package/dist/core/personas/index.d.ts +4 -4
  265. package/dist/core/personas/index.js +2 -2
  266. package/dist/core/personas/loader.d.ts +51 -0
  267. package/dist/core/personas/loader.js +54 -50
  268. package/dist/core/personas/resolve.d.ts +43 -27
  269. package/dist/core/personas/resolve.js +336 -94
  270. package/dist/core/runtime/auth-reload.d.ts +7 -0
  271. package/dist/core/runtime/auth-reload.js +69 -0
  272. package/dist/core/runtime/bearings.d.ts +35 -15
  273. package/dist/core/runtime/bearings.js +305 -44
  274. package/dist/core/runtime/branded-host.d.ts +4 -2
  275. package/dist/core/runtime/branded-host.js +66 -4
  276. package/dist/core/runtime/broker-protocol.d.ts +38 -3
  277. package/dist/core/runtime/broker.js +343 -18
  278. package/dist/core/runtime/close.js +37 -29
  279. package/dist/core/runtime/connectivity.d.ts +12 -0
  280. package/dist/core/runtime/connectivity.js +73 -0
  281. package/dist/core/runtime/fault-recovery-nudge.d.ts +2 -0
  282. package/dist/core/runtime/fault-recovery-nudge.js +2 -0
  283. package/dist/core/runtime/fault-recovery.d.ts +23 -0
  284. package/dist/core/runtime/fault-recovery.js +74 -0
  285. package/dist/core/runtime/fault.d.ts +25 -0
  286. package/dist/core/runtime/fault.js +176 -0
  287. package/dist/core/runtime/front-door.d.ts +1 -1
  288. package/dist/core/runtime/front-door.js +2 -2
  289. package/dist/core/runtime/host.d.ts +10 -0
  290. package/dist/core/runtime/host.js +71 -9
  291. package/dist/core/runtime/kickoff.js +31 -45
  292. package/dist/core/runtime/launch.d.ts +32 -18
  293. package/dist/core/runtime/launch.js +148 -43
  294. package/dist/core/runtime/model-swap.d.ts +18 -0
  295. package/dist/core/runtime/model-swap.js +95 -0
  296. package/dist/core/runtime/naming.d.ts +7 -0
  297. package/dist/core/runtime/naming.js +61 -9
  298. package/dist/core/runtime/nodes.js +6 -1
  299. package/dist/core/runtime/persona.js +16 -17
  300. package/dist/core/runtime/placement.d.ts +36 -31
  301. package/dist/core/runtime/placement.js +149 -64
  302. package/dist/core/runtime/promote.d.ts +2 -0
  303. package/dist/core/runtime/promote.js +23 -3
  304. package/dist/core/runtime/recycle.js +6 -4
  305. package/dist/core/runtime/reset.d.ts +5 -2
  306. package/dist/core/runtime/reset.js +2 -2
  307. package/dist/core/runtime/revive.d.ts +4 -0
  308. package/dist/core/runtime/revive.js +27 -9
  309. package/dist/core/runtime/roadmap.d.ts +1 -1
  310. package/dist/core/runtime/roadmap.js +1 -1
  311. package/dist/core/runtime/spawn.d.ts +2 -2
  312. package/dist/core/runtime/spawn.js +30 -6
  313. package/dist/core/runtime/stop-guard.js +6 -1
  314. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  315. package/dist/core/runtime/tmux-chrome.js +1 -1
  316. package/dist/core/runtime/tmux.d.ts +29 -6
  317. package/dist/core/runtime/tmux.js +134 -80
  318. package/dist/core/runtime/view-socket-client.d.ts +46 -0
  319. package/dist/core/runtime/view-socket-client.js +203 -0
  320. package/dist/core/substrate/index.d.ts +1 -1
  321. package/dist/core/substrate/index.js +1 -1
  322. package/dist/core/substrate/on-read.js +16 -4
  323. package/dist/core/substrate/render.d.ts +10 -13
  324. package/dist/core/substrate/render.js +56 -51
  325. package/dist/core/tui/host.js +83 -18
  326. package/dist/core/view/__tests__/transport-cache.test.d.ts +1 -0
  327. package/dist/core/view/__tests__/transport-cache.test.js +62 -0
  328. package/dist/core/view/contract.d.ts +20 -0
  329. package/dist/core/view/stream-local.d.ts +3 -0
  330. package/dist/core/view/stream-local.js +184 -0
  331. package/dist/core/view/transport-cache.d.ts +8 -0
  332. package/dist/core/view/transport-cache.js +38 -0
  333. package/dist/core/view/transport-local.js +4 -0
  334. package/dist/core/view/transport.d.ts +7 -1
  335. package/dist/daemon/crtrd.d.ts +3 -19
  336. package/dist/daemon/crtrd.js +139 -178
  337. package/dist/daemon/manage.d.ts +18 -1
  338. package/dist/daemon/manage.js +54 -9
  339. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.d.ts +1 -0
  340. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.js +289 -0
  341. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.d.ts +1 -0
  342. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.js +34 -0
  343. package/dist/hearth/wake-proxy/__tests__/guest-source.test.d.ts +1 -0
  344. package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +203 -0
  345. package/dist/hearth/wake-proxy/__tests__/hardening.test.d.ts +1 -0
  346. package/dist/hearth/wake-proxy/__tests__/hardening.test.js +59 -0
  347. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.d.ts +1 -0
  348. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.js +372 -0
  349. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.d.ts +1 -0
  350. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.js +258 -0
  351. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.d.ts +1 -0
  352. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.js +437 -0
  353. package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.d.ts +1 -0
  354. package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.js +455 -0
  355. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.d.ts +1 -0
  356. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.js +15 -0
  357. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.d.ts +1 -0
  358. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.js +141 -0
  359. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.d.ts +1 -0
  360. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.js +143 -0
  361. package/dist/hearth/wake-proxy/anthropic-oauth.d.ts +58 -0
  362. package/dist/hearth/wake-proxy/anthropic-oauth.js +189 -0
  363. package/dist/hearth/wake-proxy/auth.d.ts +22 -0
  364. package/dist/hearth/wake-proxy/auth.js +128 -0
  365. package/dist/hearth/wake-proxy/config.d.ts +2 -0
  366. package/dist/hearth/wake-proxy/config.js +151 -0
  367. package/dist/hearth/wake-proxy/guest-source.d.ts +14 -0
  368. package/dist/hearth/wake-proxy/guest-source.js +130 -0
  369. package/dist/hearth/wake-proxy/hearth-status.d.ts +44 -0
  370. package/dist/hearth/wake-proxy/hearth-status.js +267 -0
  371. package/dist/hearth/wake-proxy/home.d.ts +67 -0
  372. package/dist/hearth/wake-proxy/home.js +297 -0
  373. package/dist/hearth/wake-proxy/main.d.ts +1 -0
  374. package/dist/hearth/wake-proxy/main.js +134 -0
  375. package/dist/hearth/wake-proxy/model-auth.d.ts +13 -0
  376. package/dist/hearth/wake-proxy/model-auth.js +345 -0
  377. package/dist/hearth/wake-proxy/proxy.d.ts +35 -0
  378. package/dist/hearth/wake-proxy/proxy.js +716 -0
  379. package/dist/hearth/wake-proxy/public-source-gate.d.ts +9 -0
  380. package/dist/hearth/wake-proxy/public-source-gate.js +409 -0
  381. package/dist/hearth/wake-proxy/redact.d.ts +1 -0
  382. package/dist/hearth/wake-proxy/redact.js +17 -0
  383. package/dist/hearth/wake-proxy/server.d.ts +11 -0
  384. package/dist/hearth/wake-proxy/server.js +142 -0
  385. package/dist/hearth/wake-proxy/state.d.ts +18 -0
  386. package/dist/hearth/wake-proxy/state.js +342 -0
  387. package/dist/hearth/wake-proxy/types.d.ts +76 -0
  388. package/dist/hearth/wake-proxy/types.js +1 -0
  389. package/dist/index.js +3 -2
  390. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +6 -5
  391. package/dist/pi-extensions/canvas-context-intro.js +7 -7
  392. package/dist/pi-extensions/canvas-doc-substrate.js +19 -22
  393. package/dist/pi-extensions/canvas-nav.js +15 -8
  394. package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
  395. package/dist/pi-extensions/canvas-stophook.js +78 -11
  396. package/dist/types.d.ts +39 -20
  397. package/dist/types.js +38 -13
  398. package/dist/web/runtime.js +141 -42
  399. package/dist/web/transport-http.js +7 -5
  400. package/dist/web/transport-stream.d.ts +3 -0
  401. package/dist/web/transport-stream.js +204 -0
  402. package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
  403. package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
  404. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
  405. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
  406. package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
  407. package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
  408. package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
  409. package/dist/web-client/assets/index-BZUxTkv5.css +2 -0
  410. package/dist/web-client/assets/index-D36PNBj4.js +80 -0
  411. package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
  412. package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
  413. package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
  414. package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
  415. package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
  416. package/dist/web-client/index.html +3 -2
  417. package/package.json +32 -10
  418. package/dist/core/__tests__/error-stall-recycle.test.js +0 -141
  419. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +0 -57
  420. package/dist/core/skill-sync/builtins.js +0 -112
  421. package/dist/core/skill-sync/claude-plugins.d.ts +0 -23
  422. package/dist/core/skill-sync/claude-plugins.js +0 -71
  423. package/dist/core/skill-sync/engine.d.ts +0 -42
  424. package/dist/core/skill-sync/engine.js +0 -633
  425. package/dist/core/skill-sync/manifest.d.ts +0 -64
  426. package/dist/core/skill-sync/manifest.js +0 -181
  427. package/dist/core/skill-sync/profile.d.ts +0 -76
  428. package/dist/core/skill-sync/profile.js +0 -173
  429. package/dist/core/skill-sync/snapshot.d.ts +0 -57
  430. package/dist/core/skill-sync/snapshot.js +0 -120
  431. package/dist/pi-extensions/canvas-commands.d.ts +0 -37
  432. package/dist/pi-extensions/canvas-commands.js +0 -113
  433. package/dist/pi-extensions/canvas-resume.d.ts +0 -21
  434. package/dist/pi-extensions/canvas-resume.js +0 -83
  435. package/dist/pi-extensions/canvas-view.d.ts +0 -21
  436. package/dist/pi-extensions/canvas-view.js +0 -76
  437. package/dist/web-client/assets/index-BUvQb4hR.css +0 -2
  438. package/dist/web-client/assets/index-ClLQXYAE.js +0 -10
  439. /package/dist/{core/__tests__/error-stall-recycle.test.d.ts → clients/attach/__tests__/autocomplete-and-bash-mode.test.d.ts} +0 -0
  440. /package/dist/{core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts → clients/attach/__tests__/bash-bang-routing.test.d.ts} +0 -0
@@ -12,7 +12,7 @@
12
12
  // caller owns the Containers and the refresh trigger. beginFrame() memoizes the
13
13
  // canvas.db reads for this one build pass (mirrors canvas-nav's per-frame cache),
14
14
  // so a whole panel rebuild is a single fan-out of disk/db reads.
15
- import { beginFrame, cNode, managerOf, liveReports, climbRoot, computeSubtreeActivity, navLabel, coloredGlyph, truncate, tokensCell, cycleBadge, childBadge, liveBelowBadge, askBadge, activityCell, YELLOW, RESET, } from '../../core/canvas/nav-model.js';
15
+ import { beginFrame, cNode, managerOf, liveReports, climbRoot, computeSubtreeActivity, navLabel, nodeGlyph, truncate, tokensCell, cycleBadge, childBadge, liveBelowBadge, askBadge, activityCell, YELLOW, RESET, } from '../../core/canvas/nav-model.js';
16
16
  /** Column widths for the aligned panel rows (visible columns). name is sized to
17
17
  * the widest label across BOTH panels (capped); kind is wide enough for the
18
18
  * longest persona kind (`orchestrator` = 12) so real kinds never truncate;
@@ -57,7 +57,7 @@ export function buildCanvasPanelLines(nodeId, asks, palette) {
57
57
  const kind = faint(nameCell(n?.kind ?? '', KIND_W));
58
58
  const tokens = muted(padStartVisible(tokensCell(id), TOK_W));
59
59
  const badges = `${cycleBadge(n)}${childBadge(n)}${liveBelowBadge(n, activity.activeBelow)}${askBadge(id, asks)}${activityCell(id, n)}`;
60
- return truncate(` ${coloredGlyph(n)} ${name} ${kind} ${tokens}${badges}`);
60
+ return truncate(` ${nodeGlyph(n)} ${name} ${kind} ${tokens}${badges}`);
61
61
  };
62
62
  // --- managers panel (↑) ---
63
63
  const managers = [];
@@ -50,6 +50,8 @@ export declare class ChatView {
50
50
  private streamingComponent;
51
51
  /** Tool-call id → its execution component, for the duration of the call. */
52
52
  private readonly pendingTools;
53
+ /** The `!` bash component currently streaming (between bash_start and bash_end). */
54
+ private bashComponent;
53
55
  /** The single active activity loader (working/compaction/retry), if any. */
54
56
  private activityLoader;
55
57
  /** While true, `append` skips re-pinning the status container (bulk rebuild). */
@@ -78,6 +80,14 @@ export declare class ChatView {
78
80
  * the container, so the loop covers it too). Returns the new HIDDEN state. */
79
81
  toggleThinking(): boolean;
80
82
  handleEvent(event: AgentSessionEvent): void;
83
+ bashStart(command: string, excludeFromContext?: boolean): void;
84
+ bashOutput(chunk: string): void;
85
+ bashEnd(result: {
86
+ exitCode: number | undefined;
87
+ cancelled: boolean;
88
+ truncated: boolean;
89
+ fullOutputPath?: string;
90
+ }): void;
81
91
  private addMessageToChat;
82
92
  /** Default-path tool component (toolDef undefined → builtin/default rendering). */
83
93
  private makeToolComponent;
@@ -73,6 +73,8 @@ export class ChatView {
73
73
  streamingComponent;
74
74
  /** Tool-call id → its execution component, for the duration of the call. */
75
75
  pendingTools = new Map();
76
+ /** The `!` bash component currently streaming (between bash_start and bash_end). */
77
+ bashComponent;
76
78
  /** The single active activity loader (working/compaction/retry), if any. */
77
79
  activityLoader;
78
80
  /** While true, `append` skips re-pinning the status container (bulk rebuild). */
@@ -382,6 +384,27 @@ export class ChatView {
382
384
  this.tui.requestRender();
383
385
  }
384
386
  // -------------------------------------------------------------------------
387
+ // `!` bash live stream (broker bash_start/bash_output/bash_end frames). Mirrors
388
+ // pi interactive mode's `!` handling: create the component on start, stream
389
+ // chunks into it, mark complete on end. The persisted `bashExecution` message
390
+ // also rides the next snapshot, so this only drives the LIVE view.
391
+ // -------------------------------------------------------------------------
392
+ bashStart(command, excludeFromContext) {
393
+ const component = new BashExecutionComponent(command, this.tui, excludeFromContext);
394
+ this.bashComponent = component;
395
+ this.append(component);
396
+ this.tui.requestRender();
397
+ }
398
+ bashOutput(chunk) {
399
+ this.bashComponent?.appendOutput(chunk);
400
+ this.tui.requestRender();
401
+ }
402
+ bashEnd(result) {
403
+ this.bashComponent?.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true } : undefined, result.fullOutputPath);
404
+ this.bashComponent = undefined;
405
+ this.tui.requestRender();
406
+ }
407
+ // -------------------------------------------------------------------------
385
408
  // Rebuild path (ports addMessageToChat, interactive-mode.js:2483).
386
409
  // -------------------------------------------------------------------------
387
410
  addMessageToChat(message) {
@@ -63,11 +63,15 @@ export interface AttachPalette {
63
63
  error: (s: string) => string;
64
64
  /** Warning / transient notices. Same constraint as `error` → ANSI yellow. */
65
65
  warning: (s: string) => string;
66
- /** Editor border when the input is in shell/bash mode (leading `!`). pi paints
67
- * this from its `bashMode` theme color (default `green`), which pi does NOT
68
- * re-export through its public surface so, like `error`/`warning`, this is
69
- * the semantically-correct ANSI green, not an ad-hoc hardcode. */
66
+ /** Editor border for a SINGLE-`!` bash command (output IS added to the agent's
67
+ * context). Dull/normal ANSI green pi paints shell mode from its `bashMode`
68
+ * theme color (default `green`), which pi does NOT re-export through its public
69
+ * surface, so like `error`/`warning` this is the semantically-correct ANSI green. */
70
70
  bashMode: (s: string) => string;
71
+ /** Editor border for a `!!` bash command (output is HIDDEN from the agent).
72
+ * BRIGHT ANSI green, so the more consequential "agent won't see this" form
73
+ * reads as visually hotter than the plain `!`. */
74
+ bashModeAlt: (s: string) => string;
71
75
  }
72
76
  /**
73
77
  * Build the attach chrome's color palette from the LIVE theme. Call AFTER
@@ -206,6 +206,7 @@ const FAINT = (s) => `\x1b[2m${s}\x1b[22m`;
206
206
  const RED = (s) => `\x1b[31m${s}\x1b[39m`;
207
207
  const YELLOW = (s) => `\x1b[33m${s}\x1b[39m`;
208
208
  const GREEN = (s) => `\x1b[32m${s}\x1b[39m`;
209
+ const BRIGHT_GREEN = (s) => `\x1b[92m${s}\x1b[39m`;
209
210
  /**
210
211
  * Build the attach chrome's color palette from the LIVE theme. Call AFTER
211
212
  * `applyTheme()` — the colors are pulled from pi's `getMarkdownTheme()` /
@@ -237,6 +238,7 @@ export function attachPalette() {
237
238
  error: RED,
238
239
  warning: YELLOW,
239
240
  bashMode: GREEN,
241
+ bashModeAlt: BRIGHT_GREEN,
240
242
  surface: (line) => `${bgOn}${line.replace(/\x1b\[0m/g, `\x1b[0m${bgOn}`)}\x1b[49m`,
241
243
  };
242
244
  }
@@ -6,9 +6,8 @@ import type { RpcExtensionUIRequest, RpcExtensionUIResponse } from '../../core/r
6
6
  export interface DialogHandle {
7
7
  dismiss(): void;
8
8
  }
9
- /** The 4 user-blocking dialog methods. The other 5 RpcExtensionUIRequest methods
10
- * are non-blocking display ops (notify/setStatus/setWidget/setTitle/
11
- * set_editor_text) the broker never forwards as a request. */
9
+ /** The 4 user-blocking dialog methods. Other RpcExtensionUIRequest methods are
10
+ * non-blocking display ops; callers handle them before rendering a dialog. */
12
11
  export declare function isBlockingDialog(req: RpcExtensionUIRequest): boolean;
13
12
  /**
14
13
  * Render a blocking extension dialog as a focused overlay over `tui`, and resolve
@@ -5,12 +5,11 @@
5
5
  // with pi's matching exported component and ships the resolved
6
6
  // `extension_ui_response` (RpcExtensionUIResponse) back over the socket.
7
7
  //
8
- // Only the 4 blocking methods carry behavior and they are the only ones the
9
- // broker ever routes as a request: `makeBrokerUiContext` (broker.ts) no-ops
10
- // notify/setStatus/setWidget/setTitle/set_editor_text, so those never arrive as
11
- // a blocking dialog. `isBlockingDialog` lets the caller filter cleanly; for a
12
- // non-blocking method, `renderDialog` returns an inert handle (no overlay, no
13
- // response — there is nothing to answer).
8
+ // Only the 4 blocking methods render overlays here. Non-blocking display methods
9
+ // are handled by the viewer before calling `renderDialog` (notify becomes the
10
+ // normal notice line; status/widget/title use dedicated display frames). If one
11
+ // still reaches `renderDialog`, it returns an inert handle no overlay, no
12
+ // response, because there is nothing to answer.
14
13
  //
15
14
  // Mapping (mirrors interactive-mode.js): select → ExtensionSelectorComponent;
16
15
  // confirm → the SAME selector over ["Yes","No"] (pi has no separate confirm
@@ -22,9 +21,8 @@
22
21
  // responding (the response would be a no-op the broker drops by id).
23
22
  import { KeybindingsManager, TUI_KEYBINDINGS, } from '@earendil-works/pi-tui';
24
23
  import { ExtensionEditorComponent, ExtensionInputComponent, ExtensionSelectorComponent, } from '@earendil-works/pi-coding-agent';
25
- /** The 4 user-blocking dialog methods. The other 5 RpcExtensionUIRequest methods
26
- * are non-blocking display ops (notify/setStatus/setWidget/setTitle/
27
- * set_editor_text) the broker never forwards as a request. */
24
+ /** The 4 user-blocking dialog methods. Other RpcExtensionUIRequest methods are
25
+ * non-blocking display ops; callers handle them before rendering a dialog. */
28
26
  export function isBlockingDialog(req) {
29
27
  return (req.method === 'select' ||
30
28
  req.method === 'confirm' ||
@@ -64,8 +64,9 @@ export class GraphOverlay {
64
64
  return;
65
65
  this.scrollTop = 0;
66
66
  this.pendingConfirm = undefined;
67
- if (this.cursorId === undefined || cNode(this.cursorId) === null)
68
- this.cursorId = this.self;
67
+ // Open with the cursor on the CURRENT (attached) node every time, not wherever
68
+ // it was left on the last open — the overlay should always default to self.
69
+ this.cursorId = this.self;
69
70
  this.handle = this.tui.showOverlay(this, OVERLAY_OPTIONS);
70
71
  this.tui.requestRender();
71
72
  }
@@ -64,9 +64,10 @@ export declare class InputController {
64
64
  /** Timestamp of the last lone Esc, for double-tap → tree detection (0 = none). */
65
65
  private lastEscapeAt;
66
66
  constructor(tui: TUI, editor: CustomEditor, keybindings: KeybindingsManager, hooks: InputControllerHooks);
67
- /** Render a blocking extension dialog and route its answer to the broker.
68
- * Supersedes any dialog already on screen (e.g. a re-route on control
69
- * handoff) by dismissing it first. */
67
+ /** Render extension UI requests from the broker. Blocking dialogs route their
68
+ * answer back to the broker; non-blocking notify requests become the viewer's
69
+ * normal notice line. Supersedes any dialog already on screen (e.g. a re-route
70
+ * on control handoff) by dismissing it first. */
70
71
  attachDialog(req: RpcExtensionUIRequest): void;
71
72
  /** Feed the latest engine state so read-only commands (e.g. `/session`) report
72
73
  * current values. Optional; the controller works without it. */
@@ -56,10 +56,17 @@ export class InputController {
56
56
  this.hooks = hooks;
57
57
  this.wire();
58
58
  }
59
- /** Render a blocking extension dialog and route its answer to the broker.
60
- * Supersedes any dialog already on screen (e.g. a re-route on control
61
- * handoff) by dismissing it first. */
59
+ /** Render extension UI requests from the broker. Blocking dialogs route their
60
+ * answer back to the broker; non-blocking notify requests become the viewer's
61
+ * normal notice line. Supersedes any dialog already on screen (e.g. a re-route
62
+ * on control handoff) by dismissing it first. */
62
63
  attachDialog(req) {
64
+ if (req.method === 'notify') {
65
+ const prefix = req.notifyType === 'error' ? 'Error: ' : req.notifyType === 'warning' ? 'Warning: ' : '';
66
+ this.notify(`${prefix}${req.message}`);
67
+ this.tui.requestRender();
68
+ return;
69
+ }
63
70
  this.dialog?.dismiss();
64
71
  this.dialog = renderDialog(this.tui, req, (resp) => {
65
72
  this.dialog = undefined;
@@ -292,6 +299,20 @@ export class InputController {
292
299
  return;
293
300
  }
294
301
  }
302
+ // `!cmd` / `!!cmd` → run bash in the engine and inject output into context,
303
+ // WITHOUT starting an agent turn (pi-native `!` semantics). `!!` withholds
304
+ // the output from the LLM. The broker relays the run as bash_* frames.
305
+ if (trimmed.startsWith('!')) {
306
+ const excludeFromContext = trimmed.startsWith('!!');
307
+ const command = trimmed.slice(excludeFromContext ? 2 : 1).trim();
308
+ if (command === '')
309
+ return; // bare `!`/`!!`: nothing to run
310
+ if (!this.emitDrive({ type: 'bash', command, excludeFromContext }))
311
+ return;
312
+ this.editor.addToHistory(trimmed);
313
+ this.editor.setText('');
314
+ return;
315
+ }
295
316
  // Submit-while-streaming = steer (pi-native parity): interject into the
296
317
  // running turn instead of queueing a fresh prompt. `isStreaming` is the
297
318
  // broker snapshot's busy signal; the broker routes `steer` → session.steer()
@@ -44,13 +44,13 @@ export interface SlashContext {
44
44
  /** Open the native /logout provider selector. */
45
45
  openLogoutPicker?: () => void;
46
46
  }
47
- /** Canvas slash-commands — reimplemented NATIVE in the viewer (the canvas chrome
48
- * pi-extensions cannot load here; see findings-chrome-parity.md Feature 3). Each
49
- * ports the action of its extension: `/promote` canvas-commands.ts, `/resume-node`
50
- * canvas-resume.ts, `/view` ← canvas-view.ts, `/graph` canvas-nav.ts. Three of
51
- * them shell a `crtr` subcommand via `tmux display-popup` (the viewer runs inside a
52
- * tmux pane, so this works exactly as in the extensions); `/graph` is a stub for the
53
- * overlay (Unit C/Q). Exported so Unit Q can fold these into the autocomplete list. */
47
+ /** Canvas slash-commands — implemented NATIVE in the viewer, the ONLY live host:
48
+ * the broker binds mode:'print', so broker pi-extensions never run their
49
+ * interactive (mode==='tui') paths. `/promote` triggers a turn after shelling
50
+ * `crtr node promote`; `/resume-node`, `/view`, `/context` each shell a `crtr`
51
+ * subcommand via `tmux display-popup` (the viewer runs inside a tmux pane);
52
+ * `/graph` toggles the in-process overlay. Exported so the editor folds these
53
+ * into the slash autocomplete list. */
54
54
  export declare const CANVAS_SLASH_COMMANDS: ReadonlyArray<{
55
55
  name: string;
56
56
  description: string;
@@ -24,18 +24,19 @@
24
24
  import { execFile } from 'node:child_process';
25
25
  import { BUILTIN_SLASH_COMMANDS } from '../../core/runtime/pi-vendored.js';
26
26
  import { surfaceTmuxStyleArgs } from '../../core/runtime/surface-bg.js';
27
- /** Canvas slash-commands — reimplemented NATIVE in the viewer (the canvas chrome
28
- * pi-extensions cannot load here; see findings-chrome-parity.md Feature 3). Each
29
- * ports the action of its extension: `/promote` canvas-commands.ts, `/resume-node`
30
- * canvas-resume.ts, `/view` ← canvas-view.ts, `/graph` canvas-nav.ts. Three of
31
- * them shell a `crtr` subcommand via `tmux display-popup` (the viewer runs inside a
32
- * tmux pane, so this works exactly as in the extensions); `/graph` is a stub for the
33
- * overlay (Unit C/Q). Exported so Unit Q can fold these into the autocomplete list. */
27
+ /** Canvas slash-commands — implemented NATIVE in the viewer, the ONLY live host:
28
+ * the broker binds mode:'print', so broker pi-extensions never run their
29
+ * interactive (mode==='tui') paths. `/promote` triggers a turn after shelling
30
+ * `crtr node promote`; `/resume-node`, `/view`, `/context` each shell a `crtr`
31
+ * subcommand via `tmux display-popup` (the viewer runs inside a tmux pane);
32
+ * `/graph` toggles the in-process overlay. Exported so the editor folds these
33
+ * into the slash autocomplete list. */
34
34
  export const CANVAS_SLASH_COMMANDS = [
35
35
  { name: 'graph', description: 'Toggle the canvas GRAPH view (your local subscription tree)' },
36
36
  { name: 'promote', description: 'Promote this node to an orchestrator — /promote, or /promote <kind> to specialize' },
37
37
  { name: 'resume-node', description: 'Open the canvas navigator (search/scope/sort/tree) and resume the chosen node' },
38
38
  { name: 'view', description: 'Open a view in a popup — bare for the picker, or /view <name> to open that view directly' },
39
+ { name: 'context', description: 'Browse this node + its subscriptions’ context dirs in a read-only nvim popup' },
39
40
  ];
40
41
  const CANVAS_NAMES = new Set(CANVAS_SLASH_COMMANDS.map((c) => c.name));
41
42
  /** Builtins with no Phase-4 engine method — scoped out (review m2). */
@@ -245,19 +246,24 @@ function dispatchCanvasCommand(name, arg, ctx) {
245
246
  promoteNode(arg, ctx);
246
247
  return true;
247
248
  case 'resume-node':
248
- // canvas-resume.ts: open the full-screen canvas navigator as a popup; on
249
- // Enter it focuses the chosen node back into THIS pane via `crtr node focus`.
249
+ // Open the full-screen canvas navigator as a popup; on Enter it focuses the
250
+ // chosen node back into THIS pane via `crtr node focus`.
250
251
  return resumeNode(ctx);
251
252
  case 'view':
252
- // canvas-view.ts: bare `/view` opens the picker, `/view <name>` opens that
253
- // view directly — each as a self-contained popup (no node-focus, no return-pane).
253
+ // Bare `/view` opens the picker, `/view <name>` opens that view directly —
254
+ // each as a self-contained popup (no node-focus, no return-pane).
254
255
  return openView(arg, ctx);
256
+ case 'context':
257
+ // A read-only nvim explorer over this node's + its subscriptions' context
258
+ // dirs, as a self-contained popup.
259
+ return openContext(ctx);
255
260
  default:
256
261
  ctx.notify(`/${name} is not available in attach`);
257
262
  return true;
258
263
  }
259
264
  }
260
- /** `/promote [kind]` canvas-commands.ts. Shells `crtr node promote --json` for
265
+ /** `/promote [kind]` the live promote path (the broker is headless, so promotion
266
+ * has no in-engine command). Shells `crtr node promote --json` for
261
267
  * this node (out-of-process), notifies the result, then triggers a turn so the
262
268
  * persona injector steers in the new-role guidance at the turn boundary — exactly
263
269
  * as the node would by running the command itself. */
@@ -309,7 +315,7 @@ function promoteNode(arg, ctx) {
309
315
  });
310
316
  });
311
317
  }
312
- /** `/resume-node` ← canvas-resume.ts. Opens `crtr canvas browse` as a tmux popup,
318
+ /** `/resume-node`. Opens `crtr canvas browse` as a tmux popup,
313
319
  * scoped to this node's cwd; on Enter it focuses the chosen node back into this
314
320
  * pane via `crtr node focus --pane` (which `canvas browse` shells). */
315
321
  function resumeNode(ctx) {
@@ -323,7 +329,7 @@ function resumeNode(ctx) {
323
329
  popup(cmd);
324
330
  return true;
325
331
  }
326
- /** `/view [name]` ← canvas-view.ts. Bare opens the picker (`crtr view pick`);
332
+ /** `/view [name]`. Bare opens the picker (`crtr view pick`);
327
333
  * `/view <name>` opens that view directly (`crtr view run <name>`). Self-contained
328
334
  * popup — no node-focus, no return-pane. */
329
335
  function openView(arg, ctx) {
@@ -336,6 +342,24 @@ function openView(arg, ctx) {
336
342
  popup(cmd);
337
343
  return true;
338
344
  }
345
+ /** `/context`. Opens `crtr node context` as a popup — a
346
+ * read-only nvim explorer over this node's + its subscriptions' context dirs.
347
+ * Self-contained popup — no node-focus, no return-pane. */
348
+ function openContext(ctx) {
349
+ if (process.env['TMUX'] === undefined) {
350
+ ctx.notify('/context needs tmux');
351
+ return true;
352
+ }
353
+ // The popup runs server-side and won't reliably inherit CRTR_NODE_ID, so pass
354
+ // this viewer's node id explicitly (mirrors /promote, /resume-node).
355
+ const nodeId = (ctx.nodeId ?? process.env['CRTR_NODE_ID'] ?? '').trim();
356
+ if (nodeId === '') {
357
+ ctx.notify('/context: no node to root on (viewer has no node id)');
358
+ return true;
359
+ }
360
+ popup(`crtr node context --node ${shellQuote(nodeId)}`);
361
+ return true;
362
+ }
339
363
  /** Open `cmd` as a fire-and-forget tmux display-popup (same geometry as the canvas
340
364
  * extensions). tmux runs the trailing string through `sh -c`; the popup owns its
341
365
  * screen and closes itself when the command exits, dropping back to this pane. */
@@ -7,22 +7,26 @@
7
7
  // Both are pure render-layer chrome; nothing here touches the socket or session.
8
8
  import { CustomEditor } from '@earendil-works/pi-coding-agent';
9
9
  import { truncateToWidth, visibleWidth, } from '@earendil-works/pi-tui';
10
- /** Per-level border/chip color as a 24-bit RGB triple. Each level ROTATES HUE by
11
- * a large step (~35–45°) along a "heat" ramp indigo violet → magenta → pink
12
- * red-orange so adjacent levels are unmistakably different at a glance (a
13
- * constant-hue saturation climb read as nearly identical; see #8). All sit at
14
- * roughly constant, high-ish lightness so each is vivid on a dark terminal, and
15
- * the cool→hot direction maps intuitively onto a climbing thinking budget. (pi
16
- * paints its own `thinking*` theme colors off its public palette surface, so we
17
- * own this ramp directly — truecolor, not the 16-color ANSI hues, so the hue
18
- * sweep actually reads.) `off` falls back to the caller's default border color. */
10
+ /** The thinking color scale is a deliberate cool→warm ramp between a blue and
11
+ * its orange complement, evenly spaced across the five active levels so each
12
+ * step stays readable without jumping through unrelated hues. `off` falls
13
+ * back to the caller's default border color. */
14
+ const THINKING_START = [68, 120, 228]; // blue
15
+ const THINKING_END = [228, 152, 68]; // orange
16
+ function interpolateRgb(start, end, t) {
17
+ return [
18
+ Math.round(start[0] + (end[0] - start[0]) * t),
19
+ Math.round(start[1] + (end[1] - start[1]) * t),
20
+ Math.round(start[2] + (end[2] - start[2]) * t),
21
+ ];
22
+ }
19
23
  const THINKING_RGB = {
20
24
  off: undefined, // default border color
21
- minimal: [96, 132, 220], // indigo / cornflower blue
22
- low: [150, 110, 214], // violet
23
- medium: [200, 98, 196], // magenta
24
- high: [224, 102, 142], // pink-red
25
- xhigh: [228, 120, 84], // red-orange
25
+ minimal: interpolateRgb(THINKING_START, THINKING_END, 0 / 4),
26
+ low: interpolateRgb(THINKING_START, THINKING_END, 1 / 4),
27
+ medium: interpolateRgb(THINKING_START, THINKING_END, 2 / 4),
28
+ high: interpolateRgb(THINKING_START, THINKING_END, 3 / 4),
29
+ xhigh: interpolateRgb(THINKING_START, THINKING_END, 4 / 4),
26
30
  };
27
31
  /** The default (thinking `off`) title chip: reverse video + a space of padding
28
32
  * each side, so the name reads as a label sitting on the border rule. Used as
@@ -46,6 +46,7 @@ export declare class ViewSocketClient extends EventEmitter {
46
46
  * frame in {@link onData}, the request's timeout, or socket teardown. */
47
47
  private pending;
48
48
  constructor(nodeId: string);
49
+ private recordSocketFault;
49
50
  /** Issue a correlated read-op and resolve with the broker's `data` reply (or
50
51
  * reject on the correlated `error`, a timeout, or socket teardown). Mints the
51
52
  * `id`, sends `{...frame, id}`, and parks a resolver consumed by {@link onData}.
@@ -54,7 +55,7 @@ export declare class ViewSocketClient extends EventEmitter {
54
55
  /** Reject + clear every in-flight request (socket gone / decode error) so a
55
56
  * picker fetch never hangs past the connection it rode on. */
56
57
  private rejectAllPending;
57
- /** The broker binds `join(nodeDir(id), 'view.sock')` — resolve it the same way. */
58
+ /** The broker binds `viewSocketPath(id)` — resolve it the same way. */
58
59
  get socketPath(): string;
59
60
  /** Open the connection. Wire up listeners; emits `connect` on success or
60
61
  * `error` (a {@link BrokerUnavailableError} for ECONNREFUSED/ENOENT) on
@@ -20,8 +20,9 @@
20
20
  import { EventEmitter } from 'node:events';
21
21
  import { randomUUID } from 'node:crypto';
22
22
  import { createConnection } from 'node:net';
23
- import { join } from 'node:path';
24
- import { nodeDir } from '../../core/canvas/paths.js';
23
+ import { viewSocketPath } from '../../core/canvas/paths.js';
24
+ import { classify } from '../../core/fault-classifier.js';
25
+ import { clearFault, recordFault } from '../../core/runtime/fault.js';
25
26
  import { CLIENT_READ_CAPS, encodeFrame, FrameDecoder, FrameOverflowError, } from '../../core/runtime/broker-protocol.js';
26
27
  /** How long {@link ViewSocketClient.request} waits for the correlated reply before
27
28
  * rejecting — bounds a pending picker fetch if the broker drops the request
@@ -64,6 +65,18 @@ export class ViewSocketClient extends EventEmitter {
64
65
  super();
65
66
  this.nodeId = nodeId;
66
67
  }
68
+ recordSocketFault(op, err) {
69
+ const classified = classify('viewer↔broker', err);
70
+ recordFault(this.nodeId, {
71
+ link: 'viewer↔broker',
72
+ op,
73
+ kind: classified.kind,
74
+ retry: op === 'redial'
75
+ ? { disposition: 'auto', by: 'client' }
76
+ : { disposition: 'manual' },
77
+ message: err instanceof Error ? err.message : String(err),
78
+ });
79
+ }
67
80
  /** Issue a correlated read-op and resolve with the broker's `data` reply (or
68
81
  * reject on the correlated `error`, a timeout, or socket teardown). Mints the
69
82
  * `id`, sends `{...frame, id}`, and parks a resolver consumed by {@link onData}.
@@ -98,9 +111,9 @@ export class ViewSocketClient extends EventEmitter {
98
111
  }
99
112
  this.pending.clear();
100
113
  }
101
- /** The broker binds `join(nodeDir(id), 'view.sock')` — resolve it the same way. */
114
+ /** The broker binds `viewSocketPath(id)` — resolve it the same way. */
102
115
  get socketPath() {
103
- return join(nodeDir(this.nodeId), 'view.sock');
116
+ return viewSocketPath(this.nodeId);
104
117
  }
105
118
  /** Open the connection. Wire up listeners; emits `connect` on success or
106
119
  * `error` (a {@link BrokerUnavailableError} for ECONNREFUSED/ENOENT) on
@@ -108,9 +121,12 @@ export class ViewSocketClient extends EventEmitter {
108
121
  connect() {
109
122
  const socket = createConnection(this.socketPath);
110
123
  this.socket = socket;
111
- socket.on('connect', () => this.emit('connect'));
124
+ socket.on('connect', () => {
125
+ clearFault(this.nodeId, { link: 'viewer↔broker' });
126
+ this.emit('connect');
127
+ });
112
128
  socket.on('data', (chunk) => this.onData(chunk));
113
- socket.on('error', (err) => this.onError(err));
129
+ socket.on('error', (err) => this.onError(err, 'connect'));
114
130
  socket.on('close', () => this.onClose());
115
131
  }
116
132
  /** Re-establish the socket after a broker exit (a yield→revive cycle), on the
@@ -132,16 +148,18 @@ export class ViewSocketClient extends EventEmitter {
132
148
  if (settled)
133
149
  return;
134
150
  settled = true;
151
+ clearFault(this.nodeId, { link: 'viewer↔broker' });
135
152
  this.emit('connect');
136
153
  resolve();
137
154
  });
138
155
  socket.on('data', (chunk) => this.onData(chunk));
139
156
  socket.on('error', (err) => {
140
157
  if (settled) {
141
- this.onError(err);
158
+ this.onError(err, 'redial');
142
159
  return;
143
160
  }
144
161
  settled = true;
162
+ this.recordSocketFault('redial', err);
145
163
  try {
146
164
  socket.destroy();
147
165
  }
@@ -214,7 +232,8 @@ export class ViewSocketClient extends EventEmitter {
214
232
  this.emit('frame', frame);
215
233
  }
216
234
  }
217
- onError(err) {
235
+ onError(err, op) {
236
+ this.recordSocketFault(op, err);
218
237
  if (err.code === 'ECONNREFUSED' || err.code === 'ENOENT') {
219
238
  this.emitError(new BrokerUnavailableError(this.nodeId));
220
239
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ import assert from 'node:assert/strict';
2
+ import { test } from 'node:test';
3
+ import { isCoalescableSourceRequest } from '../source-cache.js';
4
+ const exec = (args, extra = {}) => ({ kind: 'exec', bin: 'crtr', args, ...extra });
5
+ test('coalesces the idempotent crtr --json reads the SSE burst fans out', () => {
6
+ // Regression: a single SSE invalidation re-issued human/view/node reads as
7
+ // duplicate subprocesses because shouldCache only matched `canvas snapshot`.
8
+ assert.equal(isCoalescableSourceRequest(exec(['canvas', 'snapshot', '--json'])), true);
9
+ assert.equal(isCoalescableSourceRequest(exec(['human', 'list', '--json'])), true);
10
+ assert.equal(isCoalescableSourceRequest(exec(['view', 'list', '--json'])), true);
11
+ assert.equal(isCoalescableSourceRequest(exec(['node', 'snapshot', 'abc-123', '--json'])), true);
12
+ assert.equal(isCoalescableSourceRequest(exec(['node', 'inspect', 'show', 'abc-123', '--json'])), true);
13
+ });
14
+ test('does NOT coalesce writes, side-effecting, or target-mismatched requests', () => {
15
+ // Writes — must never be cached (would drop a mutation).
16
+ assert.equal(isCoalescableSourceRequest(exec(['node', 'new', '--json'], { stdin: 'prompt' })), false);
17
+ assert.equal(isCoalescableSourceRequest(exec(['canvas', 'revive', 'abc', '--json'])), false);
18
+ assert.equal(isCoalescableSourceRequest(exec(['node', 'msg', 'abc', '--json'], { stdin: 'hi' })), false);
19
+ // human resolve / deck are on-demand writes, not the cached list read.
20
+ assert.equal(isCoalescableSourceRequest(exec(['human', 'resolve', 'q1', '--json'])), false);
21
+ // Any stdin or cwd override disqualifies even an allowlisted command shape.
22
+ assert.equal(isCoalescableSourceRequest(exec(['canvas', 'snapshot', '--json'], { stdin: 'x' })), false);
23
+ assert.equal(isCoalescableSourceRequest(exec(['view', 'list', '--json'], { cwd: '/elsewhere' })), false);
24
+ // Non-exec requests are never cacheable.
25
+ assert.equal(isCoalescableSourceRequest({ kind: 'file', path: '/tmp/x' }), false);
26
+ assert.equal(isCoalescableSourceRequest({ kind: 'http', method: 'GET', url: 'http://x' }), false);
27
+ // Non-crtr bin, missing --json, or wrong arity must not match.
28
+ assert.equal(isCoalescableSourceRequest(exec(['human', 'list', '--json'], { bin: 'sh' })), false);
29
+ assert.equal(isCoalescableSourceRequest(exec(['human', 'list'])), false);
30
+ assert.equal(isCoalescableSourceRequest(exec(['node', 'snapshot', '--json'])), false);
31
+ assert.equal(isCoalescableSourceRequest(exec(['node', 'inspect', 'show', '--json'])), false);
32
+ });
@@ -41,6 +41,7 @@ export async function createDevServer(httpServer) {
41
41
  alias: {
42
42
  react: join(nodeModules, 'react'),
43
43
  'react-dom': join(nodeModules, 'react-dom'),
44
+ '@': shellRoot,
44
45
  // The crtr web runtime is a self-subpath of crouter's own package;
45
46
  // pin it to the built barrel so the shell can import it from any root.
46
47
  '@crouton-kit/crouter/web': join(packageRoot, 'dist', 'web', 'index.js'),
@@ -1,11 +1,10 @@
1
- // events.ts — the `crtr web` realtime push lane (the §7 SSE seam, built for v1).
1
+ // events.ts — the `crtr web` realtime push lane.
2
2
  //
3
- // ONE server-sent-events stream, `GET /__crtr/events`, that carries CHANGE
4
- // INVALIDATIONS — never data. The shell / its ViewPanes subscribe and, on an
5
- // event, re-pull through their existing Sources (the bridge) instead of waiting
6
- // on a fixed poll tick. SSE carries "something in this class changed"; the
7
- // consumer decides what to re-read. This keeps the event vocabulary tiny and
8
- // generic and the contract unchanged (a ViewPane still just runs `refresh`).
3
+ // ONE server-sent-events stream, `GET /__crtr/events`, that carries change
4
+ // invalidations — never data. The shell and built-in views subscribe and, on an
5
+ // event, re-pull through their existing bridge reads instead of waiting on a
6
+ // fixed poll tick. SSE carries "something in this class changed"; the consumer
7
+ // decides what to re-read.
9
8
  //
10
9
  // Event vocabulary (deliberately minimal): { kind: 'nodes' | 'inbox', ts }.
11
10
  // - 'nodes' — the node graph changed (topology, status/intent transitions,
@@ -15,10 +14,9 @@
15
14
  // - 'inbox' — a node's inbox.jsonl gained a message. Source: a recursive watch
16
15
  // on the nodes/ tree, filtered to `inbox.jsonl` writes.
17
16
  //
18
- // WHY these sources (the §7 seam left the event source open): they are the
19
- // cheapest things that detect node-graph + inbox changes without a daemon hook
20
- // or a db trigger — pure fs.watch on the two on-disk authorities (canvas.db for
21
- // the graph, inbox.jsonl appends for messages). No polling, no canvas.db reads.
17
+ // WHY these sources: they are the cheapest things that detect node-graph +
18
+ // inbox changes without a daemon hook or a db trigger pure fs.watch on the
19
+ // two on-disk authorities (canvas.db for the graph, inbox.jsonl for messages).
22
20
  import { watch } from 'node:fs';
23
21
  import { crtrHome, nodesRoot, ensureHome } from '../../core/canvas/paths.js';
24
22
  /** Coalesce a burst of fs events into one emission per kind. WAL writes and
@@ -1,6 +1,7 @@
1
1
  export interface WebServerOptions {
2
2
  port: number;
3
3
  host?: string;
4
+ token?: string;
4
5
  /** --dev: mount a Vite dev server (middleware mode, HMR) for asset serving
5
6
  * instead of the static dist/web-client/ bundle. The bridge + SSE + WS relay
6
7
  * are identical in both modes. */
@@ -12,7 +13,7 @@ export interface RunningWebServer {
12
13
  clientDir: string;
13
14
  close: () => Promise<void>;
14
15
  }
15
- /** Build + start the unified web server. Binds 127.0.0.1 only (remote access is
16
- * out of scope — §9). Resolves once the server is listening. Async because
17
- * --dev awaits a Vite middleware server before listening. */
16
+ /** Build + start the unified web server. Binds the requested host/port and
17
+ * resolves once the server is listening. Async because --dev awaits a Vite
18
+ * middleware server before listening. */
18
19
  export declare function startWebServer(opts: WebServerOptions): Promise<RunningWebServer>;