@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
@@ -38,71 +38,6 @@ export function rootOfSpine(nodeId) {
38
38
  cur = row.parent;
39
39
  }
40
40
  }
41
- /** The single, shared tmux session that ALL canvas node windows live in.
42
- * Overridable with CRTR_NODE_SESSION (default `crtr`). Every root and every
43
- * child opens a window here rather than cluttering the user's own working
44
- * session — switch to it to browse the whole live graph, ignore it otherwise.
45
- * Pure policy (env only, no tmux call), so it lives in the node layer, not the
46
- * driver; the tmux driver imports it from here for installMenuBinding's use. */
47
- export function nodeSession() {
48
- const v = process.env['CRTR_NODE_SESSION'];
49
- return v !== undefined && v !== '' ? v : 'crtr';
50
- }
51
- // ---------------------------------------------------------------------------
52
- // REVIVE-HOME (home_session) — the durable session a node is (re)opened into
53
- // ---------------------------------------------------------------------------
54
- /** Resolve the tmux session a freshly-born node's window/pane opens into — and
55
- * thus its durable REVIVE-HOME (`home_session`). Pure so the birth decision is
56
- * unit-testable without a live tmux:
57
- * - managed background child (`adoptCaller=false`) → the shared backstage:
58
- * the inherited `CRTR_ROOT_SESSION`, else `nodeSession()` (`crtr`).
59
- * - independent `--root` / inline front door (`adoptCaller=true`) → the
60
- * caller's CURRENT session when inside tmux (`here`), else the backstage.
61
- * This is exactly the session each birth site already places the node into;
62
- * centralizing it keeps `home_session` and the actual placement in lockstep. */
63
- export function resolveBirthSession(opts) {
64
- const backstage = opts.rootSession !== undefined && opts.rootSession !== null && opts.rootSession !== ''
65
- ? opts.rootSession
66
- : nodeSession();
67
- if (opts.adoptCaller && opts.here !== null)
68
- return opts.here.session;
69
- return backstage;
70
- }
71
- /** A node's durable REVIVE-HOME, with the legacy back-compat default. Nodes born
72
- * before `home_session` existed have no such field in meta — they fall back to
73
- * their last live LOCATION (`tmux_session`), then to the shared backstage
74
- * (`nodeSession()`). The defaulted read for the placement layer; a present
75
- * `home_session` is always returned verbatim. */
76
- export function homeSessionOf(nodeId) {
77
- const meta = getNode(nodeId);
78
- if (meta === null)
79
- return nodeSession();
80
- return meta.home_session ?? meta.tmux_session ?? nodeSession();
81
- }
82
- /** The session a node's CHILDREN spawn into — the value its pi process exports
83
- * as CRTR_ROOT_SESSION (the subtree's backstage). NOT the same as where the
84
- * node's OWN pane lives.
85
- *
86
- * For a managed CHILD this is its `home_session`: a child's home_session is
87
- * ALWAYS the backstage it was born into (focus may move its live pane into a
88
- * user session, but home_session stays the taint-immune backstage), so its
89
- * descendants correctly inherit that backstage.
90
- *
91
- * For a ROOT (`parent === null`) `home_session` may be a USER session it
92
- * ADOPTED — the inline front door keeps the root's own pane where the user is
93
- * working (`home_session = cli`). Sourcing children's CRTR_ROOT_SESSION from
94
- * that would exile the root's entire subtree into the user's session. A root's
95
- * children must instead flow to the shared backstage `nodeSession()`, NEVER the
96
- * user's session — mirroring spawnRoot's birth-time split (root pane → user
97
- * session; children → nodeSession() via CRTR_ROOT_SESSION). The revive paths
98
- * MUST honor the same split, or a refreshed front-door root re-points its whole
99
- * subtree into the user's session on every yield (the bug this fixes). */
100
- export function childBackstageOf(nodeId) {
101
- const meta = getNode(nodeId);
102
- if (meta !== null && meta.parent === null)
103
- return nodeSession();
104
- return homeSessionOf(nodeId);
105
- }
106
41
  /** Read the current node's identity from the environment. A spawned pi process
107
42
  * runs with CRTR_NODE_ID set; its own `crtr` invocations spawn children under
108
43
  * it by reading CRTR_NODE_ID as the parent. */
@@ -131,14 +66,9 @@ export function nodeEnv(meta) {
131
66
  const home = process.env['CRTR_HOME'];
132
67
  if (home !== undefined && home !== '')
133
68
  env['CRTR_HOME'] = home;
134
- // Propagate the root's tmux session so every descendant spawns its windows
135
- // into the same root session.
136
- const rootSession = process.env['CRTR_ROOT_SESSION'];
137
- if (rootSession !== undefined && rootSession !== '')
138
- env['CRTR_ROOT_SESSION'] = rootSession;
139
- // Propagate the subtree-root node id (additive in Phase 3; tmux still keys on
140
- // CRTR_ROOT_SESSION). The broker uses it to group a subtree; each launch site
141
- // also emits the authoritative rootOfSpine(nodeId) over this passthrough.
69
+ // Propagate the subtree-root node id. The broker uses it to group a subtree;
70
+ // each launch site also emits the authoritative rootOfSpine(nodeId) over this
71
+ // passthrough.
142
72
  const subtree = process.env['CRTR_SUBTREE'];
143
73
  if (subtree !== undefined && subtree !== '')
144
74
  env['CRTR_SUBTREE'] = subtree;
@@ -167,7 +97,9 @@ export function spawnNode(opts) {
167
97
  cycles: 0,
168
98
  created: new Date().toISOString(),
169
99
  cwd: opts.cwd,
170
- host_kind: opts.hostKind ?? 'tmux',
100
+ // Broker is the only host: every node runs on the headless broker; a tmux
101
+ // pane is only an attach viewer.
102
+ host_kind: 'broker',
171
103
  kind: opts.kind,
172
104
  mode,
173
105
  lifecycle,
@@ -1,358 +1,114 @@
1
- import { type NodeRow, type FocusRow } from '../canvas/index.js';
2
- import { homeSessionOf, childBackstageOf } from './nodes.js';
3
- export { homeSessionOf, childBackstageOf };
1
+ import { type FocusRow } from '../canvas/index.js';
4
2
  export type { FocusRow };
5
- export { piCommand, paneLocation, currentTmux, inTmux, ensureSession, openNodeWindow, splitWindow, focusWindow, windowAlive, windowOfPane, respawnPane, respawnPaneSync, setPaneOption, getPaneOption, paneCurrentPath, listLivePanes, } from './tmux.js';
3
+ export { splitWindow, focusWindow, selectWindow, switchClient, openNodeWindow, ensureSession, paneLocation, paneExists, paneRunning, paneCurrentPath, currentTmux, inTmux, installMenuBinding, setPaneOption, getPaneOption, piCommand, respawnPaneSync, closePane, windowOfPane, renameWindow, paneOfWindow, windowAlive, listLivePanes, } from './tmux.js';
6
4
  export type { RespawnPaneOpts } from './tmux.js';
7
- export { nodeSession } from './nodes.js';
8
- /** The focus a node occupies, or null. UNIQUE(node_id) ⇒ at most one. */
5
+ /** The viewer a node has on screen, or null. UNIQUE(node_id) ⇒ at most one. */
9
6
  export declare function focusOf(nodeId: string): FocusRow | null;
10
- /** Is this node on a viewport? */
7
+ /** Does this node have a live viewer on screen? */
11
8
  export declare function isFocused(nodeId: string): boolean;
12
- /** The focus realized by a given pane (`%id`), or null. */
9
+ /** The viewer realized by a given pane (`%id`), or null. */
13
10
  export declare function focusByPane(pane: string): FocusRow | null;
14
- /** The set of node ids currently on some focus. */
11
+ /** The set of node ids that currently have a live viewer. */
15
12
  export declare function focusedNodes(): Set<string>;
16
- /** Every focus row (every live viewport). */
13
+ /** Every live viewer row (GC'ing any whose pane has gone). */
17
14
  export declare function listFocuses(): FocusRow[];
18
- /** The on-screen viewport a human-in-the-loop prompt raised by `nodeId` should
19
- * surface into: the HIGHEST FOCUSED node of nodeId's graph — the focused node
20
- * closest to the graph root, i.e. the session/window the user is actually
15
+ /** The on-screen viewer a human-in-the-loop prompt raised by `nodeId` should
16
+ * surface into: the HIGHEST node of nodeId's graph that has a viewer — the
17
+ * viewer closest to the graph root, i.e. the session/window the user is actually
21
18
  * watching this work in. Walks nodeId's spine to its root, enumerates the whole
22
- * tree root-first (`view` is BFS ⇒ shallowest first), and returns the focus row
23
- * of the first node that occupies a viewport. null when nothing in the graph is
24
- * on screen — the caller then surfaces in the user's attached pane rather than
25
- * the backstage node session. PURE (db reads only): no tmux probe, so the pane
26
- * may be stale; the caller liveness-checks before targeting it. */
19
+ * tree root-first (`view` is BFS ⇒ shallowest first), and returns the viewer row
20
+ * of the first node that has one. null when nothing in the graph is on screen —
21
+ * the caller then surfaces in the user's attached pane. PURE (db reads only): no
22
+ * tmux probe, so the pane may be stale; the caller liveness-checks before
23
+ * targeting it. */
27
24
  export declare function graphSurfaceTarget(nodeId: string): FocusRow | null;
28
- /** The cached LOCATION as stored on a node row: the authoritative `pane` handle
29
- * plus its derived window/session cache. */
30
- export interface CachedLocation {
31
- pane: string | null;
32
- tmux_session: string | null;
33
- window: string | null;
34
- }
35
- /** What `reconcile` resolved from tmux for the cached location. The shell does
36
- * the two driver reads; the pure decision interprets them.
37
- * - `paneLoc`: `paneLocation(cached.pane)`the pane's CURRENT session/window,
38
- * or null when the pane is gone. Only meaningful when `cached.pane != null`.
39
- * - `windowPane`: `paneOfWindow(cached.tmux_session, cached.window)` — the live
40
- * window's active pane, for the legacy backfill case (`cached.pane == null`). */
41
- export interface LiveProbe {
42
- paneLoc: {
43
- session: string;
44
- window: string;
45
- } | null;
46
- windowPane: string | null;
47
- }
48
- /** The presence patch `reconcile` should write, or `{ kind: 'none' }` for a no-op.
49
- * - `none` — the cache already matches reality (or there's nothing to do).
50
- * - `gone` — the durable pane is gone → null the whole LOCATION.
51
- * - `follow` — the pane moved (user move) → re-point the cache at its new
52
- * window/session, keeping the same pane id.
53
- * - `backfill` — a legacy row had no pane but a live window → adopt the
54
- * window's active pane as the durable handle (begins populating
55
- * `pane` for pre-existing nodes). */
56
- export type ReconcileDecision = {
57
- kind: 'none';
58
- } | {
59
- kind: 'gone';
60
- } | {
61
- kind: 'follow';
62
- pane: string;
63
- tmux_session: string;
64
- window: string;
65
- } | {
66
- kind: 'backfill';
67
- pane: string;
68
- tmux_session: string;
69
- window: string;
70
- };
71
- /** PURE reconciliation decision (§2.4) — unit-testable without a live tmux.
72
- * Given the cached row LOCATION and what tmux currently reports, decide the
73
- * presence patch. Mirrors the pure-core/impure-shell split (cf. `livenessVerdict`
74
- * vs `handleLiveWindow`): this is the decision, `reconcile` wires it to the
75
- * driver reads + `setPresence`. */
76
- export declare function reconcileDecision(cached: CachedLocation, live: LiveProbe): ReconcileDecision;
77
- /** Reconcile a node's LOCATION against tmux reality (§2.4) — the impure shell.
78
- * Reads `row.pane`, resolves its CURRENT session/window via the driver, and
79
- * writes the resulting presence patch through `setPresence` (never a raw UPDATE):
80
- * - pane moved → FOLLOW (re-point window/session, keep the pane id)
81
- * - pane gone → null the whole LOCATION
82
- * - legacy/no pane + live window → backfill the pane from `paneOfWindow`
83
- * A no-op when there's nothing to resolve (genuinely no pane, or the cache is
84
- * already current). Call this before any swap/kill/focus/revive so the act lands
85
- * on the pane's current window, never a stale one. */
86
- export declare function reconcile(nodeId: string): void;
87
- /** Reconcile a FOCUS's derived `session` cache against tmux reality (§2.4, Q4) —
88
- * the focus-row analogue of `reconcile`. A focus is anchored on its durable
89
- * `%pane_id`; `session` is a derived cache. If the user moved the focus pane to
90
- * another session, re-point the cache so a resume-into-focus lands in the pane's
91
- * CURRENT session. A no-op when the focus has no pane, the cache is already
92
- * current, or the pane is GONE — in the gone case reconcileFocus does NOT null
93
- * the row; the caller (reviveIntoPlacement) instead falls to the backstage
94
- * branch via `paneExists(pane)` being false. */
95
- export declare function reconcileFocus(focusId: string): void;
96
- /** Is this node's pane (its LOCATION) alive? The v3 PRIMARY liveness probe,
97
- * PURE / non-mutating so the daemon can gate on it without side effects:
98
- * - `pane != null` → `paneExists(pane)` (display-message on the `%id`), so a
99
- * user moving the pane to another window/session never reads as "gone".
100
- * - `pane == null` → window-keyed FALLBACK (`windowAlive`) for legacy/no-pane
101
- * rows that haven't been backfilled yet.
102
- * Accepts a node id (re-reads the row) or a `NodeRow` already in hand. */
103
- export declare function isNodePaneAlive(node: string | NodeRow): boolean;
104
- /** The launch recipe `reviveIntoPlacement` plays into a pane. `command` is the
105
- * full shell string (`piCommand(argv)`); `env`/`cwd`/`name` describe the
106
- * window/pane; `resuming` is carried through for the caller's ReviveResult. */
107
- export interface ReviveLaunch {
108
- command: string;
109
- env: Record<string, string>;
110
- cwd: string;
111
- name: string;
112
- resuming: boolean;
113
- }
114
- /** Where a revive physically landed: the new/derived window, the session it ran
115
- * in, and the durable pane id. */
116
- export interface PlacementResult {
117
- window: string | null;
118
- session: string;
119
- pane: string | null;
120
- }
121
- /** The PURE revive-target decision (§1.4/§5.1) — THE assertion that the
122
- * "unbidden windows" bug is structurally dead. Given a node's focus (or null),
123
- * whether that focus's pane is still alive, and the node's durable REVIVE-HOME
124
- * (`home_session`), decide WHERE a revive must land:
125
- * - occupies a LIVE focus → resume IN PLACE in that focus pane (no new window).
126
- * - otherwise → a new window in `homeSession`, and NOTHING ELSE.
127
- *
128
- * The backstage branch's session is `homeSession` ONLY — never
129
- * `meta.tmux_session`, the field focus taints to a user session. For a
130
- * post-Step-1 child `homeSession` is the backstage `crtr` (never a user
131
- * session), so a non-focused child — INCLUDING a once-focused-now-unfocused
132
- * child whose `tmux_session` was tainted — can NEVER revive into a user session.
133
- * A root's `homeSession` is its own session, so reviving a root into its own
134
- * session is correct, not the bug. */
135
- export type ReviveTargetDecision = {
136
- kind: 'focus-pane';
137
- pane: string;
138
- session: string;
139
- } | {
140
- kind: 'backstage';
141
- session: string;
142
- };
143
- export declare function reviveTarget(focus: FocusRow | null, focusPaneAlive: boolean, homeSession: string): ReviveTargetDecision;
144
- /** Place a reviving node into its CORRECT location (§1.4) — the single decision
145
- * that replaces revive.ts's old `session = meta.tmux_session ?? nodeSession()` +
146
- * `openNodeWindow`. Reconcile first (§2.4), then dispatch on `reviveTarget`:
147
- * - the node occupies a LIVE focus → `reconcileFocus` (resolve the pane's
148
- * CURRENT session, Q4) and `respawn-pane -k` the pi INTO that focus pane —
149
- * no new window (F3 resume-in-place).
150
- * - otherwise → the node is NOT focused (or its focus pane already collapsed,
151
- * the Step-5 limitation: remain-on-exit lands in Step 6), so it may ONLY
152
- * (re)appear in its durable REVIVE-HOME: a fresh window in `homeSession`.
153
- * **There is NO code path here by which a non-focused node's new-window
154
- * targets a user session** — `openNodeWindow`'s session is `homeSession` and
155
- * nothing else. That is the structural bug-kill.
156
- *
157
- * `setPresence` (the one atomic LOCATION write) records where the node landed.
158
- * CRTR_ROOT_SESSION is forced to the node's children-backstage (childBackstageOf)
159
- * in BOTH branches so the node's children always flow to the backstage, never
160
- * into the focus session — and, for a front-door ROOT, never into the user
161
- * session its own pane adopted. */
162
- export declare function reviveIntoPlacement(nodeId: string, launch: ReviveLaunch): PlacementResult;
163
- /** Relocate a node's still-running agent to the background `crtr` session,
164
- * freeing the foreground pane WITHOUT killing the pi. `break-pane` moves the
165
- * pane out of the foreground window into a fresh window in the shared backstage
166
- * (the pi keeps generating); the node becomes a background window — switchable
167
- * but not rendered, like any other node. Reconcile first (act on the pane's
168
- * CURRENT location, §2.4) and again after (presence FOLLOWS the move). No-op
169
- * (false) when there is no live pane to relocate or tmux refuses the break.
170
- * `pane` is the authoritative node pane the caller acts on (the Alt+C menu's
171
- * `#{pane_id}`); falls back to the node's durable handle.
172
- *
173
- * Invariant P gate (Bug 1): the backstage holds ONLY generating-but-unfocused
174
- * nodes. A node that is NOT mid-turn (e.g. a resident root parked idle between
175
- * turns, detached via Alt+C → D) must never be parked there as a live-idle pi —
176
- * nothing would re-enter agent_end to release it and, with no live subscription,
177
- * nothing would wake it, so it would hold a backstage pane forever, reaped by
178
- * nothing. Such a node is RELEASED to dormant instead (mirrors the parked-viewer
179
- * release on focus-away in retargetFocus). */
180
- export declare function detachToBackground(nodeId: string, pane?: string): boolean;
181
- /** A reviver: resume a DORMANT node into its backstage placement (a fresh `crtr`
182
- * window via reviveIntoPlacement). Injected so placement.ts need not import
183
- * revive.ts (which imports placement.ts — a cycle). The node's landed pane is
184
- * read back from its row afterwards. */
25
+ /** Synchronously wait until a broker's view.sock accepts a connection. The spawn
26
+ * and focus flows are sync (the command layer calls them directly), so the
27
+ * readiness probe lives in a short child Node process that can use async net
28
+ * events while this process blocks in `spawnSync`. Success proves more than file
29
+ * existence: it is robust to a stale leftover socket the launching broker has
30
+ * not unlinked yet, because only an accepting listener exits 0. */
31
+ export declare function waitForBrokerViewSocket(nodeId: string): boolean;
32
+ /** A reviver: ensure a node's broker ENGINE is alive (idempotent reviveNode →
33
+ * headlessBrokerHost.launch). Injected so placement.ts need not import revive.ts
34
+ * (which imports placement.tsa cycle). */
185
35
  export type Reviver = (nodeId: string) => void;
186
- /** Result of a focus/retarget op. */
36
+ /** Result of a focus op. `inPlace` = navigated to an existing viewer (no new
37
+ * pane); false = a fresh viewer pane was opened/moved. `revived` = this call
38
+ * had to (re)launch the broker engine. */
187
39
  export interface FocusResult {
188
40
  focused: boolean;
189
41
  session: string | null;
190
42
  inPlace: boolean;
191
43
  revived: boolean;
192
44
  }
193
- /** PURE disposition of a focus's outgoing occupant after a retarget swap (§2.5/
194
- * §1.3). Four signals decide one of three fates; unit-testable in isolation:
195
- * - `kill` — a holder pane (no row) or a done/dead/canceled node: reap the
196
- * (now-backstage) pane (Invariant P: not-focused + not-live ⇒
197
- * no pane).
198
- * - `backstage`— a human-driven RESIDENT node (editor/root/orchestrator — NEVER
199
- * despawned on focus-away), or a terminal worker that is
200
- * genuinely MID-TURN (Invariant F2 keeps running off-screen).
201
- * - `release` a PARKED terminal viewer (live but not mid-turn): a node
202
- * revived only for inspection. Despawn it back to dormant
203
- * (transition `release` idle/idle-release) and reap its pane;
204
- * the daemon revives it on its inbox, or the user re-focuses.
205
- * This is the bug fix: such a node was misclassified as
206
- * generating and left stuck active forever.
207
- * Order matters: `resident` is checked BEFORE `generating` so a resident node is
208
- * always kept warm regardless of whether it happens to be mid-turn. */
209
- export type OutgoingAction = {
210
- kind: 'backstage';
211
- } | {
212
- kind: 'kill';
213
- } | {
214
- kind: 'release';
215
- };
216
- export declare function outgoingDisposition(o: {
217
- exists: boolean;
218
- live: boolean;
219
- resident: boolean;
220
- generating: boolean;
221
- }): OutgoingAction;
222
- /** Open a NEW viewport (§2.3, F4) — the ONLY path a new pane appears in a user
223
- * session. Default: `splitWindow(callerPane)` beside (Q3); `newWindow` opens a
224
- * fresh window in the caller pane's session instead. Arms `remain-on-exit` on
225
- * the new pane's window (F3) and inserts a focuses row anchored on it, occupied
226
- * by a HOLDER until retargetFocus swaps a real node in. A benign long-sleep
227
- * holds the pane open until the swap; retargetFocus reaps it. Returns the row,
228
- * or null if tmux failed. */
229
- export declare function openFocus(callerPane: string, opts?: {
230
- newWindow?: boolean;
45
+ /** Env for a `crtr attach` VIEWER pane (the focus split, the spawn viewer
46
+ * window, AND recycle's re-attach respawn): propagate CRTR_HOME so the viewer
47
+ * resolves the SAME canvas home and thus the right view.sock under a
48
+ * non-default override; otherwise an empty env (the pane inherits the tmux
49
+ * server env, like every other crtr chrome pane). One helper so the viewer-pane
50
+ * sites can't drift. */
51
+ export declare function viewerSplitEnv(): Record<string, string>;
52
+ /** Register a `crtr attach` VIEWER pane as the node's one viewer focus row.
53
+ * UNIQUE(node_id) one viewer per node: returns
54
+ * the existing row (no insert) when the pane or the node is already registered.
55
+ * `window` is accepted for the call contract (spawn passes it) but not stored
56
+ * the focuses table keys on pane + session only. The attach client self-tags the
57
+ * pane `@crtr_node` on connect, so `nodeInPane` resolves it either way. */
58
+ export declare function registerViewerFocus(nodeId: string, pane: string, session: string | null, window: string | null): FocusRow | null;
59
+ /** Open a `crtr attach` VIEWER for `nodeId` and register its viewer focus row —
60
+ * the shared opener used by `focus` (a split beside the caller) and by a --root
61
+ * spawn (a fresh window in the caller's current session). Wraps `ensureSession`
62
+ * + `openNodeWindow`/`splitWindow` + `registerViewerFocus`. The attach client
63
+ * connects to the node's `view.sock` and renders/drives the live broker engine
64
+ * — it NEVER launches the engine (the one-writer invariant; the caller ensures
65
+ * the broker is alive first). Returns the registered viewer row, or null if tmux
66
+ * refused.
67
+ * - default → a fresh WINDOW in `session` (the --root spawn-viewer path).
68
+ * - `besidePane` → a SPLIT beside that pane in its own session (the focus
69
+ * open-beside-the-caller path); `session` is then only a fallback. */
70
+ export declare function openViewerWindow(nodeId: string, session: string, opts?: {
71
+ name?: string;
72
+ cwd?: string;
73
+ besidePane?: string;
231
74
  }): FocusRow | null;
232
- /** Register the FOREGROUND root's pane as focus #1 at boot 2.6). The inline
233
- * root owns the user's viewport, so its own pane becomes a durable focuswith
234
- * `remain-on-exit` so a clean exit FREEZES the pane rather than detaching the
235
- * terminal (F1). A background `--root` does NOT call this (§6): it stays a plain
236
- * window until the user `node focus`es it. No-op when the pane or this node is
237
- * already a focus. The focus row IS the record no pointer to mirror. */
238
- export declare function registerRootFocus(nodeId: string, pane: string, session: string | null, window: string | null): FocusRow | null;
239
- /** retargetFocus — the unified hot-swap (§2.5, Invariant P + Q5). Swap `incoming`
240
- * onto focus `focusId`'s viewport, keeping the screen position invariant (no new
241
- * window). One sqlite txn updates the focus row + BOTH nodes' presence:
242
- * - Q5: if `incoming` already occupies ANOTHER focus, VACATE it first (close
243
- * its row + kill its pane the node MOVES here, no auto-retarget).
244
- * - resolve `incoming`'s live pin pane (a backstage pane), else `revive` it
245
- * into the backstage and read back its pane.
246
- * - `swapPaneInPlace(pin, focusPane)`: incoming the viewport slot; the
247
- * outgoing occupant incoming's old (backstage) slot, %ids preserved
248
- * (cross-session swap confirmed by the spike).
249
- * - outgoing resident OR still mid-turn backstage (kept warm / F2); a parked
250
- * terminal viewer → RELEASE (status → idle, pane reaped); a holder or
251
- * done/dormant occupant reap its now-backstage pane (Invariant P).
252
- * Arms remain-on-exit on the viewport (F3); the focus row is the record. */
253
- export declare function retargetFocus(focusId: string, incoming: string, revive: Reviver): FocusResult;
254
- /** The front door for `node focus` / `node cycle` (§2.3): resolve which focus the
255
- * caller's pane acts on, then retarget `nodeId` onto it.
256
- * - `newPane` → `openFocus` a fresh viewport beside the caller (F4), then
257
- * retarget into it.
258
- * - else → retarget the caller pane's focus IN PLACE (`focusByPane`); if the
259
- * caller's pane is not yet a viewport, adopt it as one (occupied by whatever
260
- * node sits there now — `callerNode`, else resolved by pane).
261
- * - no caller pane (not in tmux) → best-effort: reconcile + report status,
262
- * not-in-place (no viewport to swap into). */
75
+ /** Bring a node's ONE viewer pane forefrontA.4). Every node is a detached
76
+ * broker, so there is no engine pane to swap into the caller's viewportfocus
77
+ * instead ensures the node's single `crtr attach` viewer is on screen beside (or
78
+ * navigated to) the caller:
79
+ * (a) ensure the broker ENGINE is alive (idempotent injected reviver) and its
80
+ * view.sock is ACCEPTING (closes the cold-start race against attach's
81
+ * single connect) without this the freshly-opened viewer would exit
82
+ * "no broker".
83
+ * (b) `--new-pane` always a fresh viewer beside the caller (e.g. two
84
+ * different nodes side by side); drop any prior viewer first (UNIQUE).
85
+ * (c) the node already has a live viewer:
86
+ * - in the caller's session navigate to it (switchClient+selectWindow),
87
+ * no new pane (inPlace).
88
+ * - elsewhere (e.g. a --root's viewer in another session) ⇒ MOVE it: close the old
89
+ * viewer pane (the broker runs on; the fresh viewer replays full
90
+ * scrollback from the broker `welcome` snapshot), drop its row, reopen
91
+ * beside the caller.
92
+ * (d) no live viewer open one beside the caller and register the row.
93
+ *
94
+ * crtr is tmux-only: with no caller pane there is no viewport to open and no
95
+ * non-tmux fallback report not-focused. */
263
96
  export declare function focus(nodeId: string, opts: {
264
97
  pane?: string;
265
98
  newPane?: boolean;
266
- callerNode?: string;
267
99
  revive: Reviver;
268
100
  }): FocusResult;
269
- /** Synchronously wait until a broker's view.sock accepts a connection. `focus()`
270
- * is sync today (the command layer calls it directly), so the readiness probe
271
- * lives in a short child Node process that can use async net events while this
272
- * process blocks in `spawnSync`. Success proves more than file existence: it is
273
- * robust to a stale leftover socket that the launching broker has not unlinked
274
- * yet, because only an accepting listener exits 0. */
275
- export declare function waitForBrokerViewSocket(nodeId: string): boolean;
276
- /** Env for a `crtr attach` VIEWER pane (the focusBroker split AND recycle's
277
- * re-attach respawn): propagate CRTR_HOME so the viewer resolves the SAME canvas
278
- * home — and thus the right view.sock — under a non-default override; otherwise
279
- * an empty env (the pane inherits the tmux server env, like every other crtr
280
- * chrome pane). One helper so the two viewer-pane sites can't drift. */
281
- export declare function viewerSplitEnv(): Record<string, string>;
282
- /** Tear a node off its placement (close/reset teardown, §2.3, flow (e)).
283
- * Reconcile first (follow a manual move / backfill a legacy pane), close the
284
- * focus row it occupies (if any), kill its pane (pane-keyed via the durable
285
- * `%id` — the window collapses once its last pane goes), and null its LOCATION.
286
- * The focus row close is the record. Best-effort tmux; the
287
- * DB writes always land. The pane kill is the sole teardown unit (Q1/§6: a
288
- * split-pane focus returns its space to the surviving split; a standalone-window
289
- * focus closes the window). */
101
+ /** Tear a node off its viewer (close/reset/cancel teardown). The broker engine
102
+ * is killed by its own host teardown over view.sock this only closes the
103
+ * on-screen viewer: kill its pane (the window collapses once its last pane goes)
104
+ * and drop its registry row. Best-effort tmux; the DB write always lands. No-op
105
+ * when the node has no viewer. */
290
106
  export declare function tearDownNode(nodeId: string): void;
291
- /** Demote's in-pane relaunch (§2.3, flow (e)): respawn `nodeId`'s launch into an
292
- * EXISTING `pane`, keeping the durable `%id` (respawn-pane -k), and record its
293
- * presence keyed on that pane. The session/window are DERIVED from the pane
294
- * itself (paneLocation), so the recycled node's LOCATION follows the pane it was
295
- * recycled into. `launch.env` is passed through verbatim the caller (demote)
296
- * already sets CRTR_ROOT_SESSION (children backstage) + FRONT_DOOR. Detached
297
- * respawn, since the pane is often the caller's own. Returns whether the respawn
298
- * dispatched. */
299
- export declare function recycleFocusPane(nodeId: string, pane: string, launch: ReviveLaunch): boolean;
300
- /** §1.6 lifecycle successor — hand a truly-done focused node's viewport to its
301
- * manager. Repoints the focus row `focusId` to `managerId` (a DB swap of the
302
- * occupant). Two takeover realizations, split on the manager's liveness:
303
- * - DORMANT manager (dead pi) — ONLY when it is idle + idle-release: the row
304
- * repoint is all this does; the manager, woken by the finished node's `push
305
- * final` landing in its inbox, is revived by the external daemon INTO this
306
- * node's now-frozen focus pane (remain-on-exit), where reviveIntoPlacement's
307
- * focus-pane branch resumes it in place — no new window, no taint. A dormant
308
- * manager that is NOT idle-release (done/dead/canceled, or idle with another
309
- * intent) is one the daemon will NEVER revive, so this returns false WITHOUT
310
- * repointing — the caller then disarms the freeze (see below).
311
- * - RUNNING backstage pane (a live pi — the normal multi-child state — OR a
312
- * pi still BOOTING because the daemon already revived the manager off the
313
- * `push final` before this handoff ran): the daemon never (re)revives it,
314
- * so we must bring it into the viewport SYNCHRONOUSLY here — swap its
315
- * backstage pane into the focus slot (MAJOR 1). Otherwise the manager runs
316
- * off-screen forever while %m sits orphaned in the viewport and the focus
317
- * row lies about LOCATION.
318
- * Returns false — the caller closes the focus (Q1: closeFocusToShell disarms
319
- * remain-on-exit so the pane REAPS on exit) — whenever NO successor will claim
320
- * the frozen pane: no manager, the manager IS this node, the manager already
321
- * occupies another viewport (UNIQUE node_id: do NOT move it, §1.6 edge), the
322
- * manager row is missing, or it is a dormant manager the daemon won't revive
323
- * (not idle-release) / a live-but-paneless inline root. It does NOT repoint the
324
- * occupant on any false path — repointing while returning false would strand the
325
- * pane frozen forever with no reaper (the dead-focus-pane bug).
326
- *
327
- * Why the live swap is NOT the forbidden self-saw: `swap-pane -d` only EXCHANGES
328
- * two panes' slot positions; it never respawns or kills the finishing node's own
329
- * pi. The forbidden move is a synchronous `respawn-pane -k %m` from inside %m —
330
- * we never do that here. After the swap, %m (the dying node's pane) sits in the
331
- * manager's old backstage slot; the caller nulls this node's presence so nothing
332
- * tracks the corpse. */
333
- export declare function handFocusToManager(focusId: string, managerId: string | null): boolean;
334
- /** Q1 close-to-shell for a truly-done focused node with no successor (§1.6 /
335
- * flow (b)): close its focus row and DISARM the pane's
336
- * freeze (`remain-on-exit` off) so it reaps when the finishing pi exits. The
337
- * stophook calls this instead of `tearDownNode` because it runs INSIDE the pane
338
- * it is closing: it cannot `closePane` its own pane (self-saw), but it is still
339
- * alive to disarm the freeze, so the pane closes on exit (return-to-shell)
340
- * rather than freezing into an orphan. (Keeps the stophook off the tmux driver,
341
- * §2.1.) */
342
- export declare function closeFocusToShell(focusId: string, nodeId: string): void;
343
- export interface SpreadResult {
344
- window: string | null;
345
- session: string | null;
346
- /** Child node ids whose panes were joined into the target window. */
347
- joined: string[];
348
- focused: boolean;
349
- }
350
- /** Join each of `childIds`' live panes into `targetId`'s window, lay them out
351
- * (target wide on the left, children stacked right), and focus it. Reconcile
352
- * drives both the target resolution and the per-join fix-up (a joined pane keeps
353
- * its `%id` but changes window, so its LOCATION must FOLLOW — else the daemon
354
- * reads it dormant). Caller revives dormant nodes first so they have live panes.
355
- * No-op result when the target has no live pane. */
356
- export declare function spreadNode(targetId: string, childIds: string[], opts?: {
357
- mainPaneWidth?: string;
358
- }): SpreadResult;
107
+ /** Detach a node from the foreground (the `node lifecycle --detach` / Alt+C → D
108
+ * half). Every node is a headless broker — the only thing on screen is its
109
+ * `crtr attach` VIEWER pane so "detach" means STOP FOREGROUNDING it: close the
110
+ * viewer pane (and drop its row) and leave the broker engine running untouched,
111
+ * reconnectable by a later `focus`. `pane` is the authoritative pane the caller
112
+ * acts on (the Alt+C menu's `#{pane_id}`); falls back to the node's registered
113
+ * viewer pane. No-op (false) when there is no live viewer pane to close. */
114
+ export declare function detachToBackground(nodeId: string, pane?: string): boolean;