@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
@@ -6,7 +6,6 @@
6
6
  // the spine (`focus`/`msg`). The push/feed half lives under `crtr push`.
7
7
  import { defineLeaf, defineBranch } from '../core/command.js';
8
8
  import { InputError } from '../core/io.js';
9
- import { readConfig } from '../core/config.js';
10
9
  import { spawnChild } from '../core/runtime/spawn.js';
11
10
  import { promote, requestYield } from '../core/runtime/promote.js';
12
11
  import { writeYieldMessage, readGoal } from '../core/runtime/kickoff.js';
@@ -99,7 +98,7 @@ function callerSubPersonasBlock() {
99
98
  }
100
99
  }
101
100
  // ---------------------------------------------------------------------------
102
- // node new — spawn a terminal worker as a background window under the root
101
+ // node new — spawn a terminal worker as a detached headless broker under the root
103
102
  // ---------------------------------------------------------------------------
104
103
  const nodeNew = defineLeaf({
105
104
  name: 'new',
@@ -108,7 +107,7 @@ const nodeNew = defineLeaf({
108
107
  tier: 'important',
109
108
  help: {
110
109
  name: 'node new',
111
- summary: 'spawn a terminal worker onto the canvas as a background window — returns its node id',
110
+ summary: 'spawn a terminal worker onto the canvas as a detached broker — returns its node id',
112
111
  params: [
113
112
  { kind: 'stdin', name: 'prompt', required: true, constraint: 'First user message for the spawned node. Deliver it on stdin from a quoted heredoc (`<<\'EOF\'`) or a file (`< prompt.md`).' },
114
113
  { kind: 'flag', name: 'kind', type: 'string', required: false, default: 'general', constraint: 'Persona kind — match the work to the kind. The <kinds> list below names every installable kind and when to use each; the <sub-personas> block (when present) names the specialist sub-personas available to YOU, each spawnable by its full kind string (e.g. plan/reviewers/security).' },
@@ -118,14 +117,13 @@ const nodeNew = defineLeaf({
118
117
  { kind: 'flag', name: 'parent', type: 'string', required: false, constraint: 'Parent node id. Defaults to the calling node (CRTR_NODE_ID).' },
119
118
  { kind: 'flag', name: 'root', type: 'bool', required: false, constraint: 'Spawn an INDEPENDENT root instead of a managed child: no parent (top-level on the canvas), NO subscription back to you (you are NOT woken by it), resident lifecycle. It records spawned_by=you for provenance and is brought forefront so it can be driven directly. Use for a node you hand off and do not manage (e.g. a sub-orchestrator a human will discuss with).' },
120
119
  { kind: 'flag', name: 'fork-from', type: 'string', required: false, constraint: 'FORK the new node from an existing pi conversation instead of starting it fresh: pass a node id (forks from that node\'s session), an absolute session `.jsonl` path, or a partial pi session uuid. pi copies that whole history into a NEW session for the child (the source is untouched), then the prompt is delivered as the next message — i.e. the child wakes up as a continuation of that conversation. Use to branch exploratory work off a node that already built up the context you need, instead of re-deriving it. One-shot at birth: the fork resumes its own session thereafter.' },
121
- { kind: 'flag', name: 'headless', type: 'bool', required: false, constraint: 'Spawn the node on the HEADLESS broker host (no tmux pane) instead of a tmux window. Overrides the `headless` config default for this spawn; omit to use that default (which itself defaults to a tmux pane).' },
122
120
  { kind: 'flag', name: 'model', type: 'enum', choices: ['ultra', 'strong', 'medium', 'light'], required: false, constraint: 'Override the model the node runs on, by capability TIER: ultra (frontier), strong (opus), medium (sonnet), light (haiku). Omit to use the kind\'s persona default (explore=light, every other kind=strong). The override is durable — it survives revives and polymorphs (promote/demote).' },
123
121
  ],
124
122
  output: [
125
123
  { name: 'node_id', type: 'string', required: true, constraint: 'The new node id.' },
126
124
  { name: 'name', type: 'string', required: true, constraint: 'Display name.' },
127
- { name: 'window', type: 'string', required: false, constraint: 'tmux window id of the background window.' },
128
- { name: 'session', type: 'string', required: true, constraint: 'The tmux session the node was placed in the shared crtr session for a child; your current session for an in-tmux --root.' },
125
+ { name: 'window', type: 'string', required: false, constraint: 'tmux window id of the viewer opened for an in-tmux --root (a `crtr attach` view onto the node\'s broker); absent for a managed child, which opens no viewer on spawn.' },
126
+ { name: 'session', type: 'string', required: false, constraint: 'The tmux session a --root\'s viewer opened into (your current session); absent for a managed child and for a --root spawned outside tmux.' },
129
127
  { name: 'status', type: 'string', required: true, constraint: 'Always "active" on spawn.' },
130
128
  { name: 'follow_up', type: 'string', required: true, constraint: 'Decision road sign for the caller: the child runs independently and its finish wakes you on its own, so never wait or poll on it — either pick up other work now or end your turn. If you are an orchestrator already deep in context (>100k), it instead steers you to `crtr node yield` now so your fresh revive absorbs the child\'s result. Read it, then act.' },
131
129
  ],
@@ -134,7 +132,7 @@ const nodeNew = defineLeaf({
134
132
  effects: [
135
133
  'Creates a node under ~/.crouter/canvas/nodes/<id>/ and indexes it in canvas.db.',
136
134
  'Default (managed child): parent auto-subscribes (active) and is woken on the child\'s pushes. With --root: no subscription — records a spawned_by edge for provenance only.',
137
- 'Opens a tmux window running pi: a background (non-focus-stealing) window in the shared crtr session for a child; with --root, a new window in your current session (in-tmux) or the shared session (outside tmux), with the client switched to it.',
135
+ 'Launches the node\'s engine as a detached broker (the only host). A managed child opens NO viewer — it runs headless and you open one on demand via `crtr node focus` / `crtr attach`. With --root inside tmux, opens one viewer window in your CURRENT session and switches the client to it so the root can be driven directly; outside tmux a --root opens no viewer.',
138
136
  ],
139
137
  },
140
138
  run: async (input) => {
@@ -150,17 +148,15 @@ const nodeNew = defineLeaf({
150
148
  const root = input['root'] === true;
151
149
  const forkFrom = input['forkFrom'];
152
150
  const model = input['model'];
153
- // Host precedence: explicit --headless flag > config `headless` default > tmux.
154
- const hostKind = input['headless'] === true ? 'broker' : readConfig('user').headless === true ? 'broker' : 'tmux';
155
- const res = spawnChild({ kind, mode, cwd, name, prompt, parent, root, forkFrom, hostKind, model });
151
+ const res = spawnChild({ kind, mode, cwd, name, prompt, parent, root, forkFrom, model });
156
152
  return {
157
153
  node_id: res.node.node_id,
158
154
  name: res.node.name,
159
155
  window: res.window ?? undefined,
160
- session: res.session,
156
+ session: res.session ?? undefined,
161
157
  status: res.node.status,
162
158
  follow_up: root
163
- ? "Independent root spawned — it is NOT under you. You are not subscribed, so its finish will NOT wake you and it does not hold you alive; it carries spawned_by=you for lineage only. It opened in its own window and the client was switched to it so it can be driven directly. Hand it off and move on — you will not be notified of its progress."
159
+ ? "Independent root spawned — it is NOT under you. You are not subscribed, so its finish will NOT wake you and it does not hold you alive; it carries spawned_by=you for lineage only. Inside tmux it opened a viewer in your current session (the client switched to it) so it can be driven directly. Hand it off and move on — you will not be notified of its progress."
164
160
  : childFollowUp(parent ?? process.env['CRTR_NODE_ID']),
165
161
  };
166
162
  },
@@ -184,7 +180,7 @@ const nodeList = defineLeaf({
184
180
  name: 'node inspect list',
185
181
  summary: 'list nodes on the canvas, optionally by status',
186
182
  params: [
187
- { kind: 'flag', name: 'status', type: 'string', required: false, constraint: 'Filter: active | idle | done | dead | canceled. Comma-separated for several.' },
183
+ { kind: 'flag', name: 'status', type: 'string', required: false, constraint: 'Filter: active | idle | done | dead | canceled. Comma-separated for several. NOTE: `active` means the engine process is live (never closed), NOT that the node is generating right now — an active node is usually dormant between turns. To tell working-vs-idle, check the pi session-file mtime or CPU, not status.' },
188
184
  ],
189
185
  output: [
190
186
  { name: 'nodes', type: 'object[]', required: true, constraint: 'Rows: {node_id, name, kind, mode, lifecycle, status, cwd, parent, created}.' },
@@ -243,28 +239,28 @@ const nodeInspect = defineBranch({
243
239
  children: [nodeList, nodeShow],
244
240
  });
245
241
  // ---------------------------------------------------------------------------
246
- // node focus — bring a node's window forefront (across roots if needed)
242
+ // node focus — bring a node's viewer forefront (across roots if needed)
247
243
  // ---------------------------------------------------------------------------
248
244
  const nodeFocus = defineLeaf({
249
245
  name: 'focus',
250
- description: 'bring a node window forefront',
251
- whenToUse: 'you want to bring a specific node into view — swapped into your current pane — to watch or steer it directly, reviving it first if dormant. Use `node cycle` instead to walk neighbors one window at a time rather than jump to a named node, and `node msg` to steer a node without leaving where you are',
246
+ description: 'bring a node\'s viewer forefront',
247
+ whenToUse: 'you want to bring a specific node into view — open or move its viewer beside your current pane — to watch or steer it directly, reviving its broker first if dormant. Use `node cycle` instead to walk neighbors one viewer at a time rather than jump to a named node, and `node msg` to steer a node without leaving where you are',
252
248
  help: {
253
249
  name: 'node focus',
254
- summary: 'bring a node into your CURRENT pane in place (swap-pane) the agent appears where you are instead of navigating you to its window',
250
+ summary: 'bring a node\'s viewer forefront — open a `crtr attach` view onto the node\'s broker beside your current pane, or navigate to its existing viewer. The engine runs on the broker (the only host); the pane is just a window onto it.',
255
251
  params: [
256
252
  { kind: 'positional', name: 'node', required: true, constraint: 'Node id to focus.' },
257
- { kind: 'flag', name: 'new-pane', type: 'bool', required: false, constraint: 'Open the node in a NEW viewport SIDE-BY-SIDE with your current pane (a second focus) instead of swapping it into your pane. Two agents on screen at once (F4).' },
258
- { kind: 'flag', name: 'pane', type: 'string', required: false, constraint: 'tmux pane id to focus INTO (default: caller TMUX_PANE). Used by the canvas browser popup to focus back into the originating pane.' },
253
+ { kind: 'flag', name: 'new-pane', type: 'bool', required: false, constraint: 'Always open a NEW viewer SIDE-BY-SIDE with your current pane instead of moving/reusing the node\'s existing viewer. Two agents on screen at once (F4).' },
254
+ { kind: 'flag', name: 'pane', type: 'string', required: false, constraint: 'tmux pane id to focus beside (default: caller TMUX_PANE). Used by the canvas browser popup to focus back into the originating pane.' },
259
255
  ],
260
256
  output: [
261
- { name: 'focused', type: 'boolean', required: true, constraint: 'True when the node was brought into view.' },
262
- { name: 'session', type: 'string', required: false, constraint: 'The tmux session the node lives in.' },
263
- { name: 'revived', type: 'boolean', required: true, constraint: 'True when a dormant node was revived to be focused.' },
264
- { name: 'in_place', type: 'boolean', required: true, constraint: 'True when the node was swapped into the caller pane; false when it fell back to window focus (no caller pane).' },
257
+ { name: 'focused', type: 'boolean', required: true, constraint: 'True when the node\'s viewer was brought into view.' },
258
+ { name: 'session', type: 'string', required: false, constraint: 'The tmux session the viewer lives in.' },
259
+ { name: 'revived', type: 'boolean', required: true, constraint: 'True when a dormant node\'s broker was relaunched to be focused.' },
260
+ { name: 'in_place', type: 'boolean', required: true, constraint: 'True when an existing viewer was navigated to (no new pane); false when a fresh viewer pane was opened or moved beside you.' },
265
261
  ],
266
262
  outputKind: 'object',
267
- effects: ['Swaps the node\'s pane into the caller\'s current pane (tmux swap-pane -d) and retargets the caller\'s focus to it (focus pointer updated).', 'With --new-pane: splits a new viewport beside the caller (a second live focus) instead of swapping in place.', 'Revives a dormant node (resume) into the backstage if it has no live pane, then swaps it into the focus.'],
263
+ effects: ['Ensures the node\'s broker engine is alive (revives it if dormant) and opens/moves its one viewer pane (a `crtr attach` view) beside the caller, or navigates to it if a live viewer already sits in the caller\'s session.', 'With --new-pane: always splits a fresh viewer beside the caller (two agents on screen) instead of reusing the existing viewer.'],
268
264
  },
269
265
  run: async (input) => {
270
266
  const id = input['node'];
@@ -282,14 +278,13 @@ const nodeFocus = defineLeaf({
282
278
  next: `The pending question is already on the human's screen; see it with \`crtr human list\` / \`crtr human inbox\`, or retract it with \`crtr human cancel ${id}\`.`,
283
279
  });
284
280
  }
285
- // Placement owns the whole act (§2.3): resolve the caller's focus (or open a
286
- // new viewport with --new-pane), revive the target into the backstage if it
287
- // is dormant, then hot-swap it onto the focus. The reviver is injected so
288
- // placement need not import revive.ts.
281
+ // Placement owns the whole act: ensure the target's broker engine is alive
282
+ // (revive it if dormant), then open/move its one viewer pane beside the
283
+ // caller (or split a fresh viewer with --new-pane). The reviver is injected
284
+ // so placement need not import revive.ts.
289
285
  const res = placementFocus(id, {
290
286
  pane: input['pane'],
291
287
  newPane: input['newPane'] === true,
292
- callerNode: process.env['CRTR_NODE_ID'],
293
288
  revive: (nid) => { reviveNode(nid, { resume: true }); },
294
289
  });
295
290
  return { focused: res.focused, session: res.session, revived: res.revived, in_place: res.inPlace };
@@ -314,7 +309,7 @@ function nodeByWindow(win) {
314
309
  /** The live node "in front of you" in a tmux pane, HOST-AGNOSTIC. Defaults to
315
310
  * $TMUX_PANE / the caller's current pane when `pane` is omitted — shared by
316
311
  * `node recycle` / `node demote` / `node lifecycle` / `node close` / `node
317
- * cycle`, and the `canvas chord` / `canvas tmux-spread` leaves.
312
+ * cycle`, and the `canvas chord` leaf.
318
313
  *
319
314
  * Two resolutions, tried in order:
320
315
  * 1. VIEWER pane (broker host): a `crtr attach` viewer self-tags its pane with
@@ -483,11 +478,11 @@ function liveDfsOrder() {
483
478
  }
484
479
  const nodeCycle = defineLeaf({
485
480
  name: 'cycle',
486
- description: 'DFS-walk to the next/prev live node in place',
487
- whenToUse: 'sweeping the canvas one window at a time, descending into children before siblings (bound to Alt+] forward / Alt+[ back). Use `node focus` instead to jump straight to a named node',
481
+ description: 'DFS-walk to the next/prev live node\'s viewer',
482
+ whenToUse: 'sweeping the canvas one viewer at a time, descending into children before siblings (bound to Alt+] forward / Alt+[ back). Use `node focus` instead to jump straight to a named node',
488
483
  help: {
489
484
  name: 'node cycle',
490
- summary: 'focus the next/previous live node in DFS pre-order — the canvas walked one window at a time, descending into a node\'s children before its siblings (bound to Alt+] forward / Alt+[ back)',
485
+ summary: 'focus the next/previous live node\'s viewer in DFS pre-order — the canvas walked one viewer at a time, descending into a node\'s children before its siblings (bound to Alt+] forward / Alt+[ back)',
491
486
  params: [
492
487
  { kind: 'flag', name: 'dir', type: 'enum', choices: ['next', 'prev'], required: false, default: 'next', constraint: 'Direction along the pre-order: next (Alt+], rightward/deeper into children) or prev (Alt+[, back). Wraps at the ends.' },
493
488
  { kind: 'flag', name: 'pane', type: 'string', required: false, constraint: 'tmux pane to cycle FROM. Defaults to $TMUX_PANE / your current pane. The Alt+] / Alt+[ bindings pass this for you.' },
@@ -499,7 +494,7 @@ const nodeCycle = defineLeaf({
499
494
  { name: 'from', type: 'string', required: false, constraint: 'The node you cycled away from.' },
500
495
  ],
501
496
  outputKind: 'object',
502
- effects: ['Swaps the neighbor\'s pane into the caller pane (like `node focus`); the node you were viewing drops to the background.', 'Revives the neighbor first if its window was released.'],
497
+ effects: ['Brings the neighbor\'s viewer forefront beside the caller (like `node focus`); the viewer you were watching drops to the background.', 'Relaunches the neighbor\'s broker first if it was dormant.'],
503
498
  },
504
499
  run: async (input) => {
505
500
  const pane = input['pane'] ?? process.env['TMUX_PANE'] ?? currentTmux()?.pane ?? undefined;
@@ -516,12 +511,10 @@ const nodeCycle = defineLeaf({
516
511
  const target = getNode(targetId);
517
512
  if (target === null)
518
513
  return { focused: false, from: fromId };
519
- // Placement retargets the caller pane's focus to the neighbor (§2.3),
520
- // reviving it into the backstage first if its pane was released. callerNode
521
- // is the node we cycled AWAY from — the current occupant of the caller pane.
514
+ // Placement brings the neighbor's viewer forefront beside the caller,
515
+ // relaunching its broker engine first if it was dormant.
522
516
  const res = placementFocus(targetId, {
523
517
  pane,
524
- callerNode: fromId,
525
518
  revive: (nid) => { reviveNode(nid, { resume: true }); },
526
519
  });
527
520
  return { focused: res.focused, node_id: targetId, name: target.name, from: fromId };
@@ -666,12 +659,14 @@ const nodePromote = defineLeaf({
666
659
  params: [
667
660
  { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Specialize as this kind of orchestrator. The <kinds> list below names every installable kind and when to use each; the <sub-personas> block (when present) names the specialist sub-personas available to YOU, spawnable by full kind string. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one — it sets the orchestrator persona you revive into.' },
668
661
  { kind: 'flag', name: 'resident', type: 'bool', required: false, constraint: 'ALSO flip lifecycle→resident: make the node interactable — it stays dormant, woken by inbox/human, and is never forced to submit a final. Omit to stay terminal/orchestrator (delegates + holds a roadmap, but still owes a final up the spine and reaps when done).' },
662
+ { kind: 'flag', name: 'model', type: 'enum', choices: ['ultra', 'strong'], required: false, constraint: 'Change the model you run on, by capability tier: `ultra` (frontier — reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus — regular dev work). Omit to keep your current model. An orchestrator steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives.' },
669
663
  { kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to promote. Defaults to the caller (CRTR_NODE_ID).' },
670
664
  ],
671
665
  output: [
672
666
  { name: 'node_id', type: 'string', required: true, constraint: 'The promoted node.' },
673
667
  { name: 'kind', type: 'string', required: true, constraint: 'The kind it now orchestrates as.' },
674
668
  { name: 'mode', type: 'string', required: true, constraint: 'Now "orchestrator".' },
669
+ { name: 'model', type: 'string', required: false, constraint: 'The model tier it now runs on, present only when you changed it with --model.' },
675
670
  { name: 'lifecycle', type: 'string', required: true, constraint: '"resident" if you passed --resident, else unchanged (typically "terminal").' },
676
671
  { name: 'roadmap_written', type: 'boolean', required: true, constraint: 'True if a roadmap scaffold was seeded by this call.' },
677
672
  { name: 'roadmap_path', type: 'string', required: true, constraint: 'Absolute path to your roadmap doc (context/roadmap.md) — edit it to author your plan.' },
@@ -679,7 +674,7 @@ const nodePromote = defineLeaf({
679
674
  ],
680
675
  dynamicState: () => kindsStateBlock(),
681
676
  outputKind: 'object',
682
- effects: ['Flips mode→orchestrator + kind→chosen (lifecycle unchanged unless --resident, which also flips lifecycle→resident); rewrites the launch spec to that kind\'s orchestrator persona; seeds context/roadmap.md scaffold if absent.', 'Your new-role guidance is injected automatically at the turn boundary by the persona injector — the command no longer returns it.'],
677
+ effects: ['Flips mode→orchestrator + kind→chosen (lifecycle unchanged unless --resident, which also flips lifecycle→resident); --model durably pins the model tier; rewrites the launch spec to that kind\'s orchestrator persona; seeds context/roadmap.md scaffold if absent.', 'Your new-role guidance is injected automatically at the turn boundary by the persona injector — the command no longer returns it.'],
683
678
  },
684
679
  run: async (input) => {
685
680
  const id = input['node'] ?? process.env['CRTR_NODE_ID'];
@@ -688,9 +683,10 @@ const nodePromote = defineLeaf({
688
683
  const kind = input['kind'];
689
684
  if (kind !== undefined)
690
685
  assertKind(kind);
686
+ const model = input['model'];
691
687
  const resident = input['resident'] === true;
692
- const res = promote(id, { ...(kind !== undefined ? { kind } : {}), ...(resident ? { resident: true } : {}) });
693
- return { node_id: res.meta.node_id, kind: res.meta.kind, mode: res.meta.mode, lifecycle: res.meta.lifecycle, roadmap_written: res.roadmapWritten, roadmap_path: res.roadmapPath, goal_path: res.goalPath };
688
+ const res = promote(id, { ...(kind !== undefined ? { kind } : {}), ...(resident ? { resident: true } : {}), ...(model !== undefined ? { model } : {}) });
689
+ return { node_id: res.meta.node_id, kind: res.meta.kind, mode: res.meta.mode, lifecycle: res.meta.lifecycle, ...(res.meta.model_override != null ? { model: res.meta.model_override } : {}), roadmap_written: res.roadmapWritten, roadmap_path: res.roadmapPath, goal_path: res.goalPath };
694
690
  },
695
691
  });
696
692
  // ---------------------------------------------------------------------------
@@ -713,8 +709,8 @@ function resolveLifecycleNode(input, pane) {
713
709
  throw new InputError({ error: 'not_found', message: `no node: ${id}`, next: 'List nodes with `crtr node inspect list`.' });
714
710
  return id;
715
711
  }
716
- /** Set a node's lifecycle axis and, with `detach`, relocate its still-running
717
- * pane to the background crtr session. Rebuilds the launch spec so a future
712
+ /** Set a node's lifecycle axis and, with `detach`, close its foreground viewer
713
+ * pane (the broker engine keeps running). Rebuilds the launch spec so a future
718
714
  * revive comes back with the new lifecycle's prompt baked in (the live session
719
715
  * is steered by the persona injector; this fixes the static prompt the daemon
720
716
  * replays). Spine is fixed by parent-ness, so it carries through unchanged.
@@ -728,9 +724,9 @@ function setLifecycle(id, value, opts) {
728
724
  model: target.model_override ?? undefined,
729
725
  });
730
726
  const meta = updateNode(id, { lifecycle: value, launch });
731
- // --detach: shove the still-running agent into the background crtr session,
732
- // freeing the foreground pane. The pi is untouched (it keeps generating); now
733
- // terminal, it pushes a final up the spine when it finishes.
727
+ // --detach: stop foregrounding the agent by closing its viewer pane (the
728
+ // broker engine keeps generating off-screen); now terminal, it pushes a final
729
+ // up the spine when it finishes, and a later `focus` reattaches a viewer.
734
730
  let detached = false;
735
731
  if (opts.detach === true)
736
732
  detached = detachToBackground(id, opts.pane);
@@ -745,23 +741,23 @@ function setLifecycle(id, value, opts) {
745
741
  // ---------------------------------------------------------------------------
746
742
  const nodeDemote = defineLeaf({
747
743
  name: 'demote',
748
- description: 'demote a node to terminal in place; it stays focused and running but now owes a final; --detach also sends it to the backstage crtr session',
749
- whenToUse: 'you are watching a resident/interactive node and want to put it on a finishing track WITHOUT disturbing it: flip it terminal IN PLACE — it keeps its pane and your focus, keeps running, but now owes a final report up the spine and reaps when done. The friendly counterpart to `node promote`. You can also let go entirely and send the still-running agent off-screen to finish in the background. Use `node recycle` instead to FINISH it now and reboot a fresh root in its pane, and `node lifecycle` for the orthogonal low-level flip (incl. terminal→resident)',
744
+ description: 'demote a node to terminal in place; it stays focused and running but now owes a final; --detach also closes its viewer (broker keeps running off-screen)',
745
+ whenToUse: 'you are watching a resident/interactive node and want to put it on a finishing track WITHOUT disturbing it: flip it terminal IN PLACE — it keeps its viewer and your focus, keeps running, but now owes a final report up the spine and reaps when done. The friendly counterpart to `node promote`. You can also let go entirely and close its viewer so the agent finishes off-screen on its broker. Use `node recycle` instead to FINISH it now and reboot a fresh root in its pane, and `node lifecycle` for the orthogonal low-level flip (incl. terminal→resident)',
750
746
  help: {
751
747
  name: 'node demote',
752
- summary: 'demote a node to terminal IN PLACE — it stays focused and running but now owes a final up the spine and reaps when done. Pairs with `node promote` (mode↑); `--detach` also relocates the still-running agent to the background crtr session',
748
+ summary: 'demote a node to terminal IN PLACE — it stays focused and running but now owes a final up the spine and reaps when done. Pairs with `node promote` (mode↑); `--detach` also closes its viewer (the broker engine keeps running off-screen)',
753
749
  params: [
754
750
  { kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to demote. Defaults to the node in --pane, else the caller (CRTR_NODE_ID).' },
755
751
  { kind: 'flag', name: 'pane', type: 'string', required: false, constraint: 'tmux pane id whose node to demote, when --node is omitted. Defaults to $TMUX_PANE. The Alt+C menu passes this for you.' },
756
- { kind: 'flag', name: 'detach', type: 'bool', required: false, constraint: 'After flipping terminal, send the still-running agent to the background crtr session (break its pane out of the foreground). The pi keeps generating and — now terminal — pushes a final up the spine when done. The Alt+C → D move.' },
752
+ { kind: 'flag', name: 'detach', type: 'bool', required: false, constraint: 'After flipping terminal, stop foregrounding the agent by closing its viewer pane. The broker engine keeps generating off-screen and — now terminal — pushes a final up the spine when done; a later `focus` reattaches a viewer. The Alt+C → D move.' },
757
753
  ],
758
754
  output: [
759
755
  { name: 'node_id', type: 'string', required: true, constraint: 'The demoted node.' },
760
756
  { name: 'lifecycle', type: 'string', required: true, constraint: 'Always "terminal" after a demote.' },
761
- { name: 'detached', type: 'boolean', required: false, constraint: 'True when --detach relocated the agent to the background crtr session.' },
757
+ { name: 'detached', type: 'boolean', required: false, constraint: 'True when --detach closed the agent\'s viewer pane (broker keeps running).' },
762
758
  ],
763
759
  outputKind: 'object',
764
- effects: ['Flips the node\'s lifecycle→terminal and rebuilds its launch spec so a future revive boots terminal — it stays focused and running, now owing a final up the spine.', 'The persona injector delivers the transition guidance at the next turn boundary (or on the node\'s next revive if it is dormant).', 'With --detach: relocates the agent\'s live pane to the background crtr session (break-pane) WITHOUT killing the pi — it keeps generating off-screen.'],
760
+ effects: ['Flips the node\'s lifecycle→terminal and rebuilds its launch spec so a future revive boots terminal — it stays focused and running, now owing a final up the spine.', 'The persona injector delivers the transition guidance at the next turn boundary (or on the node\'s next revive if it is dormant).', 'With --detach: closes the agent\'s viewer pane WITHOUT touching the broker engine — it keeps generating off-screen, reconnectable by a later `focus`.'],
765
761
  },
766
762
  run: async (input) => {
767
763
  const pane = input['pane'] ?? process.env['TMUX_PANE'];
@@ -769,7 +765,7 @@ const nodeDemote = defineLeaf({
769
765
  const res = setLifecycle(id, 'terminal', { pane, detach: input['detach'] === true });
770
766
  return { node_id: res.node_id, lifecycle: res.lifecycle, detached: res.detached };
771
767
  },
772
- render: (r) => `Demoted ${r['node_id']} — lifecycle now ${r['lifecycle']} (in place)${r['detached'] === true ? ', relocated to the background crtr session' : ''}.`,
768
+ render: (r) => `Demoted ${r['node_id']} — lifecycle now ${r['lifecycle']} (in place)${r['detached'] === true ? ', viewer closed (broker still running off-screen)' : ''}.`,
773
769
  });
774
770
  // ---------------------------------------------------------------------------
775
771
  // node lifecycle — flip the lifecycle axis (terminal ↔ resident), independent
@@ -781,20 +777,20 @@ const nodeLifecycle = defineLeaf({
781
777
  whenToUse: 'you want to flip a node\'s LIFECYCLE independent of its mode: make a node RESIDENT so it becomes interactable — it stays dormant, wakes on inbox/human, and is never forced to submit a final; or make a node TERMINAL so it owes a final result up the spine and reaps when done. Orthogonal to `node promote`, which changes MODE (base↔orchestrator), not lifecycle. You can also let go of a still-running agent and send it off-screen to finish in the background. For the human-driver flip-to-terminal pair reach for `node demote` (the friendly terminal-only skin over this)',
782
778
  help: {
783
779
  name: 'node lifecycle',
784
- summary: 'set a node\'s lifecycle axis — terminal (owes a final up the spine, reaps when done) or resident (interactable, stays dormant, woken by inbox/human, never forced to submit). Orthogonal to mode; promotion does not touch it. `--detach` also relocates a live agent to the background crtr session',
780
+ summary: 'set a node\'s lifecycle axis — terminal (owes a final up the spine, reaps when done) or resident (interactable, stays dormant, woken by inbox/human, never forced to submit). Orthogonal to mode; promotion does not touch it. `--detach` also closes a live agent\'s viewer (broker keeps running off-screen)',
785
781
  params: [
786
782
  { kind: 'positional', name: 'lifecycle', required: true, constraint: 'terminal | resident.' },
787
783
  { kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to change. Defaults to the node in --pane, else the caller (CRTR_NODE_ID).' },
788
784
  { kind: 'flag', name: 'pane', type: 'string', required: false, constraint: 'tmux pane id whose node to change, when --node is omitted. Defaults to $TMUX_PANE. The Alt+C menu passes this for you.' },
789
- { kind: 'flag', name: 'detach', type: 'bool', required: false, constraint: 'After flipping lifecycle, send the still-running agent to the background crtr session (break its pane out of the foreground). The pi keeps generating and — now terminal — pushes a final up the spine when done. The human-driver "I am done foregrounding this" move (Alt+C → D).' },
785
+ { kind: 'flag', name: 'detach', type: 'bool', required: false, constraint: 'After flipping lifecycle, stop foregrounding the agent by closing its viewer pane. The broker engine keeps generating off-screen and — now terminal — pushes a final up the spine when done; a later `focus` reattaches a viewer. The human-driver "I am done foregrounding this" move (Alt+C → D).' },
790
786
  ],
791
787
  output: [
792
788
  { name: 'node_id', type: 'string', required: true, constraint: 'The node.' },
793
789
  { name: 'lifecycle', type: 'string', required: true, constraint: 'Its new lifecycle (terminal | resident).' },
794
- { name: 'detached', type: 'boolean', required: false, constraint: 'True when --detach relocated the agent to the background crtr session.' },
790
+ { name: 'detached', type: 'boolean', required: false, constraint: 'True when --detach closed the agent\'s viewer pane (broker keeps running).' },
795
791
  ],
796
792
  outputKind: 'object',
797
- effects: ['Sets lifecycle on the node meta and rebuilds its launch spec so a future revive boots with the new lifecycle\'s prompt baked in.', 'The persona injector delivers the transition guidance at the next turn boundary (or on the node\'s next revive if it is dormant).', 'With --detach: relocates the agent\'s live pane to the background crtr session (break-pane) WITHOUT killing the pi — it keeps generating in the background.'],
793
+ effects: ['Sets lifecycle on the node meta and rebuilds its launch spec so a future revive boots with the new lifecycle\'s prompt baked in.', 'The persona injector delivers the transition guidance at the next turn boundary (or on the node\'s next revive if it is dormant).', 'With --detach: closes the agent\'s viewer pane WITHOUT touching the broker engine — it keeps generating off-screen, reconnectable by a later `focus`.'],
798
794
  },
799
795
  run: async (input) => {
800
796
  const value = input['lifecycle']?.trim().toLowerCase();
@@ -806,7 +802,7 @@ const nodeLifecycle = defineLeaf({
806
802
  const res = setLifecycle(id, value, { pane, detach: input['detach'] === true });
807
803
  return { node_id: res.node_id, lifecycle: res.lifecycle, detached: res.detached };
808
804
  },
809
- render: (r) => `Set ${r['node_id']} lifecycle → ${r['lifecycle']}${r['detached'] === true ? ', relocated to the background crtr session' : ''}.`,
805
+ render: (r) => `Set ${r['node_id']} lifecycle → ${r['lifecycle']}${r['detached'] === true ? ', viewer closed (broker still running off-screen)' : ''}.`,
810
806
  });
811
807
  // ---------------------------------------------------------------------------
812
808
  // node yield — refresh: discard context, revive fresh against the roadmap
@@ -814,21 +810,23 @@ const nodeLifecycle = defineLeaf({
814
810
  const nodeYield = defineLeaf({
815
811
  name: 'yield',
816
812
  description: 'refresh your context against your roadmap',
817
- whenToUse: 'your context window is filling up but the mandate is unfinished: request a refresh — end your turn and revive fresh against your roadmap, leaving a note to your future self for the moment you wake. A base node auto-promotes to orchestrator first (a yield needs a roadmap to refresh against). Use `node promote` instead when you need to BECOME an orchestrator with no refresh pending',
813
+ whenToUse: 'your context window is filling up but the mandate is unfinished: request a refresh — end your turn and revive fresh against your roadmap, leaving a note to your future self for the moment you wake. A base node auto-promotes to orchestrator first (a yield needs a roadmap to refresh against). You may also respecialize (--kind) or raise your model (--model) as you refresh. Use `node promote` instead when you need to BECOME an orchestrator with no refresh pending',
818
814
  help: {
819
815
  name: 'node yield',
820
- summary: 'request a context refresh — you will be respawned fresh against your roadmap on your next stop (a base node auto-promotes to orchestrator first)',
816
+ summary: 'request a context refresh — you will be respawned fresh against your roadmap on your next stop (a base node auto-promotes to orchestrator first); optionally change your kind or model as you refresh',
821
817
  params: [
822
- { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'If this yield auto-promotes a base node, specialize it as this kind of orchestrator (developer, review, spec, design, plan, explore, general). Defaults to your current kind.' },
818
+ { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Respecialize as you refresh change your kind to this (developer, review, spec, design, plan, explore, general); a base node also auto-promotes into an orchestrator of this kind. Defaults to your current kind.' },
819
+ { kind: 'flag', name: 'model', type: 'enum', choices: ['ultra', 'strong'], required: false, constraint: 'Change the model your fresh revive runs on, by capability tier: `ultra` (frontier — reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus — regular dev work). Omit to keep your current model. A node steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives.' },
823
820
  { kind: 'stdin', name: 'message', required: true, constraint: 'A note to your future self — what to do the moment you wake fresh. Surfaced as <yield-message> in the next revive. Pass as a positional or pipe via heredoc.' },
824
821
  ],
825
822
  output: [
826
823
  { name: 'node_id', type: 'string', required: true, constraint: 'The yielding node.' },
827
824
  { name: 'promoted', type: 'boolean', required: true, constraint: 'True if this yield promoted a base node to orchestrator.' },
825
+ { name: 'model', type: 'string', required: false, constraint: 'The model tier your fresh revive will run on, present only when you changed it with --model.' },
828
826
  { name: 'will_refresh', type: 'boolean', required: true, constraint: 'Always true: end your turn and you will revive fresh against your roadmap.' },
829
827
  ],
830
828
  outputKind: 'object',
831
- effects: ['Sets intent=refresh; the stophook shuts the process down on next stop and the daemon revives it fresh.'],
829
+ effects: ['Sets intent=refresh; the stophook shuts the process down on next stop and the daemon revives it fresh.', '--kind and/or --model durably reshape the node (kind respecialization, model-tier pin) before the refresh-revive; a base node also auto-promotes to orchestrator.'],
832
830
  },
833
831
  run: async (input) => {
834
832
  const id = process.env['CRTR_NODE_ID'];
@@ -837,13 +835,14 @@ const nodeYield = defineLeaf({
837
835
  const kind = input['kind'];
838
836
  if (kind !== undefined)
839
837
  assertKind(kind);
838
+ const model = input['model'];
840
839
  const message = typeof input['message'] === 'string' ? input['message'].trim() : '';
841
840
  if (message === '') {
842
841
  throw new InputError({ error: 'empty_message', message: 'a yield message is required (stdin or positional)', next: 'Pass a note to your future self as an argument or pipe it on stdin.' });
843
842
  }
844
843
  writeYieldMessage(id, message);
845
- const res = requestYield(id, kind !== undefined ? { kind } : {});
846
- return { node_id: res.meta.node_id, promoted: res.promoted, will_refresh: res.willRefresh };
844
+ const res = requestYield(id, { ...(kind !== undefined ? { kind } : {}), ...(model !== undefined ? { model } : {}) });
845
+ return { node_id: res.meta.node_id, promoted: res.promoted, ...(res.meta.model_override != null ? { model: res.meta.model_override } : {}), will_refresh: res.willRefresh };
847
846
  },
848
847
  });
849
848
  // ---------------------------------------------------------------------------
@@ -1349,7 +1348,7 @@ export function registerNode() {
1349
1348
  summary: 'spawn and navigate canvas nodes',
1350
1349
  model: 'Every agent is a node in one directed graph (the canvas); `subscribes_to` is the spine — spawn a child and you auto-subscribe (active) to it, so its finish wakes you.\n\n' +
1351
1350
  'WHEN TO DELEGATE: hand any self-contained unit of work to a child instead of doing it inline — it keeps your own context window (your scarce resource) free for steering. Spawn independent units in PARALLEL; a wake with idle workers is wasted. Serialize only true dependencies, and never let two live children edit the same files.\n\n' +
1352
- 'HOW: `crtr node new "<task>" --kind <kind>` returns a node id immediately and runs the worker in a background window. Match the kind to the work (see `node new -h`). You are woken when a child finishes — the wake message ALREADY IS the coalesced digest (the watcher drains your inbox to wake you), so don\'t re-run `crtr feed read` to "open" it (it would read empty, the cursor already advanced); instead dereference the report paths in that digest that matter, don\'t act on a one-line label. (`crtr feed read` is for proactively polling before a wake, or inspecting a child\'s inbox via `--node`; `--all` re-reads history with full message bodies.) Integrate, then either delegate the next units or finish.\n\n' +
1351
+ 'HOW: `crtr node new "<task>" --kind <kind>` returns a node id immediately and runs the worker as a detached headless broker (no window opens; open a viewer on demand with `crtr node focus`). Match the kind to the work (see `node new -h`). You are woken when a child finishes — the wake message ALREADY IS the coalesced digest (the watcher drains your inbox to wake you), so don\'t re-run `crtr feed read` to "open" it (it would read empty, the cursor already advanced); instead dereference the report paths in that digest that matter, don\'t act on a one-line label. (`crtr feed read` is for proactively polling before a wake, or inspecting a child\'s inbox via `--node`; `--all` re-reads history with full message bodies.) Integrate, then either delegate the next units or finish.\n\n' +
1353
1352
  'FINISH: a worker ends its own work with `crtr push final "<result>"` (writes the canonical result, marks done, closes the window) — stopping without it is not finishing. For a job too big for one context window, `node promote` to an orchestrator (holds a roadmap, delegates phases); when context fills, `node yield` to refresh against that roadmap.',
1354
1353
  },
1355
1354
  children: [nodeNew, nodeInspect, nodeFocus, nodeCycle, nodeRecycle, nodeClose, nodeMsg, nodeSubscribe, nodeUnsubscribe, nodePromote, nodeDemote, nodeLifecycle, nodeYield, nodeWake],
@@ -4,7 +4,6 @@
4
4
  import { defineBranch } from '../core/command.js';
5
5
  import { pluginBranch } from './pkg/plugin.js';
6
6
  import { marketBranch } from './pkg/market.js';
7
- import { bridgeBranch } from './pkg/bridge.js';
8
7
  export function registerPkg() {
9
8
  return defineBranch({
10
9
  name: 'pkg',
@@ -17,6 +16,6 @@ export function registerPkg() {
17
16
  name: 'pkg',
18
17
  summary: 'manage plugins and plugin marketplaces',
19
18
  },
20
- children: [pluginBranch, marketBranch, bridgeBranch],
19
+ children: [pluginBranch, marketBranch],
21
20
  });
22
21
  }
@@ -6,6 +6,7 @@
6
6
  import { defineLeaf } from '../core/command.js';
7
7
  import { InputError } from '../core/io.js';
8
8
  import { reviveNode } from '../core/runtime/revive.js';
9
+ import { waitForBrokerViewSocket } from '../core/runtime/placement.js';
9
10
  import { getNode } from '../core/canvas/index.js';
10
11
  // ---------------------------------------------------------------------------
11
12
  // revive node
@@ -34,14 +35,16 @@ export const reviveLeaf = defineLeaf({
34
35
  },
35
36
  ],
36
37
  output: [
37
- { name: 'window', type: 'string', required: false, constraint: 'New tmux window id.' },
38
- { name: 'session', type: 'string', required: true, constraint: 'Tmux session the node was placed in.' },
38
+ { name: 'window', type: 'string', required: false, constraint: 'Always null — the revived broker is headless and opens no tmux window. Kept for caller back-compat.' },
39
+ { name: 'session', type: 'string', required: false, constraint: 'The node\'s last live location session, or null — the headless broker has no tmux session of its own.' },
39
40
  { name: 'resumed', type: 'boolean', required: true, constraint: 'True when pi was told to --session the saved conversation.' },
41
+ { name: 'ready', type: 'boolean', required: true, constraint: 'True when the revived broker\'s view.sock accepted a connection before return — the node is immediately attachable/drivable.' },
40
42
  ],
41
43
  outputKind: 'object',
42
44
  effects: [
43
- 'Opens a background (non-focus-stealing) tmux window running pi.',
45
+ 'Launches the node\'s detached headless broker engine (no tmux window).',
44
46
  'Updates the node\'s canvas record: status=active, intent=null, window=<new>.',
47
+ 'Blocks until the broker\'s view.sock accepts a connection (up to ~30s), so a caller can attach/dial immediately on return.',
45
48
  ],
46
49
  },
47
50
  run: async (input) => {
@@ -57,10 +60,15 @@ export const reviveLeaf = defineLeaf({
57
60
  });
58
61
  }
59
62
  const result = reviveNode(nodeId, { resume: !fresh });
63
+ // Revive returns once the broker process is launched, which can be seconds
64
+ // before its view.sock listens. Callers (attach, the web shell's Wake) dial
65
+ // immediately on return, so block here until the socket accepts.
66
+ const ready = waitForBrokerViewSocket(nodeId);
60
67
  return {
61
68
  window: result.window ?? undefined,
62
69
  session: result.session,
63
70
  resumed: result.resumed,
71
+ ready,
64
72
  };
65
73
  },
66
74
  });
@@ -0,0 +1 @@
1
+ export declare const sysFeedbackLeaf: import("../../core/command.js").LeafDef;