@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
@@ -0,0 +1,86 @@
1
+ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { defineLeaf } from '../../core/command.js';
5
+ import { InputError } from '../../core/io.js';
6
+ import { getNode, jobDir } from '../../core/canvas/index.js';
7
+ import { focusOf, openNodeWindow } from '../../core/runtime/placement.js';
8
+ function shellQuote(s) {
9
+ return `'${s.replace(/'/g, `'\\''`)}'`;
10
+ }
11
+ export const sysSyspromptLeaf = defineLeaf({
12
+ name: 'sysprompt',
13
+ description: 'print a node\'s assembled system prompt',
14
+ whenToUse: 'you want to inspect a node\'s assembled system prompt headlessly, without paying conversation/context cost — the broker writes it to disk while live, and this command reads that artifact back as markdown.',
15
+ help: {
16
+ name: 'sys sysprompt',
17
+ summary: 'print a node\'s assembled system prompt',
18
+ params: [
19
+ { kind: 'positional', name: 'node', required: false, constraint: 'Node id. Defaults to the calling node (CRTR_NODE_ID).' },
20
+ { kind: 'flag', name: 'window', type: 'bool', required: false, constraint: 'Open the assembled prompt in a NEW tmux window in the session currently showing that node instead of printing to stdout.' },
21
+ ],
22
+ output: [
23
+ { name: 'node_id', type: 'string', required: true, constraint: 'The node whose prompt was read.' },
24
+ { name: 'captured_at', type: 'string', required: true, constraint: 'File mtime in ISO-8601 UTC.' },
25
+ { name: 'chars', type: 'number', required: true, constraint: 'Character count of the stored prompt.' },
26
+ { name: 'prompt', type: 'string', required: true, constraint: 'The assembled system prompt, exactly as pi built it.' },
27
+ { name: 'session', type: 'string', required: false, constraint: 'tmux session showing the node (only when --window is used).' },
28
+ { name: 'window', type: 'string', required: false, constraint: 'tmux window opened for the prompt (only when --window is used).' },
29
+ ],
30
+ outputKind: 'object',
31
+ effects: ['Read-only: reads the node\'s job/system-prompt.md artifact, or opens it in a tmux window.'],
32
+ },
33
+ run: async (input) => {
34
+ const nodeId = input['node']?.trim() || process.env['CRTR_NODE_ID'];
35
+ if (nodeId === undefined || nodeId === '') {
36
+ throw new InputError({ error: 'empty_node', message: 'a node id is required', field: 'node', next: 'Pass a node id or run from inside a node.' });
37
+ }
38
+ if (getNode(nodeId) === null) {
39
+ throw new InputError({ error: 'not_found', message: `no node: ${nodeId}`, field: 'node', next: 'List nodes with `crtr node inspect list`.' });
40
+ }
41
+ const path = join(jobDir(nodeId), 'system-prompt.md');
42
+ if (!existsSync(path)) {
43
+ throw new InputError({
44
+ error: 'not_found',
45
+ message: `system prompt not captured yet for ${nodeId}`,
46
+ field: 'node',
47
+ next: 'Focus or revive the node once while its broker is live, then rerun `crtr sys sysprompt <node>`.',
48
+ });
49
+ }
50
+ const prompt = readFileSync(path, 'utf8');
51
+ const capturedAt = statSync(path).mtime.toISOString();
52
+ if (input['window'] === true) {
53
+ const focus = focusOf(nodeId);
54
+ if (focus === null || focus.session === null || focus.session === '') {
55
+ throw new InputError({
56
+ error: 'not_focused',
57
+ message: `node ${nodeId} is not currently shown in tmux`,
58
+ field: 'node',
59
+ next: `Focus the node first with \`crtr node focus ${nodeId}\`, then rerun \`crtr sys sysprompt --window ${nodeId}\`.`,
60
+ });
61
+ }
62
+ const tmpPath = join(tmpdir(), `crtr-sysprompt-${nodeId}.md`);
63
+ writeFileSync(tmpPath, prompt, 'utf8');
64
+ const q = shellQuote(tmpPath);
65
+ // Syntax-highlight the prompt (markdown + its structural XML tags both stay visible).
66
+ // bat pages by default; markdown RENDERERS (termrender, glow) are wrong here — they
67
+ // silently swallow block-level XML tags, dropping most of the assembled prompt.
68
+ // Fall back to `less -R` when bat is not installed.
69
+ const command = `if command -v bat >/dev/null 2>&1; then bat --language=markdown --color=always --paging=always --style=plain ${q}; else less -R ${q}; fi`;
70
+ const opened = openNodeWindow({ session: focus.session, name: `sysprompt:${nodeId}`, cwd: process.cwd(), env: {}, command });
71
+ if (opened === null) {
72
+ throw new InputError({
73
+ error: 'window_open_failed',
74
+ message: `tmux could not open a window for node ${nodeId}`,
75
+ field: 'node',
76
+ next: 'Check the tmux server is reachable, then retry.',
77
+ });
78
+ }
79
+ return { node_id: nodeId, session: focus.session, window: opened.window };
80
+ }
81
+ return { node_id: nodeId, captured_at: capturedAt, chars: prompt.length, prompt };
82
+ },
83
+ render: (r) => ('window' in r)
84
+ ? `Opened system prompt for node ${r['node_id']} in tmux session ${r['session']} — window ${r['window']}.`
85
+ : `- node_id: ${r['node_id']}\n- captured_at: ${r['captured_at']}\n- chars: ${r['chars']}\n\n${r['prompt']}`,
86
+ });
@@ -1,9 +1,13 @@
1
- // `crtr sys` subtree: config {get,set,path}, doctor, update, version.
1
+ // `crtr sys` subtree: config {get,set,path}, doctor, sync, update, version.
2
2
  // Replaces old config.ts + doctor.ts + update.ts command files.
3
3
  import { defineBranch } from '../core/command.js';
4
4
  import { configBranch } from './sys/config.js';
5
5
  import { sysDoctorLeaf } from './sys/doctor.js';
6
6
  import { sysFeedbackLeaf } from './sys/feedback.js';
7
+ import { sysSettingsLeaf } from './sys/settings.js';
8
+ import { sysSyspromptLeaf } from './sys/sysprompt.js';
9
+ import { promptReviewLeaf } from './sys/prompt-review.js';
10
+ import { sysPromptStudioLeaf } from './sys/promptstudio.js';
7
11
  import { sysSyncLeaf } from './sys/sync.js';
8
12
  import { sysUpdateLeaf, sysVersionLeaf } from './sys/update.js';
9
13
  export function registerSys() {
@@ -11,13 +15,13 @@ export function registerSys() {
11
15
  name: 'sys',
12
16
  rootEntry: {
13
17
  concept: 'crtr configuration, diagnostics, and self-management',
14
- desc: 'config, doctor, sync, update, version, feedback',
15
- useWhen: 'managing the crtr installation',
18
+ desc: 'config, settings, doctor, sysprompt, prompt-review, promptstudio, sync, update, version, feedback',
19
+ useWhen: 'managing the crtr installation or opening built-in system views',
16
20
  },
17
21
  help: {
18
22
  name: 'sys',
19
- summary: 'crtr system configuration, diagnostics, and self-management',
23
+ summary: 'crtr system configuration, settings, diagnostics, and self-management',
20
24
  },
21
- children: [configBranch, sysDoctorLeaf, sysFeedbackLeaf, sysSyncLeaf, sysUpdateLeaf, sysVersionLeaf],
25
+ children: [configBranch, sysSettingsLeaf, sysDoctorLeaf, sysSyspromptLeaf, promptReviewLeaf, sysPromptStudioLeaf, sysFeedbackLeaf, sysUpdateLeaf, sysVersionLeaf, sysSyncLeaf],
22
26
  });
23
27
  }
@@ -3,8 +3,8 @@
3
3
  // Writes the four contract files (core.mjs + tui.mjs + web.jsx + text.mjs) under
4
4
  // <viewsDir(scope)>/<name>/ from the stubs in prompts/view.ts (mkdir -p first).
5
5
  // Refuses if a view of that name already resolves. The scaffold runs as-is via
6
- // `crtr view run <name>` (TUI) and `crtr view serve <name>` (web). Mirrors how
7
- // skill authoring scaffolds (resolve scope → ensure dir → write → point at run).
6
+ // `crtr view run <name>` (TUI) and `crtr view serve <name>` (web). Mirrors the
7
+ // scope → ensure dir → write → point-at-run shape of other authoring commands.
8
8
  import { mkdirSync, existsSync } from 'node:fs';
9
9
  import { writeFileSync } from 'node:fs';
10
10
  import { join } from 'node:path';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ import { test, before, beforeEach, after } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { existsSync, mkdtempSync, mkdirSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { interactionsRoot, migrateLegacyWorkspaceDirs, workspaceRoot } from '../artifact.js';
7
+ let home;
8
+ let origHome;
9
+ before(() => {
10
+ home = mkdtempSync(join(tmpdir(), 'crtr-workspaces-'));
11
+ origHome = process.env['HOME'];
12
+ process.env['HOME'] = home;
13
+ });
14
+ beforeEach(() => {
15
+ rmSync(home, { recursive: true, force: true });
16
+ mkdirSync(join(home, '.crouter'), { recursive: true });
17
+ });
18
+ after(() => {
19
+ rmSync(home, { recursive: true, force: true });
20
+ if (origHome === undefined)
21
+ delete process.env['HOME'];
22
+ else
23
+ process.env['HOME'] = origHome;
24
+ });
25
+ test('workspace roots live under ~/.crouter/workspaces/<mangled-cwd>', () => {
26
+ const cwd = '/Users/silasrhyneer/Code/cli/crouter';
27
+ const root = join(home, '.crouter', 'workspaces', '-Users-silasrhyneer-Code-cli-crouter');
28
+ assert.equal(workspaceRoot(cwd), root);
29
+ assert.equal(interactionsRoot(cwd), join(root, 'interactions'));
30
+ });
31
+ test('migrateLegacyWorkspaceDirs moves only legacy mangled cwd dirs', () => {
32
+ const legacy = join(home, '.crouter', '-Users-silasrhyneer-Code-cli-crouter');
33
+ const workspace = join(home, '.crouter', 'workspaces', '-Users-silasrhyneer-Code-cli-crouter');
34
+ const memory = join(home, '.crouter', 'memory');
35
+ const canvas = join(home, '.crouter', 'canvas');
36
+ mkdirSync(legacy, { recursive: true });
37
+ mkdirSync(memory, { recursive: true });
38
+ mkdirSync(canvas, { recursive: true });
39
+ migrateLegacyWorkspaceDirs();
40
+ assert.ok(!existsSync(legacy));
41
+ assert.ok(existsSync(workspace));
42
+ assert.ok(existsSync(memory));
43
+ assert.ok(existsSync(canvas));
44
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,85 @@
1
+ // Run with: node --import tsx/esm --test src/core/__tests__/broker-preflight.test.ts
2
+ //
3
+ // Bug-regression: preflight broker launch validation should resolve the default
4
+ // broker package, reject broken custom engines, and fail before a detached broker
5
+ // process is spawned when session args are invalid or missing.
6
+ import { test, before, after } from 'node:test';
7
+ import assert from 'node:assert/strict';
8
+ import { mkdtempSync, rmSync, readFileSync } from 'node:fs';
9
+ import { tmpdir } from 'node:os';
10
+ import { join, dirname } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { preflightBrokerLaunch } from '../runtime/host.js';
13
+ const HERE = dirname(fileURLToPath(import.meta.url));
14
+ const FAKE_ENGINE = join(HERE, 'fixtures', 'fake-engine.ts');
15
+ let home;
16
+ before(() => {
17
+ home = mkdtempSync(join(tmpdir(), 'crtr-preflight-'));
18
+ process.env['CRTR_HOME'] = home;
19
+ });
20
+ after(() => {
21
+ rmSync(home, { recursive: true, force: true });
22
+ delete process.env['CRTR_HOME'];
23
+ });
24
+ function assertLaunchWrote(nodeId, inv) {
25
+ const launchPath = join(home, 'nodes', nodeId, 'broker-launch.json');
26
+ const parsed = JSON.parse(readFileSync(launchPath, 'utf8'));
27
+ assert.deepEqual(parsed, inv, 'the launch recipe exists and parses as JSON');
28
+ }
29
+ test('preflightBrokerLaunch uses the installed default broker package when no engine override is set', () => {
30
+ const nodeId = 'preflight-default-engine';
31
+ const inv = {
32
+ argv: ['hello'],
33
+ env: {},
34
+ };
35
+ const prev = process.env['CRTR_BROKER_ENGINE'];
36
+ delete process.env['CRTR_BROKER_ENGINE'];
37
+ try {
38
+ assert.doesNotThrow(() => preflightBrokerLaunch(nodeId, inv, '/tmp/work'));
39
+ }
40
+ finally {
41
+ if (prev === undefined)
42
+ delete process.env['CRTR_BROKER_ENGINE'];
43
+ else
44
+ process.env['CRTR_BROKER_ENGINE'] = prev;
45
+ }
46
+ assertLaunchWrote(nodeId, inv);
47
+ });
48
+ test('preflightBrokerLaunch fails fast when the broker engine module cannot be resolved', () => {
49
+ const nodeId = 'preflight-bad-engine';
50
+ const inv = {
51
+ argv: ['hello'],
52
+ env: { CRTR_BROKER_ENGINE: '@crouton-kit/does-not-exist' },
53
+ };
54
+ assert.throws(() => preflightBrokerLaunch(nodeId, inv, '/tmp/work'), /cannot resolve broker engine/i);
55
+ assertLaunchWrote(nodeId, inv);
56
+ });
57
+ test('preflightBrokerLaunch validates the exact broker engine env passed to the detached broker', () => {
58
+ const nodeId = 'preflight-empty-engine';
59
+ const inv = {
60
+ argv: ['hello'],
61
+ env: { CRTR_BROKER_ENGINE: ' ' },
62
+ };
63
+ assert.throws(() => preflightBrokerLaunch(nodeId, inv, '/tmp/work'), /cannot resolve broker engine/i);
64
+ assertLaunchWrote(nodeId, inv);
65
+ });
66
+ test('preflightBrokerLaunch fails fast when a bare --session id would be rejected by the broker', () => {
67
+ const nodeId = 'preflight-bare-session';
68
+ const inv = {
69
+ argv: ['--session', 'uuid-123'],
70
+ env: { CRTR_BROKER_ENGINE: FAKE_ENGINE },
71
+ };
72
+ assert.throws(() => preflightBrokerLaunch(nodeId, inv, '/tmp/work'), /bare --session id/i);
73
+ assertLaunchWrote(nodeId, inv);
74
+ });
75
+ test('preflightBrokerLaunch fails fast when a plainly-encoded session file path is missing', () => {
76
+ const nodeId = 'preflight-missing-session';
77
+ const inv = {
78
+ argv: ['--session', join(home, 'sessions', 'missing.jsonl')],
79
+ env: { CRTR_BROKER_ENGINE: FAKE_ENGINE },
80
+ };
81
+ assert.throws(() => preflightBrokerLaunch(nodeId, inv, '/tmp/work'), /missing session file/i);
82
+ const launchPath = join(home, 'nodes', nodeId, 'broker-launch.json');
83
+ const parsed = JSON.parse(readFileSync(launchPath, 'utf8'));
84
+ assert.deepEqual(parsed, inv, 'the launch recipe exists and parses as JSON');
85
+ });
@@ -71,6 +71,21 @@ test('C3 — services path registers an extension model provider; the broker ses
71
71
  rmSync(cwd, { recursive: true, force: true });
72
72
  }
73
73
  });
74
+ test('C3b — broker splits model thinking suffix before SDK registry lookup', async () => {
75
+ const cwd = mkdtempSync(join(tmpdir(), 'crtr-c3b-'));
76
+ try {
77
+ const { session } = await buildBrokerSession(realEngine, cfg(cwd, { extensionPaths: [C3_EXT], model: 'c3prov/c3model:high' }));
78
+ try {
79
+ assert.equal(session.model?.id, 'c3model', 'C3b: suffixed model specs resolve by model id instead of falling back to SDK default');
80
+ }
81
+ finally {
82
+ session.dispose();
83
+ }
84
+ }
85
+ finally {
86
+ rmSync(cwd, { recursive: true, force: true });
87
+ }
88
+ });
74
89
  // ===========================================================================
75
90
  // C4 — project context (AGENTS.md) is injected into the system prompt. The
76
91
  // services path loads project context files; on the 0.78.1 pin there is no
@@ -1,6 +1,6 @@
1
1
  import { test, before, after, beforeEach } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs';
3
+ import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync, statSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
  import { createNode, getNode, getRow, updateNode, setStatus, listNodes, subscribe, unsubscribe, setSubscriptionActive, recordSpawn, subscribersOf, subscriptionsOf, view, hasActiveLiveSubscription, rebuildIndex, } from '../canvas/canvas.js';
@@ -139,6 +139,42 @@ test('recordSpawn writes the audit-only spawned_by edge', () => {
139
139
  assert.equal(subscriptionsOf('child').length, 0);
140
140
  assert.equal(subscribersOf('parent').length, 0);
141
141
  });
142
+ // --- Regression: Hearth M0 cold-boot 0-byte meta.json -----------------------
143
+ // A bare write-temp-then-rename WITHOUT fsync left meta.json 0-byte on the guest's
144
+ // durable NFS home after a sandbox recreate (the open inode is silly-renamed and the
145
+ // new content never flushed). Cold-boot's `canvas rebuild-index` + `canvas revive`
146
+ // then JSON.parse('') → "Unexpected end of JSON input" → the chain dies → guest
147
+ // daemon-only, no web on 7878. Fix: writeMeta fsyncs temp+parent dir (durable), and
148
+ // readMeta/rebuildIndex tolerate an empty/corrupt meta as MISSING instead of throwing.
149
+ test('writeMeta produces a durable, non-empty, atomically-named meta.json', () => {
150
+ createNode(node('a'));
151
+ const p = nodeMetaPath('a');
152
+ assert.ok(statSync(p).size > 0, 'meta.json must be non-empty');
153
+ assert.ok(!existsSync(`${p}.tmp`), 'temp file must be renamed away, not left behind');
154
+ assert.equal(getNode('a')?.node_id, 'a');
155
+ });
156
+ test('getNode returns null (not throw) on a 0-byte meta.json', () => {
157
+ createNode(node('a'));
158
+ writeFileSync(nodeMetaPath('a'), ''); // the observed NFS 0-byte corruption
159
+ assert.equal(getNode('a'), null);
160
+ });
161
+ test('getNode returns null (not throw) on an unparseable meta.json', () => {
162
+ createNode(node('a'));
163
+ writeFileSync(nodeMetaPath('a'), '{ broken json');
164
+ assert.equal(getNode('a'), null);
165
+ });
166
+ test('rebuildIndex skips a corrupt-meta node instead of throwing', () => {
167
+ createNode(node('good'));
168
+ createNode(node('bad'));
169
+ writeFileSync(nodeMetaPath('bad'), ''); // 0-byte corruption on one node
170
+ closeDb();
171
+ rmSync(join(home, 'canvas.db'), { force: true });
172
+ rmSync(join(home, 'canvas.db-wal'), { force: true });
173
+ rmSync(join(home, 'canvas.db-shm'), { force: true });
174
+ assert.doesNotThrow(() => rebuildIndex());
175
+ assert.equal(getRow('good')?.node_id, 'good'); // healthy node reindexed
176
+ assert.equal(getRow('bad'), null); // corrupt node skipped, no crash
177
+ });
142
178
  test('rebuildIndex reconstructs node rows from on-disk metas', () => {
143
179
  createNode(node('a', { parent: null }));
144
180
  createNode(node('b', { parent: 'a' }));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
1
+ import assert from 'node:assert/strict';
2
+ import { test } from 'node:test';
3
+ // @ts-ignore -- chat core is a plain built view module with no generated .d.ts.
4
+ import core from '../../builtin-views/chat/core.mjs';
5
+ import { openLocalStream } from '../view/stream-local.js';
6
+ const flushMicrotasks = async () => {
7
+ await Promise.resolve();
8
+ };
9
+ test('local invalid/no-node stream failures close asynchronously after the channel returns', async () => {
10
+ for (const target of ['not/valid', 'definitely-not-a-real-node-review-5362b32']) {
11
+ const events = [];
12
+ const channel = openLocalStream({ kind: 'stream', target, role: 'controller' }, {
13
+ onFrame() {
14
+ events.push('frame');
15
+ },
16
+ onClose(reason) {
17
+ events.push(`close:${reason}`);
18
+ },
19
+ onError(err) {
20
+ events.push(`error:${err.kind}`);
21
+ },
22
+ });
23
+ assert.equal(events.length, 0, `${target}: callbacks stay deferred until after openLocalStream returns`);
24
+ assert.equal(typeof channel.close, 'function', `${target}: channel is returned before failure callbacks fire`);
25
+ await flushMicrotasks();
26
+ assert.ok(events[0]?.startsWith('error:'), `${target}: reports the terminal open failure as an error`);
27
+ assert.equal(events[1]?.startsWith('close:'), true, `${target}: follows with streamClose`);
28
+ }
29
+ });
30
+ test('control_changed demotion updates reconnect intent without clobbering an explicit controller request', () => {
31
+ let current = {
32
+ target: 'n1',
33
+ desiredRole: 'controller',
34
+ channel: null,
35
+ clientId: 'client-1',
36
+ conn: 'open',
37
+ role: 'controller',
38
+ controllerId: 'client-1',
39
+ session: null,
40
+ transcript: [],
41
+ draft: '',
42
+ notices: [],
43
+ pendingDialog: null,
44
+ queued: [],
45
+ contextTokens: undefined,
46
+ };
47
+ const ctx = {
48
+ get state() {
49
+ return current;
50
+ },
51
+ set(next) {
52
+ current = typeof next === 'function' ? next(current) : next;
53
+ },
54
+ signal: {
55
+ clearBanner() { },
56
+ setBanner() { },
57
+ setStatus() { },
58
+ setSubtitle() { },
59
+ setMode() { },
60
+ quit() { },
61
+ },
62
+ };
63
+ core.intents.streamFrame(ctx, { type: 'control_changed', controller_id: 'other-client' });
64
+ assert.equal(current.role, 'observer');
65
+ assert.equal(current.desiredRole, 'observer');
66
+ current = { ...current, role: 'observer', desiredRole: 'controller' };
67
+ core.intents.streamFrame(ctx, { type: 'control_changed', controller_id: 'other-client' });
68
+ assert.equal(current.role, 'observer');
69
+ assert.equal(current.desiredRole, 'controller');
70
+ });
@@ -23,7 +23,7 @@
23
23
  // the one genuine-terminal wake that still lives in core: close.ts.
24
24
  import { test, before, after, beforeEach } from 'node:test';
25
25
  import assert from 'node:assert/strict';
26
- import { mkdtempSync, rmSync } from 'node:fs';
26
+ import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'node:fs';
27
27
  import { tmpdir } from 'node:os';
28
28
  import { join } from 'node:path';
29
29
  import { spawnSync } from 'node:child_process';
@@ -46,6 +46,15 @@ function node(id, over = {}) {
46
46
  ...over,
47
47
  };
48
48
  }
49
+ /** Write a minimal pi session with one assistant message so the node reads as
50
+ * NON-empty (produced AI output) — otherwise closeNode reaps it outright. */
51
+ function withSession(id) {
52
+ const dir = join(home, 'sessions');
53
+ mkdirSync(dir, { recursive: true });
54
+ const f = join(dir, `${id}.jsonl`);
55
+ writeFileSync(f, JSON.stringify({ type: 'message', message: { role: 'assistant', content: [{ type: 'text', text: 'hi' }] } }) + '\n');
56
+ return f;
57
+ }
49
58
  /** A pid that is guaranteed dead. */
50
59
  function deadPid() {
51
60
  const r = spawnSync('true', [], { stdio: 'ignore' });
@@ -115,7 +124,7 @@ test('pid-only liveness: a dead-engine booted child is revivable, NOT reaped, an
115
124
  test('node close of a child wakes its SURVIVING manager (the parent outside the closing set)', async () => {
116
125
  inboxWaitingParent('PARENT');
117
126
  // A booted child; closeNode tears its (paneless-for-the-test) engine down.
118
- createNode(node('CHILD', { pane: GONE_PANE, tmux_session: 'crtr-cdw', window: '@1', pi_session_id: 'booted', status: 'active' }));
127
+ createNode(node('CHILD', { pane: GONE_PANE, tmux_session: 'crtr-cdw', window: '@1', pi_session_id: 'booted', pi_session_file: withSession('CHILD'), status: 'active' }));
119
128
  subscribe('PARENT', 'CHILD', true); // PARENT is a manager OUTSIDE the close set
120
129
  // Closing CHILD: closingSet({CHILD}) cannot pull in PARENT (PARENT is a
121
130
  // SUBSCRIBER of CHILD, never a descendant), so PARENT survives + must wake.
@@ -1,13 +1,25 @@
1
1
  import { test, before, after, beforeEach } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { mkdtempSync, rmSync } from 'node:fs';
3
+ import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
  import { createNode, getNode, subscribe } from '../canvas/canvas.js';
7
+ import { nodeDir } from '../canvas/paths.js';
7
8
  import { closeDb } from '../canvas/db.js';
8
9
  import { closeNode } from '../runtime/close.js';
9
10
  import { readInboxSince } from '../feed/inbox.js';
10
11
  let home;
12
+ /** Write a minimal pi session containing one assistant message, so the node
13
+ * reads as NON-empty (it produced AI output). Without this a node has no
14
+ * session and closeNode reaps it outright instead of cancel+notice — see the
15
+ * dedicated empty-reap test below. */
16
+ function withSession(id) {
17
+ const dir = join(home, 'sessions');
18
+ mkdirSync(dir, { recursive: true });
19
+ const f = join(dir, `${id}.jsonl`);
20
+ writeFileSync(f, JSON.stringify({ type: 'message', message: { role: 'assistant', content: [{ type: 'text', text: 'hi' }] } }) + '\n');
21
+ return f;
22
+ }
11
23
  function node(id, over = {}) {
12
24
  return {
13
25
  node_id: id,
@@ -18,6 +30,7 @@ function node(id, over = {}) {
18
30
  mode: 'base',
19
31
  lifecycle: 'terminal',
20
32
  status: 'active',
33
+ pi_session_file: withSession(id),
21
34
  ...over,
22
35
  };
23
36
  }
@@ -132,6 +145,31 @@ test('closing a leaf node closes only itself', () => {
132
145
  test('throws on an unknown node', () => {
133
146
  assert.throws(() => closeNode('ghost'), /unknown node/);
134
147
  });
148
+ // Regression: empty nodes (engine never produced an assistant message) are
149
+ // USELESS shells, so closing one must DELETE it from the graph + disk, not park
150
+ // a canceled husk. (Reported: "many nodes on the canvas that haven't generated
151
+ // anything yet, just taking up space" — close/detach now reap them.)
152
+ test('closing an EMPTY node (no AI output) deletes it from the graph + disk', () => {
153
+ createNode(node('shell', { pi_session_file: null }));
154
+ const dir = nodeDir('shell');
155
+ assert.ok(existsSync(dir), 'dir exists before close');
156
+ const res = closeNode('shell');
157
+ assert.deepEqual(res.closed, ['shell']);
158
+ assert.equal(getNode('shell'), null, 'row deleted from the graph');
159
+ assert.equal(existsSync(dir), false, 'on-disk dir removed');
160
+ });
161
+ // A close that reaps an EMPTY child still wakes a SURVIVING manager — the child
162
+ // is gone either way, so a parent that delegated must learn it vanished.
163
+ test('reaping an empty child still notifies a surviving external manager', () => {
164
+ createNode(node('child', { pi_session_file: null })); // empty → reaped
165
+ createNode(node('mgr')); // external manager, NOT closed
166
+ spawnEdge('mgr', 'child');
167
+ closeNode('child');
168
+ assert.equal(getNode('child'), null, 'empty child reaped');
169
+ const note = readInboxSince('mgr').at(-1);
170
+ assert.match(note.label, /Child closed/);
171
+ assert.equal(note.data?.['child'], 'child');
172
+ });
135
173
  // NOTE (broker-host cut): the former "Step 7: closing a FOCUSED node closes its
136
174
  // focus row + nulls its pane (tearDownNode)" test was DELETED. close.ts no longer
137
175
  // routes through the synchronous tearDownNode — it sends the broker `shutdown`
@@ -0,0 +1,37 @@
1
+ // Run with: node --import tsx/esm --test src/core/__tests__/connection-reconnect-resume.test.ts
2
+ //
3
+ // BUG-REGRESSION: connection faults follow the shared broker-owned retry schedule.
4
+ // The broker exercises this policy table directly.
5
+ import { test } from 'node:test';
6
+ import assert from 'node:assert/strict';
7
+ import { nextFaultRetry, policyFor } from '../runtime/fault-recovery.js';
8
+ const NOW = Date.parse('2026-06-23T20:00:00.000Z');
9
+ const ISO = (ms) => new Date(ms).toISOString();
10
+ // ---------------------------------------------------------------------------
11
+ // J1 — the policy table itself: connection faults retry on a 5s → 2x → 60s
12
+ // cap, and the planner advances the next attempt.
13
+ // ---------------------------------------------------------------------------
14
+ test('J1 policy table: connection faults use the shared retry schedule', () => {
15
+ assert.deepEqual(policyFor('connection'), {
16
+ kind: 'connection',
17
+ gate: 'online',
18
+ max: 8,
19
+ baseDelayMs: 5_000,
20
+ capDelayMs: 60_000,
21
+ fatal: false,
22
+ });
23
+ const plan = nextFaultRetry({
24
+ link: 'pi→provider',
25
+ op: 'generation turn',
26
+ kind: 'connection',
27
+ retry: { disposition: 'auto', by: 'daemon' },
28
+ message: 'Connection error.',
29
+ since: ISO(NOW),
30
+ logPath: '/tmp/crtr-fault.log',
31
+ }, NOW);
32
+ assert.equal(plan.attempt, 1, 'the first plan primes attempt 1');
33
+ assert.equal(plan.max, 8, 'connection max attempts stay at 8');
34
+ assert.equal(plan.nextAt, ISO(NOW + 5_000), 'connection starts at 5s');
35
+ assert.equal(plan.fault.retry.attempt, 1, 'planned marker carries the primed attempt');
36
+ assert.equal(plan.fault.retry.disposition, 'auto', 'planned marker stays auto');
37
+ });