@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
@@ -1,33 +1,31 @@
1
- // Root reset + relaunch the `/new` equivalents, plus clean-exit termination.
1
+ // `/new` handling root relaunch + child session-id refresh plus clean-exit
2
+ // termination.
2
3
  //
3
- // A live pi process is bound to one node via CRTR_NODE_ID (set at launch, not
4
- // rebindable mid-process). When the user runs `/new`, the conversation resets
5
- // but the OS process — and thus the node id — stays the same. To make `/new`
6
- // behave like re-running `crtr` we have two strategies:
4
+ // A node's engine is a detached broker process bound to one CRTR_NODE_ID, and
5
+ // its on-screen viewer is a SEPARATE `crtr attach` pane. `/new` splits by who
6
+ // ran it:
7
7
  //
8
- // • relaunchRoot (option C) for a ROOT in a tmux pane: PARK the old root
9
- // (mark canceled, keep its id/edges/pi_session_id intact as history), mint a
10
- // FRESH node id, and re-exec pi in the current pane bound to the new id.
11
- // The old id never changes meaning; external refs stay valid.
12
- // resetRoot (fallback) for a non-root child (session-id refresh only) or
13
- // a root with no pane (no tmux): the legacy in-place reset of the SAME id.
8
+ // • relaunchRoot a `/new` on a ROOT starts a genuinely new node: the old
9
+ // root is parked `done` (kept as history), a fresh node id + broker is
10
+ // minted in the same pane/cwd, and the viewer pane is re-pointed at the new
11
+ // broker. The new broker is booted FIRST, so any failure before the commit
12
+ // point leaves the old root fully intact.
13
+ // resetRoot a `/new` on a non-root child refreshes its session id only, so
14
+ // a later `--session <id>` wakes the right conversation.
14
15
  //
15
16
  // Termination semantics: a pi that ends cleanly resolves its node to `done`
16
- // (markCleanExitDone); only a true crash leaves it `dead`. A force-kill
17
- // (closeWindow / respawn-pane -k) fires NO clean session_shutdown, so reaped
18
- // descendants are marked `canceled` explicitly here (A5: an externally-reaped
19
- // node did not finish its own work — done is reserved for finalize).
17
+ // (markCleanExitDone); only a true crash leaves it `dead`. A force-kill fires NO
18
+ // clean session_shutdown, so reaped descendants are marked `canceled` explicitly
19
+ // here (A5: an externally-reaped node did not finish its own work — done is
20
+ // reserved for finalize).
20
21
  //
21
- // Best-effort throughout: a tmux/fs failure on one node never aborts the reset.
22
- import { existsSync, rmSync } from 'node:fs';
23
- import { getNode, updateNode, setPresence, clearPid, setFocusOccupant, subscriptionsOf, unsubscribe, view, reportsDir, inboxPath, nodeDir, openDb, } from '../canvas/index.js';
22
+ // Best-effort throughout: a tmux/fs failure on one node never aborts the reap.
23
+ import { getNode, updateNode, fullName, closeFocusRow, view, } from '../canvas/index.js';
24
24
  import { transition } from './lifecycle.js';
25
- import { hostFor } from './host.js';
26
- import { paneLocation, focusOf } from './placement.js';
27
- import { buildLaunchSpec } from './launch.js';
28
- import { roadmapPath } from './roadmap.js';
29
- import { spawnNode, newNodeId, nodeSession } from './nodes.js';
30
- import { relaunchRootInPane } from './revive.js';
25
+ import { headlessBrokerHost } from './host.js';
26
+ import { tearDownNode, focusOf, registerViewerFocus, respawnPaneSync, viewerSplitEnv, windowOfPane, renameWindow, waitForBrokerViewSocket, } from './placement.js';
27
+ import { buildLaunchSpec, buildPiArgv } from './launch.js';
28
+ import { spawnNode, rootOfSpine } from './nodes.js';
31
29
  // ---------------------------------------------------------------------------
32
30
  // reapDescendants — tear down a root's descendant sub-DAG (shared helper)
33
31
  // ---------------------------------------------------------------------------
@@ -37,25 +35,25 @@ import { relaunchRootInPane } from './revive.js';
37
35
  * keep parent=rootId. No wipe. Returns the reaped ids.
38
36
  *
39
37
  * Why `canceled` (A5, human-confirmed 2026-06-06): an externally-reaped node —
40
- * whether via `node close` OR a root reset/relaunch — did not finish its OWN
41
- * work, so it unifies on `canceled`; `done` is reserved for finalize. Why
42
- * marking is STILL explicit: a `closeWindow`/`respawn-pane -k` kill is abrupt
43
- * and fires NO clean `session_shutdown`, so the general quit→done rule does NOT
44
- * auto-resolve a force-killed descendant — we mark it `canceled` here via the
45
- * same `cancel` event the close cascade uses. Shared by relaunchRoot (option C)
46
- * and resetRoot's in-place fallback, so both leave their descendants `canceled`. */
38
+ * whether via `node close` OR a root reset — did not finish its OWN work, so it
39
+ * unifies on `canceled`; `done` is reserved for finalize. Why marking is STILL
40
+ * explicit: an abrupt broker teardown fires NO clean `session_shutdown`, so the
41
+ * general quit→done rule does NOT auto-resolve a force-killed descendant — we
42
+ * mark it `canceled` here via the same `cancel` event the close cascade uses. */
47
43
  export function reapDescendants(rootId) {
48
44
  const reaped = [];
49
45
  for (const id of view(rootId)) {
50
46
  try {
51
- // Reap BEFORE tearing down the placement (the crash-safety invariant the
52
- // `cancel` event encodes): a non-supervised status + cleared intent first, so
53
- // the daemon can't revive a descendant mid-teardown. Teardown then routes
54
- // through the Host seam (hostFor): a tmux node closes any focus row it held,
55
- // kills its pane (pane-keyed), and nulls its LOCATION; a broker node sends
56
- // the `shutdown` frame so its PROCESS exits and releases the .jsonl writer.
57
- const m = transition(id, 'cancel');
58
- hostFor(m).teardown(id);
47
+ // Reap BEFORE tearing down the engine (the crash-safety invariant the
48
+ // `cancel` event encodes): a non-supervised status + cleared intent first,
49
+ // so the daemon can't revive a descendant mid-teardown. Teardown then sends
50
+ // the broker the `shutdown` frame so its PROCESS exits and releases the
51
+ // sole .jsonl writer; tearDownNode then proactively closes the on-screen
52
+ // viewer pane + registry row (attach auto-reconnects, so the viewer must be
53
+ // closed here or it lingers ~30s in a misleading "reconnecting…" state).
54
+ transition(id, 'cancel');
55
+ headlessBrokerHost.teardown(id);
56
+ tearDownNode(id);
59
57
  reaped.push(id);
60
58
  }
61
59
  catch {
@@ -64,194 +62,109 @@ export function reapDescendants(rootId) {
64
62
  }
65
63
  return reaped;
66
64
  }
67
- /** Reset a root node to a pristine, empty graph (the legacy `/new` semantics —
68
- * now used as the no-pane fallback and the non-root session-id refresh).
69
- *
70
- * For a non-root (spawned child), a `/new` is not a graph reset — we only
71
- * refresh its session id so a later `--session <id>` wakes the right conversation. */
72
- export function resetRoot(nodeId, newSessionId, newSessionFile) {
73
- const meta = getNode(nodeId);
74
- if (meta === null)
75
- return { reaped: [], detached: [], reset: false };
76
- // Only roots own a graph in the "ran crtr again" sense.
77
- if (meta.parent != null) {
78
- if (newSessionId !== undefined) {
79
- updateNode(nodeId, {
80
- pi_session_id: newSessionId,
81
- ...(newSessionFile !== undefined ? { pi_session_file: newSessionFile } : {}),
82
- });
83
- }
84
- return { reaped: [], detached: [], reset: false };
85
- }
86
- // 1) Reap the descendant sub-DAG (mark canceled + kill windows; shared helper).
87
- const reaped = reapDescendants(nodeId);
88
- // 2) Detach the root's own subscriptions so its view is empty.
89
- const detached = [];
90
- for (const sub of subscriptionsOf(nodeId)) {
91
- unsubscribe(nodeId, sub.node_id);
92
- detached.push(sub.node_id);
93
- }
94
- // 3) Wipe the root's working state (reports / inbox / roadmap).
95
- for (const p of [
96
- reportsDir(nodeId),
97
- inboxPath(nodeId),
98
- `${inboxPath(nodeId)}.cursor`,
99
- roadmapPath(nodeId),
100
- ]) {
101
- try {
102
- if (existsSync(p))
103
- rmSync(p, { recursive: true, force: true });
104
- }
105
- catch {
106
- /* */
107
- }
108
- }
109
- // 4) Re-point the root at a fresh base persona + the new pi session id. A
110
- // root is resident by definition (this only runs on roots — see the early
111
- // return above), so resetting to base/resident is the model, not a bypass.
112
- // Re-seed persona_ack to the fresh persona so the pristine `/new`
113
- // conversation never gets a spurious mode/lifecycle transition steer (the
114
- // persona injector compares against this ack).
115
- const { launch } = buildLaunchSpec(meta.kind, 'base', { lifecycle: 'resident', hasManager: false, model: meta.model_override ?? undefined });
116
- updateNode(nodeId, {
65
+ /** Relaunch a ROOT on `/new`: park the old root `done` (kept as history) and
66
+ * mint a fresh node id + broker in the same pane/cwd, re-pointing the viewer at
67
+ * it. The new broker is booted FIRST and its pid confirmed BEFORE the old root
68
+ * is touched, so any pre-commit failure leaves the old root fully intact and
69
+ * live. Returns null when `oldId` is not a relaunchable root (unknown, a child,
70
+ * or already parked), or when the new broker failed to launch. */
71
+ export function relaunchRoot(oldId, deps = {}) {
72
+ const launchBroker = deps.launchBroker ?? headlessBrokerHost.launch;
73
+ const waitForViewSocket = deps.waitForViewSocket ?? waitForBrokerViewSocket;
74
+ const respawnViewer = deps.respawnViewer ?? respawnPaneSync;
75
+ const teardownBroker = deps.teardownBroker ?? headlessBrokerHost.teardown;
76
+ const old = getNode(oldId);
77
+ if (old === null || old.parent != null)
78
+ return null; // roots only
79
+ if (old.status === 'done')
80
+ return null; // defensive: a double `/new`
81
+ const oldFocus = focusOf(oldId); // capture the viewer BEFORE any teardown
82
+ // --- mint + boot the NEW broker FIRST (a failure here leaves the old root
83
+ // untouched) ---
84
+ // A relaunched root is a fresh resident base, exactly like the front door.
85
+ const { launch } = buildLaunchSpec(old.kind, 'base', {
86
+ lifecycle: 'resident',
87
+ hasManager: false,
88
+ model: old.model_override ?? undefined,
89
+ });
90
+ const newMeta = spawnNode({
91
+ kind: old.kind,
117
92
  mode: 'base',
118
93
  lifecycle: 'resident',
119
- persona_ack: { mode: 'base', lifecycle: 'resident' },
94
+ cwd: old.cwd,
95
+ name: old.kind,
96
+ parent: null,
120
97
  launch,
121
- ...(newSessionId !== undefined ? { pi_session_id: newSessionId } : {}),
122
- ...(newSessionFile !== undefined ? { pi_session_file: newSessionFile } : {}),
98
+ modelOverride: old.model_override ?? undefined,
123
99
  });
124
- transition(nodeId, 'revive');
125
- return { reaped, detached, reset: true };
126
- }
127
- /** The single entry the stophook calls on a detected `/new` (session id change).
128
- * Policy lives here so the stophook stays thin and this stays unit-testable:
129
- * - non-root child → resetRoot(nodeId, newSessionId) (session-id refresh only)
130
- * - root + pane present → relaunchRoot(nodeId, pane) (option C)
131
- * - root + no pane (no tmux) → resetRoot(nodeId, newSessionId) (in-place fallback)
132
- * On a respawn-dispatch failure the live pi never died, so we degrade to the
133
- * legacy in-place reset. */
134
- export function handleNewSession(nodeId, newSessionId, pane, deps = {}, newSessionFile) {
135
- const meta = getNode(nodeId);
136
- if (meta === null)
137
- return { path: 'noop' };
138
- // Non-root child: a `/new` only refreshes its session id (unchanged).
139
- if (meta.parent != null) {
140
- resetRoot(nodeId, newSessionId, newSessionFile);
141
- return { path: 'reset-child' };
142
- }
143
- // Root with no pane (not inside tmux): in-place reset fallback. Option C needs
144
- // a pane to respawn into; resetRoot needs the new session id (available here
145
- // because the trigger is session_start).
146
- if (pane === undefined || pane.trim() === '') {
147
- resetRoot(nodeId, newSessionId, newSessionFile);
148
- return { path: 'reset-root' };
149
- }
150
- // Root with a pane: option C relaunch. relaunchRoot self-rolls-back its DB
151
- // writes on a respawn-dispatch failure and rethrows; we then degrade to the
152
- // legacy in-place reset (the live pi is still alive, never killed).
153
- try {
154
- const result = relaunchRoot(nodeId, pane, deps);
155
- if (result === null)
156
- return { path: 'noop' }; // defensive guard hit (e.g. rapid double /new)
157
- return { path: 'relaunch', newNodeId: result.newNodeId };
100
+ const inv = buildPiArgv(newMeta, {});
101
+ // Mirror bootRoot's subtree routing on inv.env (the broker host merges it; it
102
+ // sets CRTR_FRONT_DOOR itself).
103
+ inv.env = {
104
+ ...inv.env,
105
+ CRTR_SUBTREE: rootOfSpine(newMeta.node_id),
106
+ };
107
+ const placed = launchBroker(newMeta.node_id, inv, {
108
+ cwd: old.cwd,
109
+ name: fullName(newMeta),
110
+ resuming: false,
111
+ });
112
+ if (placed.pid == null) {
113
+ // The new broker never started — crash the half-born node and BAIL. The old
114
+ // root is still live and untouched (nothing below this point has run).
115
+ transition(newMeta.node_id, 'crash');
116
+ return null;
158
117
  }
159
- catch {
160
- resetRoot(nodeId, newSessionId, newSessionFile);
161
- return { path: 'reset-root' };
118
+ waitForViewSocket(newMeta.node_id); // best-effort; attach auto-redials on miss
119
+ // --- COMMIT: park + reap the old root, re-point the viewer, kill the old
120
+ // broker. Past this point the new node is the live root. ---
121
+ reapDescendants(oldId); // old workers → canceled + torn down
122
+ // Park the old root DONE (kept as history; NOT canceled — a relaunched root
123
+ // finished cleanly, the user just started fresh). Its reports/inbox/roadmap
124
+ // are left on disk as the record of that session.
125
+ transition(oldId, 'finalize');
126
+ if (oldFocus?.pane != null) {
127
+ closeFocusRow(oldFocus.focus_id);
128
+ respawnViewer({
129
+ pane: oldFocus.pane,
130
+ cwd: old.cwd,
131
+ env: viewerSplitEnv(),
132
+ command: `crtr attach to ${newMeta.node_id}`,
133
+ });
134
+ const window = windowOfPane(oldFocus.pane);
135
+ if (window !== null)
136
+ renameWindow(window, fullName(newMeta));
137
+ registerViewerFocus(newMeta.node_id, oldFocus.pane, oldFocus.session, window);
162
138
  }
139
+ teardownBroker(oldId); // old broker exits cleanly (its /new work is discarded)
140
+ return { newNodeId: newMeta.node_id };
163
141
  }
164
- /** Park the old root + create+launch a fresh root in `pane` (option C). All DB
165
- * writes are synchronous and happen BEFORE the respawn (the respawn kills the
166
- * caller). Returns the new node id, or null on a defensive guard (not a root /
167
- * already parked). Throws only if the respawn dispatch fails — and self-rolls-
168
- * back its writes first so the caller can degrade to resetRoot. */
169
- export function relaunchRoot(oldId, pane, deps = {}) {
170
- const oldMeta = getNode(oldId);
171
- if (oldMeta === null || oldMeta.parent != null)
172
- return null; // defensive: not a root
173
- if (oldMeta.status === 'canceled')
174
- return null; // defensive: already parked (rapid double /new)
175
- const respawn = deps.relaunchRootInPane ?? relaunchRootInPane;
176
- // Resolve where the new pi will live (pane authoritative; fall back to old
177
- // meta when paneLocation can't resolve, e.g. outside a live tmux server).
178
- const loc = paneLocation(pane) ?? {
179
- session: oldMeta.tmux_session ?? null,
180
- window: oldMeta.window ?? null,
181
- };
182
- const newId = newNodeId();
183
- const { launch } = buildLaunchSpec(oldMeta.kind, 'base', { lifecycle: 'resident', hasManager: false, model: oldMeta.model_override ?? undefined });
184
- // Park-old + mint-new is the single most fragile spot in the runtime, so it is
185
- // ONE atomic unit: every ROW write below runs inside a sqlite transaction. A
186
- // failure anywhere — including the respawn DISPATCH — rolls the whole thing
187
- // back, leaving the old root EXACTLY as it was (no hand-rolled compensation).
188
- // Only the *detached* respawn (the async pane kill) lands outside the txn — it
189
- // must, since it kills this caller, and by then COMMIT has made the new state
190
- // durable. The focus repoint (step 4) is INSIDE the txn, so a ROLLBACK undoes
191
- // it automatically — there is no file to restore.
192
- const db = openDb();
193
- db.exec('BEGIN');
194
- try {
195
- // 1) Reap descendants (mark canceled + kill windows, keep edges, no wipe).
196
- reapDescendants(oldId);
197
- // 2) Create the fresh root node (new id, empty context dir via
198
- // ensureNodeDirs) seeded active; `yield` adds the refresh safety net so
199
- // that if the pane dies before boot the daemon revives it in a new window.
200
- spawnNode({
201
- kind: oldMeta.kind,
202
- mode: 'base',
203
- lifecycle: 'resident',
204
- cwd: oldMeta.cwd,
205
- name: oldMeta.kind,
206
- parent: null,
207
- spawnedBy: oldId, // audit-only successor link; does NOT touch the spine
208
- nodeId: newId,
209
- modelOverride: oldMeta.model_override,
210
- launch,
142
+ /** Refresh a non-root child's pi session id on `/new`, so a later
143
+ * `--session <id>` wakes the right conversation. A `/new` on a child is NOT a
144
+ * graph reset a root's `/new` is handled by relaunchRoot, never here; a root
145
+ * that reaches this is a no-op. */
146
+ export function resetRoot(nodeId, newSessionId, newSessionFile) {
147
+ const meta = getNode(nodeId);
148
+ const empty = { reaped: [], detached: [], reset: false };
149
+ if (meta === null || meta.parent == null)
150
+ return empty; // unknown or a root
151
+ if (newSessionId !== undefined) {
152
+ updateNode(nodeId, {
153
+ pi_session_id: newSessionId,
154
+ ...(newSessionFile !== undefined ? { pi_session_file: newSessionFile } : {}),
211
155
  });
212
- transition(newId, 'yield'); // active (from spawn) + intent=refresh safety net
213
- setPresence(newId, { tmux_session: loc.session, window: loc.window });
214
- // REVIVE-HOME: the relaunched root's durable revive target is the session
215
- // of the pane it is respawned into (same pane-recycle rule as demote).
216
- updateNode(newId, { home_session: loc.session ?? nodeSession() });
217
- clearPid(newId); // no pi yet → daemon 'leave' until boot records the pid
218
- // 3) Park the old root: cancel (canceled + intent cleared) and detach its
219
- // window so it never claims the pane, but KEEP pi_session_id (resumable),
220
- // parent=null, and all edges. A5: a superseded old root did not finish its
221
- // own work — it unifies on `canceled` like every other external reap.
222
- transition(oldId, 'cancel');
223
- setPresence(oldId, { window: null, tmux_session: null });
224
- // 4) Focus follows content: repoint the old root's focus row to the new root
225
- // (same pane — respawn-pane -k below keeps the %id). Inside the txn → the
226
- // ROLLBACK path restores the old occupant automatically (no file to restore).
227
- const oldFocus = focusOf(oldId);
228
- if (oldFocus !== null)
229
- setFocusOccupant(oldFocus.focus_id, newId);
230
- // 5) Re-exec pi in this pane bound to newId; the dispatch is the LAST thing
231
- // inside the txn. If it throws the txn rolls back (old root untouched); on
232
- // success we COMMIT and the async detached kill of this pane lands after.
233
- respawn(newId, pane);
234
- db.exec('COMMIT');
235
156
  }
236
- catch (err) {
237
- // Dispatch failed (or a write threw) — the live pi never died. Roll the whole
238
- // transaction back so the old root is FULLY restored, then degrade.
239
- try {
240
- db.exec('ROLLBACK');
241
- }
242
- catch { /* */ }
243
- // The rolled-back new node's row is gone, but spawnNode already scaffolded its
244
- // on-disk dir (ensureNodeDirs). With no row, prune never sees it — so remove
245
- // the orphan dir here, otherwise it is permanent disk litter on this rare path.
246
- try {
247
- rmSync(nodeDir(newId), { recursive: true, force: true });
248
- }
249
- catch { /* */ }
250
- // The focus repoint was inside the txn; ROLLBACK already restored the old
251
- // occupant — nothing to undo here.
252
- throw err instanceof Error ? err : new Error(String(err));
253
- }
254
- return { newNodeId: newId };
157
+ return empty;
158
+ }
159
+ /** The child-side `/new` entry the stophook calls (the root side goes to
160
+ * relaunchRoot directly). The broker already drove the engine-side new_session;
161
+ * this only refreshes the child's session id on the SAME node id. */
162
+ export function handleNewSession(nodeId, newSessionId, newSessionFile) {
163
+ const meta = getNode(nodeId);
164
+ if (meta === null)
165
+ return { path: 'noop' };
166
+ resetRoot(nodeId, newSessionId, newSessionFile);
167
+ return { path: 'reset-child' };
255
168
  }
256
169
  // ---------------------------------------------------------------------------
257
170
  // markCleanExitDone — the clean-exit→done termination guard
@@ -1,6 +1,5 @@
1
1
  import { type NodeMeta } from '../canvas/index.js';
2
2
  import type { WakeOrigin } from './bearings.js';
3
- import { type RespawnPaneOpts } from './placement.js';
4
3
  /** Pick the `--session` source for a revive. resume=true prefers the absolute
5
4
  * session-file path (immune to cwd; pi opens it directly) and keeps the bare
6
5
  * session id as the fallback for older nodes booted before pi_session_file was
@@ -12,41 +11,22 @@ export declare function resumeArgs(meta: NodeMeta, resume: boolean): {
12
11
  resumeSessionPath?: string;
13
12
  };
14
13
  export interface ReviveResult {
15
- /** The new tmux window id, or null if openNodeWindow failed. */
14
+ /** Always null the broker engine is never placed in a tmux window. Kept on
15
+ * the result for caller back-compat. */
16
16
  window: string | null;
17
- /** The tmux session the node was placed in. */
18
- session: string;
17
+ /** The node's last live LOCATION session, or null — the broker engine has no
18
+ * tmux session of its own (it opens no viewer). Kept for caller back-compat. */
19
+ session: string | null;
19
20
  /** True when pi was instructed to resume its saved conversation (`--session <id>`). */
20
21
  resumed: boolean;
21
22
  }
22
- /** Open a fresh background tmux window for `nodeId` and update canvas meta.
23
+ /** Relaunch `nodeId`'s broker engine from its persisted recipe and update canvas
24
+ * meta. Opens no viewer (engine-only).
23
25
  *
24
- * Throws if the node does not exist. All other failures (e.g. tmux not
25
- * available) propagate as-is — callers (daemon, command) decide how to handle.
26
+ * Throws if the node does not exist. All other failures propagate as-is
27
+ * callers (daemon, command) decide how to handle.
26
28
  */
27
29
  export declare function reviveNode(nodeId: string, opts: {
28
30
  resume: boolean;
29
31
  wakeReason?: WakeOrigin;
30
32
  }): ReviveResult;
31
- /** Re-exec a node's pi FRESH in its EXISTING tmux pane (the refresh-yield
32
- * path). Unlike `reviveNode`, this opens no new window: the pane's current pi
33
- * is replaced in place via `respawn-pane -k`, so a foreground/interactive
34
- * session keeps its terminal and a background node keeps its window. Always
35
- * fresh (no resume) — the node re-reads its roadmap/context dir.
36
- *
37
- * `pane` is the target pane id (the yielding node reads it from $TMUX_PANE).
38
- * Throws on unknown node or when the respawn could not be dispatched, so the
39
- * caller can fall back to a plain shutdown (daemon revives in a new window). */
40
- export declare function reviveInPlace(nodeId: string, pane: string, respawn?: (opts: RespawnPaneOpts) => boolean): ReviveResult;
41
- /** Re-exec a FRESH pi for `nodeId` in EXISTING `pane` (respawn-pane -k), with
42
- * NO prompt and NO resume — a clean root conversation (goal-capture /
43
- * context-intro handle the first message + bearings, exactly like bare
44
- * `crtr`). Unlike reviveInPlace: no buildReviveKickoff prompt, no cycles bump,
45
- * and it sets CRTR_FRONT_DOOR=1 (REQUIRED — src/core/runtime/CLAUDE.md: any
46
- * path that boots a pi must guard against a removed/renamed subcommand
47
- * fork-bombing). Throws if the respawn could not be dispatched.
48
- *
49
- * Used by relaunchRoot (reset.ts) for the `/new`-in-a-root relaunch. Kept
50
- * SEPARATE from reviveInPlace so the refresh-yield path's exact semantics
51
- * (kickoff + cycle bump) are untouched. */
52
- export declare function relaunchRootInPane(nodeId: string, pane: string): void;