@crouton-kit/crouter 0.3.27 → 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 (343) 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 +8 -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__/reconnect-giveup.test.js +30 -0
  49. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
  50. package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
  51. package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
  52. package/dist/clients/attach/attach-cmd.js +445 -41
  53. package/dist/clients/attach/auth-pickers.d.ts +9 -0
  54. package/dist/clients/attach/auth-pickers.js +194 -0
  55. package/dist/clients/attach/canvas-panels.d.ts +12 -0
  56. package/dist/clients/attach/canvas-panels.js +78 -0
  57. package/dist/clients/attach/chat-view.d.ts +27 -2
  58. package/dist/clients/attach/chat-view.js +98 -9
  59. package/dist/clients/attach/clipboard-image.d.ts +47 -8
  60. package/dist/clients/attach/clipboard-image.js +150 -26
  61. package/dist/clients/attach/clipboard-text.d.ts +3 -0
  62. package/dist/clients/attach/clipboard-text.js +39 -0
  63. package/dist/clients/attach/config-load.d.ts +54 -7
  64. package/dist/clients/attach/config-load.js +99 -3
  65. package/dist/clients/attach/context-message.d.ts +12 -0
  66. package/dist/clients/attach/context-message.js +72 -0
  67. package/dist/clients/attach/git-info.d.ts +20 -0
  68. package/dist/clients/attach/git-info.js +44 -0
  69. package/dist/clients/attach/graph-overlay.d.ts +43 -0
  70. package/dist/clients/attach/graph-overlay.js +306 -0
  71. package/dist/clients/attach/input-controller.d.ts +81 -13
  72. package/dist/clients/attach/input-controller.js +307 -67
  73. package/dist/clients/attach/pickers.d.ts +58 -0
  74. package/dist/clients/attach/pickers.js +171 -0
  75. package/dist/clients/attach/slash-commands.d.ts +44 -1
  76. package/dist/clients/attach/slash-commands.js +250 -27
  77. package/dist/clients/attach/titled-editor.d.ts +33 -0
  78. package/dist/clients/attach/titled-editor.js +84 -0
  79. package/dist/clients/attach/view-socket.d.ts +37 -2
  80. package/dist/clients/attach/view-socket.js +131 -7
  81. package/dist/clients/web/dev-server.d.ts +7 -0
  82. package/dist/clients/web/dev-server.js +59 -0
  83. package/dist/clients/web/events.d.ts +14 -0
  84. package/dist/clients/web/events.js +151 -0
  85. package/dist/clients/web/server.d.ts +18 -0
  86. package/dist/clients/web/server.js +450 -0
  87. package/dist/clients/web/web-cmd.d.ts +2 -0
  88. package/dist/clients/web/web-cmd.js +120 -0
  89. package/dist/commands/canvas.js +1 -2
  90. package/dist/commands/chord.js +1 -1
  91. package/dist/commands/dashboard.js +5 -1
  92. package/dist/commands/human/prompts.js +3 -3
  93. package/dist/commands/human/queue.d.ts +17 -0
  94. package/dist/commands/human/queue.js +111 -4
  95. package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
  96. package/dist/commands/memory/lint.d.ts +5 -4
  97. package/dist/commands/memory/lint.js +23 -7
  98. package/dist/commands/memory/read.js +2 -2
  99. package/dist/commands/memory/shared.d.ts +7 -1
  100. package/dist/commands/memory/shared.js +18 -3
  101. package/dist/commands/memory/write.js +20 -7
  102. package/dist/commands/memory.js +4 -4
  103. package/dist/commands/node.d.ts +1 -1
  104. package/dist/commands/node.js +64 -65
  105. package/dist/commands/pkg.js +1 -2
  106. package/dist/commands/revive.js +11 -3
  107. package/dist/commands/sys/feedback.d.ts +1 -0
  108. package/dist/commands/sys/feedback.js +163 -0
  109. package/dist/commands/sys/sync.d.ts +1 -0
  110. package/dist/commands/sys/sync.js +187 -0
  111. package/dist/commands/sys.js +4 -2
  112. package/dist/commands/view-cycle.js +2 -2
  113. package/dist/commands/view-list.js +8 -8
  114. package/dist/commands/view-new.js +21 -17
  115. package/dist/commands/view-pick.js +1 -1
  116. package/dist/commands/view-run.js +35 -14
  117. package/dist/commands/view.js +8 -6
  118. package/dist/commands/workspace.d.ts +2 -0
  119. package/dist/commands/workspace.js +161 -0
  120. package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
  121. package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
  122. package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
  123. package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
  124. package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
  125. package/dist/core/__tests__/broker-snapshot-history.test.d.ts +1 -0
  126. package/dist/core/__tests__/broker-snapshot-history.test.js +105 -0
  127. package/dist/core/__tests__/child-death-wake.test.js +56 -111
  128. package/dist/core/__tests__/close.test.js +14 -20
  129. package/dist/core/__tests__/context-intro.test.js +19 -19
  130. package/dist/core/__tests__/daemon-boot.test.js +30 -23
  131. package/dist/core/__tests__/draw-style.test.js +1 -1
  132. package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
  133. package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
  134. package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -1
  135. package/dist/core/__tests__/fixtures/fake-engine.js +32 -2
  136. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  137. package/dist/core/__tests__/focuses.test.js +25 -13
  138. package/dist/core/__tests__/fork.test.js +22 -5
  139. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
  140. package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
  141. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
  142. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
  143. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
  144. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
  145. package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
  146. package/dist/core/__tests__/full/cascade-close.test.js +22 -8
  147. package/dist/core/__tests__/full/detach-focus.test.js +66 -169
  148. package/dist/core/__tests__/helpers/harness.d.ts +6 -9
  149. package/dist/core/__tests__/helpers/harness.js +19 -36
  150. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
  151. package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
  152. package/dist/core/__tests__/human-stranded-deliver.test.d.ts +1 -0
  153. package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
  154. package/dist/core/__tests__/lifecycle.test.js +1 -19
  155. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
  156. package/dist/core/__tests__/memory-resolver.test.js +1 -1
  157. package/dist/core/__tests__/on-read-dedup-resume.test.d.ts +1 -0
  158. package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
  159. package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
  160. package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
  161. package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
  162. package/dist/core/__tests__/relaunch-root.test.js +114 -0
  163. package/dist/core/__tests__/reset.test.js +19 -79
  164. package/dist/core/__tests__/steer-note.test.js +9 -7
  165. package/dist/core/__tests__/tmux-surface.test.js +13 -7
  166. package/dist/core/bootstrap.d.ts +0 -3
  167. package/dist/core/bootstrap.js +1 -143
  168. package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
  169. package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
  170. package/dist/core/canvas/browse/app.js +131 -8
  171. package/dist/core/canvas/browse/model.d.ts +23 -5
  172. package/dist/core/canvas/browse/model.js +68 -13
  173. package/dist/core/canvas/browse/render.d.ts +7 -1
  174. package/dist/core/canvas/browse/render.js +178 -47
  175. package/dist/core/canvas/canvas.js +1 -1
  176. package/dist/core/canvas/focuses.d.ts +4 -4
  177. package/dist/core/canvas/focuses.js +7 -7
  178. package/dist/core/canvas/nav-model.d.ts +162 -0
  179. package/dist/core/canvas/nav-model.js +487 -0
  180. package/dist/core/canvas/paths.d.ts +7 -0
  181. package/dist/core/canvas/paths.js +9 -0
  182. package/dist/core/canvas/render.d.ts +68 -9
  183. package/dist/core/canvas/render.js +195 -61
  184. package/dist/core/canvas/types.d.ts +16 -23
  185. package/dist/core/config.js +1 -4
  186. package/dist/core/memory-resolver.js +43 -5
  187. package/dist/core/runtime/bearings.d.ts +1 -1
  188. package/dist/core/runtime/bearings.js +8 -8
  189. package/dist/core/runtime/branded-host.d.ts +16 -0
  190. package/dist/core/runtime/branded-host.js +127 -0
  191. package/dist/core/runtime/broker-protocol.d.ts +212 -6
  192. package/dist/core/runtime/broker-sdk.d.ts +0 -12
  193. package/dist/core/runtime/broker-sdk.js +77 -6
  194. package/dist/core/runtime/broker.d.ts +5 -1
  195. package/dist/core/runtime/broker.js +520 -59
  196. package/dist/core/runtime/close.d.ts +12 -2
  197. package/dist/core/runtime/close.js +37 -16
  198. package/dist/core/runtime/front-door.js +14 -7
  199. package/dist/core/runtime/host.d.ts +14 -34
  200. package/dist/core/runtime/host.js +14 -50
  201. package/dist/core/runtime/launch.d.ts +12 -9
  202. package/dist/core/runtime/launch.js +27 -15
  203. package/dist/core/runtime/lifecycle.d.ts +1 -1
  204. package/dist/core/runtime/lifecycle.js +15 -19
  205. package/dist/core/runtime/nodes.d.ts +0 -55
  206. package/dist/core/runtime/nodes.js +6 -74
  207. package/dist/core/runtime/placement.d.ts +91 -334
  208. package/dist/core/runtime/placement.js +262 -828
  209. package/dist/core/runtime/promote.d.ts +2 -0
  210. package/dist/core/runtime/promote.js +24 -6
  211. package/dist/core/runtime/recap.d.ts +8 -0
  212. package/dist/core/runtime/recap.js +107 -0
  213. package/dist/core/runtime/recycle.js +25 -61
  214. package/dist/core/runtime/reset.d.ts +43 -37
  215. package/dist/core/runtime/reset.js +131 -218
  216. package/dist/core/runtime/revive.d.ts +9 -29
  217. package/dist/core/runtime/revive.js +45 -135
  218. package/dist/core/runtime/spawn.d.ts +15 -11
  219. package/dist/core/runtime/spawn.js +168 -136
  220. package/dist/core/runtime/surface-bg.d.ts +11 -0
  221. package/dist/core/runtime/surface-bg.js +68 -0
  222. package/dist/core/runtime/tmux.d.ts +15 -43
  223. package/dist/core/runtime/tmux.js +38 -80
  224. package/dist/core/scope.d.ts +1 -2
  225. package/dist/core/scope.js +1 -5
  226. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
  227. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
  228. package/dist/core/skill-sync/builtins.d.ts +42 -0
  229. package/dist/core/skill-sync/builtins.js +112 -0
  230. package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
  231. package/dist/core/skill-sync/claude-plugins.js +71 -0
  232. package/dist/core/skill-sync/engine.d.ts +42 -0
  233. package/dist/core/skill-sync/engine.js +633 -0
  234. package/dist/core/skill-sync/export.d.ts +23 -0
  235. package/dist/core/skill-sync/export.js +86 -0
  236. package/dist/core/skill-sync/manifest.d.ts +64 -0
  237. package/dist/core/skill-sync/manifest.js +181 -0
  238. package/dist/core/skill-sync/profile.d.ts +76 -0
  239. package/dist/core/skill-sync/profile.js +173 -0
  240. package/dist/core/skill-sync/snapshot.d.ts +57 -0
  241. package/dist/core/skill-sync/snapshot.js +120 -0
  242. package/dist/core/spawn.d.ts +14 -0
  243. package/dist/core/spawn.js +29 -9
  244. package/dist/core/substrate/index.d.ts +2 -2
  245. package/dist/core/substrate/index.js +7 -7
  246. package/dist/core/substrate/injected-store.d.ts +10 -0
  247. package/dist/core/substrate/injected-store.js +55 -0
  248. package/dist/core/substrate/on-read.js +68 -6
  249. package/dist/core/substrate/render.d.ts +8 -11
  250. package/dist/core/substrate/render.js +29 -43
  251. package/dist/core/substrate/schema.d.ts +16 -11
  252. package/dist/core/substrate/schema.js +32 -31
  253. package/dist/core/tui/host.d.ts +12 -4
  254. package/dist/core/tui/host.js +280 -149
  255. package/dist/core/view/bridge.d.ts +10 -0
  256. package/dist/core/view/bridge.js +31 -0
  257. package/dist/core/view/chrome.d.ts +9 -0
  258. package/dist/core/view/chrome.js +22 -0
  259. package/dist/core/view/contract.d.ts +171 -0
  260. package/dist/core/view/contract.js +23 -0
  261. package/dist/core/view/loader.d.ts +31 -0
  262. package/dist/core/view/loader.js +188 -0
  263. package/dist/core/view/transport-local.d.ts +7 -0
  264. package/dist/core/view/transport-local.js +70 -0
  265. package/dist/core/view/transport.d.ts +4 -0
  266. package/dist/core/view/transport.js +15 -0
  267. package/dist/daemon/crtrd.d.ts +38 -27
  268. package/dist/daemon/crtrd.js +303 -376
  269. package/dist/daemon/manage.js +6 -1
  270. package/dist/index.js +1 -1
  271. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
  272. package/dist/pi-extensions/canvas-context-intro.js +4 -4
  273. package/dist/pi-extensions/canvas-doc-substrate.js +29 -20
  274. package/dist/pi-extensions/canvas-nav.js +35 -385
  275. package/dist/pi-extensions/canvas-recap.d.ts +37 -0
  276. package/dist/pi-extensions/canvas-recap.js +236 -0
  277. package/dist/pi-extensions/canvas-resume.js +2 -1
  278. package/dist/pi-extensions/canvas-stophook.d.ts +15 -13
  279. package/dist/pi-extensions/canvas-stophook.js +119 -103
  280. package/dist/pi-extensions/canvas-view.js +2 -1
  281. package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
  282. package/dist/pi-extensions/widget-order-bus.js +34 -0
  283. package/dist/prompts/view.d.ts +2 -2
  284. package/dist/prompts/view.js +145 -73
  285. package/dist/types.d.ts +1 -6
  286. package/dist/types.js +1 -3
  287. package/dist/web/ViewChrome.d.ts +7 -0
  288. package/dist/web/ViewChrome.js +28 -0
  289. package/dist/web/ViewPane.d.ts +39 -0
  290. package/dist/web/ViewPane.js +48 -0
  291. package/dist/web/index.d.ts +6 -0
  292. package/dist/web/index.js +16 -0
  293. package/dist/web/runtime.d.ts +39 -0
  294. package/dist/web/runtime.js +133 -0
  295. package/dist/web/states.d.ts +24 -0
  296. package/dist/web/states.js +24 -0
  297. package/dist/web/transport-http.d.ts +5 -0
  298. package/dist/web/transport-http.js +28 -0
  299. package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
  300. package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
  301. package/dist/web-client/index.html +13 -0
  302. package/package.json +20 -6
  303. package/dist/builtin-views/canvas/client.mjs +0 -303
  304. package/dist/builtin-views/canvas/view.mjs +0 -576
  305. package/dist/builtin-views/git-pr/client.mjs +0 -440
  306. package/dist/builtin-views/git-pr/view.mjs +0 -675
  307. package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
  308. package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
  309. package/dist/builtin-views/inbox/view.mjs +0 -889
  310. package/dist/builtin-views/linkedin/client.mjs +0 -610
  311. package/dist/builtin-views/linkedin/view.mjs +0 -1171
  312. package/dist/commands/pkg/bridge.d.ts +0 -1
  313. package/dist/commands/pkg/bridge.js +0 -137
  314. package/dist/commands/skill/author.d.ts +0 -3
  315. package/dist/commands/skill/author.js +0 -140
  316. package/dist/commands/skill/shared.d.ts +0 -3
  317. package/dist/commands/skill/shared.js +0 -19
  318. package/dist/commands/skill.d.ts +0 -2
  319. package/dist/commands/skill.js +0 -21
  320. package/dist/commands/tmux-spread.d.ts +0 -2
  321. package/dist/commands/tmux-spread.js +0 -144
  322. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
  323. package/dist/core/__tests__/full/placement-focus.test.js +0 -309
  324. package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
  325. package/dist/core/__tests__/full/placement-revive.test.js +0 -238
  326. package/dist/core/__tests__/full/placement-teardown.test.js +0 -221
  327. package/dist/core/__tests__/home-session.test.js +0 -186
  328. package/dist/core/__tests__/relaunch.test.js +0 -335
  329. package/dist/core/bridge-map.d.ts +0 -19
  330. package/dist/core/bridge-map.js +0 -73
  331. package/dist/core/tui/contract.d.ts +0 -83
  332. package/dist/core/tui/contract.js +0 -8
  333. package/dist/core/tui/loader.d.ts +0 -16
  334. package/dist/core/tui/loader.js +0 -94
  335. package/dist/prompts/skill.d.ts +0 -2
  336. package/dist/prompts/skill.js +0 -650
  337. /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
  338. /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
  339. /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
  340. /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
  341. /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
  342. /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/reconnect-giveup.test.d.ts} +0 -0
  343. /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
@@ -10,5 +10,15 @@ export interface CloseNodeResult {
10
10
  }
11
11
  /** Close `rootId` and its exclusive subtree. Best-effort throughout: a tmux/db
12
12
  * failure on one node never aborts the cascade. Throws only on an unknown root
13
- * so the command can surface a clean not-found error. */
14
- export declare function closeNode(rootId: string): CloseNodeResult;
13
+ * so the command can surface a clean not-found error.
14
+ *
15
+ * `rootEvent` decides the ROOT's terminal status (descendants are always
16
+ * `cancel`led — they did not finish their own work, they were torn down with the
17
+ * parent):
18
+ * 'cancel' (default) — the node was abandoned/torn down → canceled (⊜).
19
+ * 'finalize' — the user is marking it COMPLETE → done (✓). Used by
20
+ * the canvas browser's `x` (close-out) action. A root already in a terminal
21
+ * status keeps it (finalize is only legal from a live status). */
22
+ export declare function closeNode(rootId: string, opts?: {
23
+ rootEvent?: 'cancel' | 'finalize';
24
+ }): CloseNodeResult;
@@ -12,10 +12,12 @@
12
12
  // only ever revives an active|idle node, so flipping to canceled first
13
13
  // closes the race where the supervisor sees a window-gone live node and
14
14
  // either revives it or marks it dead (overwriting our canceled).
15
- // 2. Kill its tmux PANE (the window closes once its last pane goes) — which
16
- // kills pi and, with it, the inbox watcher. Pane-granular so that nodes
17
- // the user co-located as panes in ONE window (via swap-pane focus) are not
18
- // all taken down when one of them is closed.
15
+ // 2. Tear down the broker ENGINE (the `shutdown` frame SIGTERM fallback so
16
+ // the broker process exits and releases the sole .jsonl writer; this also
17
+ // ends the inbox watcher) AND proactively close the node's on-screen viewer
18
+ // pane + registry row. The viewer teardown is explicit because attach
19
+ // auto-reconnects: left to the socket drop alone, the viewer would sit in a
20
+ // misleading "reconnecting…" state for ~30s on a deliberate close.
19
21
  // 3. Append the cancellation notice to its inbox AFTER the watcher is gone.
20
22
  // The watcher advances its cursor when it READS an entry, so appending
21
23
  // while it is still live would let it consume + skip the notice (cursor
@@ -30,7 +32,8 @@
30
32
  // to by the agent being closed", generalized to any depth via a fixpoint.
31
33
  import { getNode, subscriptionsOf, subscribersOf, } from '../canvas/index.js';
32
34
  import { transition } from './lifecycle.js';
33
- import { hostFor } from './host.js';
35
+ import { headlessBrokerHost } from './host.js';
36
+ import { tearDownNode } from './placement.js';
34
37
  import { appendInbox } from '../feed/inbox.js';
35
38
  import { appendPassive } from '../feed/passive.js';
36
39
  /** The set of nodes to close: the root plus every descendant reachable down the
@@ -102,10 +105,19 @@ function cancellationLabel(isRoot, deadChildren) {
102
105
  }
103
106
  /** Close `rootId` and its exclusive subtree. Best-effort throughout: a tmux/db
104
107
  * failure on one node never aborts the cascade. Throws only on an unknown root
105
- * so the command can surface a clean not-found error. */
106
- export function closeNode(rootId) {
108
+ * so the command can surface a clean not-found error.
109
+ *
110
+ * `rootEvent` decides the ROOT's terminal status (descendants are always
111
+ * `cancel`led — they did not finish their own work, they were torn down with the
112
+ * parent):
113
+ * 'cancel' (default) — the node was abandoned/torn down → canceled (⊜).
114
+ * 'finalize' — the user is marking it COMPLETE → done (✓). Used by
115
+ * the canvas browser's `x` (close-out) action. A root already in a terminal
116
+ * status keeps it (finalize is only legal from a live status). */
117
+ export function closeNode(rootId, opts = {}) {
107
118
  if (getNode(rootId) === null)
108
119
  throw new Error(`closeNode: unknown node ${rootId}`);
120
+ const rootEvent = opts.rootEvent ?? 'cancel';
109
121
  const closing = closingSet(rootId);
110
122
  const order = killOrder(rootId, closing);
111
123
  // Descendants reachable from the subtree but kept alive (shared managers).
@@ -127,15 +139,24 @@ export function closeNode(rootId) {
127
139
  const deadChildren = subscriptionsOf(id)
128
140
  .map((s) => s.node_id)
129
141
  .filter((c) => closing.has(c));
130
- // 1) Canceled + intent cleared BEFORE the window dies (daemon race).
131
- transition(id, 'cancel');
132
- // 2) Tear the node's ENGINE down through the Host seam (hostFor): a tmux
133
- // node runs tearDownNode (close any focus row it occupies, kill its
134
- // PANE the window closes once its last pane goes, so sibling nodes the
135
- // user co-located in one window survive and null its LOCATION); a
136
- // broker node sends the `shutdown` frame so the broker PROCESS exits and
137
- // releases the sole .jsonl writer (a paneless node has no pane to kill).
138
- hostFor(m).teardown(id);
142
+ // 1) Terminal status set BEFORE the window dies (daemon race). The root may
143
+ // finalize to `done` (a deliberate "mark complete" close-out); every
144
+ // descendant, and the root by default, `cancel`s. finalize is legal only
145
+ // from a live status an already-terminal root keeps its status.
146
+ if (id === rootId && rootEvent === 'finalize') {
147
+ if (m.status === 'active' || m.status === 'idle')
148
+ transition(id, 'finalize');
149
+ }
150
+ else {
151
+ transition(id, 'cancel');
152
+ }
153
+ // 2) Tear the node's ENGINE down: send the `shutdown` frame so the broker
154
+ // PROCESS exits and releases the sole .jsonl writer. Then proactively
155
+ // close the viewer pane + registry row — attach auto-reconnects, so on a
156
+ // deliberate close the viewer must be torn down here or it lingers ~30s
157
+ // showing a misleading "reconnecting…" instead of going away at once.
158
+ headlessBrokerHost.teardown(id);
159
+ tearDownNode(id);
139
160
  // 3) Leave the resume notice AFTER the watcher is gone, so it survives.
140
161
  appendInbox(id, {
141
162
  from: null,
@@ -21,8 +21,13 @@ function isDir(p) {
21
21
  return false;
22
22
  }
23
23
  }
24
- /** Parse `[dir] [prompt]` positionals + `--name`/`--kind` flags out of the
25
- * leftover tokens after the bare `crtr`. */
24
+ /** The `--`flags the front door OWNS (everything else after a bare `crtr` is a
25
+ * positional dir/prompt). A leading token in this set still boots a root —
26
+ * without it, `crtr --name X` would fall through to the dispatcher and error as
27
+ * an unknown subcommand. */
28
+ const FRONT_DOOR_FLAGS = new Set(['--name', '--kind']);
29
+ /** Parse `[dir] [prompt]` positionals + the front-door flags out of the leftover
30
+ * tokens after the bare `crtr`. */
26
31
  function parseRootArgs(tokens) {
27
32
  let cwd = process.cwd();
28
33
  let name;
@@ -79,14 +84,16 @@ export function maybeBootRoot(root, argv) {
79
84
  // • bare `crtr` (no tokens)
80
85
  // • `crtr <dir> [prompt]` (first positional is an existing dir)
81
86
  // • `crtr "multi word prompt"` (first token contains whitespace)
82
- // Anything else — a bare word like `job`, or a leading flag — is treated as a
83
- // mistyped/removed subcommand and handed to the dispatcher, which errors with
84
- // "unknown subcommand: <token>". Booting pi for such tokens is what let the
85
- // renamed `agent`/`job` subcommands fork-bomb the front door.
87
+ // `crtr --name X` (a leading front-door flag)
88
+ // Anything else a bare word like `job`, or an UNKNOWN leading flag — is
89
+ // treated as a mistyped/removed subcommand and handed to the dispatcher, which
90
+ // errors with "unknown subcommand: <token>". Booting pi for such tokens is what
91
+ // let the renamed `agent`/`job` subcommands fork-bomb the front door.
86
92
  if (first !== undefined) {
87
93
  const looksLikePrompt = /\s/.test(first);
88
94
  const looksLikeDir = !first.startsWith('-') && isDir(resolvePath(first));
89
- if (!looksLikePrompt && !looksLikeDir)
95
+ const looksLikeFrontDoorFlag = FRONT_DOOR_FLAGS.has(first);
96
+ if (!looksLikePrompt && !looksLikeDir && !looksLikeFrontDoorFlag)
90
97
  return false;
91
98
  }
92
99
  // Unambiguous front-door launch → boot a resident root inline (exec pi in
@@ -1,53 +1,33 @@
1
- import { type NodeMeta, type NodeRow } from '../canvas/index.js';
1
+ import { type NodeRow } from '../canvas/index.js';
2
2
  import type { PiInvocation } from './launch.js';
3
- /** View-side launch hints the legacy host needs beyond the PiInvocation. This is
4
- * today's ReviveLaunch (placement.ts) MINUS `command`/`env` the host derives
5
- * `command = piCommand(inv.argv)` and `env = inv.env` from the PiInvocation
6
- * itself. The broker (Phase 3) uses only `cwd` + `resuming`. */
3
+ /** View-side launch hints the broker host needs beyond the PiInvocation. The
4
+ * broker uses `cwd` + `resuming`; `name` is carried for the node's display. */
7
5
  export interface LaunchPlacement {
8
6
  cwd: string;
9
7
  name: string;
10
8
  resuming: boolean;
11
9
  }
12
- /** A handle to a node's running engine container.
13
- * §3.2 specifies { kind, pid }; Phase 1 carries the placement coords too,
14
- * because launch REPLACES reviveIntoPlacement (which returned PlacementResult)
15
- * and reviveNode's unchanged ReviveResult is built from window+session. The
16
- * daemon never reads window/session/pane (it supervises via pid / isAlive); the
17
- * broker (Phase 3) returns window=null, pane=null. */
10
+ /** A handle to a node's running engine container. The broker host supervises via
11
+ * the broker pid recorded as pi_pid; no tmux placement coords exist (the engine
12
+ * is never in a pane). */
18
13
  export interface HostHandle {
19
- kind: 'tmux' | 'broker';
20
- /** Supervised pid (signal-0 target). null at launch for TmuxPaneHost: the
21
- * pane's fresh pi records its own pid via the stophook (session_start →
22
- * recordPid), and reviveNode clearPid()s right after launch — so no Phase-1
23
- * caller reads this. Present for the §3.2 contract + the daemon. */
14
+ /** Supervised pid (signal-0 target). May be null right at launch before the
15
+ * detached child reports its pid; the broker re-records pi_pid during its
16
+ * extension bind, and reviveNode clearPid()s right after launch. */
24
17
  pid: number | null;
25
- window: string | null;
26
- session: string;
27
- pane: string | null;
28
18
  }
29
19
  export interface Host {
30
20
  /** Bring a node's ENGINE into existence from its launch recipe; return a
31
- * supervisable handle. TmuxPaneHost ALSO performs placement (the pane IS the
32
- * engine container). */
21
+ * supervisable handle. */
33
22
  launch(nodeId: string, inv: PiInvocation, opts: LaunchPlacement): HostHandle;
34
- /** Is this node's engine container present? PURE / non-mutating. §3.2 types
35
- * this `NodeRow`; we keep `string | NodeRow` to match isNodePaneAlive (which
36
- * it IS) so both call sites the guard (passes the id) and the Phase-2 daemon
37
- * (passes a row) — stay one-token changes. */
23
+ /** Is this node's engine alive? PURE / non-mutating. `string | NodeRow` so
24
+ * both call sites the revive guard (passes the id) and the daemon (passes a
25
+ * row) share one selector. For the broker this IS isPidAlive(pi_pid). */
38
26
  isAlive(node: string | NodeRow): boolean;
39
27
  /** Tear the engine down (close/cancel teardown). */
40
28
  teardown(nodeId: string): void;
41
29
  /** Deliver an OS signal to the engine container — present so the daemon never
42
- * reaches around the abstraction. No Phase-1 call site. */
30
+ * reaches around the abstraction. */
43
31
  signal(nodeId: string, sig: NodeJS.Signals): void;
44
32
  }
45
- /** The sole Phase-1 host: today's behavior, wrapped. Stateless → a frozen
46
- * singleton, no class needed. Every method delegates verbatim. */
47
- export declare const tmuxPaneHost: Host;
48
33
  export declare const headlessBrokerHost: Host;
49
- /** Select the Host for a node by its persisted `host_kind` (NULL/'tmux' → the
50
- * tmux host; 'broker' → the headless broker host). The param is `NodeMeta |
51
- * NodeRow` so both call sites — reviveNode `hostFor(meta)` and the daemon
52
- * `hostFor(row)` — share one selector. */
53
- export declare function hostFor(node: NodeMeta | NodeRow): Host;
@@ -1,58 +1,26 @@
1
1
  // src/core/runtime/host.ts
2
2
  //
3
3
  // The Host abstraction (design §3): owns an engine PROCESS's lifecycle, not its
4
- // display. TWO impls live here: TmuxPaneHost a thin wrapper over today's
5
- // placement functions (the pane IS the engine container) and, since Phase 3,
6
- // HeadlessBrokerHost a detached broker process with no tmux pane. `host_kind`
7
- // (on the node row/meta since Wave 1) selects between them via hostFor().
4
+ // display. After the broker-is-the-host cut there is ONE impl
5
+ // HeadlessBrokerHost a detached broker process with no tmux pane. Every node
6
+ // (managed child, named node, --root, front-door root) runs on it; a tmux pane
7
+ // is only an attach VIEWER. The `Host` interface is kept as a one-impl seam (the
8
+ // test/future-host boundary).
8
9
  //
9
- // IMPORT-LINT (tmux-surface.test.ts §5.1): this file must NOT import './tmux.js'
10
- // directly. It reaches every driver verb (piCommand, reviveIntoPlacement,
11
- // isNodePaneAlive, tearDownNode) through placement.ts's re-exports — placement.ts
12
- // and tmux-chrome.ts are the only sanctioned tmux.ts importers. The broker host
13
- // needs no tmux verb at all.
10
+ // This file imports neither './tmux.js' nor './placement.js' — the broker host
11
+ // needs no tmux verb at all (it supervises via the broker pid, tears down over
12
+ // the node's unix socket).
14
13
  import { spawn } from 'node:child_process';
15
14
  import { mkdirSync, writeFileSync, existsSync, unlinkSync, openSync, closeSync } from 'node:fs';
16
15
  import { dirname, join } from 'node:path';
17
16
  import { fileURLToPath } from 'node:url';
18
17
  import { connect } from 'node:net';
19
- import { reviveIntoPlacement, isNodePaneAlive, tearDownNode, piCommand, } from './placement.js';
20
18
  import { getNode } from '../canvas/index.js';
21
19
  import { nodeDir, jobDir } from '../canvas/paths.js';
22
20
  import { encodeFrame } from './broker-protocol.js';
23
21
  import { FRONT_DOOR_ENV } from './front-door.js';
22
+ import { hostExecPath } from './branded-host.js';
24
23
  import { isPidAlive } from '../canvas/pid.js';
25
- /** The sole Phase-1 host: today's behavior, wrapped. Stateless → a frozen
26
- * singleton, no class needed. Every method delegates verbatim. */
27
- export const tmuxPaneHost = {
28
- launch(nodeId, inv, opts) {
29
- const placed = reviveIntoPlacement(nodeId, {
30
- command: piCommand(inv.argv),
31
- env: inv.env,
32
- cwd: opts.cwd,
33
- name: opts.name,
34
- resuming: opts.resuming,
35
- });
36
- return { kind: 'tmux', pid: null, window: placed.window, session: placed.session, pane: placed.pane };
37
- },
38
- isAlive(node) {
39
- return isNodePaneAlive(node);
40
- },
41
- teardown(nodeId) {
42
- tearDownNode(nodeId);
43
- },
44
- signal(nodeId, sig) {
45
- const pid = getNode(nodeId)?.pi_pid;
46
- if (pid != null) {
47
- try {
48
- process.kill(pid, sig);
49
- }
50
- catch {
51
- /* already gone */
52
- }
53
- }
54
- },
55
- };
56
24
  // ---------------------------------------------------------------------------
57
25
  // HeadlessBrokerHost (design §3.3): a detached broker PROCESS, no tmux pane. It
58
26
  // boots the dedicated broker-cli entry directly (NEVER src/cli.ts), supervises
@@ -84,7 +52,10 @@ export const headlessBrokerHost = {
84
52
  const logDir = jobDir(nodeId);
85
53
  mkdirSync(logDir, { recursive: true });
86
54
  const logFd = openSync(join(logDir, 'broker.log'), 'a');
87
- const child = spawn(process.execPath, [resolveBrokerEntry(), nodeId], {
55
+ // Launch from the crouter-branded host binary (a copy of node) so the
56
+ // broker shows "crouter" in macOS Full Disk Access, not "node". On
57
+ // non-darwin / dev / tests this is just process.execPath (see branded-host).
58
+ const child = spawn(hostExecPath(), [resolveBrokerEntry(), nodeId], {
88
59
  cwd: opts.cwd,
89
60
  detached: true,
90
61
  stdio: ['ignore', logFd, logFd],
@@ -94,7 +65,7 @@ export const headlessBrokerHost = {
94
65
  // launching process (CLI or daemon) never leaks it.
95
66
  closeSync(logFd);
96
67
  child.unref();
97
- return { kind: 'broker', pid: child.pid ?? null, window: null, session: '', pane: null };
68
+ return { pid: child.pid ?? null };
98
69
  },
99
70
  isAlive(node) {
100
71
  return isPidAlive((typeof node === 'string' ? getNode(node) : node)?.pi_pid);
@@ -177,10 +148,3 @@ export const headlessBrokerHost = {
177
148
  }
178
149
  },
179
150
  };
180
- /** Select the Host for a node by its persisted `host_kind` (NULL/'tmux' → the
181
- * tmux host; 'broker' → the headless broker host). The param is `NodeMeta |
182
- * NodeRow` so both call sites — reviveNode `hostFor(meta)` and the daemon
183
- * `hostFor(row)` — share one selector. */
184
- export function hostFor(node) {
185
- return node.host_kind === 'broker' ? headlessBrokerHost : tmuxPaneHost;
186
- }
@@ -2,6 +2,7 @@ import type { NodeMeta, LaunchSpec, Mode, Lifecycle } from '../canvas/index.js';
2
2
  export declare const CANVAS_STOPHOOK_PATH: string;
3
3
  export declare const CANVAS_INBOX_WATCHER_PATH: string;
4
4
  export declare const CANVAS_NAV_PATH: string;
5
+ export declare const CANVAS_RECAP_PATH: string;
5
6
  export declare const CANVAS_GOAL_CAPTURE_PATH: string;
6
7
  export declare const CANVAS_PASSIVE_CONTEXT_PATH: string;
7
8
  export declare const CANVAS_CONTEXT_INTRO_PATH: string;
@@ -11,12 +12,14 @@ export declare const CANVAS_RESUME_PATH: string;
11
12
  export declare const CANVAS_VIEW_PATH: string;
12
13
  /** The canvas extensions every node loads, in order: stophook (routing +
13
14
  * telemetry + session-id capture), inbox-watcher (wake), nav (in-editor
14
- * graph chrome), goal-capture (persist the first user message as the goal),
15
+ * graph chrome), recap (the per-node inactivity recap card — Haiku over the
16
+ * conversation, shown above the manager line after 300s of no message),
17
+ * goal-capture (persist the first user message as the goal),
15
18
  * passive-context (drain passive backlog as pre-text on the next message),
16
- * context-intro (inject the <crtr-context> bearings block as its own session
17
- * message, once per brand-new chat), doc-substrate (the unified document
18
- * substrate's two hooks: <skills> + <preferences> + <memory-guidance> at boot,
19
- * on-read context injection), commands (the /promote slash-command), resume (the /resume-node
19
+ * context-intro (inject the <crtr-context> bearings block carrying the
20
+ * <knowledge> catalog — as its own session message, once per brand-new chat),
21
+ * doc-substrate (the unified document substrate's two hooks: <preferences> +
22
+ * <memory-guidance> at boot, on-read context injection), commands (the /promote slash-command), resume (the /resume-node
20
23
  * whole-canvas picker → `crtr node focus`), view (the /view popup → `crtr
21
24
  * view pick` / `crtr view run <name>`).
22
25
  * All self-gate on CRTR_NODE_ID. goal-capture precedes passive-context so it
@@ -27,9 +30,9 @@ export declare const CANVAS_EXTENSIONS: string[];
27
30
  * resolve to the latest opus/sonnet/haiku; `ultra` to the frontier model. */
28
31
  export declare const MODEL_TIERS: Record<string, string>;
29
32
  /** Resolve a model token to the spec pi gets via `--model`. A named tier
30
- * (ultra/strong/medium/light) maps to its concrete spec; a bare alias
31
- * (sonnet/opus/haiku) resolves to the anthropic provider under pi (avoids the
32
- * bedrock default); anything with a `/` or an unknown name passes through. */
33
+ * (ultra/strong/medium/light) maps to its concrete spec; a bare family alias
34
+ * (sonnet/opus/haiku) maps to that family's current versioned id; anything
35
+ * with a `/` or an unknown name passes through. */
33
36
  export declare function normalizeModel(model: string): string;
34
37
  /** Compose a node's full pi launch recipe from its persona. The system prompt
35
38
  * is composed from FOUR inputs: kind×mode (the persona body) plus lifecycle
@@ -105,5 +108,5 @@ export interface BrokerSdkConfig {
105
108
  * never drift. Safe because `buildPiArgv` is the SOLE producer of these flags
106
109
  * (we own both ends). SIDE EFFECT: merges `inv.env` into `process.env` so the
107
110
  * in-process engine + the bound canvas extensions see the same env a forked pi
108
- * would (CRTR_NODE_ID, CRTR_ROOT_SESSION, CRTR_SUBTREE, …). */
111
+ * would (CRTR_NODE_ID, CRTR_SUBTREE, …). */
109
112
  export declare function piInvocationToSdkConfig(inv: PiInvocation): BrokerSdkConfig;
@@ -29,6 +29,7 @@ function resolveExtension(name) {
29
29
  export const CANVAS_STOPHOOK_PATH = resolveExtension('canvas-stophook');
30
30
  export const CANVAS_INBOX_WATCHER_PATH = resolveExtension('canvas-inbox-watcher');
31
31
  export const CANVAS_NAV_PATH = resolveExtension('canvas-nav');
32
+ export const CANVAS_RECAP_PATH = resolveExtension('canvas-recap');
32
33
  export const CANVAS_GOAL_CAPTURE_PATH = resolveExtension('canvas-goal-capture');
33
34
  export const CANVAS_PASSIVE_CONTEXT_PATH = resolveExtension('canvas-passive-context');
34
35
  export const CANVAS_CONTEXT_INTRO_PATH = resolveExtension('canvas-context-intro');
@@ -38,12 +39,14 @@ export const CANVAS_RESUME_PATH = resolveExtension('canvas-resume');
38
39
  export const CANVAS_VIEW_PATH = resolveExtension('canvas-view');
39
40
  /** The canvas extensions every node loads, in order: stophook (routing +
40
41
  * telemetry + session-id capture), inbox-watcher (wake), nav (in-editor
41
- * graph chrome), goal-capture (persist the first user message as the goal),
42
+ * graph chrome), recap (the per-node inactivity recap card — Haiku over the
43
+ * conversation, shown above the manager line after 300s of no message),
44
+ * goal-capture (persist the first user message as the goal),
42
45
  * passive-context (drain passive backlog as pre-text on the next message),
43
- * context-intro (inject the <crtr-context> bearings block as its own session
44
- * message, once per brand-new chat), doc-substrate (the unified document
45
- * substrate's two hooks: <skills> + <preferences> + <memory-guidance> at boot,
46
- * on-read context injection), commands (the /promote slash-command), resume (the /resume-node
46
+ * context-intro (inject the <crtr-context> bearings block carrying the
47
+ * <knowledge> catalog — as its own session message, once per brand-new chat),
48
+ * doc-substrate (the unified document substrate's two hooks: <preferences> +
49
+ * <memory-guidance> at boot, on-read context injection), commands (the /promote slash-command), resume (the /resume-node
47
50
  * whole-canvas picker → `crtr node focus`), view (the /view popup → `crtr
48
51
  * view pick` / `crtr view run <name>`).
49
52
  * All self-gate on CRTR_NODE_ID. goal-capture precedes passive-context so it
@@ -52,6 +55,7 @@ export const CANVAS_EXTENSIONS = [
52
55
  CANVAS_STOPHOOK_PATH,
53
56
  CANVAS_INBOX_WATCHER_PATH,
54
57
  CANVAS_NAV_PATH,
58
+ CANVAS_RECAP_PATH,
55
59
  CANVAS_GOAL_CAPTURE_PATH,
56
60
  CANVAS_PASSIVE_CONTEXT_PATH,
57
61
  CANVAS_CONTEXT_INTRO_PATH,
@@ -65,20 +69,28 @@ export const CANVAS_EXTENSIONS = [
65
69
  * resolve to the latest opus/sonnet/haiku; `ultra` to the frontier model. */
66
70
  export const MODEL_TIERS = {
67
71
  ultra: 'anthropic/claude-fable-5',
68
- strong: 'anthropic/opus',
69
- medium: 'anthropic/sonnet',
70
- light: 'anthropic/haiku',
72
+ strong: 'anthropic/claude-opus-4-8',
73
+ medium: 'anthropic/claude-sonnet-4-6',
74
+ light: 'anthropic/claude-haiku-4-5',
75
+ };
76
+ /** Bare family aliases → the same concrete versioned ids as the tiers. These
77
+ * MUST be real registry ids (see `pi --list-models`): an unversioned spec like
78
+ * `anthropic/sonnet` is NOT in the registry and silently falls back to the SDK
79
+ * default (opus), so it can never be the resolution target. */
80
+ const BARE_ALIASES = {
81
+ opus: 'anthropic/claude-opus-4-8',
82
+ sonnet: 'anthropic/claude-sonnet-4-6',
83
+ haiku: 'anthropic/claude-haiku-4-5',
71
84
  };
72
85
  /** Resolve a model token to the spec pi gets via `--model`. A named tier
73
- * (ultra/strong/medium/light) maps to its concrete spec; a bare alias
74
- * (sonnet/opus/haiku) resolves to the anthropic provider under pi (avoids the
75
- * bedrock default); anything with a `/` or an unknown name passes through. */
86
+ * (ultra/strong/medium/light) maps to its concrete spec; a bare family alias
87
+ * (sonnet/opus/haiku) maps to that family's current versioned id; anything
88
+ * with a `/` or an unknown name passes through. */
76
89
  export function normalizeModel(model) {
77
90
  if (model in MODEL_TIERS)
78
91
  return MODEL_TIERS[model];
79
- const bare = new Set(['sonnet', 'opus', 'haiku']);
80
- if (bare.has(model))
81
- return `anthropic/${model}`;
92
+ if (model in BARE_ALIASES)
93
+ return BARE_ALIASES[model];
82
94
  return model;
83
95
  }
84
96
  // ---------------------------------------------------------------------------
@@ -188,7 +200,7 @@ export function buildPiArgv(meta, opts = {}) {
188
200
  * never drift. Safe because `buildPiArgv` is the SOLE producer of these flags
189
201
  * (we own both ends). SIDE EFFECT: merges `inv.env` into `process.env` so the
190
202
  * in-process engine + the bound canvas extensions see the same env a forked pi
191
- * would (CRTR_NODE_ID, CRTR_ROOT_SESSION, CRTR_SUBTREE, …). */
203
+ * would (CRTR_NODE_ID, CRTR_SUBTREE, …). */
192
204
  export function piInvocationToSdkConfig(inv) {
193
205
  for (const [k, v] of Object.entries(inv.env))
194
206
  process.env[k] = v;
@@ -2,7 +2,7 @@ import type { NodeMeta } from '../canvas/types.js';
2
2
  /** The lifecycle events — the only vocabulary for moving a node's status/intent.
3
3
  * Each maps (in the table below) to a target status and/or intent plus the set
4
4
  * of from-statuses it is legal from. */
5
- export type LifecycleEvent = 'finalize' | 'cancel' | 'crash' | 'yield' | 'release' | 'revive' | 'boot';
5
+ export type LifecycleEvent = 'finalize' | 'cancel' | 'crash' | 'yield' | 'release' | 'revive';
6
6
  /** Enact a lifecycle event on a node: validate the from-status against the
7
7
  * table, then write status+intent in ONE atomic statement (so they can never
8
8
  * disagree). Returns the hydrated node view after the write.
@@ -13,7 +13,7 @@
13
13
  // lifecycle.ts is the single source of which status/intent move is LEGAL. Two
14
14
  // parallel, legible state machines instead of scattered enactment.
15
15
  //
16
- // Crash-safety invariant (was a comment repeated in reset/close/reapDescendants):
16
+ // Crash-safety invariant (was a comment repeated in close/reapDescendants):
17
17
  // "flip status to a non-supervised value + clear intent BEFORE killing the
18
18
  // host" (tmux pane or headless broker) — the daemon only ever revives
19
19
  // active|idle nodes, so a teardown must
@@ -22,12 +22,13 @@
22
22
  // and only THEN tear the host down.
23
23
  //
24
24
  // Unification (A5, human-confirmed 2026-06-06): an externally-reaped node — torn
25
- // down because the user moved on (close cascade) OR because a root reset/relaunch
26
- // superseded it — ends `canceled`, NOT `done`. `done` is reserved for a node that
27
- // finished its OWN work (finalize). The old `reap` event (→ done) was identical to
28
- // `cancel` in every field and side effect once unified on status, so it was
29
- // COLLAPSED into `cancel`; reset.ts's reapDescendants + relaunchRoot park-old now
30
- // route through `cancel`.
25
+ // down because the user moved on (close cascade) OR because a root relaunch
26
+ // superseded its workers — ends `canceled`, NOT `done`. `done` is reserved for a
27
+ // node that finished its OWN work (finalize) including a relaunched root, which
28
+ // is parked `done` as history, not canceled. The old `reap` event (→ done) was
29
+ // identical to `cancel` in every field and side effect once unified on status, so
30
+ // it was COLLAPSED into `cancel`; reset.ts's reapDescendants routes through
31
+ // `cancel`.
31
32
  //
32
33
  // Layering note: lifecycle.ts is runtime, but it is the canvas write surface's
33
34
  // `transition` verb (the only writer of status+intent), so it owns its atomic
@@ -42,11 +43,12 @@ const LIVE = ['active', 'idle'];
42
43
  * the runtime actually wrote at its audited call sites, so behavior is preserved
43
44
  * by construction. Each entry's comment names its writer(s). */
44
45
  const TRANSITIONS = {
45
- // feed.push(final) · queue.cancelJob · markCleanExitDone (clean quit).
46
+ // feed.push(final) · queue.cancelJob · markCleanExitDone (clean quit) ·
47
+ // relaunchRoot park-old (a relaunched root is parked `done` as history).
46
48
  finalize: { status: 'done', intent: 'done', from: LIVE },
47
- // closeNode cascade · reapDescendants · relaunchRoot park-old. Forced teardown
48
- // of a node that did NOT finish its own work → canceled, intent cleared. (A5:
49
- // done is reserved for finalize; every external reap unifies on canceled.)
49
+ // closeNode cascade · reapDescendants. Forced teardown of a node that did NOT
50
+ // finish its own work → canceled, intent cleared. (A5: done is reserved for
51
+ // finalize; every external reap unifies on canceled.)
50
52
  cancel: { status: 'canceled', intent: null, from: ANY },
51
53
  // daemon superviseTick: engine container gone (tmux pane or broker process)
52
54
  // with no yield/release intent. Intent KEPT (the dead log line still reports it).
@@ -57,11 +59,6 @@ const TRANSITIONS = {
57
59
  release: { status: 'idle', intent: 'idle-release', from: LIVE },
58
60
  // reviveNode · resetRoot · stophook boot-confirm (clear a pending refresh net).
59
61
  revive: { status: 'active', intent: null, from: ANY },
60
- // reviveInPlace: re-exec a fresh pi in the SAME pane. Status (re)affirmed
61
- // active; intent KEPT so a pending refresh survives as proof-of-boot until the
62
- // fresh pi's session_start clears it (a premature clear is how a failed
63
- // respawn became a silent death — see revive.ts).
64
- boot: { status: 'active', from: LIVE },
65
62
  };
66
63
  /** Enact a lifecycle event on a node: validate the from-status against the
67
64
  * table, then write status+intent in ONE atomic statement (so they can never
@@ -96,9 +93,8 @@ export function transition(nodeId, event) {
96
93
  // Event-gated; exactly ONE delegated wakeups helper per event, never inline
97
94
  // wakeups SQL here (design §6.7 / D2 / D6, Q1/Q2):
98
95
  // cancel → cancelWakesFor: reap node-anchored AND owner-armed detached
99
- // wakes this node armed (ruling A) — covers all three cancel
100
- // sites uniformly (close cascade · reapDescendants · relaunch
101
- // park-old).
96
+ // wakes this node armed (ruling A) — covers both cancel sites
97
+ // uniformly (close cascade · reapDescendants).
102
98
  // finalize → cancelSelfAlarms: clear this node's one-shot self-alarms only;
103
99
  // node-anchored declarative crons SURVIVE a finishing instance (Q1).
104
100
  // crash → NOTHING. A fault is not a deliberate end-of-waiting; self-alarms
@@ -7,58 +7,6 @@ export declare function newNodeId(): string;
7
7
  * without an import cycle — `CRTR_SUBTREE` is emitted as `rootOfSpine(nodeId)`
8
8
  * at every launch site. */
9
9
  export declare function rootOfSpine(nodeId: string): string;
10
- /** The single, shared tmux session that ALL canvas node windows live in.
11
- * Overridable with CRTR_NODE_SESSION (default `crtr`). Every root and every
12
- * child opens a window here rather than cluttering the user's own working
13
- * session — switch to it to browse the whole live graph, ignore it otherwise.
14
- * Pure policy (env only, no tmux call), so it lives in the node layer, not the
15
- * driver; the tmux driver imports it from here for installMenuBinding's use. */
16
- export declare function nodeSession(): string;
17
- /** Resolve the tmux session a freshly-born node's window/pane opens into — and
18
- * thus its durable REVIVE-HOME (`home_session`). Pure so the birth decision is
19
- * unit-testable without a live tmux:
20
- * - managed background child (`adoptCaller=false`) → the shared backstage:
21
- * the inherited `CRTR_ROOT_SESSION`, else `nodeSession()` (`crtr`).
22
- * - independent `--root` / inline front door (`adoptCaller=true`) → the
23
- * caller's CURRENT session when inside tmux (`here`), else the backstage.
24
- * This is exactly the session each birth site already places the node into;
25
- * centralizing it keeps `home_session` and the actual placement in lockstep. */
26
- export declare function resolveBirthSession(opts: {
27
- /** True for an independent root or the inline front door (both adopt the
28
- * caller's session); false for a managed background child. */
29
- adoptCaller: boolean;
30
- /** The caller's current tmux location, or null when not inside tmux. */
31
- here: {
32
- session: string;
33
- } | null;
34
- /** The inherited CRTR_ROOT_SESSION (the backstage the subtree flows into). */
35
- rootSession?: string | null;
36
- }): string;
37
- /** A node's durable REVIVE-HOME, with the legacy back-compat default. Nodes born
38
- * before `home_session` existed have no such field in meta — they fall back to
39
- * their last live LOCATION (`tmux_session`), then to the shared backstage
40
- * (`nodeSession()`). The defaulted read for the placement layer; a present
41
- * `home_session` is always returned verbatim. */
42
- export declare function homeSessionOf(nodeId: string): string;
43
- /** The session a node's CHILDREN spawn into — the value its pi process exports
44
- * as CRTR_ROOT_SESSION (the subtree's backstage). NOT the same as where the
45
- * node's OWN pane lives.
46
- *
47
- * For a managed CHILD this is its `home_session`: a child's home_session is
48
- * ALWAYS the backstage it was born into (focus may move its live pane into a
49
- * user session, but home_session stays the taint-immune backstage), so its
50
- * descendants correctly inherit that backstage.
51
- *
52
- * For a ROOT (`parent === null`) `home_session` may be a USER session it
53
- * ADOPTED — the inline front door keeps the root's own pane where the user is
54
- * working (`home_session = cli`). Sourcing children's CRTR_ROOT_SESSION from
55
- * that would exile the root's entire subtree into the user's session. A root's
56
- * children must instead flow to the shared backstage `nodeSession()`, NEVER the
57
- * user's session — mirroring spawnRoot's birth-time split (root pane → user
58
- * session; children → nodeSession() via CRTR_ROOT_SESSION). The revive paths
59
- * MUST honor the same split, or a refreshed front-door root re-points its whole
60
- * subtree into the user's session on every yield (the bug this fixes). */
61
- export declare function childBackstageOf(nodeId: string): string;
62
10
  export interface NodeContext {
63
11
  nodeId: string | null;
64
12
  kind: string | null;
@@ -91,9 +39,6 @@ export interface SpawnNodeOpts {
91
39
  * the boot intro can re-assert this node's OWN identity over the source's
92
40
  * copied-in conversation. Omit for a fresh node. */
93
41
  forkFrom?: string | null;
94
- /** Which HOST launches + supervises this node: a tmux pane (default) or the
95
- * headless broker. Persisted to `meta.host_kind` (NULL/'tmux' ⇒ tmux). */
96
- hostKind?: 'tmux' | 'broker';
97
42
  /** New subscriptions this node opens default to passive when true. */
98
43
  passiveDefault?: boolean;
99
44
  /** Resolved pi launch recipe (from resolve(kind,mode)). */