@crouton-kit/crouter 0.3.31 → 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 (443) 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 +9 -3
  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 +37 -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/__tests__/empty-node-guard.test.d.ts +1 -0
  230. package/dist/core/feed/__tests__/empty-node-guard.test.js +45 -0
  231. package/dist/core/feed/feed.js +8 -0
  232. package/dist/core/feed/inbox.d.ts +15 -0
  233. package/dist/core/feed/inbox.js +40 -14
  234. package/dist/core/frontmatter.d.ts +1 -11
  235. package/dist/core/frontmatter.js +1 -55
  236. package/dist/core/hearth/config.d.ts +2 -0
  237. package/dist/core/hearth/config.js +75 -0
  238. package/dist/core/hearth/guest-env.d.ts +2 -0
  239. package/dist/core/hearth/guest-env.js +11 -0
  240. package/dist/core/hearth/index.d.ts +5 -0
  241. package/dist/core/hearth/index.js +5 -0
  242. package/dist/core/hearth/provider.d.ts +21 -0
  243. package/dist/core/hearth/provider.js +10 -0
  244. package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +10 -0
  245. package/dist/core/hearth/providers/blaxel-bootstrap.js +39 -0
  246. package/dist/core/hearth/providers/blaxel-home.d.ts +11 -0
  247. package/dist/core/hearth/providers/blaxel-home.js +126 -0
  248. package/dist/core/hearth/providers/blaxel.d.ts +26 -0
  249. package/dist/core/hearth/providers/blaxel.js +208 -0
  250. package/dist/core/hearth/providers/types.d.ts +72 -0
  251. package/dist/core/hearth/providers/types.js +1 -0
  252. package/dist/core/hearth/registry.d.ts +15 -0
  253. package/dist/core/hearth/registry.js +179 -0
  254. package/dist/core/hearth/types.d.ts +121 -0
  255. package/dist/core/hearth/types.js +1 -0
  256. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +1 -0
  257. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +79 -0
  258. package/dist/core/{skill-sync → host-exports}/builtins.d.ts +21 -13
  259. package/dist/core/host-exports/builtins.js +68 -0
  260. package/dist/core/{skill-sync → host-exports}/export.d.ts +9 -6
  261. package/dist/core/{skill-sync → host-exports}/export.js +37 -14
  262. package/dist/core/io.js +1 -1
  263. package/dist/core/log.d.ts +9 -0
  264. package/dist/core/log.js +113 -0
  265. package/dist/core/memory-resolver.d.ts +8 -8
  266. package/dist/core/memory-resolver.js +15 -28
  267. package/dist/core/personas/index.d.ts +4 -4
  268. package/dist/core/personas/index.js +2 -2
  269. package/dist/core/personas/loader.d.ts +51 -0
  270. package/dist/core/personas/loader.js +54 -50
  271. package/dist/core/personas/resolve.d.ts +43 -27
  272. package/dist/core/personas/resolve.js +336 -94
  273. package/dist/core/runtime/auth-reload.d.ts +7 -0
  274. package/dist/core/runtime/auth-reload.js +69 -0
  275. package/dist/core/runtime/bearings.d.ts +35 -15
  276. package/dist/core/runtime/bearings.js +305 -44
  277. package/dist/core/runtime/branded-host.d.ts +4 -2
  278. package/dist/core/runtime/branded-host.js +66 -4
  279. package/dist/core/runtime/broker-protocol.d.ts +38 -3
  280. package/dist/core/runtime/broker.js +343 -18
  281. package/dist/core/runtime/close.js +37 -29
  282. package/dist/core/runtime/connectivity.d.ts +12 -0
  283. package/dist/core/runtime/connectivity.js +73 -0
  284. package/dist/core/runtime/fault-recovery-nudge.d.ts +2 -0
  285. package/dist/core/runtime/fault-recovery-nudge.js +2 -0
  286. package/dist/core/runtime/fault-recovery.d.ts +23 -0
  287. package/dist/core/runtime/fault-recovery.js +74 -0
  288. package/dist/core/runtime/fault.d.ts +25 -0
  289. package/dist/core/runtime/fault.js +176 -0
  290. package/dist/core/runtime/front-door.d.ts +1 -1
  291. package/dist/core/runtime/front-door.js +2 -2
  292. package/dist/core/runtime/host.d.ts +10 -0
  293. package/dist/core/runtime/host.js +71 -9
  294. package/dist/core/runtime/kickoff.js +31 -45
  295. package/dist/core/runtime/launch.d.ts +32 -18
  296. package/dist/core/runtime/launch.js +148 -43
  297. package/dist/core/runtime/model-swap.d.ts +18 -0
  298. package/dist/core/runtime/model-swap.js +95 -0
  299. package/dist/core/runtime/naming.d.ts +7 -0
  300. package/dist/core/runtime/naming.js +61 -9
  301. package/dist/core/runtime/nodes.js +6 -1
  302. package/dist/core/runtime/persona.js +16 -17
  303. package/dist/core/runtime/placement.d.ts +36 -31
  304. package/dist/core/runtime/placement.js +149 -64
  305. package/dist/core/runtime/promote.d.ts +2 -0
  306. package/dist/core/runtime/promote.js +23 -3
  307. package/dist/core/runtime/recycle.js +6 -4
  308. package/dist/core/runtime/reset.d.ts +5 -2
  309. package/dist/core/runtime/reset.js +2 -2
  310. package/dist/core/runtime/revive.d.ts +4 -0
  311. package/dist/core/runtime/revive.js +27 -9
  312. package/dist/core/runtime/roadmap.d.ts +1 -1
  313. package/dist/core/runtime/roadmap.js +1 -1
  314. package/dist/core/runtime/spawn.d.ts +2 -2
  315. package/dist/core/runtime/spawn.js +30 -6
  316. package/dist/core/runtime/stop-guard.js +6 -1
  317. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  318. package/dist/core/runtime/tmux-chrome.js +1 -1
  319. package/dist/core/runtime/tmux.d.ts +29 -6
  320. package/dist/core/runtime/tmux.js +134 -80
  321. package/dist/core/runtime/view-socket-client.d.ts +46 -0
  322. package/dist/core/runtime/view-socket-client.js +203 -0
  323. package/dist/core/substrate/index.d.ts +1 -1
  324. package/dist/core/substrate/index.js +1 -1
  325. package/dist/core/substrate/on-read.js +16 -4
  326. package/dist/core/substrate/render.d.ts +10 -13
  327. package/dist/core/substrate/render.js +56 -51
  328. package/dist/core/tui/host.js +83 -18
  329. package/dist/core/view/__tests__/transport-cache.test.d.ts +1 -0
  330. package/dist/core/view/__tests__/transport-cache.test.js +62 -0
  331. package/dist/core/view/contract.d.ts +20 -0
  332. package/dist/core/view/stream-local.d.ts +3 -0
  333. package/dist/core/view/stream-local.js +184 -0
  334. package/dist/core/view/transport-cache.d.ts +8 -0
  335. package/dist/core/view/transport-cache.js +38 -0
  336. package/dist/core/view/transport-local.js +4 -0
  337. package/dist/core/view/transport.d.ts +7 -1
  338. package/dist/daemon/crtrd.d.ts +3 -19
  339. package/dist/daemon/crtrd.js +139 -178
  340. package/dist/daemon/manage.d.ts +18 -1
  341. package/dist/daemon/manage.js +54 -9
  342. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.d.ts +1 -0
  343. package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.js +289 -0
  344. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.d.ts +1 -0
  345. package/dist/hearth/wake-proxy/__tests__/config-timeout.test.js +34 -0
  346. package/dist/hearth/wake-proxy/__tests__/guest-source.test.d.ts +1 -0
  347. package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +203 -0
  348. package/dist/hearth/wake-proxy/__tests__/hardening.test.d.ts +1 -0
  349. package/dist/hearth/wake-proxy/__tests__/hardening.test.js +59 -0
  350. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.d.ts +1 -0
  351. package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.js +372 -0
  352. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.d.ts +1 -0
  353. package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.js +258 -0
  354. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.d.ts +1 -0
  355. package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.js +437 -0
  356. package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.d.ts +1 -0
  357. package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.js +455 -0
  358. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.d.ts +1 -0
  359. package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.js +15 -0
  360. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.d.ts +1 -0
  361. package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.js +141 -0
  362. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.d.ts +1 -0
  363. package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.js +143 -0
  364. package/dist/hearth/wake-proxy/anthropic-oauth.d.ts +58 -0
  365. package/dist/hearth/wake-proxy/anthropic-oauth.js +189 -0
  366. package/dist/hearth/wake-proxy/auth.d.ts +22 -0
  367. package/dist/hearth/wake-proxy/auth.js +128 -0
  368. package/dist/hearth/wake-proxy/config.d.ts +2 -0
  369. package/dist/hearth/wake-proxy/config.js +151 -0
  370. package/dist/hearth/wake-proxy/guest-source.d.ts +14 -0
  371. package/dist/hearth/wake-proxy/guest-source.js +130 -0
  372. package/dist/hearth/wake-proxy/hearth-status.d.ts +44 -0
  373. package/dist/hearth/wake-proxy/hearth-status.js +267 -0
  374. package/dist/hearth/wake-proxy/home.d.ts +67 -0
  375. package/dist/hearth/wake-proxy/home.js +297 -0
  376. package/dist/hearth/wake-proxy/main.d.ts +1 -0
  377. package/dist/hearth/wake-proxy/main.js +134 -0
  378. package/dist/hearth/wake-proxy/model-auth.d.ts +13 -0
  379. package/dist/hearth/wake-proxy/model-auth.js +345 -0
  380. package/dist/hearth/wake-proxy/proxy.d.ts +35 -0
  381. package/dist/hearth/wake-proxy/proxy.js +716 -0
  382. package/dist/hearth/wake-proxy/public-source-gate.d.ts +9 -0
  383. package/dist/hearth/wake-proxy/public-source-gate.js +409 -0
  384. package/dist/hearth/wake-proxy/redact.d.ts +1 -0
  385. package/dist/hearth/wake-proxy/redact.js +17 -0
  386. package/dist/hearth/wake-proxy/server.d.ts +11 -0
  387. package/dist/hearth/wake-proxy/server.js +142 -0
  388. package/dist/hearth/wake-proxy/state.d.ts +18 -0
  389. package/dist/hearth/wake-proxy/state.js +342 -0
  390. package/dist/hearth/wake-proxy/types.d.ts +76 -0
  391. package/dist/hearth/wake-proxy/types.js +1 -0
  392. package/dist/index.js +3 -2
  393. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +6 -5
  394. package/dist/pi-extensions/canvas-context-intro.js +7 -7
  395. package/dist/pi-extensions/canvas-doc-substrate.js +19 -22
  396. package/dist/pi-extensions/canvas-nav.js +15 -8
  397. package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
  398. package/dist/pi-extensions/canvas-stophook.js +78 -11
  399. package/dist/types.d.ts +39 -20
  400. package/dist/types.js +38 -13
  401. package/dist/web/runtime.js +141 -42
  402. package/dist/web/transport-http.js +7 -5
  403. package/dist/web/transport-stream.d.ts +3 -0
  404. package/dist/web/transport-stream.js +204 -0
  405. package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
  406. package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
  407. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
  408. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
  409. package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
  410. package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
  411. package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
  412. package/dist/web-client/assets/index-BZUxTkv5.css +2 -0
  413. package/dist/web-client/assets/index-D36PNBj4.js +80 -0
  414. package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
  415. package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
  416. package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
  417. package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
  418. package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
  419. package/dist/web-client/index.html +3 -2
  420. package/package.json +32 -10
  421. package/dist/core/__tests__/error-stall-recycle.test.js +0 -141
  422. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +0 -57
  423. package/dist/core/skill-sync/builtins.js +0 -112
  424. package/dist/core/skill-sync/claude-plugins.d.ts +0 -23
  425. package/dist/core/skill-sync/claude-plugins.js +0 -71
  426. package/dist/core/skill-sync/engine.d.ts +0 -42
  427. package/dist/core/skill-sync/engine.js +0 -633
  428. package/dist/core/skill-sync/manifest.d.ts +0 -64
  429. package/dist/core/skill-sync/manifest.js +0 -181
  430. package/dist/core/skill-sync/profile.d.ts +0 -76
  431. package/dist/core/skill-sync/profile.js +0 -173
  432. package/dist/core/skill-sync/snapshot.d.ts +0 -57
  433. package/dist/core/skill-sync/snapshot.js +0 -120
  434. package/dist/pi-extensions/canvas-commands.d.ts +0 -37
  435. package/dist/pi-extensions/canvas-commands.js +0 -113
  436. package/dist/pi-extensions/canvas-resume.d.ts +0 -21
  437. package/dist/pi-extensions/canvas-resume.js +0 -83
  438. package/dist/pi-extensions/canvas-view.d.ts +0 -21
  439. package/dist/pi-extensions/canvas-view.js +0 -76
  440. package/dist/web-client/assets/index-BUvQb4hR.css +0 -2
  441. package/dist/web-client/assets/index-ClLQXYAE.js +0 -10
  442. /package/dist/{core/__tests__/error-stall-recycle.test.d.ts → clients/attach/__tests__/autocomplete-and-bash-mode.test.d.ts} +0 -0
  443. /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,725 @@
1
+ // @ts-check
2
+ /**
3
+ * Chat view — the portable CORE for the crtr `chat` builtin view.
4
+ *
5
+ * Import-free, dual-target, plain JS only. The core owns the state shape, the
6
+ * stream declaration, the broker frame fold, and the drive intents; presenters
7
+ * only read state and dispatch intents.
8
+ *
9
+ * @module chat/core
10
+ */
11
+
12
+ /** @typedef {'controller'|'observer'} ChatRole */
13
+ /** @typedef {'idle'|'connecting'|'open'|'closed'|'no-broker'|'no-node'|'invalid'|'error'} ChatConn */
14
+ /** @typedef {'info'|'action'|'error'} NoticeLevel */
15
+
16
+ /** @typedef {Object} ChatTranscript
17
+ * @property {any[]} messages
18
+ * @property {number|null} streamingIndex
19
+ * @property {string[]} executingToolIds
20
+ * @property {string|null} activity
21
+ * @property {string|null} lastAssistantText
22
+ */
23
+
24
+ /** @typedef {Object} ChatSession
25
+ * @property {string|null} sessionId
26
+ * @property {string|null} sessionFile
27
+ * @property {string|null} model
28
+ * @property {boolean} isStreaming
29
+ * @property {string} thinkingLevel
30
+ * @property {string} steeringMode
31
+ * @property {string} followUpMode
32
+ * @property {string|null} sessionName
33
+ * @property {boolean} autoCompactionEnabled
34
+ * @property {number} pendingMessageCount
35
+ */
36
+
37
+ /** @typedef {Object} ChatNotice
38
+ * @property {string} id
39
+ * @property {NoticeLevel} level
40
+ * @property {string} text
41
+ * @property {number} at
42
+ */
43
+
44
+ /** @typedef {Object} ChatState
45
+ * @property {string|null} target
46
+ * @property {ChatRole} desiredRole
47
+ * @property {any|null} channel
48
+ * @property {string|null} clientId
49
+ * @property {ChatConn} conn
50
+ * @property {ChatRole} role
51
+ * @property {string|null} controllerId
52
+ * @property {ChatSession|null} session
53
+ * @property {ChatTranscript} transcript
54
+ * @property {string} draft
55
+ * @property {ChatNotice[]} notices
56
+ * @property {any|null} pendingDialog
57
+ * @property {string[]} queued
58
+ * @property {number|undefined} contextTokens
59
+ */
60
+
61
+ /** @typedef {Object} ChatCloseReason
62
+ * @property {string=} reason
63
+ * @property {string=} message
64
+ * @property {string=} kind
65
+ */
66
+
67
+ function uid() {
68
+ const c = globalThis.crypto;
69
+ if (c && typeof c.randomUUID === 'function') return c.randomUUID();
70
+ return `n-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
71
+ }
72
+
73
+ /** @param {unknown} value @returns {value is Record<string, unknown>} */
74
+ function isRecord(value) {
75
+ return typeof value === 'object' && value !== null;
76
+ }
77
+
78
+ /** @param {unknown} value @returns {string} */
79
+ function asText(value) {
80
+ return typeof value === 'string' ? value : '';
81
+ }
82
+
83
+ /** @param {unknown} value @returns {string|null} */
84
+ function asMaybeText(value) {
85
+ return typeof value === 'string' && value !== '' ? value : null;
86
+ }
87
+
88
+ /** @param {unknown} value @returns {string|null} */
89
+ function messageRole(value) {
90
+ return isRecord(value) && typeof value.role === 'string' ? value.role : null;
91
+ }
92
+
93
+ /** @param {unknown} content @returns {string} */
94
+ function textFromContent(content) {
95
+ if (typeof content === 'string') return content;
96
+ if (!Array.isArray(content)) return '';
97
+ return content
98
+ .filter((block) => isRecord(block) && block.type === 'text')
99
+ .map((block) => asText(block.text))
100
+ .join('');
101
+ }
102
+
103
+ /** @param {unknown} message @returns {string} */
104
+ export function messageText(message) {
105
+ if (!isRecord(message)) return '';
106
+ const fromContent = textFromContent(message.content);
107
+ if (fromContent) return fromContent;
108
+ return asText(message.text);
109
+ }
110
+
111
+ /** @param {any[]} messages @returns {string|null} */
112
+ export function lastAssistantText(messages) {
113
+ for (let i = messages.length - 1; i >= 0; i--) {
114
+ const message = messages[i];
115
+ if (messageRole(message) !== 'assistant') continue;
116
+ const text = messageText(message).trim();
117
+ if (text) return text;
118
+ }
119
+ return null;
120
+ }
121
+
122
+ /** @returns {ChatTranscript} */
123
+ export function initialTranscript() {
124
+ return {
125
+ messages: [],
126
+ streamingIndex: null,
127
+ executingToolIds: [],
128
+ activity: null,
129
+ lastAssistantText: null,
130
+ };
131
+ }
132
+
133
+ /** @param {any} snapshot @returns {ChatTranscript} */
134
+ export function applySnapshot(snapshot) {
135
+ const messages = Array.isArray(snapshot?.messages) ? snapshot.messages.slice() : [];
136
+ return {
137
+ messages,
138
+ streamingIndex: null,
139
+ executingToolIds: [],
140
+ activity: snapshot?.state?.isStreaming === true ? 'Working…' : null,
141
+ lastAssistantText: lastAssistantText(messages),
142
+ };
143
+ }
144
+
145
+ /** @param {ChatTranscript} transcript @param {unknown} event @returns {ChatTranscript} */
146
+ export function reduceAgentEvent(transcript, event) {
147
+ switch (event?.type) {
148
+ case 'agent_start':
149
+ return transcript.activity === 'Working…' ? transcript : { ...transcript, activity: 'Working…' };
150
+
151
+ case 'agent_end':
152
+ return {
153
+ ...transcript,
154
+ streamingIndex: null,
155
+ activity: null,
156
+ executingToolIds: transcript.executingToolIds.length ? [] : transcript.executingToolIds,
157
+ };
158
+
159
+ case 'message_start': {
160
+ const messages = transcript.messages.concat(event.message);
161
+ const streamingIndex = messageRole(event.message) === 'assistant' ? messages.length - 1 : transcript.streamingIndex;
162
+ return {
163
+ ...transcript,
164
+ messages,
165
+ streamingIndex,
166
+ lastAssistantText: lastAssistantText(messages),
167
+ };
168
+ }
169
+
170
+ case 'message_update': {
171
+ let idx = transcript.streamingIndex;
172
+ if (idx === null) {
173
+ const last = transcript.messages.length - 1;
174
+ if (last >= 0 && messageRole(transcript.messages[last]) === 'assistant') idx = last;
175
+ }
176
+ if (idx === null) return transcript;
177
+ const messages = transcript.messages.slice();
178
+ messages[idx] = event.message;
179
+ return {
180
+ ...transcript,
181
+ messages,
182
+ streamingIndex: idx,
183
+ lastAssistantText: lastAssistantText(messages),
184
+ };
185
+ }
186
+
187
+ case 'message_end': {
188
+ let idx = transcript.streamingIndex;
189
+ if (idx === null) {
190
+ const last = transcript.messages.length - 1;
191
+ if (last >= 0 && messageRole(transcript.messages[last]) === 'assistant') idx = last;
192
+ }
193
+ if (idx === null) return transcript;
194
+ const messages = transcript.messages.slice();
195
+ messages[idx] = event.message;
196
+ return {
197
+ ...transcript,
198
+ messages,
199
+ streamingIndex: null,
200
+ lastAssistantText: lastAssistantText(messages),
201
+ };
202
+ }
203
+
204
+ case 'tool_execution_start': {
205
+ if (transcript.executingToolIds.includes(event.toolCallId)) return transcript;
206
+ return { ...transcript, executingToolIds: transcript.executingToolIds.concat(event.toolCallId) };
207
+ }
208
+
209
+ case 'tool_execution_end': {
210
+ if (!transcript.executingToolIds.includes(event.toolCallId)) return transcript;
211
+ return {
212
+ ...transcript,
213
+ executingToolIds: transcript.executingToolIds.filter((id) => id !== event.toolCallId),
214
+ };
215
+ }
216
+
217
+ case 'compaction_start':
218
+ return transcript.activity === 'Compacting context…' ? transcript : { ...transcript, activity: 'Compacting context…' };
219
+
220
+ case 'compaction_end':
221
+ return { ...transcript, activity: transcript.activity === 'Working…' ? 'Working…' : null };
222
+
223
+ case 'auto_retry_start': {
224
+ const attempt = typeof event.attempt === 'number' ? event.attempt : 0;
225
+ const maxAttempts = typeof event.maxAttempts === 'number' ? event.maxAttempts : 0;
226
+ const delaySeconds = typeof event.delayMs === 'number' ? Math.ceil(event.delayMs / 1000) : 0;
227
+ return {
228
+ ...transcript,
229
+ activity: `Retrying (${attempt}/${maxAttempts}) in ${delaySeconds}s…`,
230
+ };
231
+ }
232
+
233
+ case 'auto_retry_end':
234
+ return { ...transcript, activity: transcript.activity === 'Working…' ? 'Working…' : null };
235
+
236
+ default:
237
+ return transcript;
238
+ }
239
+ }
240
+
241
+ /** @param {ChatTranscript} transcript @param {string} command @param {boolean|undefined} excludeFromContext @returns {ChatTranscript} */
242
+ export function applyBashStart(transcript, command, excludeFromContext) {
243
+ const message = {
244
+ role: 'bashExecution',
245
+ command,
246
+ output: '',
247
+ exitCode: undefined,
248
+ cancelled: false,
249
+ truncated: false,
250
+ excludeFromContext,
251
+ timestamp: Date.now(),
252
+ };
253
+ return { ...transcript, messages: transcript.messages.concat(message) };
254
+ }
255
+
256
+ /** @param {ChatTranscript} transcript @param {string} chunk @returns {ChatTranscript} */
257
+ export function applyBashOutput(transcript, chunk) {
258
+ for (let i = transcript.messages.length - 1; i >= 0; i--) {
259
+ const message = transcript.messages[i];
260
+ if (messageRole(message) !== 'bashExecution') continue;
261
+ const messages = transcript.messages.slice();
262
+ const current = isRecord(messages[i]) ? messages[i] : {};
263
+ messages[i] = { ...current, output: `${asText(current.output)}${chunk}` };
264
+ return { ...transcript, messages };
265
+ }
266
+ return transcript;
267
+ }
268
+
269
+ /** @param {ChatTranscript} transcript @param {{exitCode:number|undefined,cancelled:boolean,truncated:boolean,fullOutputPath?:string}} result @returns {ChatTranscript} */
270
+ export function applyBashEnd(transcript, result) {
271
+ for (let i = transcript.messages.length - 1; i >= 0; i--) {
272
+ const message = transcript.messages[i];
273
+ if (messageRole(message) !== 'bashExecution') continue;
274
+ const messages = transcript.messages.slice();
275
+ const current = isRecord(messages[i]) ? messages[i] : {};
276
+ messages[i] = {
277
+ ...current,
278
+ exitCode: result.exitCode,
279
+ cancelled: result.cancelled,
280
+ truncated: result.truncated,
281
+ fullOutputPath: result.fullOutputPath,
282
+ };
283
+ return { ...transcript, messages };
284
+ }
285
+ return transcript;
286
+ }
287
+
288
+ /** @param {unknown} state @returns {ChatSession} */
289
+ export function normalizeSessionState(state) {
290
+ const s = isRecord(state) ? state : {};
291
+ return {
292
+ sessionId: asMaybeText(s.sessionId),
293
+ sessionFile: asMaybeText(s.sessionFile),
294
+ model: asMaybeText(s.model),
295
+ isStreaming: s.isStreaming === true,
296
+ thinkingLevel: asText(s.thinkingLevel) || 'off',
297
+ steeringMode: asText(s.steeringMode) || 'all',
298
+ followUpMode: asText(s.followUpMode) || 'all',
299
+ sessionName: asMaybeText(s.sessionName),
300
+ autoCompactionEnabled: s.autoCompactionEnabled === true,
301
+ pendingMessageCount: typeof s.pendingMessageCount === 'number' ? s.pendingMessageCount : 0,
302
+ };
303
+ }
304
+
305
+ /** @param {ChatState} state @param {Partial<ChatSession>} patch @returns {ChatState} */
306
+ export function patchSession(state, patch) {
307
+ const base = state.session ?? normalizeSessionState(null);
308
+ let changed = false;
309
+ /** @type {ChatSession} */
310
+ const nextSession = { ...base };
311
+ for (const [key, value] of Object.entries(patch)) {
312
+ if (value === undefined) continue;
313
+ // @ts-ignore - key is constrained by ChatSession at call sites.
314
+ nextSession[key] = value;
315
+ changed = true;
316
+ }
317
+ return changed ? { ...state, session: nextSession } : state;
318
+ }
319
+
320
+ /** @param {ChatState} state @param {NoticeLevel} level @param {string} text @returns {ChatState} */
321
+ export function addNotice(state, level, text) {
322
+ return {
323
+ ...state,
324
+ notices: state.notices.concat({ id: uid(), level, text, at: Date.now() }),
325
+ };
326
+ }
327
+
328
+ /** @param {unknown} reason @returns {string} */
329
+ function closeReasonText(reason) {
330
+ if (typeof reason === 'string') return reason;
331
+ if (!isRecord(reason)) return '';
332
+ if (typeof reason.reason === 'string') return reason.reason;
333
+ if (typeof reason.message === 'string') return reason.message;
334
+ if (typeof reason.kind === 'string') return reason.kind;
335
+ return '';
336
+ }
337
+
338
+ /** @param {unknown} reason @returns {'no-broker'|'no-node'|'invalid'|'closed'} */
339
+ export function classifyClose(reason) {
340
+ const text = closeReasonText(reason).toLowerCase();
341
+ if (text.includes('no running broker')) return 'no-broker';
342
+ if (text.includes('does not exist') || text.includes('no node')) return 'no-node';
343
+ if (text.includes('invalid') || text.includes('rejected')) return 'invalid';
344
+ return 'closed';
345
+ }
346
+
347
+ /** @param {any} message @returns {number|undefined} */
348
+ function assistantContextTokens(message) {
349
+ if (!isRecord(message) || message.role !== 'assistant') return undefined;
350
+ const usage = isRecord(message.usage) ? message.usage : null;
351
+ if (!usage) return undefined;
352
+ const input = typeof usage.input === 'number' ? usage.input : null;
353
+ const cacheRead = typeof usage.cacheRead === 'number' ? usage.cacheRead : null;
354
+ const cacheWrite = typeof usage.cacheWrite === 'number' ? usage.cacheWrite : null;
355
+ if (input === null || cacheRead === null || cacheWrite === null) return undefined;
356
+ return input + cacheRead + cacheWrite;
357
+ }
358
+
359
+ /** @param {ChatState} state @param {string} text @param {NoticeLevel} level @returns {ChatState} */
360
+ function notice(state, text, level = 'info') {
361
+ return addNotice(state, level, text);
362
+ }
363
+
364
+ /** @type {any} */
365
+ const core = {
366
+ manifest: {
367
+ id: 'chat',
368
+ title: 'Chat',
369
+ description: 'Portable chat-as-a-view core',
370
+ },
371
+
372
+ /** @param {Readonly<Record<string, string>>} opts @returns {ChatState} */
373
+ init(opts) {
374
+ const target = typeof opts.target === 'string' && opts.target.trim() !== '' ? opts.target : null;
375
+ return {
376
+ target,
377
+ desiredRole: 'controller',
378
+ channel: null,
379
+ clientId: null,
380
+ conn: 'idle',
381
+ role: 'observer',
382
+ controllerId: null,
383
+ session: null,
384
+ transcript: initialTranscript(),
385
+ draft: '',
386
+ notices: [],
387
+ pendingDialog: null,
388
+ queued: [],
389
+ contextTokens: undefined,
390
+ };
391
+ },
392
+
393
+ streams: {
394
+ chat: {
395
+ id: 'chat',
396
+ request: ({ target, role }) => ({ kind: 'stream', target, role }),
397
+ onOpen: 'streamOpen',
398
+ onFrame: 'streamFrame',
399
+ onClose: 'streamClose',
400
+ },
401
+ },
402
+
403
+ intents: {
404
+ /** @param {any} ctx */
405
+ refresh(ctx) {
406
+ const state = ctx.state;
407
+ if (state.channel != null) return;
408
+ if (state.target == null) {
409
+ ctx.set((prev) => ({ ...prev, conn: 'error' }));
410
+ ctx.set((prev) => notice(prev, 'missing target — run crtr view run chat --target <node-id>', 'action'));
411
+ ctx.signal.setBanner('missing target — run crtr view run chat --target <node-id>', 'action');
412
+ ctx.signal.setStatus(null);
413
+ return;
414
+ }
415
+ ctx.signal.setStatus('connecting…');
416
+ try {
417
+ const channel = ctx.connect(core.streams.chat, { target: state.target, role: state.desiredRole });
418
+ ctx.set((prev) => ({
419
+ ...prev,
420
+ channel,
421
+ clientId: channel.id,
422
+ conn: 'connecting',
423
+ }));
424
+ } catch (err) {
425
+ const message = err instanceof Error ? err.message : String(err);
426
+ ctx.set((prev) => ({ ...prev, conn: 'error' }));
427
+ ctx.set((prev) => notice(prev, `stream connect failed — ${message}`, 'error'));
428
+ ctx.signal.setBanner(`stream connect failed — ${message}`, 'error');
429
+ ctx.signal.setStatus(null);
430
+ }
431
+ },
432
+
433
+ /** @param {any} ctx @param {any} channel */
434
+ streamOpen(ctx, channel) {
435
+ if (!isRecord(channel)) return;
436
+ ctx.set((prev) => ({
437
+ ...prev,
438
+ channel: prev.channel ?? channel,
439
+ clientId: prev.clientId ?? asMaybeText(channel.id),
440
+ conn: 'connecting',
441
+ }));
442
+ ctx.signal.setStatus('connected — waiting for welcome');
443
+ },
444
+
445
+ /** @param {any} ctx @param {unknown} payload */
446
+ streamClose(ctx, payload) {
447
+ // Fence: ignore stale closes from a channel that has already been replaced.
448
+ // Both hosts now include channelId in the close payload; if it doesn't match
449
+ // the current channel the close arrived from an old handle and must not
450
+ // clear the replacement.
451
+ const channelId = isRecord(payload) && typeof payload.channelId === 'string' ? payload.channelId : undefined;
452
+ if (channelId !== undefined && ctx.state.channel != null && ctx.state.channel.id !== channelId) return;
453
+ const kind = classifyClose(payload);
454
+ if (kind === 'closed' && ctx.state.channel == null && ctx.state.conn === 'connecting') return;
455
+ const banner =
456
+ kind === 'no-broker'
457
+ ? 'node is asleep / focus or revive it first'
458
+ : kind === 'no-node'
459
+ ? 'node not found / check the target'
460
+ : kind === 'invalid'
461
+ ? 'invalid target / check the target'
462
+ : 'stream closed / reconnect';
463
+ ctx.set((prev) => ({
464
+ ...prev,
465
+ channel: null,
466
+ conn: kind,
467
+ }));
468
+ ctx.set((prev) => notice(prev, banner, 'action'));
469
+ ctx.signal.setBanner(banner, 'action');
470
+ ctx.signal.setStatus(null);
471
+ },
472
+
473
+ /** @param {any} ctx */
474
+ reconnect(ctx) {
475
+ const channel = ctx.state.channel;
476
+ ctx.set((prev) => ({
477
+ ...prev,
478
+ channel: null,
479
+ conn: 'connecting',
480
+ }));
481
+ if (channel && typeof channel.close === 'function') {
482
+ try {
483
+ channel.close();
484
+ } catch {
485
+ /* best-effort close */
486
+ }
487
+ }
488
+ void ctx.dispatch('refresh');
489
+ },
490
+
491
+ /** @param {any} ctx @param {unknown} frame */
492
+ streamFrame(ctx, frame) {
493
+ if (!isRecord(frame)) return;
494
+ switch (frame.type) {
495
+ case 'welcome': {
496
+ const snapshot = frame.snapshot;
497
+ const transcript = applySnapshot(snapshot);
498
+ const session = normalizeSessionState(snapshot?.state);
499
+ const stats = snapshot?.stats;
500
+ const contextTokens =
501
+ typeof stats?.contextUsage?.tokens === 'number'
502
+ ? stats.contextUsage.tokens
503
+ : typeof stats?.tokens?.total === 'number'
504
+ ? stats.tokens.total
505
+ : undefined;
506
+ ctx.set((prev) => ({
507
+ ...prev,
508
+ conn: 'open',
509
+ controllerId: asMaybeText(frame.controller_id),
510
+ role: frame.controller_id === prev.clientId ? 'controller' : (frame.role ?? 'observer'),
511
+ session,
512
+ transcript,
513
+ pendingDialog: frame.pending_dialog ?? null,
514
+ contextTokens,
515
+ }));
516
+ ctx.signal.clearBanner();
517
+ ctx.signal.setStatus(null);
518
+ ctx.signal.setSubtitle(session.sessionName ?? ctx.state.target);
519
+ return;
520
+ }
521
+
522
+ case 'control_changed': {
523
+ ctx.set((prev) => {
524
+ const nextRole = frame.controller_id === prev.clientId ? 'controller' : 'observer';
525
+ const changed = nextRole !== prev.role;
526
+ const demoted = prev.role === 'controller' && nextRole === 'observer';
527
+ let next = {
528
+ ...prev,
529
+ controllerId: asMaybeText(frame.controller_id),
530
+ role: nextRole,
531
+ desiredRole: demoted ? 'observer' : prev.desiredRole,
532
+ };
533
+ if (changed) {
534
+ next = notice(next, nextRole === 'controller' ? 'control changed — you are controller' : 'control changed — read-only', 'info');
535
+ }
536
+ return next;
537
+ });
538
+ return;
539
+ }
540
+
541
+ case 'model_changed': {
542
+ ctx.set((prev) => patchSession(prev, { model: asMaybeText(frame.model) }));
543
+ return;
544
+ }
545
+
546
+ case 'error': {
547
+ if (frame.code === 'not_controller') {
548
+ const text = 'read-only — take control before sending';
549
+ ctx.set((prev) => ({ ...prev, role: 'observer' }));
550
+ ctx.set((prev) => notice(prev, text, 'action'));
551
+ ctx.signal.setBanner(text, 'action');
552
+ return;
553
+ }
554
+ const text = frame.message || frame.code || 'stream error';
555
+ ctx.set((prev) => notice(prev, text, 'error'));
556
+ ctx.signal.setBanner(text, 'error');
557
+ return;
558
+ }
559
+
560
+ case 'ack': {
561
+ if (frame.ok) return;
562
+ const text = frame.detail ? `${frame.for} failed — ${frame.detail}` : `${frame.for} failed`;
563
+ ctx.set((prev) => notice(prev, text, 'error'));
564
+ ctx.signal.setBanner(text, 'error');
565
+ return;
566
+ }
567
+
568
+ case 'extension_ui_request': {
569
+ const text = 'cannot answer dialogs yet';
570
+ ctx.set((prev) => ({ ...prev, pendingDialog: frame }));
571
+ ctx.set((prev) => notice(prev, text, 'action'));
572
+ ctx.signal.setBanner(text, 'action');
573
+ return;
574
+ }
575
+
576
+ case 'display_status': {
577
+ const text = frame.text === undefined ? `${frame.key} cleared` : `${frame.key}: ${frame.text}`;
578
+ ctx.set((prev) => notice(prev, text, 'info'));
579
+ return;
580
+ }
581
+
582
+ case 'display_widget': {
583
+ const text = frame.lines === undefined ? `${frame.key} cleared` : `${frame.key}: ${frame.lines.join(' | ')}`;
584
+ ctx.set((prev) => notice(prev, text, 'info'));
585
+ return;
586
+ }
587
+
588
+ case 'display_title': {
589
+ ctx.set((prev) => notice(prev, `title: ${frame.title}`, 'info'));
590
+ return;
591
+ }
592
+
593
+ case 'bash_start': {
594
+ ctx.set((prev) => ({ ...prev, transcript: applyBashStart(prev.transcript, frame.command, frame.excludeFromContext) }));
595
+ return;
596
+ }
597
+
598
+ case 'bash_output': {
599
+ ctx.set((prev) => ({ ...prev, transcript: applyBashOutput(prev.transcript, frame.chunk) }));
600
+ return;
601
+ }
602
+
603
+ case 'bash_end': {
604
+ ctx.set((prev) => ({ ...prev, transcript: applyBashEnd(prev.transcript, frame) }));
605
+ return;
606
+ }
607
+
608
+ default: {
609
+ ctx.set((prev) => {
610
+ const transcript = reduceAgentEvent(prev.transcript, frame);
611
+ let next = { ...prev, transcript };
612
+ if (frame.type === 'agent_start') {
613
+ next = patchSession(next, { isStreaming: true });
614
+ } else if (frame.type === 'agent_end') {
615
+ next = patchSession(next, { isStreaming: false });
616
+ } else if (frame.type === 'session_info_changed') {
617
+ next = patchSession(next, { sessionName: asMaybeText(frame.name) });
618
+ } else if (frame.type === 'thinking_level_changed') {
619
+ next = patchSession(next, { thinkingLevel: asText(frame.level) || next.session?.thinkingLevel || 'off' });
620
+ } else if (frame.type === 'queue_update') {
621
+ const queued = [
622
+ ...(Array.isArray(frame.steering) ? frame.steering : []),
623
+ ...(Array.isArray(frame.followUp) ? frame.followUp : []),
624
+ ].map((value) => String(value));
625
+ next = {
626
+ ...next,
627
+ queued,
628
+ };
629
+ next = patchSession(next, { pendingMessageCount: queued.length });
630
+ }
631
+ const assistantTokens = frame.type === 'message_end' ? assistantContextTokens(frame.message) : undefined;
632
+ if (assistantTokens !== undefined) {
633
+ next = { ...next, contextTokens: assistantTokens };
634
+ }
635
+ return next;
636
+ });
637
+ }
638
+ }
639
+ },
640
+
641
+ /** @param {any} ctx @param {string} draft */
642
+ setDraft(ctx, draft) {
643
+ ctx.set((prev) => ({ ...prev, draft }));
644
+ },
645
+
646
+ /** @param {any} ctx */
647
+ submitDraft(ctx) {
648
+ const text = ctx.state.draft.trim();
649
+ if (!text) return;
650
+ const channel = ctx.state.channel;
651
+ if (!channel || ctx.state.conn !== 'open') {
652
+ const noticeText = 'not connected — reconnect';
653
+ ctx.set((prev) => notice(prev, noticeText, 'error'));
654
+ ctx.signal.setBanner(noticeText, 'error');
655
+ return;
656
+ }
657
+ if (ctx.state.role !== 'controller') {
658
+ const text = 'read-only — take control before sending';
659
+ ctx.set((prev) => notice(prev, text, 'action'));
660
+ ctx.signal.setBanner(text, 'action');
661
+ return;
662
+ }
663
+ const frame = ctx.state.session?.isStreaming === true ? { type: 'steer', text } : { type: 'prompt', text };
664
+ channel.send(frame);
665
+ ctx.set((prev) => ({ ...prev, draft: '' }));
666
+ },
667
+
668
+ /** @param {any} ctx */
669
+ abort(ctx) {
670
+ if (ctx.state.role !== 'controller') {
671
+ const text = 'read-only — take control before sending';
672
+ ctx.set((prev) => notice(prev, text, 'action'));
673
+ ctx.signal.setBanner(text, 'action');
674
+ return;
675
+ }
676
+ if (!ctx.state.channel || ctx.state.conn !== 'open') {
677
+ const text = 'not connected — reconnect';
678
+ ctx.set((prev) => notice(prev, text, 'error'));
679
+ ctx.signal.setBanner(text, 'error');
680
+ return;
681
+ }
682
+ ctx.state.channel.send({ type: 'abort' });
683
+ },
684
+
685
+ /** @param {any} ctx */
686
+ requestControl(ctx) {
687
+ ctx.set((prev) => ({ ...prev, desiredRole: 'controller' }));
688
+ if (ctx.state.channel && ctx.state.conn === 'open') ctx.state.channel.send({ type: 'request_control' });
689
+ },
690
+
691
+ /** @param {any} ctx */
692
+ releaseControl(ctx) {
693
+ ctx.set((prev) => ({ ...prev, desiredRole: 'observer' }));
694
+ const channel = ctx.state.channel;
695
+ if (channel && ctx.state.conn === 'open') {
696
+ channel.send({ type: 'release_control' });
697
+ }
698
+ // Close and reopen as observer so transport-level auto-reconnect cannot
699
+ // silently reclaim controller role (transports capture role at open time).
700
+ void ctx.dispatch('reconnect');
701
+ },
702
+
703
+ /** @param {any} ctx @param {string|undefined} id */
704
+ clearNotice(ctx, id) {
705
+ ctx.set((prev) => {
706
+ if (id === undefined || id === null || id === '') return { ...prev, notices: [] };
707
+ return { ...prev, notices: prev.notices.filter((notice) => notice.id !== id) };
708
+ });
709
+ },
710
+
711
+ /** @param {any} ctx */
712
+ quit(ctx) {
713
+ if (ctx.state.channel) {
714
+ try {
715
+ ctx.state.channel.close();
716
+ } catch {
717
+ /* best-effort */
718
+ }
719
+ }
720
+ ctx.signal.quit();
721
+ },
722
+ },
723
+ };
724
+
725
+ export default core;