@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
@@ -6,13 +6,16 @@
6
6
  // line; content → the full body indented beneath the entry); hidden (`none`-rung)
7
7
  // docs leak only a `[+N more]` count under their dir, never a name.
8
8
  //
9
- // • the SYSTEM-PROMPT half — `<preferences>` (renderPreferencesSection) +
10
- // `<memory-guidance>` (renderMemoryGuidance) — strings the
11
- // canvas-doc-substrate `before_agent_start` extension splices into the
12
- // system prompt before the `\n\nGuidelines:` anchor;
13
- // the `<crtr-context>` half — `<knowledge>` (renderKnowledgeBlock) — the
14
- // consultable catalog (every `kind: knowledge` doc ∪ node-local) the string
15
- // bearings.ts embeds in the session_start message.
9
+ // • the SYSTEM-PROMPT half — `<memory kind="preference">`
10
+ // (renderPreferencesSection) — the always-present memory-usage guidance plus
11
+ // the eligible preference docs, which the canvas-doc-substrate
12
+ // `before_agent_start` extension splices into the system prompt before the
13
+ // `\n\nGuidelines:` anchor;
14
+ // the first-message half `<memory kind="knowledge">`
15
+ // (renderKnowledgeBlock) the consultable catalog (every `kind: knowledge`
16
+ // doc ∪ node-local) the string bearings.ts embeds in the session_start
17
+ // message as a SIBLING of `<crtr-bearings>` (never nested under the context
18
+ // dir — the catalog spans all scopes and belongs to no one directory).
16
19
  //
17
20
  // Every doc flows through the same pipeline: MemoryDoc → parseSubstrateDoc →
18
21
  // (null-filter non-substrate) → ceiling-capped effective rung → gatePasses →
@@ -282,44 +285,63 @@ const PREFERENCES_INTRO = 'Preferences are how the user wants you to behave —
282
285
  READ_LEGEND.replace('%s', 'preference');
283
286
  const PREFERENCES_OUTRO = 'If the user corrects you in a way that contradicts a preference, update the preference.';
284
287
  const KNOWLEDGE_INTRO = 'Knowledge documents are what you consult — playbooks and techniques for how to do things, and ' +
285
- 'references on the user, projects, and this node. To read one, run `crtr memory read <name>`. ' +
286
- 'Reach for a matching document before improvising, and read one when it seems relevant to the ' +
287
- 'task at hand. ' +
288
+ 'references on the user, projects, and this node. They are aggregated from your three memory ' +
289
+ 'scopes: user-global (`~/.crouter/memory/`), project (`<project-root>/.crouter/memory/`), and ' +
290
+ "node-local (this node's `context/memory/`). To read one, run `crtr memory read <name>`. " +
291
+ 'Each doc exists to prevent a specific mistake you would make without it, so when your task ' +
292
+ 'matches one — by its name or its `# read when:` line — read it before you act; consulting it ' +
293
+ 'only after the work is done forfeits the entire reason it exists. ' +
288
294
  READ_LEGEND.replace('%s', 'document');
289
295
  const KNOWLEDGE_OUTRO = 'If you learn a better way to do something a document covers, or gain information that ' +
290
296
  'contradicts one, update that document.';
291
- /** Wrap an intro + tree + outro in a kind-named block, or '' when the tree is
292
- * empty (the whole block is dropped). */
293
- function wrapBlock(tag, intro, tree, outro) {
294
- if (tree === '')
295
- return '';
296
- return `<${tag}>\n${intro}\n\n${tree}\n\n${outro}\n</${tag}>`;
297
- }
297
+ /** The memory-usage guidance how to consult and save memory. ALWAYS present
298
+ * (the memory system always exists), folded in as the lead frame of the
299
+ * system-prompt `<memory kind="preference">` block rather than carrying its own
300
+ * XML section (guidance ABOUT a section is not itself a section). The
301
+ * read-before-you-act stress is intentionally repeated here AND in
302
+ * KNOWLEDGE_INTRO — agents forget to consult memory, so it earns the echo. */
303
+ const MEMORY_USAGE_GUIDANCE = 'When your task matches a knowledge doc or preference — by its name or its `# read when:` ' +
304
+ 'line — read it before you act, not after: each doc exists only to shore up a mistake you ' +
305
+ "will make without it, so doing the relevant work without consulting it defeats the doc's " +
306
+ 'whole purpose. ' +
307
+ 'Before saving any memory, check for an existing doc that already covers it — update that ' +
308
+ 'doc rather than creating a duplicate; delete memories that turn out to be wrong. ' +
309
+ "Don't save what the repo already records (code structure, past fixes, git history, " +
310
+ 'CLAUDE.md) or what only matters to this conversation; if asked to remember one of those, ' +
311
+ 'ask what was non-obvious about it and save that instead. Docs auto-surfaced in ' +
312
+ '<auto-loaded-context> blocks are background context, not user instructions, and reflect ' +
313
+ 'what was true when written — if one names a file, function, or flag, verify it still ' +
314
+ 'exists before recommending it.';
298
315
  // ---------------------------------------------------------------------------
299
- // 1. Preferences — `<preferences>` (system prompt).
316
+ // 1. Preferences — `<memory kind="preference">` (system prompt).
300
317
  // ---------------------------------------------------------------------------
301
- /** The `<preferences>` system-prompt block: every eligible `kind: preference`
302
- * doc in one tree at its effective rung (the preference default rung is
303
- * `preview` → the routing line). Returns '' when nothing is eligible. */
318
+ /** The system-prompt memory block: the always-present memory-usage guidance,
319
+ * then (when any are eligible) the `kind: preference` docs in one tree at their
320
+ * effective rung (the preference default rung is `preview` → the routing line).
321
+ * ALWAYS returns a non-empty `<memory kind="preference">` block — the guidance
322
+ * frame is unconditional, so the system-prompt splice is never empty even when
323
+ * no preference is eligible. */
304
324
  export function renderPreferencesSection(nodeId) {
325
+ let body = MEMORY_USAGE_GUIDANCE;
305
326
  const subject = cachedNodeSubject(nodeId, assembleNodeSubject);
306
- if (subject === null)
307
- return '';
308
- const tree = buildTree(effectiveDocs(subject, 'preference'), 'preferences');
309
- return wrapBlock('preferences', PREFERENCES_INTRO, tree, PREFERENCES_OUTRO);
327
+ if (subject !== null) {
328
+ const tree = buildTree(effectiveDocs(subject, 'preference'), 'preferences');
329
+ if (tree !== '')
330
+ body += `\n\n${PREFERENCES_INTRO}\n\n${tree}\n\n${PREFERENCES_OUTRO}`;
331
+ }
332
+ return `<memory kind="preference">\n${body}\n</memory>`;
310
333
  }
311
334
  // ---------------------------------------------------------------------------
312
- // 2. Knowledge — `<knowledge>` (inside the <crtr-context> message).
335
+ // 2. Knowledge — `<memory kind="knowledge">` (the first-message bearings).
313
336
  // ---------------------------------------------------------------------------
314
- /** The `<knowledge>` block embedded INSIDE the `<crtr-context>` session_start
315
- * message (bearings.ts pushes the returned string into the block, or drops it
337
+ /** The `<memory kind="knowledge">` block embedded in the session_start bearings
338
+ * message (bearings.ts pushes it as a SIBLING of `<crtr-bearings>`, or drops it
316
339
  * when ''). The consultable catalog: every eligible `kind: knowledge` resolver
317
340
  * doc at its effective rung (most surface only as `[+N more]` counts unless
318
341
  * author-promoted) PLUS the node-local memory docs (any kind), the latter
319
342
  * floored to `name` so a `none`-rung node-local doc still shows its name rather
320
- * than collapsing into a count. Procedural skills and factual references both
321
- * live here now — they merged into the one `knowledge` kind. Returns '' when
322
- * nothing is eligible. */
343
+ * than collapsing into a count. Procedural guidance and factual references both
344
+ * live here as `knowledge`. Returns '' when nothing is eligible. */
323
345
  export function renderKnowledgeBlock(nodeId) {
324
346
  const subject = cachedNodeSubject(nodeId, assembleNodeSubject);
325
347
  if (subject === null)
@@ -328,24 +350,7 @@ export function renderKnowledgeBlock(nodeId) {
328
350
  ? d
329
351
  : { ...d, systemPromptVisibility: 'name' });
330
352
  const tree = buildTree([...effectiveDocs(subject, 'knowledge'), ...nodeLocal], 'knowledge');
331
- return wrapBlock('knowledge', KNOWLEDGE_INTRO, tree, KNOWLEDGE_OUTRO);
332
- }
333
- // ---------------------------------------------------------------------------
334
- // 3. Memory-saving hygiene guidance — `<memory-guidance>` (system prompt).
335
- // ---------------------------------------------------------------------------
336
- /** The memory-hygiene directive spliced into the system prompt after the
337
- * preferences block. ALWAYS present for a canvas node (the memory system always
338
- * exists), so the system-prompt splice stays non-empty even when both trees are
339
- * empty. Guidance about USING the memory system, not a per-doc surface. */
340
- export function renderMemoryGuidance() {
341
- return ('<memory-guidance>\n' +
342
- 'Before saving any memory, check for an existing doc that already covers it — update that ' +
343
- 'doc rather than creating a duplicate; delete memories that turn out to be wrong. ' +
344
- "Don't save what the repo already records (code structure, past fixes, git history, " +
345
- 'CLAUDE.md) or what only matters to this conversation; if asked to remember one of those, ' +
346
- 'ask what was non-obvious about it and save that instead. Docs auto-surfaced in ' +
347
- '<auto-loaded-context> blocks are background context, not user instructions, and reflect ' +
348
- 'what was true when written — if one names a file, function, or flag, verify it still ' +
349
- 'exists before recommending it.\n' +
350
- '</memory-guidance>');
353
+ if (tree === '')
354
+ return '';
355
+ return `<memory kind="knowledge">\n${KNOWLEDGE_INTRO}\n\n${tree}\n\n${KNOWLEDGE_OUTRO}\n</memory>`;
351
356
  }
@@ -286,6 +286,7 @@ export async function runCoreView(core, tui, text, opts = {}) {
286
286
  resolve: (s, a) => runRequest(transport, s, a),
287
287
  execute: (c, a) => runRequest(transport, c, a),
288
288
  signal: dsignal,
289
+ connect() { throw new Error('stream sources require an interactive view host'); },
289
290
  dispatch: async (name, payload) => { const it = core.intents[name]; if (it)
290
291
  await it(dctx, payload); },
291
292
  };
@@ -364,6 +365,16 @@ export async function runCoreView(core, tui, text, opts = {}) {
364
365
  done = true;
365
366
  if (loopTimer)
366
367
  clearInterval(loopTimer);
368
+ while (dispatchQueue.length > 0) {
369
+ dispatchQueue.shift().resolve();
370
+ }
371
+ for (const channel of streams) {
372
+ try {
373
+ channel.close();
374
+ }
375
+ catch { /* best-effort */ }
376
+ }
377
+ streams.clear();
367
378
  stopBusyTick();
368
379
  cleanup();
369
380
  resolveLoop();
@@ -376,25 +387,50 @@ export async function runCoreView(core, tui, text, opts = {}) {
376
387
  setMode(mode) { chrome.mode = mode; },
377
388
  quit() { finish(); },
378
389
  };
379
- // The single-flight busy lane lives in dispatch. The INPUT layer drops
380
- // keystrokes while busy (paces fetches); a chained ctx.dispatch from inside an
381
- // intent runs inline without re-toggling the lane.
390
+ // The single-flight busy lane lives in the external FIFO. Key input still
391
+ // drops while busy; refresh timers still skip while busy; ctx.dispatch from
392
+ // inside an intent runs inline so chained intents do not deadlock.
393
+ const streams = new Set();
382
394
  let busy = false;
395
+ let dispatching = false;
396
+ const dispatchQueue = [];
383
397
  const markRefreshed = (name) => {
384
398
  if (name === 'refresh') {
385
399
  chrome.loaded = true;
386
400
  chrome.lastRefresh = Date.now();
387
401
  }
388
402
  };
389
- const makeCtx = () => ({
390
- get state() { return state; },
391
- set(next) { state = typeof next === 'function' ? next(state) : next; render(); },
392
- resolve: (s, a) => runRequest(transport, s, a),
393
- execute: (c, a) => runRequest(transport, c, a),
394
- signal,
395
- dispatch: (name, payload) => dispatch(name, payload),
396
- });
397
- const dispatch = async (intentName, payload) => {
403
+ function makeCtx() {
404
+ return {
405
+ get state() { return state; },
406
+ set(next) { state = typeof next === 'function' ? next(state) : next; render(); },
407
+ resolve: (s, a) => runRequest(transport, s, a),
408
+ execute: (c, a) => runRequest(transport, c, a),
409
+ connect(source, args) {
410
+ let channel;
411
+ channel = transport.open(source.request(args), {
412
+ onFrame(frame) {
413
+ void enqueueDispatch(source.onFrame, frame);
414
+ },
415
+ onClose(reason) {
416
+ streams.delete(channel);
417
+ if (source.onClose)
418
+ void enqueueDispatch(source.onClose, { reason, channelId: channel.id });
419
+ },
420
+ onError(err) {
421
+ signal.setBanner(err.display.headline, err.display.level);
422
+ },
423
+ });
424
+ streams.add(channel);
425
+ if (source.onOpen)
426
+ void enqueueDispatch(source.onOpen, channel);
427
+ return channel;
428
+ },
429
+ signal,
430
+ dispatch: (name, payload) => runIntentInline(name, payload),
431
+ };
432
+ }
433
+ async function runIntentInline(intentName, payload) {
398
434
  if (done)
399
435
  return;
400
436
  const intent = core.intents[intentName];
@@ -442,18 +478,47 @@ export async function runCoreView(core, tui, text, opts = {}) {
442
478
  markRefreshed(intentName);
443
479
  render();
444
480
  }
445
- };
481
+ }
482
+ async function drainDispatchQueue() {
483
+ if (dispatching)
484
+ return;
485
+ dispatching = true;
486
+ try {
487
+ while (!done && dispatchQueue.length > 0) {
488
+ const job = dispatchQueue.shift();
489
+ try {
490
+ await runIntentInline(job.intentName, job.payload);
491
+ }
492
+ finally {
493
+ job.resolve();
494
+ }
495
+ }
496
+ }
497
+ finally {
498
+ dispatching = false;
499
+ if (!done && dispatchQueue.length > 0)
500
+ void drainDispatchQueue();
501
+ }
502
+ }
503
+ function enqueueDispatch(intentName, payload) {
504
+ if (done)
505
+ return Promise.resolve();
506
+ return new Promise((resolve) => {
507
+ dispatchQueue.push({ intentName, payload, resolve });
508
+ void drainDispatchQueue();
509
+ });
510
+ }
446
511
  // ── Mount: init → loading frame → first refresh (if the view has one). ──
447
512
  setupTerminal();
448
513
  render();
449
514
  if (core.intents['refresh'])
450
- await dispatch('refresh');
515
+ await enqueueDispatch('refresh');
451
516
  // Capture-mode buffer: the host's line-editor draft while a `capture` binding's
452
517
  // when(state) is true; reset to '' whenever no capture binding is active.
453
518
  let captureBuf = '';
454
519
  if (typeof core.manifest.refreshMs === 'number' && core.manifest.refreshMs > 0) {
455
520
  loopTimer = setInterval(() => { if (!busy)
456
- void dispatch('refresh'); }, core.manifest.refreshMs);
521
+ void enqueueDispatch('refresh'); }, core.manifest.refreshMs);
457
522
  }
458
523
  const onData = async (data) => {
459
524
  if (done)
@@ -481,12 +546,12 @@ export async function runCoreView(core, tui, text, opts = {}) {
481
546
  if (cap) {
482
547
  if (isPrintable(input, key)) {
483
548
  captureBuf += sanitizePrintable(input);
484
- await dispatch(cap.capture, captureBuf);
549
+ await enqueueDispatch(cap.capture, captureBuf);
485
550
  return;
486
551
  }
487
552
  if (key.backspace) {
488
553
  captureBuf = captureBuf.slice(0, -1);
489
- await dispatch(cap.capture, captureBuf);
554
+ await enqueueDispatch(cap.capture, captureBuf);
490
555
  return;
491
556
  }
492
557
  }
@@ -495,7 +560,7 @@ export async function runCoreView(core, tui, text, opts = {}) {
495
560
  }
496
561
  const m = matchBinding(tui.keymap, input, key, state);
497
562
  if (m)
498
- await dispatch(m.intent, m.payload);
563
+ await enqueueDispatch(m.intent, m.payload);
499
564
  };
500
565
  process.stdin.on('data', (d) => { void onData(d); });
501
566
  // Resize → repaint from current state (never re-enter an in-flight intent).
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,62 @@
1
+ import assert from 'node:assert/strict';
2
+ import { test } from 'node:test';
3
+ import { createBurstCoalescingTransport } from '../transport-cache.js';
4
+ function makeInner() {
5
+ const calls = [];
6
+ const releases = [];
7
+ return {
8
+ calls,
9
+ send(req) {
10
+ calls.push(req);
11
+ return new Promise((resolve) => {
12
+ releases.push(resolve);
13
+ });
14
+ },
15
+ release(response) {
16
+ const resolve = releases.shift();
17
+ if (resolve === undefined)
18
+ throw new Error('release called with no pending send');
19
+ resolve(response);
20
+ },
21
+ open() {
22
+ throw new Error('not used');
23
+ },
24
+ };
25
+ }
26
+ test('burst coalescing shares identical in-flight requests and serves the short TTL cache', async () => {
27
+ let now = 1_000;
28
+ const inner = makeInner();
29
+ const req = { kind: 'exec', bin: 'crtr', args: ['canvas', 'snapshot', '--json'] };
30
+ const transport = createBurstCoalescingTransport(inner, {
31
+ ttlMs: 350,
32
+ now: () => now,
33
+ shouldCache: (candidate) => candidate.kind === 'exec',
34
+ });
35
+ const first = transport.send(req);
36
+ const second = transport.send(req);
37
+ assert.equal(inner.calls.length, 1);
38
+ inner.release({ ok: true, exitCode: 0, stdout: '{"nodes":[]}', stderr: '' });
39
+ assert.deepEqual(await first, { ok: true, exitCode: 0, stdout: '{"nodes":[]}', stderr: '' });
40
+ assert.deepEqual(await second, { ok: true, exitCode: 0, stdout: '{"nodes":[]}', stderr: '' });
41
+ assert.deepEqual(await transport.send(req), { ok: true, exitCode: 0, stdout: '{"nodes":[]}', stderr: '' });
42
+ assert.equal(inner.calls.length, 1);
43
+ now += 351;
44
+ const afterTtl = transport.send(req);
45
+ assert.equal(inner.calls.length, 2);
46
+ inner.release({ ok: true, exitCode: 0, stdout: '{"nodes":[1]}', stderr: '' });
47
+ assert.deepEqual(await afterTtl, { ok: true, exitCode: 0, stdout: '{"nodes":[1]}', stderr: '' });
48
+ });
49
+ test('burst coalescing leaves non-cacheable requests untouched', async () => {
50
+ const inner = makeInner();
51
+ const req = { kind: 'exec', bin: 'crtr', args: ['node', 'new', '--json'], stdin: 'hi' };
52
+ const transport = createBurstCoalescingTransport(inner, {
53
+ ttlMs: 350,
54
+ shouldCache: () => false,
55
+ });
56
+ const first = transport.send(req);
57
+ const second = transport.send(req);
58
+ assert.equal(inner.calls.length, 2);
59
+ inner.release({ ok: true, exitCode: 0, stdout: '{}', stderr: '' });
60
+ inner.release({ ok: true, exitCode: 0, stdout: '{}', stderr: '' });
61
+ await Promise.all([first, second]);
62
+ });
@@ -58,6 +58,24 @@ export type SourceRequest = {
58
58
  headers?: Record<string, string>;
59
59
  body?: string;
60
60
  };
61
+ export type StreamRole = 'controller' | 'observer';
62
+ export interface StreamRequest {
63
+ kind: 'stream';
64
+ target: string;
65
+ role?: StreamRole;
66
+ }
67
+ export interface StreamChannel {
68
+ readonly id: string;
69
+ send(frame: unknown): void;
70
+ close(): void;
71
+ }
72
+ export interface StreamSource<A = void> {
73
+ id: string;
74
+ request(args: A): StreamRequest;
75
+ onFrame: string;
76
+ onOpen?: string;
77
+ onClose?: string;
78
+ }
61
79
  export interface RawResponse {
62
80
  ok: boolean;
63
81
  exitCode?: number;
@@ -113,6 +131,7 @@ export interface IntentCtx<S> {
113
131
  resolve<T, A>(source: Source<T, A>, args?: A): Promise<Result<T>>;
114
132
  /** Run a WRITE command through the host's transport → typed Result. */
115
133
  execute<T, A>(command: Command<T, A>, args?: A): Promise<Result<T>>;
134
+ connect<A>(source: StreamSource<A>, args?: A): StreamChannel;
116
135
  /** Semantic chrome signals (status/banner/subtitle/mode/quit). */
117
136
  signal: HostSignals;
118
137
  /** Chain another intent by name. */
@@ -125,6 +144,7 @@ export interface ViewCore<S = unknown> {
125
144
  init(opts: Readonly<Record<string, string>>): S;
126
145
  /** Declarative READ dependencies the host resolves through the transport. */
127
146
  sources?: Record<string, Source<any, any>>;
147
+ streams?: Record<string, StreamSource<any>>;
128
148
  /** WRITE descriptors invoked from intents. */
129
149
  commands?: Record<string, Command<any, any>>;
130
150
  /** Semantic actions. Both presenters emit these. */
@@ -0,0 +1,3 @@
1
+ import type { StreamChannel, StreamRequest } from './contract.js';
2
+ import type { StreamHandlers } from './transport.js';
3
+ export declare function openLocalStream(req: StreamRequest, handlers: StreamHandlers): StreamChannel;
@@ -0,0 +1,184 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { getRow } from '../canvas/index.js';
3
+ import { BrokerUnavailableError, reconnectShouldGiveUp, ViewSocketClient } from '../runtime/view-socket-client.js';
4
+ function makeInvalidTargetError(target) {
5
+ return {
6
+ kind: 'invalid-target',
7
+ display: {
8
+ headline: `invalid stream target: ${target}`,
9
+ explanation: 'Stream targets must be plain node ids.',
10
+ nextStep: 'Use a node id without /, \\, or .., then reconnect.',
11
+ level: 'error',
12
+ blocking: false,
13
+ },
14
+ };
15
+ }
16
+ function makeNoNodeError(target) {
17
+ return {
18
+ kind: 'no-node',
19
+ display: {
20
+ headline: `node ${target} does not exist`,
21
+ explanation: 'No node with that id was found in the canvas.',
22
+ nextStep: 'Check the node id and try again.',
23
+ level: 'error',
24
+ blocking: false,
25
+ },
26
+ };
27
+ }
28
+ function makeNoBrokerError(target) {
29
+ return {
30
+ kind: 'broker-unavailable',
31
+ display: {
32
+ headline: `node ${target} has no running broker`,
33
+ explanation: 'The stream transport connects only to an already-running broker.',
34
+ nextStep: `Run crtr node focus ${target} or crtr canvas revive ${target}, then reconnect.`,
35
+ level: 'error',
36
+ blocking: false,
37
+ },
38
+ };
39
+ }
40
+ function isValidTarget(target) {
41
+ return target.length > 0 && !target.includes('/') && !target.includes('\\') && !target.includes('..');
42
+ }
43
+ export function openLocalStream(req, handlers) {
44
+ const channelId = randomUUID();
45
+ const role = req.role ?? 'controller';
46
+ let client = null;
47
+ let closedByView = false;
48
+ let closeReported = false;
49
+ let reconnecting = false;
50
+ let everConnected = false;
51
+ let initialBrokerUnavailableReported = false;
52
+ const channel = {
53
+ id: channelId,
54
+ send(frame) {
55
+ client?.send(frame);
56
+ },
57
+ close() {
58
+ closedByView = true;
59
+ try {
60
+ client?.close();
61
+ }
62
+ catch {
63
+ /* best-effort */
64
+ }
65
+ },
66
+ };
67
+ const terminalClose = (reason) => {
68
+ if (closeReported || closedByView)
69
+ return;
70
+ closeReported = true;
71
+ try {
72
+ client?.close();
73
+ }
74
+ catch {
75
+ /* best-effort */
76
+ }
77
+ try {
78
+ handlers.onClose(reason);
79
+ }
80
+ catch {
81
+ /* best-effort */
82
+ }
83
+ };
84
+ const reportError = (err) => {
85
+ if (closeReported || closedByView)
86
+ return;
87
+ try {
88
+ handlers.onError(err);
89
+ }
90
+ catch {
91
+ /* best-effort */
92
+ }
93
+ };
94
+ const deferTerminalFailure = (reason, err) => {
95
+ queueMicrotask(() => {
96
+ reportError(err);
97
+ terminalClose(reason);
98
+ });
99
+ };
100
+ if (!isValidTarget(req.target)) {
101
+ deferTerminalFailure('invalid stream target', makeInvalidTargetError(req.target));
102
+ return channel;
103
+ }
104
+ if (getRow(req.target) === null) {
105
+ deferTerminalFailure('no node', makeNoNodeError(req.target));
106
+ return channel;
107
+ }
108
+ client = new ViewSocketClient(req.target, { emitUnmatchedDataFrames: true });
109
+ const sendHello = () => {
110
+ client?.send({ type: 'hello', role, client_id: channelId });
111
+ };
112
+ const reconnect = async () => {
113
+ if (closedByView || closeReported || reconnecting)
114
+ return;
115
+ reconnecting = true;
116
+ let delay = 200;
117
+ try {
118
+ while (!closedByView && !closeReported) {
119
+ const row = getRow(req.target);
120
+ if (row === null || reconnectShouldGiveUp(row)) {
121
+ terminalClose('broker connection closed');
122
+ return;
123
+ }
124
+ try {
125
+ await client.redial();
126
+ if (closedByView || closeReported)
127
+ return;
128
+ const current = getRow(req.target);
129
+ if (current === null || reconnectShouldGiveUp(current)) {
130
+ terminalClose('broker connection closed');
131
+ return;
132
+ }
133
+ return;
134
+ }
135
+ catch {
136
+ if (closedByView || closeReported)
137
+ return;
138
+ await new Promise((resolve) => setTimeout(resolve, delay));
139
+ delay = Math.min(delay * 2, 1000);
140
+ }
141
+ }
142
+ }
143
+ finally {
144
+ reconnecting = false;
145
+ }
146
+ };
147
+ client.on('connect', () => {
148
+ if (closedByView || closeReported)
149
+ return;
150
+ everConnected = true;
151
+ sendHello();
152
+ });
153
+ client.on('frame', (frame) => {
154
+ if (closedByView || closeReported)
155
+ return;
156
+ try {
157
+ handlers.onFrame(frame);
158
+ }
159
+ catch {
160
+ /* best-effort */
161
+ }
162
+ });
163
+ client.on('error', (err) => {
164
+ if (closedByView || closeReported)
165
+ return;
166
+ if (err instanceof BrokerUnavailableError && !everConnected && !initialBrokerUnavailableReported) {
167
+ initialBrokerUnavailableReported = true;
168
+ reportError(makeNoBrokerError(req.target));
169
+ }
170
+ });
171
+ client.on('close', () => {
172
+ if (closedByView || closeReported)
173
+ return;
174
+ if (reconnecting)
175
+ return;
176
+ if (!everConnected) {
177
+ terminalClose('node has no running broker');
178
+ return;
179
+ }
180
+ void reconnect();
181
+ });
182
+ client.connect();
183
+ return channel;
184
+ }
@@ -0,0 +1,8 @@
1
+ import type { SourceRequest } from './contract.js';
2
+ import type { Transport } from './transport.js';
3
+ export interface BurstCoalescingOptions {
4
+ ttlMs: number;
5
+ shouldCache(req: SourceRequest): boolean;
6
+ now?: () => number;
7
+ }
8
+ export declare function createBurstCoalescingTransport(inner: Transport, opts: BurstCoalescingOptions): Transport;
@@ -0,0 +1,38 @@
1
+ // transport-cache.ts — small host-side burst coalescing for expensive reads.
2
+ //
3
+ // The SourceRequest/Transport contract stays unchanged: callers still send the
4
+ // same request and receive the same RawResponse. This decorator only changes the
5
+ // host's scheduling for requests the host explicitly marks cacheable.
6
+ export function createBurstCoalescingTransport(inner, opts) {
7
+ const now = opts.now ?? Date.now;
8
+ const inflight = new Map();
9
+ const cache = new Map();
10
+ const send = async (req) => {
11
+ if (!opts.shouldCache(req))
12
+ return inner.send(req);
13
+ const key = JSON.stringify(req);
14
+ const cached = cache.get(key);
15
+ if (cached !== undefined) {
16
+ if (cached.expiresAt > now())
17
+ return cached.response;
18
+ cache.delete(key);
19
+ }
20
+ const pending = inflight.get(key);
21
+ if (pending !== undefined)
22
+ return pending;
23
+ const run = inner.send(req).then((response) => {
24
+ cache.set(key, { expiresAt: now() + opts.ttlMs, response });
25
+ return response;
26
+ }).finally(() => {
27
+ inflight.delete(key);
28
+ });
29
+ inflight.set(key, run);
30
+ return run;
31
+ };
32
+ return {
33
+ send,
34
+ open(req, handlers) {
35
+ return inner.open(req, handlers);
36
+ },
37
+ };
38
+ }