@pellux/goodvibes-tui 2.0.21 → 2.1.1

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 (391) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/README.md +30 -29
  3. package/docs/README.md +8 -5
  4. package/docs/foundation-artifacts/operator-contract.json +1 -1
  5. package/package.json +28 -19
  6. package/src/cli/management.ts +5 -2
  7. package/src/cli/tui-startup.ts +34 -39
  8. package/src/core/agent-view-render.ts +157 -0
  9. package/src/core/approval-alert.ts +49 -11
  10. package/src/core/bookmark-navigation.ts +7 -1
  11. package/src/core/budget-breach-notifier.ts +27 -8
  12. package/src/core/compaction-receipt.ts +1 -1
  13. package/src/core/context-usage.ts +1 -1
  14. package/src/core/conversation-compaction-render.ts +3 -3
  15. package/src/core/conversation-display-only.ts +86 -0
  16. package/src/core/conversation-event-navigation.ts +27 -0
  17. package/src/core/conversation-line-cache.ts +216 -447
  18. package/src/core/conversation-render-context.ts +20 -27
  19. package/src/core/conversation-rendering.ts +157 -575
  20. package/src/core/conversation-splash-state.ts +6 -1
  21. package/src/core/conversation-types.ts +35 -0
  22. package/src/core/conversation-usage.ts +12 -0
  23. package/src/core/conversation.ts +130 -131
  24. package/src/core/long-task-notifier.ts +100 -31
  25. package/src/core/notices.ts +75 -0
  26. package/src/core/pairing-tailscale-gateway.ts +1 -1
  27. package/src/core/process-output-log.ts +143 -0
  28. package/src/core/rewind-receipt.ts +1 -1
  29. package/src/core/session-focus.ts +190 -0
  30. package/src/core/session-resume-core.ts +23 -81
  31. package/src/core/stream-event-wiring.ts +31 -5
  32. package/src/core/system-message-noise.ts +2 -2
  33. package/src/core/system-message-router.ts +16 -18
  34. package/src/core/turn-event-wiring.ts +272 -33
  35. package/src/core/work-tree-focus.ts +290 -0
  36. package/src/core/work-tree-fold-store.ts +143 -0
  37. package/src/core/work-tree-model.ts +799 -0
  38. package/src/core/work-tree-render.ts +384 -0
  39. package/src/core/work-tree-sources.ts +184 -0
  40. package/src/core/work-tree-wiring.ts +176 -0
  41. package/src/core/workstream-notification.ts +36 -6
  42. package/src/core/wrfc-notice-titles.ts +37 -0
  43. package/src/input/agents-modal-text.ts +119 -0
  44. package/src/input/agents-modal.ts +699 -0
  45. package/src/input/bookmark-modal.ts +33 -9
  46. package/src/input/changes-git.ts +251 -0
  47. package/src/input/changes-modal.ts +629 -0
  48. package/src/input/changes-model.ts +125 -0
  49. package/src/input/command-palette-types.ts +41 -0
  50. package/src/input/command-palette.ts +272 -0
  51. package/src/input/command-reference.ts +2 -2
  52. package/src/input/command-registry.ts +77 -35
  53. package/src/input/commands/acp-runtime.ts +19 -9
  54. package/src/input/commands/channel-runtime.ts +2 -2
  55. package/src/input/commands/ci-runtime.ts +1 -1
  56. package/src/input/commands/codebase-runtime.ts +1 -1
  57. package/src/input/commands/config.ts +7 -1
  58. package/src/input/commands/context-window.ts +25 -19
  59. package/src/input/commands/control-room-runtime.ts +18 -18
  60. package/src/input/commands/conversation-runtime.ts +0 -1
  61. package/src/input/commands/cost-runtime.ts +29 -20
  62. package/src/input/commands/diff-runtime.ts +35 -181
  63. package/src/input/commands/eval.ts +1 -1
  64. package/src/input/commands/experience-runtime.ts +3 -7
  65. package/src/input/commands/fresh-conversation.ts +86 -0
  66. package/src/input/commands/git-runtime.ts +5 -22
  67. package/src/input/commands/graph-runtime.ts +1 -1
  68. package/src/input/commands/guidance-runtime.ts +1 -1
  69. package/src/input/commands/health-runtime.ts +8 -9
  70. package/src/input/commands/hooks-runtime.ts +2 -2
  71. package/src/input/commands/hosted-runtime.ts +3 -3
  72. package/src/input/commands/incident-runtime.ts +3 -3
  73. package/src/input/commands/intelligence-runtime.ts +6 -9
  74. package/src/input/commands/knowledge.ts +2 -2
  75. package/src/input/commands/legacy-panel-command.ts +57 -0
  76. package/src/input/commands/local-auth-runtime.ts +8 -9
  77. package/src/input/commands/local-provider-runtime.ts +7 -4
  78. package/src/input/commands/local-runtime.ts +12 -15
  79. package/src/input/commands/local-setup-review.ts +1 -1
  80. package/src/input/commands/local-setup.ts +13 -19
  81. package/src/input/commands/memory.ts +1 -1
  82. package/src/input/commands/notify-runtime.ts +9 -0
  83. package/src/input/commands/operator-runtime.ts +10 -12
  84. package/src/input/commands/permissions-provenance.ts +3 -3
  85. package/src/input/commands/planning-runtime.ts +7 -7
  86. package/src/input/commands/platform-sandbox-runtime.ts +2 -2
  87. package/src/input/commands/platform-services-runtime.ts +1 -1
  88. package/src/input/commands/policy-dispatch.ts +2 -2
  89. package/src/input/commands/policy.ts +3 -3
  90. package/src/input/commands/provider.ts +1 -1
  91. package/src/input/commands/remote-runtime.ts +1 -1
  92. package/src/input/commands/review-runtime.ts +39 -94
  93. package/src/input/commands/rewind-runtime.ts +81 -108
  94. package/src/input/commands/runtime-services.ts +15 -17
  95. package/src/input/commands/services-runtime.ts +2 -2
  96. package/src/input/commands/session-content.ts +21 -5
  97. package/src/input/commands/session-workflow.ts +13 -38
  98. package/src/input/commands/settings-sync-runtime.ts +2 -2
  99. package/src/input/commands/shell-core.ts +19 -4
  100. package/src/input/commands/skills-runtime.ts +2 -2
  101. package/src/input/commands/status-runtime.ts +13 -0
  102. package/src/input/commands/subscription-runtime.ts +1 -1
  103. package/src/input/commands/tasks-runtime.ts +2 -8
  104. package/src/input/commands/work-plan-runtime.ts +4 -4
  105. package/src/input/commands/workstream-attempts.ts +24 -47
  106. package/src/input/commands/workstream-runtime.ts +2 -2
  107. package/src/input/commands/worktree-runtime.ts +3 -3
  108. package/src/input/commands.ts +3 -1
  109. package/src/input/composer-editor.ts +44 -0
  110. package/src/input/concealed-input.ts +1 -1
  111. package/src/input/config-modal-types.ts +20 -8
  112. package/src/input/config-modal.ts +106 -105
  113. package/src/input/confirm-dialog-types.ts +28 -0
  114. package/src/input/confirm-dialog.ts +70 -0
  115. package/src/input/delete-key-policy.ts +3 -3
  116. package/src/input/feed-context-factory.ts +15 -16
  117. package/src/input/handler-command-route.ts +7 -42
  118. package/src/input/handler-content-actions.ts +16 -15
  119. package/src/input/handler-feed-routes.ts +26 -211
  120. package/src/input/handler-feed.ts +68 -95
  121. package/src/input/handler-interactions.ts +5 -3
  122. package/src/input/handler-modal-routes.ts +215 -216
  123. package/src/input/handler-modal-stack.ts +81 -11
  124. package/src/input/handler-modal-token-routes.ts +4 -0
  125. package/src/input/handler-onboarding.ts +0 -1
  126. package/src/input/handler-picker-routes.ts +65 -107
  127. package/src/input/handler-session-view-route.ts +149 -0
  128. package/src/input/handler-shortcuts.ts +16 -154
  129. package/src/input/handler-types.ts +4 -2
  130. package/src/input/handler-ui-state.ts +45 -15
  131. package/src/input/handler-work-tree-route.ts +123 -0
  132. package/src/input/handler.ts +25 -70
  133. package/src/input/keybindings.ts +16 -71
  134. package/src/input/masked-entry-modal.ts +117 -0
  135. package/src/input/mcp-workspace.ts +48 -17
  136. package/src/input/model-picker-filter.ts +8 -0
  137. package/src/input/model-picker-open.ts +99 -0
  138. package/src/input/model-picker-types.ts +1 -1
  139. package/src/input/model-picker.ts +92 -9
  140. package/src/input/notifications-modal.ts +123 -0
  141. package/src/input/overlay-filter.ts +26 -0
  142. package/src/input/profile-picker-modal.ts +37 -8
  143. package/src/input/search.ts +3 -3
  144. package/src/input/selection-modal-queue.ts +2 -0
  145. package/src/input/selection-modal.ts +28 -16
  146. package/src/input/session-picker-modal.ts +33 -8
  147. package/src/input/settings-modal-activation.ts +7 -2
  148. package/src/input/settings-modal-data.ts +11 -6
  149. package/src/input/settings-modal-types.ts +1 -1
  150. package/src/input/settings-modal.ts +20 -20
  151. package/src/input/submission-intent.ts +1 -1
  152. package/src/input/submission-router.ts +4 -9
  153. package/src/input/surface-modal-host.ts +110 -0
  154. package/src/input/theme-settings-actions.ts +82 -0
  155. package/src/input/usage-modal.ts +183 -0
  156. package/src/input/views.ts +97 -0
  157. package/src/main.ts +175 -165
  158. package/src/permissions/hunk-selection.ts +1 -1
  159. package/src/permissions/prompt-card.ts +511 -0
  160. package/src/permissions/prompt.ts +59 -622
  161. package/src/renderer/agents-modal.ts +310 -0
  162. package/src/renderer/autocomplete-overlay.ts +42 -220
  163. package/src/renderer/back-to-bottom.ts +80 -0
  164. package/src/renderer/block-actions-overlay.ts +30 -48
  165. package/src/renderer/bookmark-modal.ts +48 -79
  166. package/src/renderer/changes-modal.ts +325 -0
  167. package/src/renderer/code-block.ts +184 -106
  168. package/src/renderer/command-palette.ts +93 -0
  169. package/src/renderer/composer-fragments.ts +4 -4
  170. package/src/renderer/composer.ts +146 -0
  171. package/src/renderer/compositor.ts +26 -160
  172. package/src/renderer/config-modal.ts +185 -81
  173. package/src/renderer/confirm-dialog.ts +50 -0
  174. package/src/renderer/context-inspector.ts +116 -154
  175. package/src/renderer/context-status-hint.ts +4 -4
  176. package/src/renderer/conversation-layout.ts +20 -0
  177. package/src/renderer/conversation-overlays.ts +94 -84
  178. package/src/renderer/conversation-surface.ts +19 -38
  179. package/src/renderer/diff-tint.ts +98 -0
  180. package/src/renderer/file-picker-overlay.ts +63 -90
  181. package/src/renderer/footer-targets.ts +38 -0
  182. package/src/renderer/git-status.ts +3 -4
  183. package/src/renderer/header-line.ts +140 -117
  184. package/src/renderer/help-overlay.ts +133 -219
  185. package/src/renderer/history-search-overlay.ts +15 -12
  186. package/src/renderer/lane-graph/bead.ts +800 -0
  187. package/src/renderer/lane-graph/glyphs.ts +134 -0
  188. package/src/renderer/lane-graph/layout.ts +248 -0
  189. package/src/renderer/lane-graph/paint.ts +537 -0
  190. package/src/renderer/lane-graph/semantic-memo.ts +77 -0
  191. package/src/renderer/layout-engine.ts +2 -57
  192. package/src/renderer/layout.ts +7 -13
  193. package/src/renderer/markdown-inline.ts +2 -1
  194. package/src/renderer/markdown-table.ts +17 -12
  195. package/src/renderer/markdown.ts +92 -34
  196. package/src/renderer/masked-entry-modal.ts +78 -0
  197. package/src/renderer/mcp-workspace.ts +166 -203
  198. package/src/renderer/model-workspace.ts +381 -436
  199. package/src/renderer/notifications-modal.ts +116 -0
  200. package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
  201. package/src/renderer/process-view.ts +192 -0
  202. package/src/renderer/profile-picker-modal.ts +58 -110
  203. package/src/renderer/search-overlay.ts +6 -3
  204. package/src/renderer/selection-modal-overlay.ts +87 -327
  205. package/src/renderer/semantic-diff.ts +2 -2
  206. package/src/renderer/session-chips.ts +102 -0
  207. package/src/renderer/session-picker-modal.ts +177 -297
  208. package/src/renderer/settings-modal-connections.ts +14 -23
  209. package/src/renderer/settings-modal-context.ts +367 -0
  210. package/src/renderer/settings-modal-helpers.ts +6 -11
  211. package/src/renderer/settings-modal.ts +322 -726
  212. package/src/renderer/shell-surface.ts +277 -175
  213. package/src/renderer/shortcuts-overlay.ts +217 -0
  214. package/src/renderer/status-line.ts +475 -0
  215. package/src/renderer/surface-compose.ts +174 -0
  216. package/src/renderer/surface-kit-extra.ts +303 -0
  217. package/src/renderer/surface-kit-list.ts +366 -0
  218. package/src/renderer/surface-kit-parts.ts +360 -0
  219. package/src/renderer/surface-kit.ts +568 -0
  220. package/src/renderer/syntax-highlighter.ts +280 -197
  221. package/src/renderer/syntax-theme.ts +51 -0
  222. package/src/renderer/system-message.ts +0 -1
  223. package/src/renderer/term-caps.ts +42 -1
  224. package/src/renderer/terminal-bg-probe.ts +116 -19
  225. package/src/renderer/terminal-palette-probe.ts +322 -0
  226. package/src/renderer/terminal-palette-reader.ts +183 -0
  227. package/src/renderer/terminal-palette.ts +58 -0
  228. package/src/renderer/theme-mode-config.ts +16 -1
  229. package/src/renderer/theme.ts +234 -207
  230. package/src/renderer/thinking.ts +0 -1
  231. package/src/renderer/throbber.ts +188 -0
  232. package/src/renderer/toast-center.ts +119 -0
  233. package/src/renderer/ui-factory.ts +39 -602
  234. package/src/renderer/ui-primitives.ts +8 -25
  235. package/src/renderer/usage-modal.ts +353 -0
  236. package/src/renderer/user-message.ts +58 -0
  237. package/src/renderer/voice-capture-chip.ts +53 -0
  238. package/src/runtime/bootstrap-command-context.ts +0 -5
  239. package/src/runtime/bootstrap-command-parts.ts +38 -89
  240. package/src/runtime/bootstrap-core.ts +13 -15
  241. package/src/runtime/bootstrap-hook-bridge.ts +12 -21
  242. package/src/runtime/bootstrap-notifier-sync.ts +17 -1
  243. package/src/runtime/bootstrap-shell.ts +16 -50
  244. package/src/runtime/bootstrap.ts +14 -14
  245. package/src/runtime/client/fleet-union.ts +5 -5
  246. package/src/runtime/client/hosted-exit.ts +2 -2
  247. package/src/runtime/client/hosted-sessions.ts +1 -1
  248. package/src/runtime/client/spine-adoption.ts +1 -1
  249. package/src/runtime/context-accounting-source.ts +5 -4
  250. package/src/runtime/index.ts +1 -0
  251. package/src/runtime/notification-dispatch.ts +102 -29
  252. package/src/runtime/recovery-offer-wiring.ts +4 -1
  253. package/src/runtime/recovery-prompt.ts +31 -15
  254. package/src/runtime/runtime-services-types.ts +10 -6
  255. package/src/runtime/services.ts +17 -12
  256. package/src/runtime/session-continuity-hints.ts +2 -5
  257. package/src/runtime/session-inbound-inputs.ts +1 -1
  258. package/src/runtime/session-resume-liveness-confirm.ts +14 -2
  259. package/src/runtime/startup-typeahead-gate.ts +103 -0
  260. package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
  261. package/src/runtime/store/domains/index.ts +0 -8
  262. package/src/runtime/store/selectors/index.ts +0 -28
  263. package/src/runtime/store/state.ts +0 -8
  264. package/src/runtime/ui-services.ts +4 -6
  265. package/src/runtime/usage-tracker.ts +348 -0
  266. package/src/runtime/wrfc-persistence.ts +4 -4
  267. package/src/shell/blocking-input.ts +92 -2
  268. package/src/shell/recovery-input-helpers.ts +1 -1
  269. package/src/shell/session-views.ts +539 -0
  270. package/src/shell/status-report.ts +86 -0
  271. package/src/shell/transcript-scroll.ts +68 -0
  272. package/src/shell/ui-openers.ts +84 -172
  273. package/src/shell/view-openers.ts +205 -0
  274. package/src/tools/wrfc-agent-guard.ts +22 -2
  275. package/src/utils/format-duration.ts +4 -8
  276. package/src/utils/splash-lines.ts +9 -1
  277. package/src/utils/terminal-width.ts +56 -0
  278. package/src/verification/live-verifier.ts +0 -10
  279. package/src/version.ts +1 -1
  280. package/src/{panels → views}/agent-inspector-shared.ts +2 -2
  281. package/src/{panels → views}/builtin-modals.ts +22 -21
  282. package/src/views/builtin-views.ts +135 -0
  283. package/src/{panels → views}/confirm-state.ts +9 -9
  284. package/src/{panels → views}/diff-review-model.ts +10 -3
  285. package/src/{panels → views}/eval-registry.ts +1 -1
  286. package/src/{panels → views}/fleet-acts.ts +49 -85
  287. package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
  288. package/src/{panels → views}/fleet-gateway.ts +5 -5
  289. package/src/{panels → views}/fleet-observed-render.ts +16 -16
  290. package/src/{panels → views}/fleet-read-model.ts +9 -9
  291. package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
  292. package/src/{panels → views}/fleet-spawn.ts +33 -49
  293. package/src/{panels → views}/fleet-steer.ts +12 -12
  294. package/src/{panels → views}/fleet-stop.ts +4 -4
  295. package/src/{panels → views}/fleet-transcript.ts +26 -27
  296. package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
  297. package/src/{panels → views}/hosted-session-feed.ts +6 -6
  298. package/src/views/modal-surface-registry.ts +41 -0
  299. package/src/{panels → views}/modals/devices-modal.ts +2 -2
  300. package/src/{panels → views}/modals/hooks-modal.ts +13 -13
  301. package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
  302. package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
  303. package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
  304. package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
  305. package/src/{panels → views}/modals/memory-modal.ts +8 -8
  306. package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
  307. package/src/{panels → views}/modals/pairing-modal.ts +9 -9
  308. package/src/{panels → views}/modals/planning-modal.ts +15 -20
  309. package/src/{panels → views}/modals/plugins-modal.ts +6 -6
  310. package/src/{panels → views}/modals/policy-modal.ts +13 -5
  311. package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
  312. package/src/{panels → views}/modals/remote-modal.ts +3 -3
  313. package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
  314. package/src/{panels → views}/modals/security-modal.ts +22 -16
  315. package/src/{panels → views}/modals/services-modal.ts +3 -3
  316. package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
  317. package/src/{panels → views}/modals/skills-modal.ts +9 -9
  318. package/src/{panels → views}/modals/subscription-modal.ts +3 -3
  319. package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
  320. package/src/views/notifications-feed.ts +314 -0
  321. package/src/{panels → views}/polish-core.ts +38 -36
  322. package/src/{panels → views}/polish-tables.ts +35 -26
  323. package/src/{panels → views}/polish.ts +101 -95
  324. package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
  325. package/src/{panels → views}/provider-health-routes.ts +14 -14
  326. package/src/{panels → views}/search-focus.ts +8 -8
  327. package/src/{panels → views}/session-maintenance.ts +11 -11
  328. package/src/views/skills-discovery.ts +159 -0
  329. package/src/views/view-deps.ts +80 -0
  330. package/src/views/view-panel-adapter.ts +53 -0
  331. package/src/{panels → views}/workstream-graph-render.ts +1 -1
  332. package/src/core/conversation-fold.ts +0 -101
  333. package/src/core/conversation-turn-structure.ts +0 -464
  334. package/src/core/conversation-user-receipts.ts +0 -42
  335. package/src/input/commands/operator-panel-runtime.ts +0 -186
  336. package/src/input/panel-integration-actions.ts +0 -33
  337. package/src/input/panel-mouse-geometry.ts +0 -97
  338. package/src/input/panel-paste-flood-guard.ts +0 -86
  339. package/src/panels/base-panel.ts +0 -301
  340. package/src/panels/builtin/agent.ts +0 -27
  341. package/src/panels/builtin/development.ts +0 -92
  342. package/src/panels/builtin/knowledge.ts +0 -12
  343. package/src/panels/builtin/operations.ts +0 -177
  344. package/src/panels/builtin/session.ts +0 -81
  345. package/src/panels/builtin/shared.ts +0 -357
  346. package/src/panels/builtin-panels.ts +0 -28
  347. package/src/panels/cost-tracker-panel.ts +0 -746
  348. package/src/panels/diff-panel.ts +0 -797
  349. package/src/panels/diff-review-panel.ts +0 -395
  350. package/src/panels/expandable-list-panel.ts +0 -189
  351. package/src/panels/fleet-deep-link.ts +0 -31
  352. package/src/panels/fleet-panel.ts +0 -799
  353. package/src/panels/git-panel.ts +0 -800
  354. package/src/panels/hosted-session-panel.ts +0 -221
  355. package/src/panels/index.ts +0 -21
  356. package/src/panels/local-auth-panel.ts +0 -478
  357. package/src/panels/notifications-feed.ts +0 -127
  358. package/src/panels/notifications-panel.ts +0 -160
  359. package/src/panels/panel-confirm-overlay.ts +0 -72
  360. package/src/panels/panel-manager.ts +0 -791
  361. package/src/panels/plugins-panel.ts +0 -371
  362. package/src/panels/scrollable-list-panel.ts +0 -498
  363. package/src/panels/skills-panel.ts +0 -454
  364. package/src/panels/token-budget-panel.ts +0 -688
  365. package/src/panels/types.ts +0 -191
  366. package/src/panels/wrfc-panel-format.ts +0 -133
  367. package/src/renderer/diff-view.ts +0 -134
  368. package/src/renderer/fleet-tab-strip.ts +0 -56
  369. package/src/renderer/footer-tips.ts +0 -53
  370. package/src/renderer/fullscreen-primitives.ts +0 -146
  371. package/src/renderer/fullscreen-workspace.ts +0 -194
  372. package/src/renderer/hint-grammar.ts +0 -52
  373. package/src/renderer/modal-factory.ts +0 -480
  374. package/src/renderer/overlay-box.ts +0 -154
  375. package/src/renderer/overlay-viewport.ts +0 -104
  376. package/src/renderer/panel-composite.ts +0 -145
  377. package/src/renderer/panel-workspace-bar.ts +0 -54
  378. package/src/renderer/process-indicator.ts +0 -163
  379. package/src/renderer/tab-strip.ts +0 -222
  380. package/src/renderer/text-layout.ts +0 -31
  381. package/src/renderer/tool-call.ts +0 -276
  382. package/src/renderer/tool-result-expanded-lines.ts +0 -48
  383. package/src/renderer/tool-result-summary.ts +0 -219
  384. package/src/runtime/diagnostics/panels/index.ts +0 -21
  385. package/src/runtime/diagnostics/panels/ops.ts +0 -156
  386. package/src/runtime/perf/panel-contracts.ts +0 -32
  387. package/src/runtime/store/domains/panels.ts +0 -117
  388. package/src/verification/verification-ledger.ts +0 -707
  389. /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
  390. /package/src/{panels → views}/modals/modal-theme.ts +0 -0
  391. /package/src/{panels → views}/provider-health-domains.ts +0 -0
@@ -1,464 +0,0 @@
1
- /**
2
- * conversation-turn-structure.ts, turns the flat message array into the
3
- * structural render plan the transcript tree draws from.
4
- *
5
- * Three jobs, deliberately not conflated:
6
- *
7
- * 1. **Turn grouping.** Which assistant messages share one `● assistant`
8
- * header (see computeAssistantTurns).
9
- *
10
- * 2. **Structural placement.** Where each row goes. A tool result is a child
11
- * of the CALL that produced it, identified by callId, not a row appended
12
- * wherever it happens to land in the message array. Two calls issued
13
- * together run concurrently and settle in whatever order they finish; the
14
- * later-finishing call's result must still render inside its own call's
15
- * subtree, which is above any call issued afterwards. This is why the plan
16
- * is a permutation of the message array rather than a walk of it.
17
- *
18
- * 3. **Connector geometry.** Each branch row's box-drawing connector and the
19
- * `│` gutters of its still-open ancestors.
20
- *
21
- * ## Node identity
22
- *
23
- * Every planned row carries a stable string id, `m:<absIdx>` for a message,
24
- * `c:<absIdx>:<callIndex>` for one tool call, each prefixed by its scope (see
25
- * nesting below). Ids derive from append-only indices that are never
26
- * renumbered, so a row inserted in the middle does not change any other row's
27
- * id. The line cache keys on these ids (not on buffer position or plan order),
28
- * so an insertion above a row reuses that row's cached lines.
29
- *
30
- * ## Nesting is a render-layer concern, not a transcript-model one
31
- *
32
- * `ConversationMessageSnapshot` is flat and stays flat. Deeper nesting needs
33
- * no schema change: a subagent keeps its own ConversationManager, and
34
- * `AgentManager.getConversationSnapshot(agentId)` already exposes it live (the
35
- * same read `fleet-panel.ts` uses to draw an attached agent's transcript). So
36
- * when a tool call spawned an agent, the plan SPLICES that agent's own plan in
37
- * beneath the call, recursively, the identical operation to lifting a result
38
- * into its call's subtree, applied to a nested snapshot. The parent
39
- * transcript, the persisted format, and the message schema are untouched; only
40
- * the render plan nests.
41
- *
42
- * Because child snapshots are read at plan time on every rebuild, a child's
43
- * rows appear as they happen rather than when the child finishes, and a
44
- * child's late row inserts into its own subtree.
45
- *
46
- * Depth is unbounded in principle and bounded in practice by MAX_NEST_DEPTH
47
- * plus a per-path cycle guard, because a spawn graph that revisits an agent id
48
- * would otherwise recurse forever. Both guards report themselves on the node
49
- * (`truncated`) rather than silently dropping rows.
50
- *
51
- * ## Connector vocabulary
52
- *
53
- * Proper box drawing: `├` for a branch with siblings below it, `└` for the
54
- * last sibling, `│` carried down the gutter for every ancestor whose subtree
55
- * is still open. Connectors are a PURE FUNCTION of current structure,
56
- * recomputed from scratch on every plan build, never cached, so a row that
57
- * stops being last flips `└`→`├` on the next rebuild.
58
- *
59
- * That flip is one cell. Indentation width, content column, and row text are
60
- * all independent of sibling status by construction: `indentCols` depends only
61
- * on depth and width, and the connector occupies a column that exists whether
62
- * it holds `├` or `└`. So a sibling arriving repaints a connector cell and
63
- * moves no text. (The rejected alternative, a uniform glyph, cannot express
64
- * "this subtree continues past here", which is what makes a deep tree
65
- * scannable.)
66
- */
67
-
68
- import type { ConversationMessageSnapshot } from '@pellux/goodvibes-sdk/platform/core';
69
-
70
- type Message = ConversationMessageSnapshot;
71
-
72
- /**
73
- * Hard ceiling on nesting depth. A spawn chain deeper than this renders its
74
- * ancestors and marks the deepest shown call as truncated rather than
75
- * recursing without bound.
76
- */
77
- export const MAX_NEST_DEPTH = 8;
78
-
79
- /** Connector drawn at a branch row's own indent column. */
80
- export type TreeConnector = '├' | '└';
81
-
82
- export interface RenderNode {
83
- /** Stable identity, survives insertions above it. Scope-prefixed. */
84
- readonly id: string;
85
- readonly kind: 'message' | 'toolcall';
86
- /** Index of the message within ITS OWN snapshot array. */
87
- readonly absIdx: number;
88
- /** Position within the owning assistant message's toolCalls, 'toolcall' only. */
89
- readonly callIndex?: number;
90
- /** 0 = flush; >=1 = branch depth. */
91
- readonly depth: number;
92
- /** The resolved message this row renders (root or nested snapshot). */
93
- readonly message: Message;
94
- /**
95
- * Scope prefix identifying which snapshot `absIdx` indexes into. Empty for
96
- * the root transcript, so root collapse keys stay byte-identical to what
97
- * they were before nesting existed.
98
- */
99
- readonly scope: string;
100
- /** Agent whose snapshot this row came from, when nested. */
101
- readonly agentId?: string;
102
- /** Connector for this row; undefined at depth 0. */
103
- readonly connector?: TreeConnector;
104
- /**
105
- * Ancestor depths (1-based) whose subtree continues below this row, so the
106
- * renderer draws `│` in those gutters. Excludes this row's own depth.
107
- */
108
- readonly openAncestorDepths: readonly number[];
109
- /** Set when recursion stopped here (depth ceiling or cycle), surfaced honestly. */
110
- readonly truncated?: 'depth' | 'cycle';
111
- }
112
-
113
- /**
114
- * Which assistant messages share one header, and what that header must say.
115
- * Keyed by the absolute index of every assistant message in the run.
116
- */
117
- export interface AssistantTurnMembership {
118
- readonly turnKey: string;
119
- readonly headIdx: number;
120
- readonly isHead: boolean;
121
- readonly toolCallCount: number;
122
- readonly sharedToolLabel: string | undefined;
123
- readonly hasReasoning: boolean;
124
- readonly memberIndexes: readonly number[];
125
- readonly resultIndexes: readonly number[];
126
- }
127
-
128
- /** True when an assistant message carries prose the user is meant to read. */
129
- export function hasUserFacingProse(message: Message): boolean {
130
- return message.role === 'assistant'
131
- && typeof message.content === 'string'
132
- && message.content.trim().length > 0;
133
- }
134
-
135
- function modelKeyOf(message: Extract<Message, { role: 'assistant' }>): string {
136
- if (!message.model && !message.provider) return '';
137
- return `${message.model ?? ''}|${message.provider ?? ''}`;
138
- }
139
-
140
- /**
141
- * True when `message` must start a fresh `● assistant` header.
142
- *
143
- * A new header starts when there is no open run (a user message intervened),
144
- * when the message carries user-facing prose (prose closes the current group
145
- * and whatever follows begins a fresh one), or when the model/provider
146
- * changed (a mid-run model switch is real information and stays visible).
147
- *
148
- * A system message does NOT break a run: operational status noise interleaves
149
- * freely with tool activity, and breaking on it would fragment nearly every
150
- * group back into the per-message headers this change removes.
151
- */
152
- function startsNewTurn(
153
- message: Extract<Message, { role: 'assistant' }>,
154
- run: { readonly modelKey: string } | null,
155
- ): boolean {
156
- if (run === null) return true;
157
- if (hasUserFacingProse(message)) return true;
158
- const key = modelKeyOf(message);
159
- return key !== '' && run.modelKey !== '' && key !== run.modelKey;
160
- }
161
-
162
- interface OpenRun {
163
- headIdx: number;
164
- modelKey: string;
165
- members: number[];
166
- toolLabels: string[];
167
- resultIndexes: number[];
168
- hasReasoning: boolean;
169
- }
170
-
171
- export function computeAssistantTurns(
172
- messages: readonly Message[],
173
- indexOffset = 0,
174
- ): ReadonlyMap<number, AssistantTurnMembership> {
175
- const runs: OpenRun[] = [];
176
- let open: OpenRun | null = null;
177
- const callOwner = new Map<string, OpenRun>();
178
-
179
- for (let i = 0; i < messages.length; i++) {
180
- const message = messages[i]!;
181
- const absIdx = indexOffset + i;
182
-
183
- if (message.role === 'user') { open = null; continue; }
184
- if (message.role === 'system') continue;
185
-
186
- if (message.role === 'tool') {
187
- const owner = message.callId ? callOwner.get(message.callId) : undefined;
188
- if (owner) owner.resultIndexes.push(absIdx);
189
- continue;
190
- }
191
-
192
- if (startsNewTurn(message, open)) {
193
- open = { headIdx: absIdx, modelKey: modelKeyOf(message), members: [], toolLabels: [], resultIndexes: [], hasReasoning: false };
194
- runs.push(open);
195
- }
196
- open!.members.push(absIdx);
197
- if (open!.modelKey === '') open!.modelKey = modelKeyOf(message);
198
- if (message.reasoningContent || message.reasoningSummary) open!.hasReasoning = true;
199
- for (const call of message.toolCalls ?? []) {
200
- open!.toolLabels.push(call.name);
201
- if (call.id !== undefined) callOwner.set(call.id, open!);
202
- }
203
- }
204
-
205
- const membership = new Map<number, AssistantTurnMembership>();
206
- for (const run of runs) {
207
- const distinct = new Set(run.toolLabels);
208
- const shared = distinct.size === 1 && run.toolLabels.length >= 2 ? run.toolLabels[0] : undefined;
209
- const base = {
210
- turnKey: `turn_${run.headIdx}`,
211
- headIdx: run.headIdx,
212
- toolCallCount: run.toolLabels.length,
213
- sharedToolLabel: shared,
214
- hasReasoning: run.hasReasoning,
215
- memberIndexes: run.members,
216
- resultIndexes: run.resultIndexes,
217
- } as const;
218
- for (const absIdx of run.members) membership.set(absIdx, { ...base, isHead: absIdx === run.headIdx });
219
- for (const absIdx of run.resultIndexes) membership.set(absIdx, { ...base, isHead: false });
220
- }
221
- return membership;
222
- }
223
-
224
- /** Resolve a spawned agent's live snapshot; return null when unavailable. */
225
- export type AgentSnapshotResolver = (agentId: string) => readonly Message[] | null;
226
-
227
- export interface RenderPlanOptions {
228
- readonly resolveAgentSnapshot?: AgentSnapshotResolver | undefined;
229
- readonly maxDepth?: number | undefined;
230
- }
231
-
232
- /**
233
- * The agent id a tool call spawned, when it spawned one.
234
- *
235
- * Read from the call's own result payload first (the spawn tool reports the id
236
- * it allocated, see AgentInput.agentId in the SDK's agent schema), falling
237
- * back to an explicit `agentId` argument on the call. Returns undefined for
238
- * every ordinary tool call, which is the overwhelmingly common case, so this
239
- * stays a cheap check.
240
- */
241
- function spawnedAgentIdOf(
242
- call: { readonly name: string; readonly arguments: Record<string, unknown> },
243
- results: readonly Message[],
244
- ): string | undefined {
245
- for (const result of results) {
246
- if (result.role !== 'tool') continue;
247
- const raw = result.content;
248
- if (typeof raw !== 'string' || !raw.includes('agentId')) continue;
249
- try {
250
- const parsed: unknown = JSON.parse(raw);
251
- if (parsed && typeof parsed === 'object') {
252
- const id = (parsed as Record<string, unknown>).agentId;
253
- if (typeof id === 'string' && id.length > 0) return id;
254
- }
255
- } catch {
256
- // Non-JSON result, fall through to the argument check.
257
- }
258
- }
259
- const argId = call.arguments.agentId;
260
- return typeof argId === 'string' && argId.length > 0 ? argId : undefined;
261
- }
262
-
263
- /** Internal tree node; flattened into RenderNode[] once sibling counts are known. */
264
- interface TreeNode {
265
- id: string;
266
- kind: 'message' | 'toolcall';
267
- absIdx: number;
268
- callIndex?: number;
269
- message: Message;
270
- scope: string;
271
- agentId?: string;
272
- truncated?: 'depth' | 'cycle';
273
- children: TreeNode[];
274
- }
275
-
276
- /**
277
- * Build the subtree for one snapshot (root transcript or a nested agent's).
278
- * Returns the top-level nodes, each already carrying its own children.
279
- */
280
- function buildScopeNodes(
281
- messages: readonly Message[],
282
- indexOffset: number,
283
- scope: string,
284
- agentId: string | undefined,
285
- depth: number,
286
- opts: RenderPlanOptions,
287
- ancestorAgentIds: readonly string[],
288
- ): TreeNode[] {
289
- const maxDepth = opts.maxDepth ?? MAX_NEST_DEPTH;
290
-
291
- const resultsByCallId = new Map<string, number[]>();
292
- for (let i = 0; i < messages.length; i++) {
293
- const message = messages[i]!;
294
- if (message.role !== 'tool' || !message.callId) continue;
295
- const bucket = resultsByCallId.get(message.callId);
296
- if (bucket) bucket.push(indexOffset + i);
297
- else resultsByCallId.set(message.callId, [indexOffset + i]);
298
- }
299
-
300
- const nodes: TreeNode[] = [];
301
- const placed = new Set<number>();
302
- const messageAt = (absIdx: number): Message => messages[absIdx - indexOffset]!;
303
- // Calls issued by later messages of the SAME run attach to the run's head
304
- // node, because the head owns the only visible header, that is what makes
305
- // them siblings of each other rather than only children of their own
306
- // (headerless) message, and therefore what makes ├/└ resolve correctly
307
- // across a merged turn.
308
- const turns = computeAssistantTurns(messages, indexOffset);
309
- const headNodes = new Map<string, TreeNode>();
310
-
311
- for (let i = 0; i < messages.length; i++) {
312
- const message = messages[i]!;
313
- const absIdx = indexOffset + i;
314
-
315
- if (message.role === 'tool') {
316
- if (placed.has(absIdx)) continue;
317
- nodes.push({ id: `${scope}m:${absIdx}`, kind: 'message', absIdx, message, scope, agentId, children: [] });
318
- continue;
319
- }
320
-
321
- if (message.role !== 'assistant') {
322
- nodes.push({ id: `${scope}m:${absIdx}`, kind: 'message', absIdx, message, scope, agentId, children: [] });
323
- continue;
324
- }
325
-
326
- const head: TreeNode = { id: `${scope}m:${absIdx}`, kind: 'message', absIdx, message, scope, agentId, children: [] };
327
- nodes.push(head);
328
-
329
- const turn = turns.get(absIdx);
330
- if (turn?.isHead) headNodes.set(turn.turnKey, head);
331
- // Non-head members hang their calls off the run's head so the whole turn's
332
- // activity is one sibling list under one header.
333
- const callParent = turn ? (headNodes.get(turn.turnKey) ?? head) : head;
334
-
335
- const calls = message.toolCalls ?? [];
336
- for (let k = 0; k < calls.length; k++) {
337
- const call = calls[k]!;
338
- const callNode: TreeNode = {
339
- id: `${scope}c:${absIdx}:${k}`,
340
- kind: 'toolcall',
341
- absIdx,
342
- callIndex: k,
343
- message,
344
- scope,
345
- agentId,
346
- children: [],
347
- };
348
- callParent.children.push(callNode);
349
-
350
- const resultIdxs = call.id === undefined ? [] : (resultsByCallId.get(call.id) ?? []);
351
- const resultMessages: Message[] = [];
352
- for (const resultIdx of resultIdxs) {
353
- if (placed.has(resultIdx)) continue;
354
- placed.add(resultIdx);
355
- const resultMessage = messageAt(resultIdx);
356
- resultMessages.push(resultMessage);
357
- callNode.children.push({
358
- id: `${scope}m:${resultIdx}`,
359
- kind: 'message',
360
- absIdx: resultIdx,
361
- message: resultMessage,
362
- scope,
363
- agentId,
364
- children: [],
365
- });
366
- }
367
-
368
- // Splice a spawned agent's own plan in beneath the call that spawned it.
369
- const childAgentId = opts.resolveAgentSnapshot
370
- ? spawnedAgentIdOf(call, resultMessages)
371
- : undefined;
372
- if (!childAgentId) continue;
373
- if (ancestorAgentIds.includes(childAgentId)) {
374
- callNode.truncated = 'cycle';
375
- continue;
376
- }
377
- // A spliced child scope's own rows start two levels below this call
378
- // (head, then its calls, then their results), so the deepest row it can
379
- // contribute sits at depth+4. Guard on that, not on the base, or the
380
- // ceiling is overshot by exactly those two levels.
381
- if (depth + 4 > maxDepth) {
382
- callNode.truncated = 'depth';
383
- continue;
384
- }
385
- const childMessages = opts.resolveAgentSnapshot!(childAgentId);
386
- if (!childMessages || childMessages.length === 0) continue;
387
- const childScope = `${scope}a:${childAgentId}/`;
388
- callNode.children.push(
389
- ...buildScopeNodes(childMessages, 0, childScope, childAgentId, depth + 2, opts, [...ancestorAgentIds, childAgentId]),
390
- );
391
- }
392
- }
393
-
394
- return nodes;
395
- }
396
-
397
- /**
398
- * Flatten the tree depth-first, resolving each row's connector and the `│`
399
- * gutters of ancestors whose subtrees are still open.
400
- *
401
- * `openAncestorDepths` is computed from live structure on every call, so a row
402
- * that stops being last flips `└`→`├` on the next rebuild with no cached state
403
- * to go stale.
404
- */
405
- function flatten(
406
- nodes: readonly TreeNode[],
407
- depth: number,
408
- openAncestorDepths: readonly number[],
409
- out: RenderNode[],
410
- ): void {
411
- for (let i = 0; i < nodes.length; i++) {
412
- const node = nodes[i]!;
413
- const isLast = i === nodes.length - 1;
414
- const connector: TreeConnector | undefined = depth === 0 ? undefined : (isLast ? '└' : '├');
415
-
416
- out.push({
417
- id: node.id,
418
- kind: node.kind,
419
- absIdx: node.absIdx,
420
- callIndex: node.callIndex,
421
- depth,
422
- message: node.message,
423
- scope: node.scope,
424
- agentId: node.agentId,
425
- connector,
426
- openAncestorDepths,
427
- truncated: node.truncated,
428
- });
429
-
430
- if (node.children.length === 0) continue;
431
- // This row's subtree continues below it only when it is NOT the last
432
- // sibling; that is exactly when descendants must draw `│` in this depth's
433
- // gutter to show the parent chain is still open.
434
- const childOpen = depth === 0 || isLast ? openAncestorDepths : [...openAncestorDepths, depth];
435
- flatten(node.children, depth + 1, childOpen, out);
436
- }
437
- }
438
-
439
- /**
440
- * Build the ordered render plan for a message slice.
441
- *
442
- * Every message appears exactly once, plus one node per tool call, plus the
443
- * spliced-in nodes of any agent a call spawned.
444
- */
445
- export function buildRenderPlan(
446
- messages: readonly Message[],
447
- indexOffset = 0,
448
- opts: RenderPlanOptions = {},
449
- ): RenderNode[] {
450
- const tree = buildScopeNodes(messages, indexOffset, '', undefined, 0, opts, []);
451
- const plan: RenderNode[] = [];
452
- flatten(tree, 0, [], plan);
453
- return plan;
454
- }
455
-
456
- /** Collapse key for a planned row, scope-aware. Root scope keys are unchanged. */
457
- export function collapseKeyForNode(node: RenderNode): string {
458
- return `msg_${node.scope}${node.absIdx}`;
459
- }
460
-
461
- /** Collapse key for an entire spawned-agent subtree. */
462
- export function agentSubtreeCollapseKey(agentId: string): string {
463
- return `agent_${agentId}`;
464
- }
@@ -1,42 +0,0 @@
1
- /**
2
- * conversation-user-receipts.ts, tracks which system-role message indices
3
- * are a direct receipt for an explicit user action (e.g. answering the
4
- * startup recovery modal's Resume/Keep/Remove choice), as opposed to ambient
5
- * boot chatter (provider registration, the resume notice, etc.). Kept in its
6
- * own file so ConversationManager (core/conversation.ts), already near its
7
- * line-count gate, doesn't have to carry the bookkeeping inline.
8
- *
9
- * ConversationManager.rebuildHistory()'s splash-vs-transcript check consults
10
- * this via ConversationManager.addUserActionReceipt(): a receipt the user is
11
- * actively waiting on must displace the splash the way a user message does,
12
- * while quiet startup plumbing keeps rendering underneath it. Indices mirror
13
- * messageKindRegistry's own lifecycle, cleared on resetAll(), purged for
14
- * indices an undo() frees for reuse, deleted when a later add overwrites the
15
- * same recycled index with a non-receipt message.
16
- */
17
- export class UserReceiptIndices {
18
- private readonly indices = new Set<number>();
19
-
20
- add(index: number): void {
21
- this.indices.add(index);
22
- }
23
-
24
- delete(index: number): void {
25
- this.indices.delete(index);
26
- }
27
-
28
- has(index: number): boolean {
29
- return this.indices.has(index);
30
- }
31
-
32
- clear(): void {
33
- this.indices.clear();
34
- }
35
-
36
- /** Drop every tracked index >= minIndex, mirrors undo()'s tail-splice. */
37
- purgeFrom(minIndex: number): void {
38
- for (const key of this.indices) {
39
- if (key >= minIndex) this.indices.delete(key);
40
- }
41
- }
42
- }
@@ -1,186 +0,0 @@
1
- import type { CommandRegistry } from '../command-registry.ts';
2
- import type { PanelDeepLinkTarget } from '../../panels/panel-manager.ts';
3
- import { requirePanelManager } from './runtime-services.ts';
4
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
5
-
6
- /**
7
- * Parse `--target <id>[:<kind>]` out of the args following `<panel-id>
8
- * [top|bottom]` (item 4). Splits the args array in place (removing the
9
- * flag + its value) so positional `pane` parsing downstream is unaffected by
10
- * where the flag appears. Only `open fleet` currently has a deep-link
11
- * consumer (FleetPanel.receiveDeepLink), other panel ids just ignore an
12
- * unused target via PanelManager's optional-chaining delivery.
13
- */
14
- function extractTargetFlag(rest: string[]): PanelDeepLinkTarget | undefined {
15
- const flagIdx = rest.indexOf('--target');
16
- if (flagIdx < 0) return undefined;
17
- const raw = rest[flagIdx + 1];
18
- rest.splice(flagIdx, 2);
19
- if (!raw) return undefined;
20
- const sep = raw.indexOf(':');
21
- return sep >= 0 ? { id: raw.slice(0, sep), kind: raw.slice(sep + 1) } : { id: raw };
22
- }
23
-
24
- export function registerOperatorPanelCommand(registry: CommandRegistry): void {
25
- registry.register({
26
- name: 'panel',
27
- aliases: ['panels'],
28
- description: 'Open, place, resize, or list panels. Usage: /panel [open <id> [top|bottom]|close <id>|list|toggle|move|focus|split|width|height]',
29
- usage: '[open <id> [top|bottom] [--target <id>[:<kind>]]|close <id>|list|toggle|move <top|bottom|other> [id]|focus <top|bottom|toggle>|split [show|hide|toggle]|width <left|right|reset>|height <up|down|reset>]',
30
- argsHint: '<open|close|list|toggle|move|focus|split|width|height> [id]',
31
- handler(args, ctx) {
32
- const pm = requirePanelManager(ctx);
33
- const sub = args[0]?.toLowerCase() ?? '';
34
- if (!sub || sub === 'toggle') {
35
- // (the purge): 'panel-list' (the browse-all-panels picker) was
36
- // DELETE-disposition, a picker over a handful of panels is dead
37
- // weight now (see the Ctrl+P selectionModal repoint in
38
- // shell/ui-openers.ts for its interactive replacement). Bare
39
- // `/panel` and `/panel toggle` now do what "toggle" actually means:
40
- // toggle the workspace, which opens 'fleet' by default when nothing
41
- // is open (PanelManager.toggle()) rather than force-opening a
42
- // specific browse panel.
43
- pm.toggle();
44
- ctx.renderRequest();
45
- } else if (sub === 'list') {
46
- // Print a text listing instead of opening a picker panel, grouped
47
- // by category from the live registry, so this can never drift from
48
- // what's actually registered.
49
- const byCategory = pm.getTypesByCategory();
50
- const lines: string[] = [];
51
- for (const [category, entries] of byCategory) {
52
- lines.push(`${category}:`);
53
- for (const entry of entries) {
54
- lines.push(` ${entry.icon} ${entry.id}: ${entry.name}`);
55
- }
56
- }
57
- ctx.print(lines.length > 0 ? lines.join('\n') : 'No panels registered.');
58
- } else if (sub === 'open') {
59
- const id = args[1];
60
- const rest = args.slice(2);
61
- const target = extractTargetFlag(rest);
62
- const pane = rest[0]?.toLowerCase();
63
- if (!id) { ctx.print('Usage: /panel open <panel-id>'); return; }
64
- if (pane && pane !== 'top' && pane !== 'bottom') {
65
- ctx.print('Usage: /panel open <panel-id> [top|bottom] [--target <id>[:<kind>]]');
66
- return;
67
- }
68
- try {
69
- // A MIGRATE-TO-MODAL id (e.g. 'sessions' -> the session-picker modal)
70
- // resolves to a modal, not a panel: opening it fires the redirect and
71
- // no panel lands in the workspace. Report that honestly rather than
72
- // claiming "Panel opened: <id>".
73
- const redirectTarget = pm.getModalRedirect(id);
74
- // focus rule 1a: the command path leaves focus in the composer
75
- // ("mid-command-flow"), showPanel does not grab panel focus here.
76
- // forward the deep-link target so the panel lands on the row.
77
- if (ctx.showPanel) ctx.showPanel(id, pane as 'top' | 'bottom' | undefined, target);
78
- else {
79
- pm.open(id, pane as 'top' | 'bottom' | undefined, target);
80
- pm.show();
81
- ctx.renderRequest();
82
- }
83
- if (redirectTarget) {
84
- ctx.print(`"${id}" moved to the ${redirectTarget} modal; opening it.`);
85
- } else {
86
- ctx.print(`Panel opened: ${id}${pane ? ` (${pane} pane)` : ''}`);
87
- }
88
- } catch (e) {
89
- // A deleted/unknown panel id throws "No panel type registered with
90
- // id: <id>" from PanelManager.open, surface the same friendly line
91
- // the bare-/panel path uses instead of leaking the raw error. Any
92
- // genuinely unexpected failure still shows its real message.
93
- const message = summarizeError(e);
94
- if (message.includes('No panel type registered with id')) {
95
- ctx.print(`Unknown panel "${id}". Use /panel list to see available panels.`);
96
- } else {
97
- ctx.print(`Error: ${message}`);
98
- }
99
- }
100
- } else if (sub === 'close') {
101
- const id = args[1];
102
- if (!id) { ctx.print('Usage: /panel close <panel-id>'); return; }
103
- try {
104
- pm.close(id);
105
- ctx.focusPrompt?.();
106
- ctx.renderRequest();
107
- ctx.print(`Panel closed: ${id}`);
108
- } catch (e) {
109
- ctx.print(`Error: ${summarizeError(e)}`);
110
- }
111
- } else if (sub === 'move') {
112
- const dest = args[1]?.toLowerCase();
113
- if (dest !== 'top' && dest !== 'bottom' && dest !== 'other') {
114
- ctx.print('Usage: /panel move <top|bottom|other> [panel-id]');
115
- return;
116
- }
117
- const panelId = args[2];
118
- try {
119
- if (dest === 'other') pm.moveToOtherPane(panelId);
120
- else pm.moveToPane(dest, panelId);
121
- ctx.renderRequest();
122
- ctx.print(`Panel moved to ${dest} pane`);
123
- } catch (e) {
124
- ctx.print(`Error: ${summarizeError(e)}`);
125
- }
126
- } else if (sub === 'focus') {
127
- const pane = args[1]?.toLowerCase();
128
- if (pane !== 'top' && pane !== 'bottom' && pane !== 'toggle') {
129
- ctx.print('Usage: /panel focus <top|bottom|toggle>');
130
- return;
131
- }
132
- if (pane === 'toggle') pm.togglePaneFocus();
133
- else pm.focusPane(pane);
134
- ctx.renderRequest();
135
- ctx.print(`Focused ${pm.getFocusedPane()} pane`);
136
- } else if (sub === 'split') {
137
- const mode = args[1]?.toLowerCase() ?? 'toggle';
138
- if (mode !== 'toggle' && mode !== 'show' && mode !== 'hide') {
139
- ctx.print('Usage: /panel split [show|hide|toggle]');
140
- return;
141
- }
142
- if (mode === 'show' && !pm.isBottomPaneVisible()) pm.toggleBottomPane();
143
- if (mode === 'hide' && pm.isBottomPaneVisible()) pm.toggleBottomPane();
144
- if (mode === 'toggle') pm.toggleBottomPane();
145
- ctx.renderRequest();
146
- ctx.print(pm.isBottomPaneVisible() ? 'Bottom pane visible' : 'Bottom pane hidden');
147
- } else if (sub === 'width') {
148
- const dir = args[1]?.toLowerCase();
149
- if (dir !== 'left' && dir !== 'right' && dir !== 'reset') {
150
- ctx.print('Usage: /panel width <left|right|reset>');
151
- return;
152
- }
153
- if (dir === 'left') pm.widenLeft();
154
- else if (dir === 'right') pm.widenRight();
155
- else pm.setSplitRatio(0.6);
156
- ctx.renderRequest();
157
- ctx.print(`Panel width ratio: ${pm.getSplitRatio().toFixed(2)}`);
158
- } else if (sub === 'height') {
159
- const dir = args[1]?.toLowerCase();
160
- if (dir !== 'up' && dir !== 'down' && dir !== 'reset') {
161
- ctx.print('Usage: /panel height <up|down|reset>');
162
- return;
163
- }
164
- if (dir === 'up') pm.setVerticalSplitRatio(pm.getVerticalSplitRatio() - 0.05);
165
- else if (dir === 'down') pm.setVerticalSplitRatio(pm.getVerticalSplitRatio() + 0.05);
166
- else pm.setVerticalSplitRatio(0.5);
167
- ctx.renderRequest();
168
- ctx.print(`Panel height ratio: ${pm.getVerticalSplitRatio().toFixed(2)}`);
169
- } else {
170
- const id = args[0]!;
171
- try {
172
- // bare `/panel <id>` is the same command path as `/panel open
173
- // <id>`, composer stays focused.
174
- if (ctx.showPanel) ctx.showPanel(id);
175
- else {
176
- pm.open(id);
177
- pm.show();
178
- ctx.renderRequest();
179
- }
180
- } catch {
181
- ctx.print(`Unknown panel "${id}". Use /panel list to see available panels.`);
182
- }
183
- }
184
- },
185
- });
186
- }