@crouton-kit/crouter 0.3.28 → 0.3.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/dist/build-root.js +4 -2
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
  5. package/dist/builtin-memory/crouter-development/personas.md +1 -1
  6. package/dist/builtin-memory/crouter-development/plugins.md +20 -18
  7. package/dist/builtin-memory/design.md +1 -1
  8. package/dist/builtin-memory/development.md +1 -1
  9. package/dist/builtin-memory/internal/INDEX.md +1 -1
  10. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
  12. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  13. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  14. package/dist/builtin-memory/planning.md +1 -1
  15. package/dist/builtin-memory/spec.md +6 -4
  16. package/dist/builtin-personas/orchestration-kernel.md +4 -5
  17. package/dist/builtin-personas/runtime-base.md +5 -4
  18. package/dist/builtin-personas/spec/PERSONA.md +8 -4
  19. package/dist/builtin-personas/spec/orchestrator.md +5 -3
  20. package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
  21. package/dist/builtin-views/canvas/core.mjs +586 -0
  22. package/dist/builtin-views/canvas/text.mjs +58 -0
  23. package/dist/builtin-views/canvas/tui.mjs +165 -0
  24. package/dist/builtin-views/canvas/web.jsx +120 -0
  25. package/dist/builtin-views/git-pr/core.mjs +673 -0
  26. package/dist/builtin-views/git-pr/text.mjs +84 -0
  27. package/dist/builtin-views/git-pr/tui.mjs +302 -0
  28. package/dist/builtin-views/git-pr/web.jsx +216 -0
  29. package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
  30. package/dist/builtin-views/inbox/core.mjs +1273 -0
  31. package/dist/builtin-views/inbox/text.mjs +73 -0
  32. package/dist/builtin-views/inbox/tui.mjs +312 -0
  33. package/dist/builtin-views/inbox/web.jsx +188 -0
  34. package/dist/builtin-views/linkedin/core.mjs +906 -0
  35. package/dist/builtin-views/linkedin/text.mjs +69 -0
  36. package/dist/builtin-views/linkedin/tui.mjs +427 -0
  37. package/dist/builtin-views/linkedin/web.jsx +206 -0
  38. package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
  39. package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
  40. package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
  41. package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
  42. package/dist/cli.js +3 -3
  43. package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
  44. package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
  45. package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
  46. package/dist/clients/attach/__tests__/git-info.test.js +31 -0
  47. package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
  48. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
  49. package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
  50. package/dist/clients/attach/attach-cmd.js +282 -46
  51. package/dist/clients/attach/auth-pickers.d.ts +9 -0
  52. package/dist/clients/attach/auth-pickers.js +194 -0
  53. package/dist/clients/attach/canvas-panels.d.ts +4 -2
  54. package/dist/clients/attach/canvas-panels.js +45 -17
  55. package/dist/clients/attach/chat-view.d.ts +27 -2
  56. package/dist/clients/attach/chat-view.js +98 -9
  57. package/dist/clients/attach/clipboard-image.d.ts +47 -8
  58. package/dist/clients/attach/clipboard-image.js +150 -26
  59. package/dist/clients/attach/clipboard-text.d.ts +3 -0
  60. package/dist/clients/attach/clipboard-text.js +39 -0
  61. package/dist/clients/attach/config-load.d.ts +54 -7
  62. package/dist/clients/attach/config-load.js +99 -3
  63. package/dist/clients/attach/context-message.d.ts +12 -0
  64. package/dist/clients/attach/context-message.js +72 -0
  65. package/dist/clients/attach/git-info.d.ts +20 -0
  66. package/dist/clients/attach/git-info.js +44 -0
  67. package/dist/clients/attach/graph-overlay.d.ts +13 -4
  68. package/dist/clients/attach/graph-overlay.js +59 -19
  69. package/dist/clients/attach/input-controller.d.ts +75 -13
  70. package/dist/clients/attach/input-controller.js +305 -67
  71. package/dist/clients/attach/pickers.d.ts +58 -0
  72. package/dist/clients/attach/pickers.js +171 -0
  73. package/dist/clients/attach/slash-commands.d.ts +22 -0
  74. package/dist/clients/attach/slash-commands.js +91 -25
  75. package/dist/clients/attach/titled-editor.d.ts +33 -0
  76. package/dist/clients/attach/titled-editor.js +84 -0
  77. package/dist/clients/attach/view-socket.d.ts +18 -1
  78. package/dist/clients/attach/view-socket.js +70 -1
  79. package/dist/clients/web/dev-server.d.ts +7 -0
  80. package/dist/clients/web/dev-server.js +59 -0
  81. package/dist/clients/web/events.d.ts +14 -0
  82. package/dist/clients/web/events.js +151 -0
  83. package/dist/clients/web/server.d.ts +18 -0
  84. package/dist/clients/web/server.js +450 -0
  85. package/dist/clients/web/web-cmd.d.ts +2 -0
  86. package/dist/clients/web/web-cmd.js +120 -0
  87. package/dist/commands/canvas.js +1 -2
  88. package/dist/commands/chord.js +1 -1
  89. package/dist/commands/dashboard.js +5 -1
  90. package/dist/commands/memory/__tests__/lint-schema.test.js +6 -6
  91. package/dist/commands/memory/lint.js +14 -2
  92. package/dist/commands/memory/read.js +2 -2
  93. package/dist/commands/memory/shared.d.ts +7 -1
  94. package/dist/commands/memory/shared.js +18 -3
  95. package/dist/commands/memory/write.js +8 -4
  96. package/dist/commands/memory.js +4 -4
  97. package/dist/commands/node.d.ts +1 -1
  98. package/dist/commands/node.js +64 -65
  99. package/dist/commands/pkg.js +1 -2
  100. package/dist/commands/revive.js +11 -3
  101. package/dist/commands/sys/sync.d.ts +1 -0
  102. package/dist/commands/sys/sync.js +187 -0
  103. package/dist/commands/sys.js +3 -2
  104. package/dist/commands/view-cycle.js +2 -2
  105. package/dist/commands/view-list.js +8 -8
  106. package/dist/commands/view-new.js +21 -17
  107. package/dist/commands/view-pick.js +1 -1
  108. package/dist/commands/view-run.js +35 -14
  109. package/dist/commands/view.js +8 -6
  110. package/dist/commands/workspace.d.ts +2 -0
  111. package/dist/commands/workspace.js +161 -0
  112. package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
  113. package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
  114. package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
  115. package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
  116. package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
  117. package/dist/core/__tests__/child-death-wake.test.js +56 -111
  118. package/dist/core/__tests__/close.test.js +14 -20
  119. package/dist/core/__tests__/context-intro.test.js +19 -19
  120. package/dist/core/__tests__/daemon-boot.test.js +30 -23
  121. package/dist/core/__tests__/draw-style.test.js +1 -1
  122. package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
  123. package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
  124. package/dist/core/__tests__/fixtures/fake-engine.d.ts +17 -1
  125. package/dist/core/__tests__/fixtures/fake-engine.js +22 -2
  126. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  127. package/dist/core/__tests__/focuses.test.js +25 -13
  128. package/dist/core/__tests__/fork.test.js +22 -5
  129. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
  130. package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
  131. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
  132. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
  133. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
  134. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
  135. package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
  136. package/dist/core/__tests__/full/cascade-close.test.js +22 -8
  137. package/dist/core/__tests__/full/detach-focus.test.js +66 -169
  138. package/dist/core/__tests__/helpers/harness.d.ts +6 -9
  139. package/dist/core/__tests__/helpers/harness.js +19 -36
  140. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
  141. package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
  142. package/dist/core/__tests__/lifecycle.test.js +1 -19
  143. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
  144. package/dist/core/__tests__/memory-resolver.test.js +1 -1
  145. package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -2
  146. package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
  147. package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
  148. package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
  149. package/dist/core/__tests__/relaunch-root.test.js +114 -0
  150. package/dist/core/__tests__/reset.test.js +19 -79
  151. package/dist/core/__tests__/steer-note.test.js +9 -7
  152. package/dist/core/__tests__/tmux-surface.test.js +13 -7
  153. package/dist/core/bootstrap.d.ts +0 -3
  154. package/dist/core/bootstrap.js +1 -143
  155. package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
  156. package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
  157. package/dist/core/canvas/browse/app.js +131 -8
  158. package/dist/core/canvas/browse/model.d.ts +23 -5
  159. package/dist/core/canvas/browse/model.js +68 -13
  160. package/dist/core/canvas/browse/render.d.ts +7 -1
  161. package/dist/core/canvas/browse/render.js +178 -47
  162. package/dist/core/canvas/canvas.js +1 -1
  163. package/dist/core/canvas/focuses.d.ts +4 -4
  164. package/dist/core/canvas/focuses.js +7 -7
  165. package/dist/core/canvas/nav-model.js +8 -7
  166. package/dist/core/canvas/render.d.ts +68 -9
  167. package/dist/core/canvas/render.js +195 -61
  168. package/dist/core/canvas/types.d.ts +16 -23
  169. package/dist/core/config.js +1 -4
  170. package/dist/core/memory-resolver.js +43 -5
  171. package/dist/core/runtime/bearings.d.ts +1 -1
  172. package/dist/core/runtime/bearings.js +8 -8
  173. package/dist/core/runtime/branded-host.d.ts +16 -0
  174. package/dist/core/runtime/branded-host.js +127 -0
  175. package/dist/core/runtime/broker-protocol.d.ts +212 -6
  176. package/dist/core/runtime/broker.d.ts +4 -1
  177. package/dist/core/runtime/broker.js +494 -58
  178. package/dist/core/runtime/close.d.ts +12 -2
  179. package/dist/core/runtime/close.js +37 -16
  180. package/dist/core/runtime/front-door.js +6 -14
  181. package/dist/core/runtime/host.d.ts +14 -34
  182. package/dist/core/runtime/host.js +14 -50
  183. package/dist/core/runtime/launch.d.ts +12 -9
  184. package/dist/core/runtime/launch.js +27 -15
  185. package/dist/core/runtime/lifecycle.d.ts +1 -1
  186. package/dist/core/runtime/lifecycle.js +15 -19
  187. package/dist/core/runtime/nodes.d.ts +0 -55
  188. package/dist/core/runtime/nodes.js +6 -74
  189. package/dist/core/runtime/placement.d.ts +91 -335
  190. package/dist/core/runtime/placement.js +262 -840
  191. package/dist/core/runtime/promote.d.ts +2 -0
  192. package/dist/core/runtime/promote.js +24 -6
  193. package/dist/core/runtime/recap.d.ts +8 -0
  194. package/dist/core/runtime/recap.js +107 -0
  195. package/dist/core/runtime/recycle.js +25 -61
  196. package/dist/core/runtime/reset.d.ts +43 -37
  197. package/dist/core/runtime/reset.js +131 -218
  198. package/dist/core/runtime/revive.d.ts +9 -29
  199. package/dist/core/runtime/revive.js +40 -139
  200. package/dist/core/runtime/spawn.d.ts +15 -16
  201. package/dist/core/runtime/spawn.js +165 -194
  202. package/dist/core/runtime/surface-bg.d.ts +11 -0
  203. package/dist/core/runtime/surface-bg.js +68 -0
  204. package/dist/core/runtime/tmux.d.ts +13 -50
  205. package/dist/core/runtime/tmux.js +33 -87
  206. package/dist/core/scope.d.ts +1 -2
  207. package/dist/core/scope.js +1 -5
  208. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
  209. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
  210. package/dist/core/skill-sync/builtins.d.ts +42 -0
  211. package/dist/core/skill-sync/builtins.js +112 -0
  212. package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
  213. package/dist/core/skill-sync/claude-plugins.js +71 -0
  214. package/dist/core/skill-sync/engine.d.ts +42 -0
  215. package/dist/core/skill-sync/engine.js +633 -0
  216. package/dist/core/skill-sync/export.d.ts +23 -0
  217. package/dist/core/skill-sync/export.js +86 -0
  218. package/dist/core/skill-sync/manifest.d.ts +64 -0
  219. package/dist/core/skill-sync/manifest.js +181 -0
  220. package/dist/core/skill-sync/profile.d.ts +76 -0
  221. package/dist/core/skill-sync/profile.js +173 -0
  222. package/dist/core/skill-sync/snapshot.d.ts +57 -0
  223. package/dist/core/skill-sync/snapshot.js +120 -0
  224. package/dist/core/substrate/index.d.ts +1 -1
  225. package/dist/core/substrate/index.js +1 -1
  226. package/dist/core/substrate/injected-store.js +3 -3
  227. package/dist/core/substrate/on-read.js +68 -6
  228. package/dist/core/substrate/render.d.ts +8 -11
  229. package/dist/core/substrate/render.js +29 -43
  230. package/dist/core/substrate/schema.d.ts +10 -3
  231. package/dist/core/substrate/schema.js +6 -3
  232. package/dist/core/tui/host.d.ts +12 -4
  233. package/dist/core/tui/host.js +280 -149
  234. package/dist/core/view/bridge.d.ts +10 -0
  235. package/dist/core/view/bridge.js +31 -0
  236. package/dist/core/view/chrome.d.ts +9 -0
  237. package/dist/core/view/chrome.js +22 -0
  238. package/dist/core/view/contract.d.ts +171 -0
  239. package/dist/core/view/contract.js +23 -0
  240. package/dist/core/view/loader.d.ts +31 -0
  241. package/dist/core/view/loader.js +188 -0
  242. package/dist/core/view/transport-local.d.ts +7 -0
  243. package/dist/core/view/transport-local.js +70 -0
  244. package/dist/core/view/transport.d.ts +4 -0
  245. package/dist/core/view/transport.js +15 -0
  246. package/dist/daemon/crtrd.d.ts +38 -27
  247. package/dist/daemon/crtrd.js +303 -376
  248. package/dist/daemon/manage.js +6 -1
  249. package/dist/index.js +1 -1
  250. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
  251. package/dist/pi-extensions/canvas-context-intro.js +4 -4
  252. package/dist/pi-extensions/canvas-doc-substrate.js +16 -16
  253. package/dist/pi-extensions/canvas-nav.js +5 -0
  254. package/dist/pi-extensions/canvas-recap.d.ts +37 -0
  255. package/dist/pi-extensions/canvas-recap.js +236 -0
  256. package/dist/pi-extensions/canvas-resume.js +2 -1
  257. package/dist/pi-extensions/canvas-stophook.d.ts +14 -12
  258. package/dist/pi-extensions/canvas-stophook.js +87 -101
  259. package/dist/pi-extensions/canvas-view.js +2 -1
  260. package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
  261. package/dist/pi-extensions/widget-order-bus.js +34 -0
  262. package/dist/prompts/view.d.ts +2 -2
  263. package/dist/prompts/view.js +145 -73
  264. package/dist/types.d.ts +1 -6
  265. package/dist/types.js +1 -3
  266. package/dist/web/ViewChrome.d.ts +7 -0
  267. package/dist/web/ViewChrome.js +28 -0
  268. package/dist/web/ViewPane.d.ts +39 -0
  269. package/dist/web/ViewPane.js +48 -0
  270. package/dist/web/index.d.ts +6 -0
  271. package/dist/web/index.js +16 -0
  272. package/dist/web/runtime.d.ts +39 -0
  273. package/dist/web/runtime.js +133 -0
  274. package/dist/web/states.d.ts +24 -0
  275. package/dist/web/states.js +24 -0
  276. package/dist/web/transport-http.d.ts +5 -0
  277. package/dist/web/transport-http.js +28 -0
  278. package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
  279. package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
  280. package/dist/web-client/index.html +13 -0
  281. package/package.json +20 -6
  282. package/dist/builtin-views/canvas/client.mjs +0 -303
  283. package/dist/builtin-views/canvas/view.mjs +0 -576
  284. package/dist/builtin-views/git-pr/client.mjs +0 -440
  285. package/dist/builtin-views/git-pr/view.mjs +0 -675
  286. package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
  287. package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
  288. package/dist/builtin-views/inbox/view.mjs +0 -889
  289. package/dist/builtin-views/linkedin/client.mjs +0 -610
  290. package/dist/builtin-views/linkedin/view.mjs +0 -1171
  291. package/dist/commands/pkg/bridge.d.ts +0 -1
  292. package/dist/commands/pkg/bridge.js +0 -137
  293. package/dist/commands/skill/author.d.ts +0 -3
  294. package/dist/commands/skill/author.js +0 -140
  295. package/dist/commands/skill/shared.d.ts +0 -3
  296. package/dist/commands/skill/shared.js +0 -19
  297. package/dist/commands/skill.d.ts +0 -2
  298. package/dist/commands/skill.js +0 -21
  299. package/dist/commands/tmux-spread.d.ts +0 -2
  300. package/dist/commands/tmux-spread.js +0 -144
  301. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
  302. package/dist/core/__tests__/full/placement-focus.test.js +0 -309
  303. package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
  304. package/dist/core/__tests__/full/placement-revive.test.js +0 -238
  305. package/dist/core/__tests__/full/placement-teardown.test.js +0 -297
  306. package/dist/core/__tests__/home-session.test.js +0 -186
  307. package/dist/core/__tests__/relaunch.test.js +0 -335
  308. package/dist/core/bridge-map.d.ts +0 -19
  309. package/dist/core/bridge-map.js +0 -73
  310. package/dist/core/tui/contract.d.ts +0 -83
  311. package/dist/core/tui/contract.js +0 -8
  312. package/dist/core/tui/loader.d.ts +0 -16
  313. package/dist/core/tui/loader.js +0 -94
  314. package/dist/prompts/skill.d.ts +0 -2
  315. package/dist/prompts/skill.js +0 -650
  316. /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
  317. /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
  318. /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
  319. /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
  320. /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
  321. /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
  322. /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/titled-editor.test.d.ts} +0 -0
@@ -18,10 +18,15 @@
18
18
  // `CLIENT_READ_CAPS`; a broker that somehow sends an oversized frame surfaces as
19
19
  // a clean error+exit, not a crash.
20
20
  import { EventEmitter } from 'node:events';
21
+ import { randomUUID } from 'node:crypto';
21
22
  import { createConnection } from 'node:net';
22
23
  import { join } from 'node:path';
23
24
  import { nodeDir } from '../../core/canvas/paths.js';
24
25
  import { CLIENT_READ_CAPS, encodeFrame, FrameDecoder, FrameOverflowError, } from '../../core/runtime/broker-protocol.js';
26
+ /** How long {@link ViewSocketClient.request} waits for the correlated reply before
27
+ * rejecting — bounds a pending picker fetch if the broker drops the request
28
+ * (it should always reply with `data` or a correlated `error`). */
29
+ const REQUEST_TIMEOUT_MS = 10_000;
25
30
  /** Surfaced when the node has no reachable broker at connect time (no socket
26
31
  * file, or a stale socket with nothing listening). The command catches this to
27
32
  * exit non-zero with a focus/revive hint. */
@@ -51,10 +56,48 @@ export class ViewSocketClient extends EventEmitter {
51
56
  socket;
52
57
  decoder = new FrameDecoder(CLIENT_READ_CAPS);
53
58
  closeEmitted = false;
59
+ /** In-flight correlated read-ops, keyed by the `id` minted in {@link request}.
60
+ * Resolved by the matching `data` frame / rejected by the matching `error`
61
+ * frame in {@link onData}, the request's timeout, or socket teardown. */
62
+ pending = new Map();
54
63
  constructor(nodeId) {
55
64
  super();
56
65
  this.nodeId = nodeId;
57
66
  }
67
+ /** Issue a correlated read-op and resolve with the broker's `data` reply (or
68
+ * reject on the correlated `error`, a timeout, or socket teardown). Mints the
69
+ * `id`, sends `{...frame, id}`, and parks a resolver consumed by {@link onData}.
70
+ * The reply is narrowed by the caller on its `kind`. */
71
+ request(frame) {
72
+ const id = randomUUID();
73
+ return new Promise((resolve, reject) => {
74
+ // Fail fast on a dead/absent socket rather than parking a promise that the
75
+ // reply will never reach (it would otherwise hang the full timeout). A
76
+ // post-`close` request lands here too — `rejectAllPending` already ran.
77
+ const sock = this.socket;
78
+ if (sock === undefined || sock.destroyed) {
79
+ reject(new Error(`cannot issue '${frame.type}': no live broker connection`));
80
+ return;
81
+ }
82
+ const timer = setTimeout(() => {
83
+ this.pending.delete(id);
84
+ reject(new Error(`request '${frame.type}' timed out after ${REQUEST_TIMEOUT_MS}ms`));
85
+ }, REQUEST_TIMEOUT_MS);
86
+ if (typeof timer.unref === 'function')
87
+ timer.unref();
88
+ this.pending.set(id, { resolve, reject, timer });
89
+ this.send({ ...frame, id });
90
+ });
91
+ }
92
+ /** Reject + clear every in-flight request (socket gone / decode error) so a
93
+ * picker fetch never hangs past the connection it rode on. */
94
+ rejectAllPending(reason) {
95
+ for (const { reject, timer } of this.pending.values()) {
96
+ clearTimeout(timer);
97
+ reject(new Error(reason));
98
+ }
99
+ this.pending.clear();
100
+ }
58
101
  /** The broker binds `join(nodeDir(id), 'view.sock')` — resolve it the same way. */
59
102
  get socketPath() {
60
103
  return join(nodeDir(this.nodeId), 'view.sock');
@@ -138,12 +181,37 @@ export class ViewSocketClient extends EventEmitter {
138
181
  const msg = err instanceof FrameOverflowError
139
182
  ? `broker sent an oversized frame (${err.message}) — disconnecting`
140
183
  : `failed to decode a broker frame: ${String(err)}`;
184
+ this.rejectAllPending(msg);
141
185
  this.emitError(new Error(msg));
142
186
  this.destroy();
143
187
  return;
144
188
  }
145
189
  for (const raw of frames) {
146
- this.emit('frame', raw);
190
+ const frame = raw;
191
+ // Correlated replies (read-ops + dequeue) are consumed by the pending-by-id
192
+ // resolver, NOT re-emitted as a generic 'frame' (the attach frame router
193
+ // would otherwise treat a `data` frame as an AgentSessionEvent). A `data`
194
+ // frame is ALWAYS a reply, so it is swallowed unconditionally — even a late
195
+ // post-timeout one (no pending entry) is dropped, never leaked to the
196
+ // router. An `error` is correlated only when its `id` matches an in-flight
197
+ // request; an uncorrelated error still flows through.
198
+ if (frame.type === 'data') {
199
+ const entry = typeof frame.id === 'string' ? this.pending.get(frame.id) : undefined;
200
+ if (entry) {
201
+ this.pending.delete(frame.id);
202
+ clearTimeout(entry.timer);
203
+ entry.resolve(frame);
204
+ }
205
+ continue;
206
+ }
207
+ if (frame.type === 'error' && typeof frame.id === 'string' && this.pending.has(frame.id)) {
208
+ const entry = this.pending.get(frame.id);
209
+ this.pending.delete(frame.id);
210
+ clearTimeout(entry.timer);
211
+ entry.reject(new Error(frame.message || `request failed: ${frame.code}`));
212
+ continue;
213
+ }
214
+ this.emit('frame', frame);
147
215
  }
148
216
  }
149
217
  onError(err) {
@@ -159,6 +227,7 @@ export class ViewSocketClient extends EventEmitter {
159
227
  if (this.closeEmitted)
160
228
  return;
161
229
  this.closeEmitted = true;
230
+ this.rejectAllPending('broker connection closed');
162
231
  this.emit('close');
163
232
  }
164
233
  destroy() {
@@ -0,0 +1,7 @@
1
+ import type { Server as HttpServer } from 'node:http';
2
+ import type { ViteDevServer } from 'vite';
3
+ /** Create a Vite dev server in middleware mode, HMR bound to `httpServer`.
4
+ * Mirrors the old `view serve` resolve setup so the shell's bare imports
5
+ * (react, the `@crouton-kit/crouter/web` self-subpath) resolve from the
6
+ * generated/shell root. Lazily imports vite so the shipped path never loads it. */
7
+ export declare function createDevServer(httpServer: HttpServer): Promise<ViteDevServer>;
@@ -0,0 +1,59 @@
1
+ // dev-server.ts — `crtr web serve --dev` Vite middleware mode.
2
+ //
3
+ // The shipped server serves a prebuilt static shell bundle from dist/web-client/
4
+ // (§11). In --dev a contributor iterating on the shell or a builtin view gets
5
+ // HMR instead: a Vite dev server in MIDDLEWARE mode mounted on the SAME HTTP
6
+ // server as the bridge + WS relay. This is the one place Vite runs at runtime —
7
+ // the mainstream "Vite middleware in dev, static dist in prod" split.
8
+ //
9
+ // The bridge (POST /__crtr/source), the SSE lane (GET /__crtr/events) and the
10
+ // WS relay are owned by server.ts and checked BEFORE Vite's middlewares; this
11
+ // module only supplies the asset/HMR middleware. Vite's HMR WebSocket shares
12
+ // the HTTP server (server.hmr.server) — server.ts's upgrade handler routes
13
+ // `/node/<id>` to the broker relay and leaves every other upgrade for Vite.
14
+ import { resolve, dirname, join } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ const HERE = dirname(fileURLToPath(import.meta.url));
17
+ /** The shell SPA Vite root — the in-tree shell project (`src/clients/web/web-client/`).
18
+ * --dev is a from-source contributor loop, so this resolves against the source
19
+ * tree (HERE is `dist/clients/web` when built, `src/clients/web` under tsx);
20
+ * walk to the package root and into src. */
21
+ function resolveShellRoot(packageRoot) {
22
+ return join(packageRoot, 'src', 'clients', 'web', 'web-client');
23
+ }
24
+ /** Create a Vite dev server in middleware mode, HMR bound to `httpServer`.
25
+ * Mirrors the old `view serve` resolve setup so the shell's bare imports
26
+ * (react, the `@crouton-kit/crouter/web` self-subpath) resolve from the
27
+ * generated/shell root. Lazily imports vite so the shipped path never loads it. */
28
+ export async function createDevServer(httpServer) {
29
+ const packageRoot = resolve(HERE, '../../..');
30
+ const nodeModules = join(packageRoot, 'node_modules');
31
+ const shellRoot = resolveShellRoot(packageRoot);
32
+ const { createServer } = await import('vite');
33
+ const react = (await import('@vitejs/plugin-react')).default;
34
+ const tailwindcss = (await import('@tailwindcss/vite')).default;
35
+ return createServer({
36
+ configFile: false,
37
+ root: shellRoot,
38
+ plugins: [react(), tailwindcss()],
39
+ appType: 'spa',
40
+ resolve: {
41
+ alias: {
42
+ react: join(nodeModules, 'react'),
43
+ 'react-dom': join(nodeModules, 'react-dom'),
44
+ // The crtr web runtime is a self-subpath of crouter's own package;
45
+ // pin it to the built barrel so the shell can import it from any root.
46
+ '@crouton-kit/crouter/web': join(packageRoot, 'dist', 'web', 'index.js'),
47
+ },
48
+ dedupe: ['react', 'react-dom'],
49
+ },
50
+ server: {
51
+ middlewareMode: true,
52
+ // Share our HTTP server for HMR; server.ts's upgrade handler hands every
53
+ // non-`/node/<id>` upgrade to Vite (the vite-hmr WebSocket).
54
+ hmr: { server: httpServer },
55
+ fs: { allow: [shellRoot, packageRoot] },
56
+ },
57
+ logLevel: 'warn',
58
+ });
59
+ }
@@ -0,0 +1,14 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ export type ChangeKind = 'nodes' | 'inbox';
3
+ export interface EventHub {
4
+ /** Register an SSE client: writes the event-stream headers, streams events
5
+ * until the connection closes, and self-removes on close. */
6
+ addClient: (res: ServerResponse) => void;
7
+ /** Tear down watchers + heartbeat and end all open streams. */
8
+ close: () => void;
9
+ }
10
+ /** Start the change-event hub: open the two fs watchers once, fan their
11
+ * debounced invalidations out to every connected SSE client. Watchers run for
12
+ * the server's lifetime (cheap — two inotify/FSEvents registrations), not
13
+ * per-client, so N browsers share one watch. */
14
+ export declare function startEventHub(): EventHub;
@@ -0,0 +1,151 @@
1
+ // events.ts — the `crtr web` realtime push lane (the §7 SSE seam, built for v1).
2
+ //
3
+ // ONE server-sent-events stream, `GET /__crtr/events`, that carries CHANGE
4
+ // INVALIDATIONS — never data. The shell / its ViewPanes subscribe and, on an
5
+ // event, re-pull through their existing Sources (the bridge) instead of waiting
6
+ // on a fixed poll tick. SSE carries "something in this class changed"; the
7
+ // consumer decides what to re-read. This keeps the event vocabulary tiny and
8
+ // generic and the contract unchanged (a ViewPane still just runs `refresh`).
9
+ //
10
+ // Event vocabulary (deliberately minimal): { kind: 'nodes' | 'inbox', ts }.
11
+ // - 'nodes' — the node graph changed (topology, status/intent transitions,
12
+ // naming). Source: canvas.db (+ its WAL sidecar) mtime. Every transition()
13
+ // and canvas mutation writes the WAL, so a watch on the home dir's
14
+ // `canvas.db*` files detects all of them with one cheap watcher.
15
+ // - 'inbox' — a node's inbox.jsonl gained a message. Source: a recursive watch
16
+ // on the nodes/ tree, filtered to `inbox.jsonl` writes.
17
+ //
18
+ // WHY these sources (the §7 seam left the event source open): they are the
19
+ // cheapest things that detect node-graph + inbox changes without a daemon hook
20
+ // or a db trigger — pure fs.watch on the two on-disk authorities (canvas.db for
21
+ // the graph, inbox.jsonl appends for messages). No polling, no canvas.db reads.
22
+ import { watch } from 'node:fs';
23
+ import { crtrHome, nodesRoot, ensureHome } from '../../core/canvas/paths.js';
24
+ /** Coalesce a burst of fs events into one emission per kind. WAL writes and
25
+ * inbox appends arrive in clusters (a single transition can touch -wal/-shm);
26
+ * a short trailing debounce collapses each cluster into a single invalidation,
27
+ * which is all a re-pull consumer needs. */
28
+ const DEBOUNCE_MS = 150;
29
+ /** SSE keepalive comment cadence — keeps proxies/clients from idling the stream
30
+ * shut. A bare `:` comment line is ignored by EventSource. */
31
+ const HEARTBEAT_MS = 25_000;
32
+ /** Start the change-event hub: open the two fs watchers once, fan their
33
+ * debounced invalidations out to every connected SSE client. Watchers run for
34
+ * the server's lifetime (cheap — two inotify/FSEvents registrations), not
35
+ * per-client, so N browsers share one watch. */
36
+ export function startEventHub() {
37
+ ensureHome();
38
+ const clients = new Set();
39
+ const watchers = [];
40
+ const timers = new Map();
41
+ const broadcast = (kind) => {
42
+ const line = `data: ${JSON.stringify({ kind, ts: Date.now() })}\n\n`;
43
+ for (const res of clients) {
44
+ try {
45
+ res.write(line);
46
+ }
47
+ catch {
48
+ /* a dead stream is reaped by its own 'close' handler */
49
+ }
50
+ }
51
+ };
52
+ // Debounce per kind: an fs event arms a trailing timer; a fresh event within
53
+ // the window resets it, so a write cluster emits exactly once.
54
+ const emit = (kind) => {
55
+ const existing = timers.get(kind);
56
+ if (existing !== undefined)
57
+ clearTimeout(existing);
58
+ timers.set(kind, setTimeout(() => {
59
+ timers.delete(kind);
60
+ broadcast(kind);
61
+ }, DEBOUNCE_MS));
62
+ };
63
+ // Watcher 1 — the canvas home dir (non-recursive). canvas.db lives in WAL
64
+ // mode, so graph writes land in canvas.db-wal and checkpoints in canvas.db;
65
+ // watching the dir catches every `canvas.db*` sidecar touch → 'nodes'.
66
+ try {
67
+ const homeWatcher = watch(crtrHome(), (_event, filename) => {
68
+ if (filename !== null && filename.startsWith('canvas.db'))
69
+ emit('nodes');
70
+ });
71
+ homeWatcher.on('error', () => {
72
+ /* watch dropped (dir replaced) — non-fatal; the poll cadence still backs us */
73
+ });
74
+ watchers.push(homeWatcher);
75
+ }
76
+ catch {
77
+ /* fs.watch unavailable on this platform — degrade to poll-only (no SSE) */
78
+ }
79
+ // Watcher 2 — the nodes/ tree (recursive). An inbox.jsonl append is a message
80
+ // landing; a meta.json write is a graph/identity change (naming, polymorph).
81
+ // Recursive fs.watch is supported on macOS/Windows always and Linux ≥ v20.
82
+ try {
83
+ const nodesWatcher = watch(nodesRoot(), { recursive: true }, (_event, filename) => {
84
+ if (filename === null)
85
+ return;
86
+ if (filename.endsWith('inbox.jsonl'))
87
+ emit('inbox');
88
+ else if (filename.endsWith('meta.json'))
89
+ emit('nodes');
90
+ });
91
+ nodesWatcher.on('error', () => {
92
+ /* recursive watch refused/dropped — non-fatal */
93
+ });
94
+ watchers.push(nodesWatcher);
95
+ }
96
+ catch {
97
+ /* recursive watch unsupported — inbox push degrades to poll-only */
98
+ }
99
+ const heartbeat = setInterval(() => {
100
+ for (const res of clients) {
101
+ try {
102
+ res.write(': ping\n\n');
103
+ }
104
+ catch {
105
+ /* reaped by its 'close' handler */
106
+ }
107
+ }
108
+ }, HEARTBEAT_MS);
109
+ heartbeat.unref();
110
+ return {
111
+ addClient(res) {
112
+ res.writeHead(200, {
113
+ 'content-type': 'text/event-stream; charset=utf-8',
114
+ 'cache-control': 'no-cache, no-transform',
115
+ connection: 'keep-alive',
116
+ // The browser EventSource is same-origin here; no CORS header needed.
117
+ });
118
+ // An initial comment flushes headers and confirms the stream is open.
119
+ res.write(': connected\n\n');
120
+ clients.add(res);
121
+ const drop = () => {
122
+ clients.delete(res);
123
+ };
124
+ res.on('close', drop);
125
+ res.on('error', drop);
126
+ },
127
+ close() {
128
+ clearInterval(heartbeat);
129
+ for (const t of timers.values())
130
+ clearTimeout(t);
131
+ timers.clear();
132
+ for (const w of watchers) {
133
+ try {
134
+ w.close();
135
+ }
136
+ catch {
137
+ /* ignore */
138
+ }
139
+ }
140
+ for (const res of clients) {
141
+ try {
142
+ res.end();
143
+ }
144
+ catch {
145
+ /* ignore */
146
+ }
147
+ }
148
+ clients.clear();
149
+ },
150
+ };
151
+ }
@@ -0,0 +1,18 @@
1
+ export interface WebServerOptions {
2
+ port: number;
3
+ host?: string;
4
+ /** --dev: mount a Vite dev server (middleware mode, HMR) for asset serving
5
+ * instead of the static dist/web-client/ bundle. The bridge + SSE + WS relay
6
+ * are identical in both modes. */
7
+ dev?: boolean;
8
+ }
9
+ export interface RunningWebServer {
10
+ url: string;
11
+ port: number;
12
+ clientDir: string;
13
+ close: () => Promise<void>;
14
+ }
15
+ /** Build + start the unified web server. Binds 127.0.0.1 only (remote access is
16
+ * out of scope — §9). Resolves once the server is listening. Async because
17
+ * --dev awaits a Vite middleware server before listening. */
18
+ export declare function startWebServer(opts: WebServerOptions): Promise<RunningWebServer>;