@crouton-kit/crouter 0.3.27 → 0.3.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/dist/build-root.js +4 -2
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
  5. package/dist/builtin-memory/crouter-development/personas.md +1 -1
  6. package/dist/builtin-memory/crouter-development/plugins.md +20 -18
  7. package/dist/builtin-memory/design.md +1 -1
  8. package/dist/builtin-memory/development.md +1 -1
  9. package/dist/builtin-memory/internal/INDEX.md +1 -1
  10. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
  12. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  13. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  14. package/dist/builtin-memory/planning.md +1 -1
  15. package/dist/builtin-memory/spec.md +6 -4
  16. package/dist/builtin-personas/orchestration-kernel.md +4 -5
  17. package/dist/builtin-personas/runtime-base.md +8 -4
  18. package/dist/builtin-personas/spec/PERSONA.md +8 -4
  19. package/dist/builtin-personas/spec/orchestrator.md +5 -3
  20. package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
  21. package/dist/builtin-views/canvas/core.mjs +586 -0
  22. package/dist/builtin-views/canvas/text.mjs +58 -0
  23. package/dist/builtin-views/canvas/tui.mjs +165 -0
  24. package/dist/builtin-views/canvas/web.jsx +120 -0
  25. package/dist/builtin-views/git-pr/core.mjs +673 -0
  26. package/dist/builtin-views/git-pr/text.mjs +84 -0
  27. package/dist/builtin-views/git-pr/tui.mjs +302 -0
  28. package/dist/builtin-views/git-pr/web.jsx +216 -0
  29. package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
  30. package/dist/builtin-views/inbox/core.mjs +1273 -0
  31. package/dist/builtin-views/inbox/text.mjs +73 -0
  32. package/dist/builtin-views/inbox/tui.mjs +312 -0
  33. package/dist/builtin-views/inbox/web.jsx +188 -0
  34. package/dist/builtin-views/linkedin/core.mjs +906 -0
  35. package/dist/builtin-views/linkedin/text.mjs +69 -0
  36. package/dist/builtin-views/linkedin/tui.mjs +427 -0
  37. package/dist/builtin-views/linkedin/web.jsx +206 -0
  38. package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
  39. package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
  40. package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
  41. package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
  42. package/dist/cli.js +3 -3
  43. package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
  44. package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
  45. package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
  46. package/dist/clients/attach/__tests__/git-info.test.js +31 -0
  47. package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
  48. package/dist/clients/attach/__tests__/reconnect-giveup.test.js +30 -0
  49. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
  50. package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
  51. package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
  52. package/dist/clients/attach/attach-cmd.js +445 -41
  53. package/dist/clients/attach/auth-pickers.d.ts +9 -0
  54. package/dist/clients/attach/auth-pickers.js +194 -0
  55. package/dist/clients/attach/canvas-panels.d.ts +12 -0
  56. package/dist/clients/attach/canvas-panels.js +78 -0
  57. package/dist/clients/attach/chat-view.d.ts +27 -2
  58. package/dist/clients/attach/chat-view.js +98 -9
  59. package/dist/clients/attach/clipboard-image.d.ts +47 -8
  60. package/dist/clients/attach/clipboard-image.js +150 -26
  61. package/dist/clients/attach/clipboard-text.d.ts +3 -0
  62. package/dist/clients/attach/clipboard-text.js +39 -0
  63. package/dist/clients/attach/config-load.d.ts +54 -7
  64. package/dist/clients/attach/config-load.js +99 -3
  65. package/dist/clients/attach/context-message.d.ts +12 -0
  66. package/dist/clients/attach/context-message.js +72 -0
  67. package/dist/clients/attach/git-info.d.ts +20 -0
  68. package/dist/clients/attach/git-info.js +44 -0
  69. package/dist/clients/attach/graph-overlay.d.ts +43 -0
  70. package/dist/clients/attach/graph-overlay.js +306 -0
  71. package/dist/clients/attach/input-controller.d.ts +81 -13
  72. package/dist/clients/attach/input-controller.js +307 -67
  73. package/dist/clients/attach/pickers.d.ts +58 -0
  74. package/dist/clients/attach/pickers.js +171 -0
  75. package/dist/clients/attach/slash-commands.d.ts +44 -1
  76. package/dist/clients/attach/slash-commands.js +250 -27
  77. package/dist/clients/attach/titled-editor.d.ts +33 -0
  78. package/dist/clients/attach/titled-editor.js +84 -0
  79. package/dist/clients/attach/view-socket.d.ts +37 -2
  80. package/dist/clients/attach/view-socket.js +131 -7
  81. package/dist/clients/web/dev-server.d.ts +7 -0
  82. package/dist/clients/web/dev-server.js +59 -0
  83. package/dist/clients/web/events.d.ts +14 -0
  84. package/dist/clients/web/events.js +151 -0
  85. package/dist/clients/web/server.d.ts +18 -0
  86. package/dist/clients/web/server.js +450 -0
  87. package/dist/clients/web/web-cmd.d.ts +2 -0
  88. package/dist/clients/web/web-cmd.js +120 -0
  89. package/dist/commands/canvas.js +1 -2
  90. package/dist/commands/chord.js +1 -1
  91. package/dist/commands/dashboard.js +5 -1
  92. package/dist/commands/human/prompts.js +3 -3
  93. package/dist/commands/human/queue.d.ts +17 -0
  94. package/dist/commands/human/queue.js +111 -4
  95. package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
  96. package/dist/commands/memory/lint.d.ts +5 -4
  97. package/dist/commands/memory/lint.js +23 -7
  98. package/dist/commands/memory/read.js +2 -2
  99. package/dist/commands/memory/shared.d.ts +7 -1
  100. package/dist/commands/memory/shared.js +18 -3
  101. package/dist/commands/memory/write.js +20 -7
  102. package/dist/commands/memory.js +4 -4
  103. package/dist/commands/node.d.ts +1 -1
  104. package/dist/commands/node.js +64 -65
  105. package/dist/commands/pkg.js +1 -2
  106. package/dist/commands/revive.js +11 -3
  107. package/dist/commands/sys/feedback.d.ts +1 -0
  108. package/dist/commands/sys/feedback.js +163 -0
  109. package/dist/commands/sys/sync.d.ts +1 -0
  110. package/dist/commands/sys/sync.js +187 -0
  111. package/dist/commands/sys.js +4 -2
  112. package/dist/commands/view-cycle.js +2 -2
  113. package/dist/commands/view-list.js +8 -8
  114. package/dist/commands/view-new.js +21 -17
  115. package/dist/commands/view-pick.js +1 -1
  116. package/dist/commands/view-run.js +35 -14
  117. package/dist/commands/view.js +8 -6
  118. package/dist/commands/workspace.d.ts +2 -0
  119. package/dist/commands/workspace.js +161 -0
  120. package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
  121. package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
  122. package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
  123. package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
  124. package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
  125. package/dist/core/__tests__/broker-snapshot-history.test.d.ts +1 -0
  126. package/dist/core/__tests__/broker-snapshot-history.test.js +105 -0
  127. package/dist/core/__tests__/child-death-wake.test.js +56 -111
  128. package/dist/core/__tests__/close.test.js +14 -20
  129. package/dist/core/__tests__/context-intro.test.js +19 -19
  130. package/dist/core/__tests__/daemon-boot.test.js +30 -23
  131. package/dist/core/__tests__/draw-style.test.js +1 -1
  132. package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
  133. package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
  134. package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -1
  135. package/dist/core/__tests__/fixtures/fake-engine.js +32 -2
  136. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  137. package/dist/core/__tests__/focuses.test.js +25 -13
  138. package/dist/core/__tests__/fork.test.js +22 -5
  139. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
  140. package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
  141. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
  142. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
  143. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
  144. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
  145. package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
  146. package/dist/core/__tests__/full/cascade-close.test.js +22 -8
  147. package/dist/core/__tests__/full/detach-focus.test.js +66 -169
  148. package/dist/core/__tests__/helpers/harness.d.ts +6 -9
  149. package/dist/core/__tests__/helpers/harness.js +19 -36
  150. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
  151. package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
  152. package/dist/core/__tests__/human-stranded-deliver.test.d.ts +1 -0
  153. package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
  154. package/dist/core/__tests__/lifecycle.test.js +1 -19
  155. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
  156. package/dist/core/__tests__/memory-resolver.test.js +1 -1
  157. package/dist/core/__tests__/on-read-dedup-resume.test.d.ts +1 -0
  158. package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
  159. package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
  160. package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
  161. package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
  162. package/dist/core/__tests__/relaunch-root.test.js +114 -0
  163. package/dist/core/__tests__/reset.test.js +19 -79
  164. package/dist/core/__tests__/steer-note.test.js +9 -7
  165. package/dist/core/__tests__/tmux-surface.test.js +13 -7
  166. package/dist/core/bootstrap.d.ts +0 -3
  167. package/dist/core/bootstrap.js +1 -143
  168. package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
  169. package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
  170. package/dist/core/canvas/browse/app.js +131 -8
  171. package/dist/core/canvas/browse/model.d.ts +23 -5
  172. package/dist/core/canvas/browse/model.js +68 -13
  173. package/dist/core/canvas/browse/render.d.ts +7 -1
  174. package/dist/core/canvas/browse/render.js +178 -47
  175. package/dist/core/canvas/canvas.js +1 -1
  176. package/dist/core/canvas/focuses.d.ts +4 -4
  177. package/dist/core/canvas/focuses.js +7 -7
  178. package/dist/core/canvas/nav-model.d.ts +162 -0
  179. package/dist/core/canvas/nav-model.js +487 -0
  180. package/dist/core/canvas/paths.d.ts +7 -0
  181. package/dist/core/canvas/paths.js +9 -0
  182. package/dist/core/canvas/render.d.ts +68 -9
  183. package/dist/core/canvas/render.js +195 -61
  184. package/dist/core/canvas/types.d.ts +16 -23
  185. package/dist/core/config.js +1 -4
  186. package/dist/core/memory-resolver.js +43 -5
  187. package/dist/core/runtime/bearings.d.ts +1 -1
  188. package/dist/core/runtime/bearings.js +8 -8
  189. package/dist/core/runtime/branded-host.d.ts +16 -0
  190. package/dist/core/runtime/branded-host.js +127 -0
  191. package/dist/core/runtime/broker-protocol.d.ts +212 -6
  192. package/dist/core/runtime/broker-sdk.d.ts +0 -12
  193. package/dist/core/runtime/broker-sdk.js +77 -6
  194. package/dist/core/runtime/broker.d.ts +5 -1
  195. package/dist/core/runtime/broker.js +520 -59
  196. package/dist/core/runtime/close.d.ts +12 -2
  197. package/dist/core/runtime/close.js +37 -16
  198. package/dist/core/runtime/front-door.js +14 -7
  199. package/dist/core/runtime/host.d.ts +14 -34
  200. package/dist/core/runtime/host.js +14 -50
  201. package/dist/core/runtime/launch.d.ts +12 -9
  202. package/dist/core/runtime/launch.js +27 -15
  203. package/dist/core/runtime/lifecycle.d.ts +1 -1
  204. package/dist/core/runtime/lifecycle.js +15 -19
  205. package/dist/core/runtime/nodes.d.ts +0 -55
  206. package/dist/core/runtime/nodes.js +6 -74
  207. package/dist/core/runtime/placement.d.ts +91 -334
  208. package/dist/core/runtime/placement.js +262 -828
  209. package/dist/core/runtime/promote.d.ts +2 -0
  210. package/dist/core/runtime/promote.js +24 -6
  211. package/dist/core/runtime/recap.d.ts +8 -0
  212. package/dist/core/runtime/recap.js +107 -0
  213. package/dist/core/runtime/recycle.js +25 -61
  214. package/dist/core/runtime/reset.d.ts +43 -37
  215. package/dist/core/runtime/reset.js +131 -218
  216. package/dist/core/runtime/revive.d.ts +9 -29
  217. package/dist/core/runtime/revive.js +45 -135
  218. package/dist/core/runtime/spawn.d.ts +15 -11
  219. package/dist/core/runtime/spawn.js +168 -136
  220. package/dist/core/runtime/surface-bg.d.ts +11 -0
  221. package/dist/core/runtime/surface-bg.js +68 -0
  222. package/dist/core/runtime/tmux.d.ts +15 -43
  223. package/dist/core/runtime/tmux.js +38 -80
  224. package/dist/core/scope.d.ts +1 -2
  225. package/dist/core/scope.js +1 -5
  226. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
  227. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
  228. package/dist/core/skill-sync/builtins.d.ts +42 -0
  229. package/dist/core/skill-sync/builtins.js +112 -0
  230. package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
  231. package/dist/core/skill-sync/claude-plugins.js +71 -0
  232. package/dist/core/skill-sync/engine.d.ts +42 -0
  233. package/dist/core/skill-sync/engine.js +633 -0
  234. package/dist/core/skill-sync/export.d.ts +23 -0
  235. package/dist/core/skill-sync/export.js +86 -0
  236. package/dist/core/skill-sync/manifest.d.ts +64 -0
  237. package/dist/core/skill-sync/manifest.js +181 -0
  238. package/dist/core/skill-sync/profile.d.ts +76 -0
  239. package/dist/core/skill-sync/profile.js +173 -0
  240. package/dist/core/skill-sync/snapshot.d.ts +57 -0
  241. package/dist/core/skill-sync/snapshot.js +120 -0
  242. package/dist/core/spawn.d.ts +14 -0
  243. package/dist/core/spawn.js +29 -9
  244. package/dist/core/substrate/index.d.ts +2 -2
  245. package/dist/core/substrate/index.js +7 -7
  246. package/dist/core/substrate/injected-store.d.ts +10 -0
  247. package/dist/core/substrate/injected-store.js +55 -0
  248. package/dist/core/substrate/on-read.js +68 -6
  249. package/dist/core/substrate/render.d.ts +8 -11
  250. package/dist/core/substrate/render.js +29 -43
  251. package/dist/core/substrate/schema.d.ts +16 -11
  252. package/dist/core/substrate/schema.js +32 -31
  253. package/dist/core/tui/host.d.ts +12 -4
  254. package/dist/core/tui/host.js +280 -149
  255. package/dist/core/view/bridge.d.ts +10 -0
  256. package/dist/core/view/bridge.js +31 -0
  257. package/dist/core/view/chrome.d.ts +9 -0
  258. package/dist/core/view/chrome.js +22 -0
  259. package/dist/core/view/contract.d.ts +171 -0
  260. package/dist/core/view/contract.js +23 -0
  261. package/dist/core/view/loader.d.ts +31 -0
  262. package/dist/core/view/loader.js +188 -0
  263. package/dist/core/view/transport-local.d.ts +7 -0
  264. package/dist/core/view/transport-local.js +70 -0
  265. package/dist/core/view/transport.d.ts +4 -0
  266. package/dist/core/view/transport.js +15 -0
  267. package/dist/daemon/crtrd.d.ts +38 -27
  268. package/dist/daemon/crtrd.js +303 -376
  269. package/dist/daemon/manage.js +6 -1
  270. package/dist/index.js +1 -1
  271. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
  272. package/dist/pi-extensions/canvas-context-intro.js +4 -4
  273. package/dist/pi-extensions/canvas-doc-substrate.js +29 -20
  274. package/dist/pi-extensions/canvas-nav.js +35 -385
  275. package/dist/pi-extensions/canvas-recap.d.ts +37 -0
  276. package/dist/pi-extensions/canvas-recap.js +236 -0
  277. package/dist/pi-extensions/canvas-resume.js +2 -1
  278. package/dist/pi-extensions/canvas-stophook.d.ts +15 -13
  279. package/dist/pi-extensions/canvas-stophook.js +119 -103
  280. package/dist/pi-extensions/canvas-view.js +2 -1
  281. package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
  282. package/dist/pi-extensions/widget-order-bus.js +34 -0
  283. package/dist/prompts/view.d.ts +2 -2
  284. package/dist/prompts/view.js +145 -73
  285. package/dist/types.d.ts +1 -6
  286. package/dist/types.js +1 -3
  287. package/dist/web/ViewChrome.d.ts +7 -0
  288. package/dist/web/ViewChrome.js +28 -0
  289. package/dist/web/ViewPane.d.ts +39 -0
  290. package/dist/web/ViewPane.js +48 -0
  291. package/dist/web/index.d.ts +6 -0
  292. package/dist/web/index.js +16 -0
  293. package/dist/web/runtime.d.ts +39 -0
  294. package/dist/web/runtime.js +133 -0
  295. package/dist/web/states.d.ts +24 -0
  296. package/dist/web/states.js +24 -0
  297. package/dist/web/transport-http.d.ts +5 -0
  298. package/dist/web/transport-http.js +28 -0
  299. package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
  300. package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
  301. package/dist/web-client/index.html +13 -0
  302. package/package.json +20 -6
  303. package/dist/builtin-views/canvas/client.mjs +0 -303
  304. package/dist/builtin-views/canvas/view.mjs +0 -576
  305. package/dist/builtin-views/git-pr/client.mjs +0 -440
  306. package/dist/builtin-views/git-pr/view.mjs +0 -675
  307. package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
  308. package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
  309. package/dist/builtin-views/inbox/view.mjs +0 -889
  310. package/dist/builtin-views/linkedin/client.mjs +0 -610
  311. package/dist/builtin-views/linkedin/view.mjs +0 -1171
  312. package/dist/commands/pkg/bridge.d.ts +0 -1
  313. package/dist/commands/pkg/bridge.js +0 -137
  314. package/dist/commands/skill/author.d.ts +0 -3
  315. package/dist/commands/skill/author.js +0 -140
  316. package/dist/commands/skill/shared.d.ts +0 -3
  317. package/dist/commands/skill/shared.js +0 -19
  318. package/dist/commands/skill.d.ts +0 -2
  319. package/dist/commands/skill.js +0 -21
  320. package/dist/commands/tmux-spread.d.ts +0 -2
  321. package/dist/commands/tmux-spread.js +0 -144
  322. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
  323. package/dist/core/__tests__/full/placement-focus.test.js +0 -309
  324. package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
  325. package/dist/core/__tests__/full/placement-revive.test.js +0 -238
  326. package/dist/core/__tests__/full/placement-teardown.test.js +0 -221
  327. package/dist/core/__tests__/home-session.test.js +0 -186
  328. package/dist/core/__tests__/relaunch.test.js +0 -335
  329. package/dist/core/bridge-map.d.ts +0 -19
  330. package/dist/core/bridge-map.js +0 -73
  331. package/dist/core/tui/contract.d.ts +0 -83
  332. package/dist/core/tui/contract.js +0 -8
  333. package/dist/core/tui/loader.d.ts +0 -16
  334. package/dist/core/tui/loader.js +0 -94
  335. package/dist/prompts/skill.d.ts +0 -2
  336. package/dist/prompts/skill.js +0 -650
  337. /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
  338. /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
  339. /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
  340. /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
  341. /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
  342. /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/reconnect-giveup.test.d.ts} +0 -0
  343. /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
@@ -2,28 +2,40 @@
2
2
  // a running pi process on the canvas. Composes canvas (birth + spine), persona
3
3
  // (resolve), launch (pi argv), and tmux (placement).
4
4
  //
5
- // bootRoot — the user-opened front door (bare `crtr`). Resident; runs pi
6
- // inline, taking over the current terminal.
5
+ // bootRoot — the user-opened front door (bare `crtr`). Resident; launches the
6
+ // root's broker engine, then execs `crtr attach` inline so THIS
7
+ // terminal becomes the broker's controller-viewer.
7
8
  // spawnChild — a node spawned by a live node (`crtr node new`). A managed,
8
9
  // terminal background worker by default; with `root`, an
9
10
  // INDEPENDENT resident root (no subscription back to the spawner,
10
11
  // provenance via spawned_by) brought forefront for direct driving.
11
12
  import { spawnSync } from 'node:child_process';
12
- import { FRONT_DOOR_ENV } from './front-door.js';
13
- import { spawnNode, currentNodeContext, resolveBirthSession, nodeSession, rootOfSpine } from './nodes.js';
13
+ import { readdirSync, existsSync } from 'node:fs';
14
+ import { isAbsolute, resolve, join } from 'node:path';
15
+ import { homedir } from 'node:os';
16
+ import { spawnNode, currentNodeContext, rootOfSpine } from './nodes.js';
14
17
  import { buildLaunchSpec, buildPiArgv } from './launch.js';
15
18
  import { writeGoal } from './kickoff.js';
16
19
  import { hasRoadmap, seedRoadmap } from './roadmap.js';
17
20
  import { buildIdentityAssertion, buildWakeBearings } from './bearings.js';
18
21
  import { installMenuBinding, installNavBindings, installViewNavBindings } from './tmux-chrome.js';
19
- import { setPresence, updateNode, getNode, fullName } from '../canvas/index.js';
20
- import { registerRootFocus, ensureSession, openNodeWindow, piCommand, currentTmux, inTmux, focusWindow, } from './placement.js';
22
+ import { getNode, fullName } from '../canvas/index.js';
23
+ import { registerViewerFocus, openViewerWindow, waitForBrokerViewSocket, viewerSplitEnv, windowOfPane, currentTmux, inTmux, focusWindow, } from './placement.js';
21
24
  import { transition } from './lifecycle.js';
22
25
  import { headlessBrokerHost } from './host.js';
23
26
  import { ensureDaemon } from '../../daemon/manage.js';
24
- /** Create a root node and bring up its pi. Returns the node; for 'inline' this
25
- * only returns after pi exits (it took over the terminal). */
27
+ /** Create the front-door root: launch its broker engine, then exec `crtr attach`
28
+ * inline so THIS terminal becomes the broker's controller-viewer. Does not
29
+ * return — it process.exit()s when the inline attach exits (detach leaves the
30
+ * resident broker running, to be reconnected later). */
26
31
  export function bootRoot(opts) {
32
+ // crtr is tmux-only: the front door execs `crtr attach` inline in THIS pane to
33
+ // become the root's controller-viewer, so there must be a tmux pane to anchor
34
+ // on. Outside tmux there is no viewer surface — throw with a friendly message
35
+ // rather than boot a root nobody can see.
36
+ if (!inTmux()) {
37
+ throw new Error('crtr must be started from inside a tmux session — start tmux first (e.g. `tmux new -s work`), then run `crtr` there.');
38
+ }
27
39
  // The thin supervisor must be up before any node exists, so a refresh-yield
28
40
  // or crash can be reaped/revived. Idempotent.
29
41
  try {
@@ -51,75 +63,134 @@ export function bootRoot(opts) {
51
63
  // mandate (bare `crtr` has none — writeGoal no-ops on empty).
52
64
  if (opts.prompt !== undefined)
53
65
  writeGoal(meta.node_id, opts.prompt);
54
- // Every node window — root or child — lives in the one shared session.
55
- const session = nodeSession();
56
- ensureSession(session, opts.cwd);
57
66
  // Make the Alt+C action menu + Alt+] / Alt+[ node-nav keys + Alt+V then ]/[
58
- // view-nav chord live on this server (idempotent, in-tmux only).
59
- if (inTmux()) {
60
- try {
61
- installMenuBinding();
62
- }
63
- catch { /* best-effort */ }
64
- try {
65
- installNavBindings();
66
- }
67
- catch { /* best-effort */ }
68
- try {
69
- installViewNavBindings();
70
- }
71
- catch { /* best-effort */ }
67
+ // view-nav chord live on this server (idempotent; guaranteed in tmux above).
68
+ try {
69
+ installMenuBinding();
70
+ }
71
+ catch { /* best-effort */ }
72
+ try {
73
+ installNavBindings();
72
74
  }
73
- // inline: the root's pi takes over THIS terminal, so its own window stays
74
- // where the user is (its tmux_session tracks that real pane so supervision
75
- // sees it alive). But its children spawn into the shared global session via
76
- // CRTR_ROOT_SESSION — they never clutter the user's working session.
75
+ catch { /* best-effort */ }
76
+ try {
77
+ installViewNavBindings();
78
+ }
79
+ catch { /* best-effort */ }
80
+ // This terminal becomes the root's attach VIEWER in the user's CURRENT session,
81
+ // so its window stays where the user already is. The node row carries NO
82
+ // presence — a broker node's pane/window/tmux_session stay NULL; the viewer
83
+ // pane lives in the focuses table (below).
77
84
  const here = currentTmux();
78
- const adopted = resolveBirthSession({ adoptCaller: true, here, rootSession: undefined });
79
- setPresence(meta.node_id, { tmux_session: adopted, window: here?.window ?? null, pane: here?.pane ?? null });
80
- // REVIVE-HOME: the inline root's durable revive target is the session it
81
- // adopts (the caller's when inside tmux, else the shared backstage). Set once
82
- // at birth, alongside the live LOCATION above.
83
- updateNode(meta.node_id, { home_session: adopted });
84
- // Root boot registers focus #1 (§2.6): the FOREGROUND inline root owns the
85
- // user's viewport, so its OWN pane becomes a durable focus (remain-on-exit so
86
- // a clean exit freezes rather than detaching the terminal). A background
87
- // `--root` (spawnChild) does NOT — it stays a plain window until the user
88
- // focuses it (§6). Only possible inside tmux (a pane to anchor on).
89
- if (here) {
85
+ // The FOREGROUND front-door terminal is the root's one viewer: record its pane
86
+ // as the node's viewer focus row so focus/nav can find it. The `crtr attach`
87
+ // exec below makes the terminal a controller-viewer of the broker; detaching
88
+ // leaves the resident broker running.
89
+ if (here !== null) {
90
90
  try {
91
- registerRootFocus(meta.node_id, here.pane, adopted, here.window);
91
+ registerViewerFocus(meta.node_id, here.pane, here.session, here.window);
92
92
  }
93
93
  catch { /* best-effort */ }
94
94
  }
95
- const withSession = getNode(meta.node_id);
96
- const inv = buildPiArgv(withSession, { prompt: opts.prompt });
97
- const env = { ...process.env, ...inv.env, CRTR_ROOT_SESSION: session, CRTR_SUBTREE: rootOfSpine(meta.node_id), [FRONT_DOOR_ENV]: '1' };
98
- const r = spawnSync('pi', inv.argv, { stdio: 'inherit', env });
95
+ const inv = buildPiArgv(meta, { prompt: opts.prompt });
96
+ // Broker is the only host: the root runs as a detached broker engine (so its
97
+ // first refresh/revive doesn't strand this terminal the daemon revives the
98
+ // broker and the viewer reconnects), and THIS terminal execs `crtr attach`
99
+ // inline to become its controller-viewer. The host sets CRTR_FRONT_DOOR.
100
+ inv.env = { ...inv.env, CRTR_SUBTREE: rootOfSpine(meta.node_id) };
101
+ const placed = headlessBrokerHost.launch(meta.node_id, inv, {
102
+ cwd: opts.cwd,
103
+ name: fullName(meta),
104
+ resuming: false,
105
+ });
106
+ // No broker pid ⇒ the root has no engine. Crash it (so the daemon won't watch a
107
+ // zombie 'active') and throw — there is nothing to attach to.
108
+ if (placed.pid === null) {
109
+ transition(meta.node_id, 'crash');
110
+ throw new Error(`failed to launch the broker engine for the front-door root ${meta.node_id} — nothing to attach to.`);
111
+ }
112
+ // The terminal has nothing to attach to until view.sock accepts. The root is
113
+ // useless without its engine, so a socket timeout IS fatal here (unlike a
114
+ // background child's optional viewer, which is skipped on timeout).
115
+ if (!waitForBrokerViewSocket(meta.node_id)) {
116
+ throw new Error(`the front-door root broker ${meta.node_id} never bound its view socket — cannot attach.`);
117
+ }
118
+ const attachEnv = { ...process.env, ...viewerSplitEnv() };
119
+ const r = spawnSync('crtr', ['attach', 'to', meta.node_id], { stdio: 'inherit', env: attachEnv });
99
120
  process.exit(r.status ?? 0);
100
121
  }
101
- /** Resolve a `--fork-from` value to the source pi gets as `--fork <path|id>`.
102
- * A live node id resolves to its captured session FILE (absolute, cwd-immune),
103
- * falling back to its bare session id; a path or partial uuid passes straight
104
- * through to pi. Throws when a known node has no session to fork yet. */
122
+ /** pi's sessions root, VENDORED from pi `config.getSessionsDir()` (= `<agentDir>/
123
+ * sessions`). pi's package `exports` map is `.`-only, so config.js can't be
124
+ * deep-imported, and a ROOT import of `getAgentDir` would eager-load the entire
125
+ * heavy pi SDK index on crtr's front-door hot path (the reason broker-sdk.ts
126
+ * dynamic-imports the engine). Mirrors pi: `PI_CODING_AGENT_DIR` env, else
127
+ * `~/.pi/agent` (APP_NAME defaults to 'pi'). Re-sync on a pi SDK bump that moves
128
+ * the sessions dir — same vendoring rationale as pi-vendored.ts. */
129
+ function piSessionsRoot() {
130
+ const env = process.env['PI_CODING_AGENT_DIR'];
131
+ const agentDir = env !== undefined && env !== '' ? env : join(homedir(), '.pi', 'agent');
132
+ return join(agentDir, 'sessions');
133
+ }
134
+ /** Resolve a bare/partial pi session uuid to its ABSOLUTE `.jsonl` path by
135
+ * scanning pi's sessions store the way pi's own CLI does (exact id, else a
136
+ * unique prefix). The broker fork seam (`SessionManager.forkFrom`) loads a FILE
137
+ * and does NOT resolve a uuid, so a bare id must be resolved here or the fork
138
+ * throws at boot. Session files are `<sessions>/<project>/<ts>_<id>.jsonl`. */
139
+ function resolveSessionUuid(uuid) {
140
+ const sessionsRoot = piSessionsRoot();
141
+ if (!existsSync(sessionsRoot)) {
142
+ throw new Error(`--fork-from '${uuid}': no pi sessions store at ${sessionsRoot} to resolve it from — pass a node id or an absolute .jsonl path.`);
143
+ }
144
+ const matches = [];
145
+ for (const dir of readdirSync(sessionsRoot, { withFileTypes: true })) {
146
+ if (!dir.isDirectory())
147
+ continue;
148
+ const projDir = join(sessionsRoot, dir.name);
149
+ for (const f of readdirSync(projDir)) {
150
+ if (!f.endsWith('.jsonl'))
151
+ continue;
152
+ const stem = f.slice(0, -'.jsonl'.length);
153
+ const id = stem.slice(stem.lastIndexOf('_') + 1);
154
+ matches.push({ id, path: join(projDir, f) });
155
+ }
156
+ }
157
+ const exact = matches.filter((m) => m.id === uuid);
158
+ const hit = exact.length > 0 ? exact : matches.filter((m) => m.id.startsWith(uuid));
159
+ if (hit.length === 0)
160
+ throw new Error(`--fork-from '${uuid}': no pi session with that id found.`);
161
+ if (hit.length > 1) {
162
+ throw new Error(`--fork-from '${uuid}': ambiguous — ${hit.length} sessions match that id prefix; pass the full id or the .jsonl path.`);
163
+ }
164
+ return hit[0].path;
165
+ }
166
+ /** Resolve a `--fork-from` value to an ABSOLUTE `.jsonl` source path for the
167
+ * broker fork (`SessionManager.forkFrom`, which loads a file — never a bare id).
168
+ * A live node id resolves to its captured session FILE (absolute, cwd-immune); a
169
+ * relative path is made absolute; a bare/partial uuid is resolved against pi's
170
+ * sessions store. Throws when a known node has no captured session file, or when
171
+ * a uuid resolves to zero or multiple sessions. */
105
172
  export function resolveForkSource(value) {
106
173
  const v = value.trim();
107
174
  if (v === '')
108
175
  throw new Error('--fork-from requires a node id, session file, or session uuid.');
109
- // A path (contains `/` or ends `.jsonl`) is a session file — hand it to pi as-is.
110
- if (v.includes('/') || v.endsWith('.jsonl'))
111
- return v;
112
- // A live node id fork from the conversation it has accumulated.
176
+ // A path (contains `/` or ends `.jsonl`) is a session file — ensure it is
177
+ // ABSOLUTE so the broker, launched in a different cwd, still resolves it.
178
+ if (v.includes('/') || v.endsWith('.jsonl')) {
179
+ return isAbsolute(v) ? v : resolve(process.cwd(), v);
180
+ }
181
+ // A live node id — fork from the conversation it has accumulated. Its captured
182
+ // session FILE is absolute; the broker cannot fork a bare session id.
113
183
  const n = getNode(v);
114
184
  if (n !== null) {
115
- const src = n.pi_session_file ?? n.pi_session_id;
185
+ const src = n.pi_session_file;
116
186
  if (src === undefined || src === null || src === '') {
117
- throw new Error(`node ${v} has no pi session yet — it has not started a conversation to fork from.`);
187
+ throw new Error(`node ${v} has no pi session yet — it has not captured a session FILE to fork from (the broker forks a .jsonl path, not a bare id).`);
118
188
  }
119
189
  return src;
120
190
  }
121
- // Not a known node — treat as a bare/partial pi session id for pi to resolve.
122
- return v;
191
+ // Not a known node — a bare/partial pi session uuid; resolve it to an absolute
192
+ // .jsonl (the broker fork won't resolve a uuid).
193
+ return resolveSessionUuid(v);
123
194
  }
124
195
  /** Resolve who a spawn is attributed to. A managed child needs a spine parent
125
196
  * (explicit `--parent` or the calling node's CRTR_NODE_ID). A --root spawn
@@ -176,7 +247,6 @@ export function spawnChild(opts) {
176
247
  // the boot intro can detect this is a fork and re-assert the node's own
177
248
  // identity over the source's copied-in conversation.
178
249
  forkFrom: opts.forkFrom,
179
- hostKind: opts.hostKind,
180
250
  modelOverride: opts.model,
181
251
  launch,
182
252
  });
@@ -210,90 +280,52 @@ export function spawnChild(opts) {
210
280
  }
211
281
  // (The three scoped long-term memory stores are seeded for EVERY node at birth
212
282
  // in spawnNode — no orchestrator-gated seeding needed here.)
213
- // A managed CHILD lands in the shared global session: inherited from the
214
- // parent's CRTR_ROOT_SESSION, else the default node session. A --root spawned
215
- // from inside tmux instead opens its window in the CALLER'S CURRENT session,
216
- // so it appears where the spawner is working rather than exiled to a separate
217
- // crtr session. Either way the root's OWN descendants still flow to the shared
218
- // session (childSession) via CRTR_ROOT_SESSION, to keep the subtree from
219
- // cluttering the user's session.
220
- const rootSessionEnv = process.env['CRTR_ROOT_SESSION'];
283
+ // A --root spawned from inside tmux opens its one viewer in the CALLER'S
284
+ // CURRENT session (below), so it appears where the spawner is working; a
285
+ // managed child is a headless broker with NO viewer at all.
221
286
  const here = root ? currentTmux() : null;
222
- // The shared backstage the whole subtree flows into (this child's own
223
- // CRTR_ROOT_SESSION): the inherited root session, else the default `crtr`.
224
- const childSession = resolveBirthSession({ adoptCaller: false, here, rootSession: rootSessionEnv });
225
- // Where THIS node's window opens — and its durable REVIVE-HOME. A managed
226
- // child lands in the backstage; a --root adopts the caller's current session
227
- // when inside tmux, so it appears where the spawner is working.
228
- const session = resolveBirthSession({ adoptCaller: root, here, rootSession: rootSessionEnv });
229
- ensureSession(session, opts.cwd);
230
- // REVIVE-HOME set once at birth: a managed child's revive target is the
231
- // backstage, never a user session — this is what keeps a background revive
232
- // off the user's screen (the focus taint cannot reach it).
233
- updateNode(meta.node_id, { home_session: session });
234
287
  const inv = buildPiArgv(meta, { prompt: kickoff, forkFrom });
235
- // Belt-and-suspenders backstage routing emitted at EVERY launch site: the
236
- // authoritative CRTR_ROOT_SESSION (this subtree's shared backstage) +
237
- // CRTR_SUBTREE (this node's spine root). Hoisted ABOVE the host branch so the
238
- // broker launch and the tmux command consume ONE authoritative env the
239
- // broker host merges inv.env, so a --headless --root broker would otherwise
240
- // inherit the SPAWNER's subtree id via the lossy nodeEnv passthrough (review
241
- // reuse MINOR-2). FRONT_DOOR is added per-consumer below (the broker host sets
242
- // it itself; the tmux env const adds it).
243
- inv.env = { ...inv.env, CRTR_ROOT_SESSION: childSession, CRTR_SUBTREE: rootOfSpine(meta.node_id) };
244
- // Birth LAUNCH branch on the PERSISTED host_kind (set at birth by spawnNode).
245
- // A broker birth diverts AWAY from the tmux pane: the headless broker host
246
- // launches a detached broker process (which records its own pid as pi_pid via
247
- // the stophook) and returns placement fields all null — so we open NO tmux
248
- // window and write NO tmux placement (presence stays null for a broker). The
249
- // tmux path below is left completely UNCHANGED (byte-identical) for every
250
- // non-broker spawn. Mirrors reviveNode's `hostFor(meta).launch(...)` shape.
251
- if (meta.host_kind === 'broker') {
252
- const placed = headlessBrokerHost.launch(meta.node_id, inv, {
253
- cwd: meta.cwd,
254
- name: fullName(meta),
255
- resuming: false,
256
- });
257
- const saved = getNode(meta.node_id);
258
- return { node: saved, window: placed.window, session: placed.session };
259
- }
260
- const env = { ...inv.env, [FRONT_DOOR_ENV]: '1' };
261
- const command = piCommand(inv.argv);
262
- // openNodeWindow now returns {window, pane}; pane is unused until the
263
- // placement layer lands, so destructure the window and proceed unchanged.
264
- const opened = openNodeWindow({
265
- session,
288
+ // CRTR_SUBTREE (the spine root) rides the detached broker's env so it can group
289
+ // the subtree; the host sets CRTR_FRONT_DOOR itself.
290
+ inv.env = { ...inv.env, CRTR_SUBTREE: rootOfSpine(meta.node_id) };
291
+ // Broker is the only host: launch the detached broker ENGINE. A managed child
292
+ // gets NO viewer spawning it returns once the engine is launched and a human
293
+ // opens a viewer on demand via `crtr node focus` / `crtr attach`. A --root gets
294
+ // one foreground viewer below.
295
+ const placed = headlessBrokerHost.launch(meta.node_id, inv, {
296
+ cwd: meta.cwd,
266
297
  name: fullName(meta),
267
- cwd: opts.cwd,
268
- env,
269
- command,
298
+ resuming: false,
270
299
  });
271
- const window = opened?.window ?? null;
272
- // Two-stage failure model. Opening the window is instant and definitive, so a
273
- // failure here is reported SYNCHRONOUSLY: crash the node (so it isn't a zombie
274
- // 'active' the daemon can't reap — it has no window to watch) and throw so
275
- // `crtr node new` exits non-zero with a clear message for the caller. The node
276
- // is still 'active' from spawnNode, so transition('crash') is a legal from-LIVE
277
- // move — the last scattered node-status write, now through the lifecycle machine.
278
- //
279
- // pi BOOTING inside the window, by contrast, is inherently slow (and slower
280
- // under load), so we stay optimistic and return status='active' the instant
281
- // the window exists. A vehicle that then dies before its first session_start
282
- // is caught by the daemon — it surfaces the boot failure up the spine rather
283
- // than letting the node die silently (see crtrd.ts surfaceBootFailure).
284
- if (window === null) {
300
+ // Definitive failure: no broker pid ⇒ the node has no engine. Crash it (so the
301
+ // daemon doesn't watch a zombie 'active') and throw so `crtr node new` exits
302
+ // non-zero. transition('crash') is a legal from-LIVE move (still 'active' from
303
+ // spawnNode). Mirrors the old window===null crash.
304
+ if (placed.pid === null) {
285
305
  transition(meta.node_id, 'crash');
286
- throw new Error(`failed to open a tmux window for ${meta.node_id} (${meta.name}) in session '${session}' — the node was not started.`);
306
+ throw new Error(`failed to launch the broker engine for ${meta.node_id} (${meta.name}) — the node was not started.`);
287
307
  }
288
- setPresence(meta.node_id, { tmux_session: session, window });
289
- const saved = getNode(meta.node_id);
290
- // A root is spawned to be driven directly bring it forefront so whoever
291
- // asked for it picks up the conversation. A child stays a background window.
292
- if (root) {
293
- try {
294
- focusWindow(session, window);
308
+ // A --root is spawned to be DRIVEN directly: when the caller is inside tmux,
309
+ // open ONE foreground viewer in the caller's CURRENT session and bring it
310
+ // forefront so whoever asked for it picks up the conversation. Outside tmux a
311
+ // --root opens no viewer (an agent handing a root off) — never a throw. A
312
+ // managed child opens nothing. Non-fatal if tmux refuses (openViewerWindow
313
+ // returns null) or the socket never binds — `focus` opens a viewer later.
314
+ let window = null;
315
+ let session = null;
316
+ if (root && here !== null && waitForBrokerViewSocket(meta.node_id)) {
317
+ const viewer = openViewerWindow(meta.node_id, here.session, { name: fullName(meta), cwd: opts.cwd });
318
+ if (viewer !== null && viewer.pane !== null && viewer.pane !== '') {
319
+ session = here.session;
320
+ window = windowOfPane(viewer.pane);
321
+ if (window !== null) {
322
+ try {
323
+ focusWindow(here.session, window);
324
+ }
325
+ catch { /* best-effort */ }
326
+ }
295
327
  }
296
- catch { /* best-effort */ }
297
328
  }
329
+ const saved = getNode(meta.node_id);
298
330
  return { node: saved, window, session };
299
331
  }
@@ -0,0 +1,11 @@
1
+ /** The SGR escape that turns ON the distinct-surface background (theme
2
+ * `selectedBg`), e.g. `\x1b[48;2;58;58;74m`. Pair with `\x1b[49m` to reset just
3
+ * the background. Throws if no process has initialized the theme — callers on a
4
+ * render path (the attach overlay) always have, by construction. */
5
+ export declare function surfaceBgAnsi(): string;
6
+ /** tmux flags that frame a `display-menu` / `display-popup` on the distinct
7
+ * surface background — a rounded border + the surface bg on the body and border.
8
+ * Empty when the theme is unavailable in this process (chrome stays default; the
9
+ * attach viewer re-installs the menu with these once it has themed). One knob:
10
+ * every crtr tmux float shares this single definition. */
11
+ export declare function surfaceTmuxStyleArgs(): string[];
@@ -0,0 +1,68 @@
1
+ // surface-bg.ts — the "distinct popup surface" background, theme-derived.
2
+ //
3
+ // crtr's overlays and popups must read as a SEPARATE surface from the normal one
4
+ // (CTO ruling, taste/inline-ui-placement #11): a popup whose background matches
5
+ // the surface has invisible edges, so you can't tell where the float begins. We
6
+ // paint them with the active pi theme's `selectedBg` role — theme-derived, so it
7
+ // adapts to light/dark and is ONE centralized knob, not a scattered hardcode.
8
+ //
9
+ // pi 0.79 does not re-export `selectedBg` through its public API (the `.` entry
10
+ // omits `getResolvedThemeColors` and the live `theme` singleton). It DOES publish
11
+ // the live `Theme` instance on a process-global symbol once `initTheme()` has run
12
+ // — the same hook pi's own SDK reads (see broker.ts `ctx.ui.theme`). We read it
13
+ // there and use the public `Theme.bg()` / `Theme.getBgAnsi()` methods.
14
+ //
15
+ // CRITICAL: this module must NOT statically import `@earendil-works/pi-coding-agent`
16
+ // — that package costs ~400ms to load and would land on the cold `crtr` front
17
+ // door (which never otherwise loads pi). We only READ the process-global symbol;
18
+ // any process that needs styling (the attach viewer, a node's broker) has already
19
+ // called `initTheme()` and populated it. Where the symbol is absent (the front
20
+ // door, before it execs into the themed `crtr attach`), the tmux helpers return
21
+ // no style args and the chrome stays at its default — the viewer re-themes it.
22
+ const THEME_SYMBOL = Symbol.for('@earendil-works/pi-coding-agent:theme');
23
+ /** The live pi Theme if a process has already run `initTheme()`, else undefined.
24
+ * Read-only: never initializes (that would pull pi into the front door). */
25
+ function liveTheme() {
26
+ const t = globalThis[THEME_SYMBOL];
27
+ return (t ?? undefined);
28
+ }
29
+ /** The SGR escape that turns ON the distinct-surface background (theme
30
+ * `selectedBg`), e.g. `\x1b[48;2;58;58;74m`. Pair with `\x1b[49m` to reset just
31
+ * the background. Throws if no process has initialized the theme — callers on a
32
+ * render path (the attach overlay) always have, by construction. */
33
+ export function surfaceBgAnsi() {
34
+ const t = liveTheme();
35
+ if (t === undefined)
36
+ throw new Error('surfaceBgAnsi: pi theme not initialized in this process');
37
+ return t.getBgAnsi('selectedBg');
38
+ }
39
+ /** The distinct-surface background as a tmux colour token (`#rrggbb` on a
40
+ * truecolor theme, `colourN` on a 256-colour theme), or undefined when the theme
41
+ * is not yet loaded in this process OR defines no selection background. tmux
42
+ * cannot read pi's theme, so we translate the SGR pi resolved for us. */
43
+ function surfaceBgTmux() {
44
+ const t = liveTheme();
45
+ if (t === undefined)
46
+ return undefined;
47
+ const ansi = t.getBgAnsi('selectedBg');
48
+ const tc = /48;2;(\d+);(\d+);(\d+)/.exec(ansi);
49
+ if (tc !== null) {
50
+ const hex = (n) => Number(n).toString(16).padStart(2, '0');
51
+ return `#${hex(tc[1])}${hex(tc[2])}${hex(tc[3])}`;
52
+ }
53
+ const c256 = /48;5;(\d+)/.exec(ansi);
54
+ if (c256 !== null)
55
+ return `colour${c256[1]}`;
56
+ return undefined; // empty/default selectedBg → nothing distinct to paint
57
+ }
58
+ /** tmux flags that frame a `display-menu` / `display-popup` on the distinct
59
+ * surface background — a rounded border + the surface bg on the body and border.
60
+ * Empty when the theme is unavailable in this process (chrome stays default; the
61
+ * attach viewer re-installs the menu with these once it has themed). One knob:
62
+ * every crtr tmux float shares this single definition. */
63
+ export function surfaceTmuxStyleArgs() {
64
+ const bg = surfaceBgTmux();
65
+ if (bg === undefined)
66
+ return [];
67
+ return ['-b', 'rounded', '-s', `bg=${bg}`, '-S', `bg=${bg}`];
68
+ }
@@ -46,6 +46,12 @@ export interface SplitWindowOpts {
46
46
  command: string;
47
47
  /** Stack the new pane below instead of beside (default: beside, `-h`). */
48
48
  vertical?: boolean;
49
+ /** Place the new pane BEFORE the target (left for `-h`, above for `-v`) via
50
+ * `-b`, instead of after it (default: after — right/below). */
51
+ before?: boolean;
52
+ /** Fixed size of the new pane in the split axis' cells (columns for `-h`, rows
53
+ * for `-v`) via `-l`. Omit ⇒ tmux's default even split. */
54
+ size?: number;
49
55
  }
50
56
  /** Split `targetPane`'s window, opening a NEW pane beside it running `command`,
51
57
  * and return the new pane id (the durable `%id`). The ONLY new-pane-beside verb
@@ -62,6 +68,10 @@ export declare function splitWindow(targetPane: string, opts: SplitWindowOpts):
62
68
  export declare function focusWindow(session: string, window: string): boolean;
63
69
  /** Close a node's window (drop it from the UI). */
64
70
  export declare function closeWindow(window: string): boolean;
71
+ /** Rename a window (`tmux rename-window -t <window> <name>`). Used by the
72
+ * root relaunch to re-title the viewer window when its pane is re-pointed at
73
+ * the freshly-minted node. Best-effort; false if tmux fails. */
74
+ export declare function renameWindow(window: string, name: string): boolean;
65
75
  /** Close a single PANE. Its window closes automatically once this was the last
66
76
  * pane, but sibling panes survive — so co-located nodes (several agents sharing
67
77
  * one window via swap-pane focus) are torn down one at a time instead of all
@@ -96,6 +106,11 @@ export declare function paneLocation(pane: string): {
96
106
  * liveness. We therefore require the echoed `#{pane_id}` to equal the requested
97
107
  * pane: a live pane echoes its own id, a gone/bogus one yields empty. */
98
108
  export declare function paneExists(pane: string): boolean;
109
+ /** Does this pane exist AND have its command still RUNNING (`#{pane_dead}` = 0)?
110
+ * Distinguishes a pane genuinely hosting a live process from a remain-on-exit
111
+ * corpse frozen after exit (`pane_dead` = 1). Node viewer panes run `crtr attach`
112
+ * as the pane command, so pane-running ⟹ a live viewer occupies it. */
113
+ export declare function paneRunning(pane: string): boolean;
99
114
  /** Every live pane id on the server (`list-panes -a`), as a Set for membership
100
115
  * probes. Returns null when tmux is unreachable (no server / transient failure)
101
116
  * so callers can tell "no panes" apart from "can't tell" — a GC pass must skip,
@@ -116,21 +131,6 @@ export declare function setPaneOption(pane: string, name: string, value: string)
116
131
  * <name>`): `-v` prints only the value, `-q` suppresses the unknown-option
117
132
  * error so an unset option yields an empty string. undefined if tmux fails. */
118
133
  export declare function getPaneOption(pane: string, name: string): string | undefined;
119
- /** Relocate a pane into another session as its own window WITHOUT killing the
120
- * process in it — `break-pane -d` moves the pane out of its current window (the
121
- * pi keeps generating) into a fresh window in `session`; `-d` leaves the caller's
122
- * client where it is rather than following the pane to the background, and `-a`
123
- * allocates the next free window index (same dodge as openNodeWindow). The
124
- * "detach to background" driver behind `node lifecycle --detach`. Best-effort;
125
- * false if tmux refuses (e.g. the pane is gone). The caller reconciles presence
126
- * so the canvas follows the move. */
127
- export declare function breakPaneToSession(pane: string, session: string): boolean;
128
- /** Swap `targetPane` into `callerPane`'s layout slot, IN PLACE. `-d` keeps the
129
- * caller's window active, so the target's pane appears where the caller is
130
- * rather than navigating the client off to the target's window. The caller's
131
- * old pane lives on in the target's former window — the move is reversible
132
- * (focusing back swaps it in again). Best-effort; never throws. */
133
- export declare function swapPaneInPlace(targetPane: string, callerPane: string): boolean;
134
134
  export interface RespawnPaneOpts {
135
135
  /** Target pane id (e.g. `%3`) — the pane to re-exec in place. */
136
136
  pane: string;
@@ -139,21 +139,11 @@ export interface RespawnPaneOpts {
139
139
  /** The full command to run in the pane (already a shell string). */
140
140
  command: string;
141
141
  }
142
- /** Re-exec a command in an EXISTING pane, in place — DETACHED. Spawned in its own
143
- * process group (unref'd) so the request reaches the tmux server even though
144
- * `-k` tears down the caller's own pi mid-flight. Used when a node respawns ITS
145
- * OWN pane (refresh-yield): the dispatch can't be awaited because it kills the
146
- * awaiter. Returns true once the request was dispatched. */
147
- export declare function respawnPaneDetached(opts: RespawnPaneOpts): boolean;
148
142
  /** Re-exec a command in an EXISTING pane, in place — SYNCHRONOUS. Runs the
149
143
  * `respawn-pane` to completion and reports the real exit status. Used when the
150
144
  * caller is NOT the pane being respawned (e.g. the daemon resuming a frozen
151
145
  * focus pane), so it can confirm the respawn landed. Returns true on success. */
152
146
  export declare function respawnPaneSync(opts: RespawnPaneOpts): boolean;
153
- /** @deprecated Use respawnPaneDetached. Retained so existing refresh-yield
154
- * callers stay green while the placement layer migrates onto the explicit
155
- * sync/detached split. */
156
- export declare function respawnPane(opts: RespawnPaneOpts): boolean;
157
147
  /** Turn a pi argv array into a single shell command string.
158
148
  *
159
149
  * The binary defaults to `CRTR_PI_BINARY` when that env var is set, else the
@@ -183,24 +173,6 @@ export declare function selectWindow(session: string, window: string): boolean;
183
173
  * `tmux switch-client -t <session>`. Best-effort; never throws. The caller is
184
174
  * responsible for following up with selectWindow to land on the right window. */
185
175
  export declare function switchClient(session: string): boolean;
186
- /** Move a source pane into a destination window (`tmux join-pane`). The source
187
- * pane's running process (e.g. a child's live pi) is preserved; its now-empty
188
- * source window auto-closes. Best-effort; false if tmux fails. */
189
- export declare function joinPane(srcPane: string, dstWindow: string): boolean;
190
- /** Apply a named tmux layout to a window (`tmux select-layout`). Use
191
- * `main-vertical` for one wide pane on the left + the rest stacked right.
192
- * Best-effort; never throws. */
193
- export declare function selectLayout(window: string, layout: string): boolean;
194
- /** Set a tmux window option (`tmux set-window-option`). Used to size the main
195
- * pane (`main-pane-width`) before a main-vertical layout. Best-effort. */
196
- export declare function setWindowOption(window: string, name: string, value: string): boolean;
197
- /** Toggle `remain-on-exit` on a window (§1.5 F3). `on` keeps a focus pane on
198
- * screen after its pi exits — the viewport survives (F1), the final transcript
199
- * is preserved, and `respawn-pane -k` can resurrect the node into the SAME pane
200
- * id. NOTE (§1.5/§2.5, spike-confirmed): a dead/frozen pane is reaped only by
201
- * `kill-pane`/`respawn-pane`, NEVER by toggling this off — the toggle does not
202
- * reap an already-dead pane. Best-effort; never throws. */
203
- export declare function setRemainOnExit(window: string, on: boolean): boolean;
204
176
  /** Type a literal (e.g. a `/graph` slash command) into a pane and press Enter
205
177
  * (`tmux send-keys -t <pane> '<text>' Enter`). Requires the pane's editor be
206
178
  * empty, same limitation as the menu's `/promote` item. Best-effort. */