@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
@@ -0,0 +1,171 @@
1
+ // pickers.ts — native interactive pickers for `crtr attach`, built against the
2
+ // operator-view wire contract (broker read-op `data` frames). Each builder turns
3
+ // a serialized picker payload into the REAL pi `*SelectorComponent`, wired so a
4
+ // selection dispatches the SAME command frame the text-arg slash path sends and
5
+ // `close()` tears the overlay down. InputController owns the overlay lifecycle
6
+ // (showOverlay/dismiss/focus) and the async data fetch; these are pure builders.
7
+ //
8
+ // FOCUS (load-bearing): some selectors put `handleInput` on an INNER list, not
9
+ // the outer Container (UserMessageSelectorComponent → getMessageList(),
10
+ // SettingsSelectorComponent → getSettingsList()). pi-tui drops every keystroke
11
+ // when the focused component has no `handleInput`, so each builder returns BOTH
12
+ // the overlay `component` AND the `focus` target the caller must focus — exactly
13
+ // as pi's own interactive-mode does (`{ component, focus }`).
14
+ //
15
+ // R1 (contract): the tree/fork/settings payloads are PURE DATA — their selectors
16
+ // build verbatim, no reconstruction. The MODEL picker's `ModelSelectorComponent`
17
+ // needs a live `ModelRegistry`/`SettingsManager`: we feed the REAL component a
18
+ // minimal in-memory `SettingsManager` plus a tiny `ModelRegistry` adapter (the
19
+ // component calls only refresh/getError/getAvailable/find — verified against
20
+ // model-selector.js), preserving its full search + scope-toggle UI without
21
+ // crossing engine state over the socket. The SESSION picker reconstructs
22
+ // faithfully from async loaders (no shim). The SCOPED-MODELS component is NOT
23
+ // re-exported by pi (through 0.79.1) AND has no mutation frame in the contract,
24
+ // so it renders read-only via a pi-tui SelectList.
25
+ //
26
+ // pi pin is `^0.79.1` (a RANGE): the duck-typed registry adapter relies on the
27
+ // component's current 4-method registry surface — a future 0.79.x adding a
28
+ // registry call would surface at runtime (caught by live verification).
29
+ import { SelectList, } from '@earendil-works/pi-tui';
30
+ import { getSelectListTheme, ModelSelectorComponent, SessionSelectorComponent, SettingsSelectorComponent, TreeSelectorComponent, UserMessageSelectorComponent, SettingsManager, } from '@earendil-works/pi-coding-agent';
31
+ /** A minimal `ModelRegistry` for `ModelSelectorComponent`: the component calls
32
+ * only `refresh()`, `getError()`, `getAvailable()`, and `find()` (verified
33
+ * against pi `model-selector.js`). We satisfy exactly those from the wire
34
+ * payload — no auth/registry state crosses the socket — and cast to the nominal
35
+ * class the ctor wants. */
36
+ function modelRegistryAdapter(all, available) {
37
+ const adapter = {
38
+ refresh() { },
39
+ getError() {
40
+ return undefined;
41
+ },
42
+ getAvailable() {
43
+ return available;
44
+ },
45
+ find(provider, id) {
46
+ return all.find((m) => m.provider === provider && m.id === id);
47
+ },
48
+ };
49
+ return adapter;
50
+ }
51
+ /** `/model` (and ctrl+l) — the real model selector with search + scope toggle.
52
+ * Select → `set_model` with the resolved `provider/id` (the broker's
53
+ * `findModelSpec` requires that form). */
54
+ export function buildModelPicker(tui, data, send, close) {
55
+ const all = data.models;
56
+ const availableSet = new Set(data.availableIds);
57
+ const available = all.filter((m) => availableSet.has(`${m.provider}/${m.id}`));
58
+ const current = data.current
59
+ ? all.find((m) => m.provider === data.current.provider && m.id === data.current.id)
60
+ : undefined;
61
+ const component = new ModelSelectorComponent(tui, current, SettingsManager.inMemory(), modelRegistryAdapter(all, available), data.scopedModels, (model) => {
62
+ send({ type: 'set_model', model: `${model.provider}/${model.id}` });
63
+ close();
64
+ }, () => close());
65
+ return { component, focus: component };
66
+ }
67
+ /** `/resume` — the real session selector. Reconstructs from async loaders that
68
+ * fetch each scope on demand (cwd is pre-fetched so the current-session marker
69
+ * shows immediately; the `all` scope loads only on toggle). Select →
70
+ * `switch_session`. Rename/delete are out of scope (no command frame). */
71
+ export function buildSessionPicker(tui, prefetchedCwd, request, keybindings, send, close) {
72
+ const revive = (d) => d.sessions.map((s) => ({ ...s, created: new Date(s.created), modified: new Date(s.modified) }));
73
+ let firstCwd = true;
74
+ const currentLoader = async () => {
75
+ if (firstCwd) {
76
+ firstCwd = false;
77
+ return revive(prefetchedCwd);
78
+ }
79
+ return revive((await request({ type: 'list_sessions', scope: 'cwd' })));
80
+ };
81
+ const allLoader = async () => revive((await request({ type: 'list_sessions', scope: 'all' })));
82
+ const component = new SessionSelectorComponent(currentLoader, allLoader, (path) => {
83
+ send({ type: 'switch_session', path });
84
+ close();
85
+ }, () => close(), () => close(), () => tui.requestRender(), { showRenameHint: false, keybindings: keybindings }, prefetchedCwd.currentSessionFile);
86
+ return { component, focus: component };
87
+ }
88
+ /** `/tree` — the real tree navigator (pure data). Select → `navigate_tree`.
89
+ * Label-edit is out of scope (no command frame) → no `onLabelChange`. */
90
+ export function buildTreePicker(tui, data, send, close) {
91
+ const terminalHeight = process.stdout.rows ?? 24;
92
+ const component = new TreeSelectorComponent(data.tree, data.currentLeafId, terminalHeight, (entryId) => {
93
+ send({ type: 'navigate_tree', targetId: entryId });
94
+ close();
95
+ }, () => close());
96
+ return { component, focus: component };
97
+ }
98
+ /** `/fork` — the real prior-user-message selector (pure data, from
99
+ * `get_tree.forkPoints`). Select → `fork`. The outer component has no
100
+ * `handleInput`; the inner `getMessageList()` does (focus target). */
101
+ export function buildForkPicker(data, send, close) {
102
+ const component = new UserMessageSelectorComponent(data.forkPoints, (entryId) => {
103
+ send({ type: 'fork', entryId });
104
+ close();
105
+ }, () => close());
106
+ return { component, focus: component.getMessageList() };
107
+ }
108
+ /** `/settings` — the real settings menu (the wire `settings` IS a SettingsConfig
109
+ * superset). Only the contract-enumerated mutations are wired: thinking level →
110
+ * `set_thinking_level`, auto-compact → `set_auto_compaction`. Every other toggle
111
+ * has no command frame (out of scope), so its callback notifies rather than
112
+ * silently lying that the change took. The theme submenu is viewer-local (CTO).
113
+ * The outer component has no `handleInput`; `getSettingsList()` is the focus
114
+ * target. */
115
+ export function buildSettingsPicker(data, send, close, notify) {
116
+ const out = () => notify('Only thinking level + auto-compaction are adjustable over view.sock');
117
+ const callbacks = {
118
+ onThinkingLevelChange: (level) => send({ type: 'set_thinking_level', level }),
119
+ onAutoCompactChange: (enabled) => send({ type: 'set_auto_compaction', enabled }),
120
+ onCancel: () => close(),
121
+ onShowImagesChange: out,
122
+ onImageWidthCellsChange: out,
123
+ onAutoResizeImagesChange: out,
124
+ onBlockImagesChange: out,
125
+ onEnableSkillCommandsChange: out,
126
+ onSteeringModeChange: out,
127
+ onFollowUpModeChange: out,
128
+ onTransportChange: out,
129
+ onHttpIdleTimeoutMsChange: out,
130
+ onThemeChange: out,
131
+ onHideThinkingBlockChange: out,
132
+ onCollapseChangelogChange: out,
133
+ onEnableInstallTelemetryChange: out,
134
+ onDoubleEscapeActionChange: out,
135
+ onTreeFilterModeChange: out,
136
+ onShowHardwareCursorChange: out,
137
+ onEditorPaddingXChange: out,
138
+ onAutocompleteMaxVisibleChange: out,
139
+ onQuietStartupChange: out,
140
+ onDefaultProjectTrustChange: out,
141
+ onClearOnShrinkChange: out,
142
+ onShowTerminalProgressChange: out,
143
+ onWarningsChange: out,
144
+ };
145
+ const component = new SettingsSelectorComponent(data.settings, callbacks);
146
+ return { component, focus: component.getSettingsList() };
147
+ }
148
+ /** `/scoped-models` — READ-ONLY via a pi-tui SelectList. CONTRACT GAP: pi (through
149
+ * 0.79.1) does not re-export `ScopedModelsSelectorComponent`, and the contract
150
+ * exposes no command frame for its enable/disable/persist mutations, so a
151
+ * faithful editable picker is not buildable in scope. This shows the registry
152
+ * with the enabled set marked; select notifies that toggling is unsupported. */
153
+ export function buildScopedModelsPicker(data, close, notify) {
154
+ const enabled = data.enabledModelIds;
155
+ const isEnabled = (m) => {
156
+ if (enabled === null)
157
+ return true; // null = no scoping, every model cycles
158
+ const ref = `${m.provider}/${m.id}`;
159
+ return enabled.includes(ref) || enabled.includes(m.id) || enabled.includes('*');
160
+ };
161
+ const items = data.allModels.map((m) => ({
162
+ value: `${m.provider}/${m.id}`,
163
+ label: `${isEnabled(m) ? '✓' : ' '} ${m.provider}/${m.id}`,
164
+ description: m.name,
165
+ }));
166
+ const theme = getSelectListTheme();
167
+ const list = new SelectList(items, 12, theme);
168
+ list.onSelect = () => notify("Enabling/disabling scoped models isn't supported over view.sock yet");
169
+ list.onCancel = () => close();
170
+ return { component: list, focus: list };
171
+ }
@@ -12,7 +12,49 @@ export interface SlashContext {
12
12
  state?: BrokerSnapshot['state'];
13
13
  /** cwd for the default `/export` path; defaults to `process.cwd()`. */
14
14
  cwd?: string;
15
+ /** The canvas node this viewer is attached to — used by `/promote` (passed
16
+ * as `--node`). Falls back to `CRTR_NODE_ID` when absent; if neither is set
17
+ * `/promote` notifies and no-ops. (Unit Q wires this from `runAttach`.) */
18
+ nodeId?: string;
19
+ /** Toggle the in-process GRAPH overlay (the local subscription-tree view).
20
+ * `/graph` is inherently an in-viewer overlay, not a tmux popup, so this unit
21
+ * registers `/graph` as a stub that calls this hook. Unit C/Q owns the overlay
22
+ * itself and populates this; when absent `/graph` notifies it isn't wired yet. */
23
+ onGraph?: () => void;
24
+ /** Detach this viewer (`/quit`). A viewer quit DETACHES — it tears down the
25
+ * local TUI/socket and leaves the shared engine running. Wired by attach-cmd to
26
+ * `teardown('detach')`; without it `/quit` cannot actually exit (just sending
27
+ * `bye` makes the broker drop the socket, which the viewer treats as a dropped
28
+ * connection and reconnects). */
29
+ onQuit?: () => void;
30
+ /** Copy the last assistant message to the clipboard (`/copy`). Wired by
31
+ * attach-cmd (it owns the ChatView that holds the text + the clipboard writer). */
32
+ onCopy?: () => void;
33
+ /** OPTIONAL native-picker openers — the InputController fills these from its
34
+ * read-op request channel. When absent (channel unwired) the value-picker
35
+ * builtins fall back to the text-arg notice. */
36
+ openModelPicker?: () => void;
37
+ openSessionPicker?: () => void;
38
+ openForkPicker?: () => void;
39
+ openTreePicker?: () => void;
40
+ openSettingsPicker?: () => void;
41
+ openScopedModelsPicker?: () => void;
42
+ /** Open the native /login provider selector. */
43
+ openLoginPicker?: () => void;
44
+ /** Open the native /logout provider selector. */
45
+ openLogoutPicker?: () => void;
15
46
  }
47
+ /** Canvas slash-commands — reimplemented NATIVE in the viewer (the canvas chrome
48
+ * pi-extensions cannot load here; see findings-chrome-parity.md Feature 3). Each
49
+ * ports the action of its extension: `/promote` ← canvas-commands.ts, `/resume-node`
50
+ * ← canvas-resume.ts, `/view` ← canvas-view.ts, `/graph` ← canvas-nav.ts. Three of
51
+ * them shell a `crtr` subcommand via `tmux display-popup` (the viewer runs inside a
52
+ * tmux pane, so this works exactly as in the extensions); `/graph` is a stub for the
53
+ * overlay (Unit C/Q). Exported so Unit Q can fold these into the autocomplete list. */
54
+ export declare const CANVAS_SLASH_COMMANDS: ReadonlyArray<{
55
+ name: string;
56
+ description: string;
57
+ }>;
16
58
  /** True if `text` is a leading-slash command (vs. a normal prompt). */
17
59
  export declare function isSlashCommand(text: string): boolean;
18
60
  /**
@@ -24,7 +66,8 @@ export declare function isSlashCommand(text: string): boolean;
24
66
  export declare function dispatchSlashCommand(text: string, ctx: SlashContext): boolean;
25
67
  /** Build slash-command autocomplete entries from the merged command list (the
26
68
  * broker's `get_commands` result, which T7 may inject) — defaults to the
27
- * vendored builtins. Shaped for pi-tui's `CombinedAutocompleteProvider`. */
69
+ * vendored builtins. The native canvas commands are always appended. Shaped for
70
+ * pi-tui's `CombinedAutocompleteProvider`. */
28
71
  export declare function slashCommandList(commands?: ReadonlyArray<{
29
72
  name: string;
30
73
  description?: string;
@@ -7,24 +7,39 @@
7
7
  // doesn't recognize) fall through to be sent as a `prompt` for the engine's
8
8
  // extension runner to handle.
9
9
  //
10
- // SELECTORS — the Phase-4 reality. The plan called for `/model`, `/resume`,
11
- // `/fork`, `/tree`, `/settings` to open pi's exported `*SelectorComponent`s. The
12
- // authoritative 0.78.1 .d.ts shows those components require ENGINE-side state a
13
- // socket-only viewer does not have (`ModelSelectorComponent` needs the
14
- // `ModelRegistry` + `SettingsManager`; `SessionSelectorComponent` needs session
15
- // loaders; `TreeSelectorComponent` needs the `SessionTreeNode[]` tree), and the
16
- // §1.2 command-frame floor set exposes NO read op to fetch any of it. So in
17
- // Phase 4 these resolve via a TEXT ARGUMENT `/model <id>`, `/resume <path>`,
18
- // `/fork <entryId>`, `/tree <entryId>` which sends the SAME resolved value the
19
- // selector would have, i.e. zero engine capability is lost; only the interactive
20
- // picker UI waits for a future read op. A no-arg invocation shows a one-line
21
- // notice with the arg form. (This protocol gap is reported up to the parent.)
10
+ // SELECTORS — native interactive pickers. `/model`, `/resume`, `/fork`, `/tree`,
11
+ // `/settings`, `/scoped-models` open pi's real `*SelectorComponent`s as overlays,
12
+ // fed by the operator-view read-op `data` frames (broker-protocol) over the
13
+ // socket. With no arg they open the picker (via the `openXPicker` SlashContext
14
+ // callbacks the InputController exposes, fed by its read-op request channel —
15
+ // which attach-cmd MUST wire via `onRequest: (f) => socket.request(f)`). With an
16
+ // arg the same resolved value is sent directly (`/model <provider/id>`, `/resume
17
+ // <path>`, `/fork <entryId>`, `/tree <entryId>`) without the UI. If the request
18
+ // channel is unwired the picker degrades to the arg-form notice. (Verified
19
+ // against pi 0.79.1 `ScopedModelsSelectorComponent` is the one selector pi does
20
+ // not re-export, so /scoped-models renders read-only via a SelectList.)
22
21
  //
23
22
  // SCOPED OUT (review m2 — no engine method): `/login`, `/logout`, `/share`,
24
23
  // `/trust` — a brief "not supported in attach" notice, never a frame.
24
+ import { execFile } from 'node:child_process';
25
25
  import { BUILTIN_SLASH_COMMANDS } from '../../core/runtime/pi-vendored.js';
26
+ import { surfaceTmuxStyleArgs } from '../../core/runtime/surface-bg.js';
27
+ /** Canvas slash-commands — reimplemented NATIVE in the viewer (the canvas chrome
28
+ * pi-extensions cannot load here; see findings-chrome-parity.md Feature 3). Each
29
+ * ports the action of its extension: `/promote` ← canvas-commands.ts, `/resume-node`
30
+ * ← canvas-resume.ts, `/view` ← canvas-view.ts, `/graph` ← canvas-nav.ts. Three of
31
+ * them shell a `crtr` subcommand via `tmux display-popup` (the viewer runs inside a
32
+ * tmux pane, so this works exactly as in the extensions); `/graph` is a stub for the
33
+ * overlay (Unit C/Q). Exported so Unit Q can fold these into the autocomplete list. */
34
+ export const CANVAS_SLASH_COMMANDS = [
35
+ { name: 'graph', description: 'Toggle the canvas GRAPH view (your local subscription tree)' },
36
+ { name: 'promote', description: 'Promote this node to an orchestrator — /promote, or /promote <kind> to specialize' },
37
+ { name: 'resume-node', description: 'Open the canvas navigator (search/scope/sort/tree) and resume the chosen node' },
38
+ { name: 'view', description: 'Open a view in a popup — bare for the picker, or /view <name> to open that view directly' },
39
+ ];
40
+ const CANVAS_NAMES = new Set(CANVAS_SLASH_COMMANDS.map((c) => c.name));
26
41
  /** Builtins with no Phase-4 engine method — scoped out (review m2). */
27
- const SCOPED_OUT = new Set(['login', 'logout', 'share', 'trust']);
42
+ const SCOPED_OUT = new Set(['trust']);
28
43
  /** Valid `/settings thinking` levels — the `SetThinkingLevelFrame['level']`
29
44
  * union (= AgentSession['thinkingLevel'], pi-agent-core ThinkingLevel),
30
45
  * mirrored as a runtime set so an invalid arg is rejected with the options
@@ -60,6 +75,10 @@ export function dispatchSlashCommand(text, ctx) {
60
75
  ctx.notify(`/${name} is not supported in attach (Phase 4)`);
61
76
  return true;
62
77
  }
78
+ // Canvas commands (reimplemented native in the viewer) — handled before the
79
+ // builtin gate since they are neither pi builtins nor engine-extension commands.
80
+ if (CANVAS_NAMES.has(name))
81
+ return dispatchCanvasCommand(name, arg, ctx);
63
82
  // Unknown to us AND not a builtin → let the engine's extension runner have it.
64
83
  if (!BUILTIN_NAMES.has(name))
65
84
  return false;
@@ -76,6 +95,13 @@ export function dispatchSlashCommand(text, ctx) {
76
95
  return true;
77
96
  case 'quit':
78
97
  // A viewer "quit" DETACHES — the shared engine keeps running (one-writer).
98
+ // onQuit tears the viewer down locally (which sends `bye` itself). Falling
99
+ // back to a bare `bye` would just make the broker close the socket, which
100
+ // the viewer reconnects to — so the detach MUST go through onQuit.
101
+ if (ctx.onQuit) {
102
+ ctx.onQuit();
103
+ return true;
104
+ }
79
105
  ctx.send({ type: 'bye' });
80
106
  ctx.notify('Detaching — the engine keeps running');
81
107
  return true;
@@ -97,27 +123,34 @@ export function dispatchSlashCommand(text, ctx) {
97
123
  return usage(ctx, '/import <path-to-session.jsonl>');
98
124
  ctx.send({ type: 'switch_session', path: arg });
99
125
  return true;
100
- // --- value-picker builtins: arg form (selector deferred, see header) ---
126
+ // --- value-picker builtins: native picker (no arg) OR direct value (arg) ---
101
127
  case 'model':
102
128
  if (!arg)
103
- return pickerHint(ctx, 'model', '/model <id>');
129
+ return openOrHint(ctx.openModelPicker, ctx, 'model', '/model <provider/id>');
104
130
  ctx.send({ type: 'set_model', model: arg });
105
131
  return true;
106
132
  case 'resume':
107
133
  if (!arg)
108
- return pickerHint(ctx, 'session', '/resume <session-path>');
134
+ return openOrHint(ctx.openSessionPicker, ctx, 'session', '/resume <session-path>');
109
135
  ctx.send({ type: 'switch_session', path: arg });
110
136
  return true;
111
137
  case 'fork':
112
138
  if (!arg)
113
- return pickerHint(ctx, 'fork point', '/fork <entryId>');
139
+ return openOrHint(ctx.openForkPicker, ctx, 'fork point', '/fork <entryId>');
114
140
  ctx.send({ type: 'fork', entryId: arg });
115
141
  return true;
116
142
  case 'tree':
117
143
  if (!arg)
118
- return pickerHint(ctx, 'tree', '/tree <entryId>');
144
+ return openOrHint(ctx.openTreePicker, ctx, 'tree', '/tree <entryId>');
119
145
  ctx.send({ type: 'navigate_tree', targetId: arg });
120
146
  return true;
147
+ case 'scoped-models':
148
+ if (ctx.openScopedModelsPicker) {
149
+ ctx.openScopedModelsPicker();
150
+ return true;
151
+ }
152
+ ctx.notify('The scoped-models view needs the read-op channel, which this viewer has not wired');
153
+ return true;
121
154
  // --- settings (sub-command form; interactive menu needs read ops) ------
122
155
  case 'settings':
123
156
  return handleSettings(arg, ctx);
@@ -125,12 +158,44 @@ export function dispatchSlashCommand(text, ctx) {
125
158
  case 'session':
126
159
  ctx.notify(describeSession(ctx.state));
127
160
  return true;
128
- // --- recognized builtins with no Phase-4 wire path --------------------
129
161
  case 'copy':
162
+ // Copy the last assistant message to the clipboard — viewer-owned (the text
163
+ // is in the ChatView; no engine round-trip). attach-cmd does the actual copy.
164
+ if (ctx.onCopy) {
165
+ ctx.onCopy();
166
+ return true;
167
+ }
168
+ ctx.notify('/copy is not available in this viewer');
169
+ return true;
170
+ // --- auth commands — viewer-local OAuth/API-key flows ------------------
171
+ case 'login':
172
+ if (ctx.openLoginPicker) {
173
+ ctx.openLoginPicker();
174
+ return true;
175
+ }
176
+ ctx.notify('/login is not available in this viewer');
177
+ return true;
178
+ case 'logout':
179
+ if (ctx.openLogoutPicker) {
180
+ ctx.openLogoutPicker();
181
+ return true;
182
+ }
183
+ ctx.notify('/logout is not available in this viewer');
184
+ return true;
185
+ // --- share / clone / changelog -----------------------------------------
186
+ case 'share':
187
+ ctx.send({ type: 'share' });
188
+ ctx.notify('Sharing session…');
189
+ return true;
190
+ case 'clone':
191
+ ctx.send({ type: 'clone' });
192
+ ctx.notify('Cloning session…');
193
+ return true;
130
194
  case 'changelog':
195
+ ctx.notify('/changelog: see https://github.com/earendil-works/pi for release notes (no bundled changelog in this build)');
196
+ return true;
197
+ // --- recognized builtins with no Phase-4 wire path --------------------
131
198
  case 'hotkeys':
132
- case 'scoped-models':
133
- case 'clone':
134
199
  ctx.notify(`/${name} is not available in attach (Phase 4)`);
135
200
  return true;
136
201
  default:
@@ -140,15 +205,153 @@ export function dispatchSlashCommand(text, ctx) {
140
205
  return true;
141
206
  }
142
207
  }
208
+ /** Append the native canvas commands to a command list, skipping any already
209
+ * present (so they survive whatever list Unit Q feeds in — builtins, or the
210
+ * broker's `get_commands` result). The four canvas commands are viewer-owned,
211
+ * so they are always surfaced in autocomplete regardless of the source list. */
212
+ function withCanvasCommands(commands) {
213
+ const seen = new Set(commands.map((c) => c.name));
214
+ return [...commands, ...CANVAS_SLASH_COMMANDS.filter((c) => !seen.has(c.name))];
215
+ }
143
216
  /** Build slash-command autocomplete entries from the merged command list (the
144
217
  * broker's `get_commands` result, which T7 may inject) — defaults to the
145
- * vendored builtins. Shaped for pi-tui's `CombinedAutocompleteProvider`. */
218
+ * vendored builtins. The native canvas commands are always appended. Shaped for
219
+ * pi-tui's `CombinedAutocompleteProvider`. */
146
220
  export function slashCommandList(commands = BUILTIN_SLASH_COMMANDS) {
147
- return commands.map((c) => ({ name: c.name, description: c.description }));
221
+ return withCanvasCommands(commands).map((c) => ({ name: c.name, description: c.description }));
148
222
  }
149
223
  /** Same list as flat autocomplete items (value/label/description). */
150
224
  export function commandAutocompleteItems(commands = BUILTIN_SLASH_COMMANDS) {
151
- return commands.map((c) => ({ value: `/${c.name}`, label: `/${c.name}`, description: c.description }));
225
+ return withCanvasCommands(commands).map((c) => ({ value: `/${c.name}`, label: `/${c.name}`, description: c.description }));
226
+ }
227
+ // ---------------------------------------------------------------------------
228
+ // Canvas commands — native reimplementations of the canvas chrome extensions.
229
+ // ---------------------------------------------------------------------------
230
+ /** Dispatch one of the four native canvas commands. Always returns `true` (the
231
+ * command is viewer-owned — never forwarded to the engine as a prompt). */
232
+ function dispatchCanvasCommand(name, arg, ctx) {
233
+ switch (name) {
234
+ case 'graph':
235
+ // `/graph` is inherently the in-process GRAPH overlay (canvas-nav.ts
236
+ // `toggleGraph`), NOT a tmux popup. The overlay is Unit C/Q's surface; this
237
+ // unit registers the command + autocomplete entry and triggers the overlay
238
+ // via the `onGraph` hook the integration unit wires in.
239
+ if (ctx.onGraph)
240
+ ctx.onGraph();
241
+ else
242
+ ctx.notify('The GRAPH overlay is not wired into the viewer yet');
243
+ return true;
244
+ case 'promote':
245
+ promoteNode(arg, ctx);
246
+ return true;
247
+ case 'resume-node':
248
+ // ← canvas-resume.ts: open the full-screen canvas navigator as a popup; on
249
+ // Enter it focuses the chosen node back into THIS pane via `crtr node focus`.
250
+ return resumeNode(ctx);
251
+ case 'view':
252
+ // ← canvas-view.ts: bare `/view` opens the picker, `/view <name>` opens that
253
+ // view directly — each as a self-contained popup (no node-focus, no return-pane).
254
+ return openView(arg, ctx);
255
+ default:
256
+ ctx.notify(`/${name} is not available in attach`);
257
+ return true;
258
+ }
259
+ }
260
+ /** `/promote [kind]` ← canvas-commands.ts. Shells `crtr node promote --json` for
261
+ * this node (out-of-process), notifies the result, then triggers a turn so the
262
+ * persona injector steers in the new-role guidance at the turn boundary — exactly
263
+ * as the node would by running the command itself. */
264
+ function promoteNode(arg, ctx) {
265
+ const nodeId = (ctx.nodeId ?? process.env['CRTR_NODE_ID'] ?? '').trim();
266
+ if (nodeId === '') {
267
+ ctx.notify('/promote: no node to promote (viewer has no node id)');
268
+ return;
269
+ }
270
+ const kind = arg.trim().toLowerCase();
271
+ ctx.notify(kind ? `Promoting → ${kind}…` : 'Promoting…');
272
+ const argv = ['node', 'promote', '--node', nodeId, '--json'];
273
+ if (kind !== '')
274
+ argv.push('--kind', kind);
275
+ execFile('crtr', argv, { timeout: 15_000, maxBuffer: 4 * 1024 * 1024 }, (err, stdout) => {
276
+ // On a non-zero exit crtr still prints the structured error to stdout, so
277
+ // prefer its `message` over the raw throw.
278
+ let result = null;
279
+ let errMsg = null;
280
+ try {
281
+ result = JSON.parse(stdout);
282
+ }
283
+ catch {
284
+ result = null;
285
+ }
286
+ if (err && result === null) {
287
+ const out = typeof err.stdout === 'string' ? err.stdout : '';
288
+ try {
289
+ const payload = JSON.parse(out);
290
+ errMsg = typeof payload.message === 'string' ? payload.message : null;
291
+ }
292
+ catch {
293
+ errMsg = null;
294
+ }
295
+ if (errMsg === null)
296
+ errMsg = err.message;
297
+ }
298
+ if (result === null) {
299
+ ctx.notify(`promote failed: ${errMsg ?? 'unknown error'}`);
300
+ return;
301
+ }
302
+ const rmPath = (result.roadmap_path ?? '').trim();
303
+ ctx.notify(`Promoted to ${result.kind ?? 'orchestrator'} orchestrator — authoring roadmap${rmPath !== '' ? ` (${rmPath})` : ''}.`);
304
+ // Trigger a turn so the persona injector (canvas-stophook turn_end) fires and
305
+ // delivers the new-role guidance — the viewer's only turn-trigger is `prompt`.
306
+ ctx.send({
307
+ type: 'prompt',
308
+ text: 'You have just been promoted to an orchestrator. Your new-role guidance is arriving — read it, author your roadmap, and start delegating.',
309
+ });
310
+ });
311
+ }
312
+ /** `/resume-node` ← canvas-resume.ts. Opens `crtr canvas browse` as a tmux popup,
313
+ * scoped to this node's cwd; on Enter it focuses the chosen node back into this
314
+ * pane via `crtr node focus --pane` (which `canvas browse` shells). */
315
+ function resumeNode(ctx) {
316
+ const origPane = process.env['TMUX_PANE'];
317
+ if (process.env['TMUX'] === undefined || origPane === undefined || origPane === '') {
318
+ ctx.notify('/resume-node needs tmux');
319
+ return true;
320
+ }
321
+ const cwd = shellQuote(ctx.cwd ?? process.cwd());
322
+ const cmd = `crtr canvas browse --return-pane ${origPane} --cwd ${cwd}`;
323
+ popup(cmd);
324
+ return true;
325
+ }
326
+ /** `/view [name]` ← canvas-view.ts. Bare opens the picker (`crtr view pick`);
327
+ * `/view <name>` opens that view directly (`crtr view run <name>`). Self-contained
328
+ * popup — no node-focus, no return-pane. */
329
+ function openView(arg, ctx) {
330
+ if (process.env['TMUX'] === undefined) {
331
+ ctx.notify('/view needs tmux');
332
+ return true;
333
+ }
334
+ const name = arg.trim();
335
+ const cmd = name === '' ? 'crtr view pick' : `crtr view run ${shellQuote(name)}`;
336
+ popup(cmd);
337
+ return true;
338
+ }
339
+ /** Open `cmd` as a fire-and-forget tmux display-popup (same geometry as the canvas
340
+ * extensions). tmux runs the trailing string through `sh -c`; the popup owns its
341
+ * screen and closes itself when the command exits, dropping back to this pane. */
342
+ function popup(cmd) {
343
+ try {
344
+ execFile('tmux', ['display-popup', '-E', '-w', '90%', '-h', '85%', ...surfaceTmuxStyleArgs(), cmd], () => {
345
+ /* best-effort: popup is self-contained */
346
+ });
347
+ }
348
+ catch {
349
+ /* best-effort */
350
+ }
351
+ }
352
+ /** Single-quote a string for safe interpolation into a `sh -c` command line. */
353
+ function shellQuote(s) {
354
+ return `'${s.replace(/'/g, `'\\''`)}'`;
152
355
  }
153
356
  // ---------------------------------------------------------------------------
154
357
  function usage(ctx, form) {
@@ -159,6 +362,16 @@ function pickerHint(ctx, what, form) {
159
362
  ctx.notify(`Interactive ${what} picker isn't available over view.sock yet — use ${form}`);
160
363
  return true;
161
364
  }
365
+ /** Open a native picker if its opener is wired; otherwise fall back to the
366
+ * text-arg hint. Keeps every value-picker builtin working even when the
367
+ * read-op request channel is absent. */
368
+ function openOrHint(open, ctx, what, form) {
369
+ if (open) {
370
+ open();
371
+ return true;
372
+ }
373
+ return pickerHint(ctx, what, form);
374
+ }
162
375
  function handleSettings(arg, ctx) {
163
376
  const [sub, ...rest] = arg.split(/\s+/);
164
377
  const value = rest.join(' ').trim();
@@ -178,12 +391,22 @@ function handleSettings(arg, ctx) {
178
391
  case 'auto-compaction':
179
392
  ctx.send({ type: 'set_auto_compaction', enabled: parseBool(value) });
180
393
  return true;
394
+ case '':
395
+ // Bare `/settings` opens the native settings menu (falls back to the
396
+ // sub-command summary when the picker channel is unwired).
397
+ if (ctx.openSettingsPicker) {
398
+ ctx.openSettingsPicker();
399
+ return true;
400
+ }
401
+ return settingsSummary(ctx);
181
402
  default:
182
- ctx.notify('Settings: /settings thinking <level> | auto-retry on|off | auto-compaction on|off ' +
183
- '(the interactive settings menu needs data not exposed over view.sock in Phase 4)');
184
- return true;
403
+ return settingsSummary(ctx);
185
404
  }
186
405
  }
406
+ function settingsSummary(ctx) {
407
+ ctx.notify('Settings: /settings thinking <level> | auto-retry on|off | auto-compaction on|off');
408
+ return true;
409
+ }
187
410
  function parseBool(value) {
188
411
  return /^(on|true|yes|1|enabled?)$/i.test(value.trim());
189
412
  }
@@ -0,0 +1,33 @@
1
+ import { CustomEditor } from '@earendil-works/pi-coding-agent';
2
+ /** Thinking levels pi cycles through (shift+tab), lowest → highest budget. */
3
+ export type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
4
+ /** The default (thinking `off`) title chip: reverse video + a space of padding
5
+ * each side, so the name reads as a label sitting on the border rule. Used as
6
+ * the fallback when no thinking color applies. */
7
+ export declare const defaultTitleStyle: (s: string) => string;
8
+ /** Resolve the editor border colorizer for a thinking level. Unknown / `off` →
9
+ * the supplied `fallback` (the theme's default border color). */
10
+ export declare function thinkingBorderColor(level: string | undefined, fallback: (s: string) => string): (s: string) => string;
11
+ /** Resolve the title-chip styler for a thinking level: the level's color as the
12
+ * chip BACKGROUND with bold white text (a space of padding each side), so the
13
+ * session name reads as a solid label in the same hue as the border. Unknown /
14
+ * `off` → the supplied `fallback` (the reverse-video default chip). */
15
+ export declare function thinkingTitleStyle(level: string | undefined, fallback: (s: string) => string): (s: string) => string;
16
+ /** Compose the replacement top-border line: solid title chip + border rule +
17
+ * info chip, never wider than `width` (pi-tui hard-crashes on an over-wide
18
+ * line). The info chip yields entirely when the chip leaves it almost no room,
19
+ * and truncates when it only partially fits. Exported pure for the overflow
20
+ * regression test. */
21
+ export declare function composeTopBorder(width: number, title: string, info: string, titleStyle: (s: string) => string, borderColor: (s: string) => string): string;
22
+ export declare class TitledEditor extends CustomEditor {
23
+ /** Session-name chip painted into the LEFT of the top border. Empty → plain. */
24
+ title: string;
25
+ /** Pre-styled context string painted into the RIGHT of the top border (cwd /
26
+ * branch / git status). Already colorized by the caller; empty → omitted. */
27
+ info: string;
28
+ /** Paint the chip solid so the name reads as a label sitting on the border
29
+ * rule. Defaults to the reverse-video chip; attach-cmd swaps in a
30
+ * thinking-colored background (bold white text) on each state update. */
31
+ titleStyle: (s: string) => string;
32
+ render(width: number): string[];
33
+ }