@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
@@ -12,41 +12,43 @@
12
12
  // is what calls `matchesKey` internally, so the controller wires intent via the
13
13
  // editor's `onSubmit`/`onEscape`/`onPasteImage` hooks + `onAction(app.*)`
14
14
  // registrations rather than matching raw keys itself.
15
+ import { spawn } from 'node:child_process';
16
+ import { readFileSync, unlinkSync, writeFileSync } from 'node:fs';
17
+ import { tmpdir } from 'node:os';
18
+ import { join } from 'node:path';
15
19
  import { BROKER_READ_CAPS, encodeFrame, } from '../../core/runtime/broker-protocol.js';
16
20
  import { renderDialog } from './extension-dialogs.js';
17
- import { readClipboardImage } from './clipboard-image.js';
21
+ import { readClipboardImage, writeClipboardImageToFile } from './clipboard-image.js';
18
22
  import { dispatchSlashCommand, isSlashCommand } from './slash-commands.js';
19
- /** Aggregate budget for images held for the next message (review M2). A drained
20
- * prompt/steer/follow_up frame inlines ALL pending images as base64 in a single
21
- * frame; these ceilings keep the images[] portion well within the broker's
22
- * 24 MiB line cap. Each clipboard image is 3 MiB base64 (clipboard-image
23
- * MAX_BYTES), so the worst case is MAX_PENDING_IMAGES × 3 MiB = 12 MiB. A paste
24
- * that would exceed EITHER bound is refused (not accumulated). The WHOLE frame
25
- * (images + unbounded text + JSON envelope) is then bounded airtight by the
26
- * MAX_FRAME_BYTES guard in `emitDrive` — this budget alone is not enough, since
27
- * `text` is otherwise unbounded. */
28
- const MAX_PENDING_IMAGE_BYTES = 16 * 1024 * 1024;
29
- const MAX_PENDING_IMAGES = 4;
23
+ import { buildForkPicker, buildModelPicker, buildScopedModelsPicker, buildSessionPicker, buildSettingsPicker, buildTreePicker, } from './pickers.js';
24
+ /** Overlay geometry for the native pickers a touch wider/taller than the
25
+ * extension-dialog default since the model/session/tree lists are denser. */
26
+ const PICKER_OVERLAY_OPTIONS = { anchor: 'center', width: '80%', maxHeight: '85%' };
27
+ /** Max gap between two Esc presses to count as a double-tap (pi uses 500ms). */
28
+ const DOUBLE_ESCAPE_MS = 500;
30
29
  /** Largest drive frame the controller will emit. The broker DESTROYS the viewer
31
30
  * socket on any client line over BROKER_READ_CAPS.maxLineBytes (24 MiB), so we
32
31
  * refuse to emit within a 4 MiB margin of that. `emitDrive` measures the ACTUAL
33
- * encoded frame (text + base64 images[] + JSON envelope), so the cap holds no
34
- * matter how the bytes split closing the gap where a large text paste plus
35
- * max images could otherwise overflow and tear down the socket. */
32
+ * encoded frame (text + JSON envelope), so the cap holds however the bytes
33
+ * split. Pasted images go to a temp file and enter the prompt as a path, never
34
+ * as inline base64, so a paste can no longer push a frame toward this ceiling. */
36
35
  const MAX_FRAME_BYTES = BROKER_READ_CAPS.maxLineBytes - 4 * 1024 * 1024;
37
36
  export class InputController {
38
37
  tui;
39
38
  editor;
40
39
  keybindings;
41
40
  hooks;
42
- /** Images pasted since the last send, attached to the next prompt/follow-up. */
43
- pendingImages = [];
44
- /** Running base64-byte total of `pendingImages` (review M2 aggregate budget). */
45
- pendingImageBytes = 0;
46
41
  /** The currently-rendered blocking dialog, if any (for supersede/dismiss). */
47
42
  dialog;
43
+ /** The currently-open native picker overlay, if any (for supersede/dismiss). */
44
+ picker;
45
+ /** Monotonic open counter — a slow read-op reply whose `seq` is no longer the
46
+ * latest is dropped, so an earlier picker never pops over a later request. */
47
+ openSeq = 0;
48
48
  /** Latest engine state from `welcome`/`session_info_changed` (for `/session`). */
49
49
  state;
50
+ /** Timestamp of the last lone Esc, for double-tap → tree detection (0 = none). */
51
+ lastEscapeAt = 0;
50
52
  constructor(tui, editor, keybindings, hooks) {
51
53
  this.tui = tui;
52
54
  this.editor = editor;
@@ -73,16 +75,52 @@ export class InputController {
73
75
  // -------------------------------------------------------------------------
74
76
  wire() {
75
77
  this.editor.onSubmit = (text) => this.handleSubmit(text);
76
- // Esc / app.interrupt abort the running turn.
77
- this.editor.onEscape = () => this.hooks.onCommand({ type: 'abort' });
78
- // Ctrl+V / Alt+V read clipboard image, hold it for the next message.
78
+ // Esc mirror pi's native interactive escape (interactive-mode
79
+ // setupKeyHandlers): while streaming, abort the turn; with an EMPTY editor
80
+ // and idle, a double-tap (≤500ms) opens the session-tree navigator to rewind
81
+ // to an earlier point (a lone tap just arms it); a non-empty editor swallows
82
+ // Esc (it never clears text). openTreePicker is the same get_tree →
83
+ // navigate_tree path that `/tree` and app.session.tree use.
84
+ this.editor.onEscape = () => {
85
+ if (this.state?.isStreaming) {
86
+ this.hooks.onCommand({ type: 'abort' });
87
+ return;
88
+ }
89
+ if (this.editor.getText().trim())
90
+ return;
91
+ const now = Date.now();
92
+ if (now - this.lastEscapeAt < DOUBLE_ESCAPE_MS) {
93
+ this.lastEscapeAt = 0;
94
+ void this.openTreePicker();
95
+ }
96
+ else {
97
+ this.lastEscapeAt = now;
98
+ }
99
+ };
100
+ // Ctrl+V / Alt+V → read clipboard image, save it to a temp file, and splice
101
+ // the file PATH into the editor at the caret (so the agent can read it).
79
102
  this.editor.onPasteImage = () => {
80
103
  void this.handlePaste();
81
104
  };
82
105
  // Keyboard shortcuts that map 1:1 to a frame needing no engine-side data.
83
106
  this.editor.onAction('app.session.new', () => this.hooks.onCommand({ type: 'new_session' }));
84
- this.editor.onAction('app.model.cycleForward', () => this.hooks.onCommand({ type: 'cycle_model' }));
107
+ this.editor.onAction('app.model.cycleForward', () => this.hooks.onCommand({ type: 'cycle_model', direction: 'forward' }));
108
+ this.editor.onAction('app.model.cycleBackward', () => this.hooks.onCommand({ type: 'cycle_model', direction: 'backward' }));
109
+ this.editor.onAction('app.thinking.cycle', () => this.hooks.onCommand({ type: 'cycle_thinking' }));
85
110
  this.editor.onAction('app.message.followUp', () => this.handleFollowUp());
111
+ this.editor.onAction('app.message.dequeue', () => void this.handleDequeue());
112
+ // Pure client-side render toggles (ChatView owns the components).
113
+ this.editor.onAction('app.tools.expand', () => this.hooks.onToggleToolsExpand?.());
114
+ this.editor.onAction('app.thinking.toggle', () => this.hooks.onToggleThinking?.());
115
+ // Ctrl+G — compose the prompt in $VISUAL/$EDITOR, then load it back.
116
+ this.editor.onAction('app.editor.external', () => void this.openExternalEditor());
117
+ // Native pickers via keybinding. `app.model.select` is bound to ctrl+l by
118
+ // default (vendored in config-load); the session.* actions ship with no
119
+ // default key but fire if the user binds one — wiring them keeps parity.
120
+ this.editor.onAction('app.model.select', () => void this.openModelPicker());
121
+ this.editor.onAction('app.session.tree', () => void this.openTreePicker());
122
+ this.editor.onAction('app.session.fork', () => void this.openForkPicker());
123
+ this.editor.onAction('app.session.resume', () => void this.openSessionPicker());
86
124
  }
87
125
  slashContext() {
88
126
  return {
@@ -90,13 +128,163 @@ export class InputController {
90
128
  notify: (message) => this.notify(message),
91
129
  state: this.state,
92
130
  cwd: process.cwd(),
131
+ nodeId: this.hooks.nodeId,
132
+ onGraph: this.hooks.onGraph,
133
+ onQuit: this.hooks.onQuit,
134
+ onCopy: this.hooks.onCopy,
135
+ openModelPicker: () => void this.openModelPicker(),
136
+ openSessionPicker: () => void this.openSessionPicker(),
137
+ openForkPicker: () => void this.openForkPicker(),
138
+ openTreePicker: () => void this.openTreePicker(),
139
+ openSettingsPicker: () => void this.openSettingsPicker(),
140
+ openScopedModelsPicker: () => void this.openScopedModelsPicker(),
141
+ openLoginPicker: this.hooks.openLoginPicker ? () => this.hooks.openLoginPicker() : undefined,
142
+ openLogoutPicker: this.hooks.openLogoutPicker ? () => this.hooks.openLogoutPicker() : undefined,
143
+ };
144
+ }
145
+ // -------------------------------------------------------------------------
146
+ // Native interactive pickers (overlays). Each fetches its payload via the
147
+ // correlated read-op, builds the REAL pi selector, and routes selection to the
148
+ // same command frame the text-arg slash path sends. Opened by slash command
149
+ // (via slashContext) AND by keybinding (onAction, below).
150
+ // -------------------------------------------------------------------------
151
+ /** Send a command frame to the broker (picker selection sink). */
152
+ send = (frame) => this.hooks.onCommand(frame);
153
+ /** Fetch a picker payload (narrowed on `kind`), then build + show its overlay
154
+ * iff this open is still the latest (m4: a stale slow reply is dropped). Any
155
+ * failure — unwired channel, wrong kind, request error, or a throwing builder
156
+ * — surfaces a notice instead of an unhandled rejection (m5). */
157
+ async openPicker(frame, kind, label, build) {
158
+ if (this.hooks.onRequest === undefined) {
159
+ this.notify(`The ${label} picker isn't available in this viewer`);
160
+ return;
161
+ }
162
+ const seq = ++this.openSeq;
163
+ let reply;
164
+ try {
165
+ reply = await this.hooks.onRequest(frame);
166
+ }
167
+ catch (err) {
168
+ this.notify(`Could not open the ${label} picker: ${err instanceof Error ? err.message : String(err)}`);
169
+ return;
170
+ }
171
+ if (seq !== this.openSeq)
172
+ return; // a newer picker open superseded this one
173
+ if (reply.kind !== kind) {
174
+ this.notify(`Unexpected reply building the ${label} picker`);
175
+ return;
176
+ }
177
+ this.showPicker((controls) => build(reply, controls.close));
178
+ }
179
+ /** Show a picker built by `build` (given a `close` that tears it down).
180
+ * Mounted INLINE under the editor when attach-cmd wires onMountPicker/
181
+ * onUnmountPicker (the integrated chrome path); otherwise falls back to a
182
+ * centered overlay. Supersedes any picker already on screen. Routes focus to
183
+ * the builder's declared focus target (an inner list for fork/settings) and
184
+ * ALWAYS restores editor focus on close — `OverlayHandle.hide()` only
185
+ * auto-restores when the OUTER component held focus, which is not the case
186
+ * once we focus an inner child. A throwing builder is caught into a notice (m5). */
187
+ showPicker(build) {
188
+ this.picker?.dismiss();
189
+ const inline = this.hooks.onMountPicker !== undefined && this.hooks.onUnmountPicker !== undefined;
190
+ let handle;
191
+ let current;
192
+ let done = false;
193
+ const disposeCurrent = () => {
194
+ const disposable = current?.component;
195
+ try {
196
+ disposable?.dispose?.();
197
+ }
198
+ catch {
199
+ /* ignore dispose errors during teardown */
200
+ }
201
+ };
202
+ const mount = (p) => {
203
+ if (inline)
204
+ this.hooks.onMountPicker(p.component);
205
+ else
206
+ handle = this.tui.showOverlay(p.component, PICKER_OVERLAY_OPTIONS);
207
+ this.tui.setFocus(p.focus);
208
+ };
209
+ const unmount = () => {
210
+ if (inline)
211
+ this.hooks.onUnmountPicker?.();
212
+ else
213
+ handle?.hide();
214
+ };
215
+ // Swap the mounted component IN PLACE (same inline slot) — multi-step flows
216
+ // like /login replace the provider selector with the login dialog without
217
+ // ever popping a centered overlay. Does NOT restore editor focus (only a
218
+ // genuine close does); focus moves to the new component.
219
+ const replace = (component, focus) => {
220
+ if (done)
221
+ return;
222
+ disposeCurrent();
223
+ unmount();
224
+ current = { component, focus };
225
+ mount(current);
226
+ this.tui.requestRender();
227
+ };
228
+ const close = () => {
229
+ if (done)
230
+ return;
231
+ done = true;
232
+ disposeCurrent();
233
+ unmount();
234
+ this.picker = undefined;
235
+ this.tui.setFocus(this.editor);
236
+ this.tui.requestRender();
237
+ };
238
+ try {
239
+ current = build({ close, replace });
240
+ }
241
+ catch (err) {
242
+ this.notify(`Could not open picker: ${err instanceof Error ? err.message : String(err)}`);
243
+ return;
244
+ }
245
+ mount(current);
246
+ this.picker = {
247
+ // Supersede: tear down without restoring editor focus — the new picker
248
+ // grabs focus next. Genuine close restores the editor (above).
249
+ dismiss: () => {
250
+ if (done)
251
+ return;
252
+ done = true;
253
+ disposeCurrent();
254
+ unmount();
255
+ },
93
256
  };
257
+ this.tui.requestRender();
258
+ }
259
+ openModelPicker() {
260
+ return this.openPicker({ type: 'list_models' }, 'list_models', 'model', (data, close) => buildModelPicker(this.tui, data, this.send, close));
261
+ }
262
+ openSessionPicker() {
263
+ return this.openPicker({ type: 'list_sessions', scope: 'cwd' }, 'list_sessions', 'session', (data, close) => buildSessionPicker(this.tui, data, this.hooks.onRequest, this.keybindings, this.send, close));
264
+ }
265
+ openTreePicker() {
266
+ return this.openPicker({ type: 'get_tree' }, 'get_tree', 'tree', (data, close) => buildTreePicker(this.tui, data, this.send, close));
267
+ }
268
+ openForkPicker() {
269
+ return this.openPicker({ type: 'get_tree' }, 'get_tree', 'fork', (data, close) => buildForkPicker(data, this.send, close));
270
+ }
271
+ openSettingsPicker() {
272
+ return this.openPicker({ type: 'get_settings' }, 'get_settings', 'settings', (data, close) => buildSettingsPicker(data, this.send, close, (m) => this.notify(m)));
273
+ }
274
+ openScopedModelsPicker() {
275
+ return this.openPicker({ type: 'list_scoped_models' }, 'list_scoped_models', 'scoped-models', (data, close) => buildScopedModelsPicker(data, close, (m) => this.notify(m)));
276
+ }
277
+ /** Open a viewer-local auth picker (login/logout) — uses the same showPicker
278
+ * machinery as read-op pickers, but with a locally-built component (no read-op
279
+ * request; auth operations are viewer-local in tmux-local attach). */
280
+ openAuthPicker(build) {
281
+ this.showPicker(build);
94
282
  }
95
283
  handleSubmit(text) {
96
284
  const trimmed = text.trim();
97
- if (!trimmed && this.pendingImages.length === 0)
285
+ if (!trimmed)
98
286
  return;
99
- if (trimmed && isSlashCommand(trimmed)) {
287
+ if (isSlashCommand(trimmed)) {
100
288
  // Recognized builtin/scoped-out → handled here; unrecognized falls through
101
289
  // and is sent to the engine as a prompt (extension command).
102
290
  if (dispatchSlashCommand(trimmed, this.slashContext())) {
@@ -104,34 +292,105 @@ export class InputController {
104
292
  return;
105
293
  }
106
294
  }
107
- const images = this.pendingImagesPayload();
108
295
  // Submit-while-streaming = steer (pi-native parity): interject into the
109
296
  // running turn instead of queueing a fresh prompt. `isStreaming` is the
110
297
  // broker snapshot's busy signal; the broker routes `steer` → session.steer()
111
298
  // and `prompt` → session.prompt(). Idle / no state yet → prompt (safe default).
112
299
  const busy = this.state?.isStreaming === true;
113
300
  const frame = busy
114
- ? { type: 'steer', text: trimmed, images }
115
- : { type: 'prompt', text: trimmed, images };
301
+ ? { type: 'steer', text: trimmed }
302
+ : { type: 'prompt', text: trimmed };
116
303
  if (!this.emitDrive(frame))
117
- return; // too large — keep editor + pending to trim
118
- this.clearPendingImages();
119
- if (trimmed)
120
- this.editor.addToHistory(trimmed);
304
+ return; // too large — keep editor to trim
305
+ this.editor.addToHistory(trimmed);
121
306
  this.editor.setText('');
122
307
  }
123
308
  handleFollowUp() {
124
309
  const text = this.editor.getText().trim();
125
- if (!text && this.pendingImages.length === 0)
310
+ if (!text)
126
311
  return;
127
- const images = this.pendingImagesPayload();
128
- if (!this.emitDrive({ type: 'follow_up', text, images }))
312
+ if (!this.emitDrive({ type: 'follow_up', text }))
129
313
  return;
130
- this.clearPendingImages();
131
- if (text)
132
- this.editor.addToHistory(text);
314
+ this.editor.addToHistory(text);
133
315
  this.editor.setText('');
134
316
  }
317
+ /** Ctrl+G / app.editor.external — open the current editor text in $VISUAL/$EDITOR
318
+ * and load the saved result back. Ported from pi's `openExternalEditor`: stop
319
+ * the TUI to release the terminal, run the editor with inherited stdio, and on
320
+ * a clean exit (status 0) replace the editor text; the socket keeps running in
321
+ * the background throughout. */
322
+ async openExternalEditor() {
323
+ const editorCmd = process.env.VISUAL || process.env.EDITOR;
324
+ if (!editorCmd) {
325
+ this.notify('No editor configured — set $VISUAL or $EDITOR');
326
+ return;
327
+ }
328
+ const editorWithExpanded = this.editor;
329
+ const currentText = editorWithExpanded.getExpandedText?.() ?? this.editor.getText();
330
+ const tmpFile = join(tmpdir(), `crtr-attach-editor-${Date.now()}.pi.md`);
331
+ try {
332
+ writeFileSync(tmpFile, currentText, 'utf-8');
333
+ this.tui.stop();
334
+ // Split on spaces so `code --wait`-style commands keep their args.
335
+ const [cmd, ...args] = editorCmd.split(' ');
336
+ process.stdout.write(`Launching external editor: ${editorCmd}\nThe viewer resumes when it exits.\n`);
337
+ const status = await new Promise((resolve) => {
338
+ const child = spawn(cmd, [...args, tmpFile], {
339
+ stdio: 'inherit',
340
+ shell: process.platform === 'win32',
341
+ });
342
+ child.on('error', () => resolve(null));
343
+ child.on('close', (code) => resolve(code));
344
+ });
345
+ if (status === 0) {
346
+ this.editor.setText(readFileSync(tmpFile, 'utf-8').replace(/\n$/, ''));
347
+ }
348
+ }
349
+ catch (err) {
350
+ this.notify(`External editor failed: ${err instanceof Error ? err.message : String(err)}`);
351
+ }
352
+ finally {
353
+ try {
354
+ unlinkSync(tmpFile);
355
+ }
356
+ catch {
357
+ /* ignore temp-file cleanup errors */
358
+ }
359
+ this.tui.start();
360
+ this.tui.requestRender(true);
361
+ }
362
+ }
363
+ /** Alt+Up / app.message.dequeue — clear the broker's queued steering + follow-up
364
+ * messages and restore them to the editor, prepended to any in-progress text
365
+ * (pi's `restoreQueuedMessagesToEditor`: queued text first, joined by blank
366
+ * lines). A read-AND-mutate op over the correlated request channel. */
367
+ async handleDequeue() {
368
+ if (this.hooks.onRequest === undefined) {
369
+ this.notify("Restoring queued messages isn't available in this viewer");
370
+ return;
371
+ }
372
+ let reply;
373
+ try {
374
+ reply = await this.hooks.onRequest({ type: 'dequeue' });
375
+ }
376
+ catch (err) {
377
+ this.notify(`Could not restore queued messages: ${err instanceof Error ? err.message : String(err)}`);
378
+ return;
379
+ }
380
+ if (reply.kind !== 'dequeue') {
381
+ this.notify('Unexpected reply restoring queued messages');
382
+ return;
383
+ }
384
+ const queued = [...reply.steering, ...reply.followUp];
385
+ if (queued.length === 0) {
386
+ this.notify('No queued messages to restore');
387
+ return;
388
+ }
389
+ const combined = [queued.join('\n\n'), this.editor.getText()].filter((t) => t.trim()).join('\n\n');
390
+ this.editor.setText(combined);
391
+ this.tui.requestRender();
392
+ this.notify(`Restored ${queued.length} queued message${queued.length > 1 ? 's' : ''} to editor`);
393
+ }
135
394
  /** Send a drive frame iff the WHOLE encoded frame fits under MAX_FRAME_BYTES
136
395
  * (the broker destroys the socket on any line over its 24 MiB read cap). Over
137
396
  * the ceiling → notify + refuse so the caller leaves the editor + pending
@@ -160,44 +419,25 @@ export class InputController {
160
419
  this.notify('No image in the clipboard');
161
420
  return;
162
421
  }
163
- // The clipboard layer read an image but DROPPED it (over its per-image
164
- // ceiling) — surface the reason and attach nothing.
422
+ // The clipboard layer read an image but DROPPED it surface the reason.
165
423
  if (!result.image) {
166
- this.notify(result.note ?? 'Image not attached');
424
+ this.notify(result.note ?? 'Image not pasted');
167
425
  return;
168
426
  }
169
- // Enforce the aggregate pending-image budget so the eventual drained frame
170
- // stays under the broker cap: refuse a paste that would breach the count or
171
- // byte ceiling rather than accumulate an over-cap images[] (review M2).
172
- const bytes = Buffer.byteLength(result.image.data);
173
- if (this.pendingImages.length + 1 > MAX_PENDING_IMAGES ||
174
- this.pendingImageBytes + bytes > MAX_PENDING_IMAGE_BYTES) {
175
- this.notify('Image not attached: pending image budget exceeded — send your message first');
176
- return;
177
- }
178
- this.pendingImages.push(result.image);
179
- this.pendingImageBytes += bytes;
180
- this.notify(result.note
181
- ? `Image attached (${result.note}) — sends with your next message`
182
- : 'Image attached — sends with your next message');
427
+ // Persist to a temp file and splice the PATH into the editor at the caret,
428
+ // so the agent reads the image off disk instead of relying on inline base64
429
+ // the viewer can't render.
430
+ const path = writeClipboardImageToFile(result.image);
431
+ const cur = this.editor.getText();
432
+ const sep = cur && !/\s$/.test(cur) ? ' ' : '';
433
+ this.editor.insertTextAtCursor(`${sep}${path} `);
434
+ this.notify(result.note ? `Image saved (${result.note}) — path inserted` : 'Image saved — path inserted');
183
435
  this.tui.requestRender();
184
436
  }
185
437
  catch {
186
438
  this.notify('Could not read the clipboard image');
187
439
  }
188
440
  }
189
- /** Snapshot the pending images as an `images?` payload (undefined when none)
190
- * WITHOUT clearing — so a frame refused by `emitDrive` (too large) keeps them
191
- * for the user to trim. The caller clears via `clearPendingImages` only after a
192
- * successful send. */
193
- pendingImagesPayload() {
194
- return this.pendingImages.length === 0 ? undefined : this.pendingImages.slice();
195
- }
196
- /** Drop all pending images + reset the aggregate byte counter (after a send). */
197
- clearPendingImages() {
198
- this.pendingImages = [];
199
- this.pendingImageBytes = 0;
200
- }
201
441
  notify(message) {
202
442
  this.hooks.onNotice?.(message);
203
443
  }
@@ -0,0 +1,58 @@
1
+ import { type Component, type KeybindingsManager, type TUI } from '@earendil-works/pi-tui';
2
+ import type { ClientToBroker, GetSettingsData, GetTreeData, ListModelsData, ListScopedModelsData, ListSessionsData } from '../../core/runtime/broker-protocol.js';
3
+ import type { ReadOpRequest } from './view-socket.js';
4
+ /** A built picker overlay: the `component` to show, and the `focus` target the
5
+ * caller must `setFocus` (the component itself, or the inner list that owns
6
+ * `handleInput`). */
7
+ export interface Picker {
8
+ component: Component;
9
+ focus: Component;
10
+ }
11
+ /** Controls handed to a picker builder by InputController.showPicker. `close`
12
+ * tears the whole picker down + restores editor focus; `replace` swaps the
13
+ * CURRENTLY-mounted component for a new one IN THE SAME inline slot (no centered
14
+ * overlay), focusing it — used by multi-step flows like /login, where selecting
15
+ * a provider replaces the selector with the login dialog right under the editor. */
16
+ export interface PickerControls {
17
+ close: () => void;
18
+ replace: (component: Component, focus: Component) => void;
19
+ }
20
+ /** Send a command frame to the broker (= InputController hooks.onCommand). */
21
+ type Send = (frame: ClientToBroker) => void;
22
+ /** Issue a correlated read-op (lazy loaders need it after construction). */
23
+ type Request = (frame: ReadOpRequest) => Promise<unknown>;
24
+ /** Tear the picker overlay down (owned by InputController). */
25
+ type Close = () => void;
26
+ /** Surface a one-line viewer notice. */
27
+ type Notify = (message: string) => void;
28
+ /** `/model` (and ctrl+l) — the real model selector with search + scope toggle.
29
+ * Select → `set_model` with the resolved `provider/id` (the broker's
30
+ * `findModelSpec` requires that form). */
31
+ export declare function buildModelPicker(tui: TUI, data: ListModelsData, send: Send, close: Close): Picker;
32
+ /** `/resume` — the real session selector. Reconstructs from async loaders that
33
+ * fetch each scope on demand (cwd is pre-fetched so the current-session marker
34
+ * shows immediately; the `all` scope loads only on toggle). Select →
35
+ * `switch_session`. Rename/delete are out of scope (no command frame). */
36
+ export declare function buildSessionPicker(tui: TUI, prefetchedCwd: ListSessionsData, request: Request, keybindings: KeybindingsManager, send: Send, close: Close): Picker;
37
+ /** `/tree` — the real tree navigator (pure data). Select → `navigate_tree`.
38
+ * Label-edit is out of scope (no command frame) → no `onLabelChange`. */
39
+ export declare function buildTreePicker(tui: TUI, data: GetTreeData, send: Send, close: Close): Picker;
40
+ /** `/fork` — the real prior-user-message selector (pure data, from
41
+ * `get_tree.forkPoints`). Select → `fork`. The outer component has no
42
+ * `handleInput`; the inner `getMessageList()` does (focus target). */
43
+ export declare function buildForkPicker(data: GetTreeData, send: Send, close: Close): Picker;
44
+ /** `/settings` — the real settings menu (the wire `settings` IS a SettingsConfig
45
+ * superset). Only the contract-enumerated mutations are wired: thinking level →
46
+ * `set_thinking_level`, auto-compact → `set_auto_compaction`. Every other toggle
47
+ * has no command frame (out of scope), so its callback notifies rather than
48
+ * silently lying that the change took. The theme submenu is viewer-local (CTO).
49
+ * The outer component has no `handleInput`; `getSettingsList()` is the focus
50
+ * target. */
51
+ export declare function buildSettingsPicker(data: GetSettingsData, send: Send, close: Close, notify: Notify): Picker;
52
+ /** `/scoped-models` — READ-ONLY via a pi-tui SelectList. CONTRACT GAP: pi (through
53
+ * 0.79.1) does not re-export `ScopedModelsSelectorComponent`, and the contract
54
+ * exposes no command frame for its enable/disable/persist mutations, so a
55
+ * faithful editable picker is not buildable in scope. This shows the registry
56
+ * with the enabled set marked; select notifies that toggling is unsupported. */
57
+ export declare function buildScopedModelsPicker(data: ListScopedModelsData, close: Close, notify: Notify): Picker;
58
+ export {};