@pellux/goodvibes-tui 2.0.20 → 2.1.0

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 (388) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +16 -16
  3. package/docs/README.md +4 -4
  4. package/docs/foundation-artifacts/operator-contract.json +59 -4
  5. package/package.json +16 -6
  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-registry.ts +77 -35
  52. package/src/input/commands/acp-runtime.ts +19 -9
  53. package/src/input/commands/channel-runtime.ts +2 -2
  54. package/src/input/commands/ci-runtime.ts +1 -1
  55. package/src/input/commands/codebase-runtime.ts +1 -1
  56. package/src/input/commands/config.ts +7 -1
  57. package/src/input/commands/context-window.ts +25 -19
  58. package/src/input/commands/control-room-runtime.ts +18 -18
  59. package/src/input/commands/conversation-runtime.ts +0 -1
  60. package/src/input/commands/cost-runtime.ts +29 -20
  61. package/src/input/commands/diff-runtime.ts +35 -181
  62. package/src/input/commands/eval.ts +1 -1
  63. package/src/input/commands/experience-runtime.ts +3 -7
  64. package/src/input/commands/fresh-conversation.ts +86 -0
  65. package/src/input/commands/git-runtime.ts +5 -22
  66. package/src/input/commands/graph-runtime.ts +1 -1
  67. package/src/input/commands/guidance-runtime.ts +1 -1
  68. package/src/input/commands/health-runtime.ts +8 -9
  69. package/src/input/commands/hooks-runtime.ts +2 -2
  70. package/src/input/commands/hosted-runtime.ts +3 -3
  71. package/src/input/commands/incident-runtime.ts +3 -3
  72. package/src/input/commands/intelligence-runtime.ts +6 -9
  73. package/src/input/commands/knowledge.ts +2 -2
  74. package/src/input/commands/legacy-panel-command.ts +57 -0
  75. package/src/input/commands/local-auth-runtime.ts +8 -9
  76. package/src/input/commands/local-provider-runtime.ts +7 -4
  77. package/src/input/commands/local-runtime.ts +12 -15
  78. package/src/input/commands/local-setup-review.ts +1 -1
  79. package/src/input/commands/local-setup.ts +13 -19
  80. package/src/input/commands/memory.ts +1 -1
  81. package/src/input/commands/notify-runtime.ts +9 -0
  82. package/src/input/commands/operator-runtime.ts +10 -12
  83. package/src/input/commands/permissions-provenance.ts +3 -3
  84. package/src/input/commands/planning-runtime.ts +7 -7
  85. package/src/input/commands/platform-sandbox-runtime.ts +2 -2
  86. package/src/input/commands/platform-services-runtime.ts +1 -1
  87. package/src/input/commands/policy-dispatch.ts +2 -2
  88. package/src/input/commands/policy.ts +3 -3
  89. package/src/input/commands/provider.ts +1 -1
  90. package/src/input/commands/remote-runtime.ts +1 -1
  91. package/src/input/commands/review-runtime.ts +39 -94
  92. package/src/input/commands/rewind-runtime.ts +81 -108
  93. package/src/input/commands/runtime-services.ts +15 -17
  94. package/src/input/commands/services-runtime.ts +2 -2
  95. package/src/input/commands/session-content.ts +21 -5
  96. package/src/input/commands/session-workflow.ts +13 -38
  97. package/src/input/commands/settings-sync-runtime.ts +2 -2
  98. package/src/input/commands/shell-core.ts +19 -4
  99. package/src/input/commands/skills-runtime.ts +2 -2
  100. package/src/input/commands/status-runtime.ts +13 -0
  101. package/src/input/commands/subscription-runtime.ts +1 -1
  102. package/src/input/commands/tasks-runtime.ts +2 -8
  103. package/src/input/commands/work-plan-runtime.ts +4 -4
  104. package/src/input/commands/workstream-attempts.ts +24 -47
  105. package/src/input/commands/workstream-runtime.ts +2 -2
  106. package/src/input/commands/worktree-runtime.ts +3 -3
  107. package/src/input/commands.ts +3 -1
  108. package/src/input/composer-editor.ts +44 -0
  109. package/src/input/concealed-input.ts +1 -1
  110. package/src/input/config-modal-types.ts +20 -8
  111. package/src/input/config-modal.ts +106 -105
  112. package/src/input/confirm-dialog-types.ts +28 -0
  113. package/src/input/confirm-dialog.ts +70 -0
  114. package/src/input/delete-key-policy.ts +3 -3
  115. package/src/input/feed-context-factory.ts +15 -16
  116. package/src/input/handler-command-route.ts +7 -42
  117. package/src/input/handler-content-actions.ts +16 -15
  118. package/src/input/handler-feed-routes.ts +26 -211
  119. package/src/input/handler-feed.ts +68 -95
  120. package/src/input/handler-interactions.ts +5 -3
  121. package/src/input/handler-modal-routes.ts +215 -216
  122. package/src/input/handler-modal-stack.ts +81 -11
  123. package/src/input/handler-modal-token-routes.ts +4 -0
  124. package/src/input/handler-onboarding.ts +0 -1
  125. package/src/input/handler-picker-routes.ts +65 -107
  126. package/src/input/handler-session-view-route.ts +149 -0
  127. package/src/input/handler-shortcuts.ts +16 -154
  128. package/src/input/handler-types.ts +4 -2
  129. package/src/input/handler-ui-state.ts +45 -15
  130. package/src/input/handler-work-tree-route.ts +123 -0
  131. package/src/input/handler.ts +25 -70
  132. package/src/input/keybindings.ts +16 -71
  133. package/src/input/masked-entry-modal.ts +117 -0
  134. package/src/input/mcp-workspace.ts +48 -17
  135. package/src/input/model-picker-filter.ts +8 -0
  136. package/src/input/model-picker-open.ts +99 -0
  137. package/src/input/model-picker-types.ts +1 -1
  138. package/src/input/model-picker.ts +92 -9
  139. package/src/input/notifications-modal.ts +123 -0
  140. package/src/input/overlay-filter.ts +26 -0
  141. package/src/input/profile-picker-modal.ts +37 -8
  142. package/src/input/search.ts +3 -3
  143. package/src/input/selection-modal-queue.ts +2 -0
  144. package/src/input/selection-modal.ts +28 -16
  145. package/src/input/session-picker-modal.ts +33 -8
  146. package/src/input/settings-modal-activation.ts +7 -2
  147. package/src/input/settings-modal-data.ts +11 -6
  148. package/src/input/settings-modal-types.ts +1 -1
  149. package/src/input/settings-modal.ts +20 -20
  150. package/src/input/submission-intent.ts +1 -1
  151. package/src/input/submission-router.ts +4 -9
  152. package/src/input/surface-modal-host.ts +110 -0
  153. package/src/input/theme-settings-actions.ts +82 -0
  154. package/src/input/usage-modal.ts +183 -0
  155. package/src/input/views.ts +97 -0
  156. package/src/main.ts +175 -165
  157. package/src/permissions/hunk-selection.ts +1 -1
  158. package/src/permissions/prompt-card.ts +511 -0
  159. package/src/permissions/prompt.ts +59 -622
  160. package/src/renderer/agents-modal.ts +310 -0
  161. package/src/renderer/autocomplete-overlay.ts +42 -220
  162. package/src/renderer/back-to-bottom.ts +80 -0
  163. package/src/renderer/block-actions-overlay.ts +30 -48
  164. package/src/renderer/bookmark-modal.ts +48 -79
  165. package/src/renderer/changes-modal.ts +325 -0
  166. package/src/renderer/code-block.ts +184 -106
  167. package/src/renderer/command-palette.ts +93 -0
  168. package/src/renderer/composer-fragments.ts +4 -4
  169. package/src/renderer/composer.ts +146 -0
  170. package/src/renderer/compositor.ts +26 -160
  171. package/src/renderer/config-modal.ts +185 -81
  172. package/src/renderer/confirm-dialog.ts +50 -0
  173. package/src/renderer/context-inspector.ts +116 -154
  174. package/src/renderer/context-status-hint.ts +4 -4
  175. package/src/renderer/conversation-layout.ts +20 -0
  176. package/src/renderer/conversation-overlays.ts +94 -84
  177. package/src/renderer/conversation-surface.ts +19 -38
  178. package/src/renderer/diff-tint.ts +98 -0
  179. package/src/renderer/file-picker-overlay.ts +63 -90
  180. package/src/renderer/footer-targets.ts +38 -0
  181. package/src/renderer/git-status.ts +3 -4
  182. package/src/renderer/header-line.ts +140 -117
  183. package/src/renderer/help-overlay.ts +133 -219
  184. package/src/renderer/history-search-overlay.ts +15 -12
  185. package/src/renderer/lane-graph/bead.ts +800 -0
  186. package/src/renderer/lane-graph/glyphs.ts +134 -0
  187. package/src/renderer/lane-graph/layout.ts +248 -0
  188. package/src/renderer/lane-graph/paint.ts +537 -0
  189. package/src/renderer/lane-graph/semantic-memo.ts +77 -0
  190. package/src/renderer/layout-engine.ts +2 -57
  191. package/src/renderer/layout.ts +7 -13
  192. package/src/renderer/markdown-inline.ts +2 -1
  193. package/src/renderer/markdown-table.ts +17 -12
  194. package/src/renderer/markdown.ts +92 -34
  195. package/src/renderer/masked-entry-modal.ts +78 -0
  196. package/src/renderer/mcp-workspace.ts +166 -203
  197. package/src/renderer/model-workspace.ts +381 -436
  198. package/src/renderer/notifications-modal.ts +116 -0
  199. package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
  200. package/src/renderer/process-view.ts +192 -0
  201. package/src/renderer/profile-picker-modal.ts +58 -110
  202. package/src/renderer/search-overlay.ts +6 -3
  203. package/src/renderer/selection-modal-overlay.ts +87 -327
  204. package/src/renderer/semantic-diff.ts +2 -2
  205. package/src/renderer/session-chips.ts +102 -0
  206. package/src/renderer/session-picker-modal.ts +177 -297
  207. package/src/renderer/settings-modal-connections.ts +14 -23
  208. package/src/renderer/settings-modal-context.ts +367 -0
  209. package/src/renderer/settings-modal-helpers.ts +6 -11
  210. package/src/renderer/settings-modal.ts +322 -726
  211. package/src/renderer/shell-surface.ts +277 -175
  212. package/src/renderer/shortcuts-overlay.ts +217 -0
  213. package/src/renderer/status-line.ts +475 -0
  214. package/src/renderer/surface-compose.ts +174 -0
  215. package/src/renderer/surface-kit-extra.ts +303 -0
  216. package/src/renderer/surface-kit-list.ts +366 -0
  217. package/src/renderer/surface-kit-parts.ts +360 -0
  218. package/src/renderer/surface-kit.ts +568 -0
  219. package/src/renderer/syntax-highlighter.ts +280 -197
  220. package/src/renderer/syntax-theme.ts +51 -0
  221. package/src/renderer/system-message.ts +0 -1
  222. package/src/renderer/term-caps.ts +42 -1
  223. package/src/renderer/terminal-bg-probe.ts +116 -19
  224. package/src/renderer/terminal-palette-probe.ts +322 -0
  225. package/src/renderer/terminal-palette-reader.ts +183 -0
  226. package/src/renderer/terminal-palette.ts +58 -0
  227. package/src/renderer/theme-mode-config.ts +16 -1
  228. package/src/renderer/theme.ts +234 -207
  229. package/src/renderer/thinking.ts +0 -1
  230. package/src/renderer/throbber.ts +188 -0
  231. package/src/renderer/toast-center.ts +119 -0
  232. package/src/renderer/ui-factory.ts +39 -602
  233. package/src/renderer/ui-primitives.ts +8 -25
  234. package/src/renderer/usage-modal.ts +353 -0
  235. package/src/renderer/user-message.ts +58 -0
  236. package/src/renderer/voice-capture-chip.ts +53 -0
  237. package/src/runtime/bootstrap-command-context.ts +0 -5
  238. package/src/runtime/bootstrap-command-parts.ts +38 -89
  239. package/src/runtime/bootstrap-core.ts +13 -15
  240. package/src/runtime/bootstrap-hook-bridge.ts +12 -21
  241. package/src/runtime/bootstrap-notifier-sync.ts +17 -1
  242. package/src/runtime/bootstrap-shell.ts +16 -50
  243. package/src/runtime/bootstrap.ts +14 -14
  244. package/src/runtime/client/fleet-union.ts +5 -5
  245. package/src/runtime/client/hosted-exit.ts +2 -2
  246. package/src/runtime/client/hosted-sessions.ts +1 -1
  247. package/src/runtime/client/spine-adoption.ts +1 -1
  248. package/src/runtime/context-accounting-source.ts +5 -4
  249. package/src/runtime/index.ts +1 -0
  250. package/src/runtime/notification-dispatch.ts +102 -29
  251. package/src/runtime/recovery-offer-wiring.ts +4 -1
  252. package/src/runtime/recovery-prompt.ts +31 -15
  253. package/src/runtime/runtime-services-types.ts +10 -6
  254. package/src/runtime/services.ts +17 -12
  255. package/src/runtime/session-continuity-hints.ts +2 -5
  256. package/src/runtime/session-inbound-inputs.ts +1 -1
  257. package/src/runtime/session-resume-liveness-confirm.ts +14 -2
  258. package/src/runtime/startup-typeahead-gate.ts +103 -0
  259. package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
  260. package/src/runtime/store/domains/index.ts +0 -8
  261. package/src/runtime/store/selectors/index.ts +0 -28
  262. package/src/runtime/store/state.ts +0 -8
  263. package/src/runtime/ui-services.ts +4 -6
  264. package/src/runtime/usage-tracker.ts +348 -0
  265. package/src/runtime/wrfc-persistence.ts +4 -4
  266. package/src/shell/blocking-input.ts +92 -2
  267. package/src/shell/recovery-input-helpers.ts +1 -1
  268. package/src/shell/session-views.ts +539 -0
  269. package/src/shell/status-report.ts +86 -0
  270. package/src/shell/transcript-scroll.ts +68 -0
  271. package/src/shell/ui-openers.ts +84 -172
  272. package/src/shell/view-openers.ts +205 -0
  273. package/src/tools/wrfc-agent-guard.ts +22 -2
  274. package/src/utils/format-duration.ts +4 -8
  275. package/src/utils/splash-lines.ts +9 -1
  276. package/src/utils/terminal-width.ts +56 -0
  277. package/src/verification/verification-ledger.ts +18 -16
  278. package/src/{panels → views}/agent-inspector-shared.ts +2 -2
  279. package/src/{panels → views}/builtin-modals.ts +22 -21
  280. package/src/views/builtin-views.ts +135 -0
  281. package/src/{panels → views}/confirm-state.ts +9 -9
  282. package/src/{panels → views}/diff-review-model.ts +10 -3
  283. package/src/{panels → views}/eval-registry.ts +1 -1
  284. package/src/{panels → views}/fleet-acts.ts +49 -85
  285. package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
  286. package/src/{panels → views}/fleet-gateway.ts +5 -5
  287. package/src/{panels → views}/fleet-observed-render.ts +16 -16
  288. package/src/{panels → views}/fleet-read-model.ts +9 -9
  289. package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
  290. package/src/{panels → views}/fleet-spawn.ts +33 -49
  291. package/src/{panels → views}/fleet-steer.ts +12 -12
  292. package/src/{panels → views}/fleet-stop.ts +4 -4
  293. package/src/{panels → views}/fleet-transcript.ts +26 -27
  294. package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
  295. package/src/{panels → views}/hosted-session-feed.ts +6 -6
  296. package/src/views/modal-surface-registry.ts +41 -0
  297. package/src/{panels → views}/modals/devices-modal.ts +2 -2
  298. package/src/{panels → views}/modals/hooks-modal.ts +13 -13
  299. package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
  300. package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
  301. package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
  302. package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
  303. package/src/{panels → views}/modals/memory-modal.ts +8 -8
  304. package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
  305. package/src/{panels → views}/modals/pairing-modal.ts +9 -9
  306. package/src/{panels → views}/modals/planning-modal.ts +15 -20
  307. package/src/{panels → views}/modals/plugins-modal.ts +6 -6
  308. package/src/{panels → views}/modals/policy-modal.ts +13 -5
  309. package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
  310. package/src/{panels → views}/modals/remote-modal.ts +3 -3
  311. package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
  312. package/src/{panels → views}/modals/security-modal.ts +22 -16
  313. package/src/{panels → views}/modals/services-modal.ts +3 -3
  314. package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
  315. package/src/{panels → views}/modals/skills-modal.ts +9 -9
  316. package/src/{panels → views}/modals/subscription-modal.ts +3 -3
  317. package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
  318. package/src/views/notifications-feed.ts +314 -0
  319. package/src/{panels → views}/polish-core.ts +38 -36
  320. package/src/{panels → views}/polish-tables.ts +35 -26
  321. package/src/{panels → views}/polish.ts +101 -95
  322. package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
  323. package/src/{panels → views}/provider-health-routes.ts +14 -14
  324. package/src/{panels → views}/search-focus.ts +8 -8
  325. package/src/{panels → views}/session-maintenance.ts +11 -11
  326. package/src/views/skills-discovery.ts +159 -0
  327. package/src/views/view-deps.ts +80 -0
  328. package/src/views/view-panel-adapter.ts +53 -0
  329. package/src/{panels → views}/workstream-graph-render.ts +1 -1
  330. package/src/core/conversation-fold.ts +0 -101
  331. package/src/core/conversation-turn-structure.ts +0 -464
  332. package/src/core/conversation-user-receipts.ts +0 -42
  333. package/src/input/commands/operator-panel-runtime.ts +0 -186
  334. package/src/input/panel-integration-actions.ts +0 -33
  335. package/src/input/panel-mouse-geometry.ts +0 -97
  336. package/src/input/panel-paste-flood-guard.ts +0 -86
  337. package/src/panels/base-panel.ts +0 -301
  338. package/src/panels/builtin/agent.ts +0 -27
  339. package/src/panels/builtin/development.ts +0 -92
  340. package/src/panels/builtin/knowledge.ts +0 -12
  341. package/src/panels/builtin/operations.ts +0 -177
  342. package/src/panels/builtin/session.ts +0 -81
  343. package/src/panels/builtin/shared.ts +0 -357
  344. package/src/panels/builtin-panels.ts +0 -28
  345. package/src/panels/cost-tracker-panel.ts +0 -746
  346. package/src/panels/diff-panel.ts +0 -797
  347. package/src/panels/diff-review-panel.ts +0 -395
  348. package/src/panels/expandable-list-panel.ts +0 -189
  349. package/src/panels/fleet-deep-link.ts +0 -31
  350. package/src/panels/fleet-panel.ts +0 -799
  351. package/src/panels/git-panel.ts +0 -800
  352. package/src/panels/hosted-session-panel.ts +0 -221
  353. package/src/panels/index.ts +0 -21
  354. package/src/panels/local-auth-panel.ts +0 -478
  355. package/src/panels/notifications-feed.ts +0 -127
  356. package/src/panels/notifications-panel.ts +0 -160
  357. package/src/panels/panel-confirm-overlay.ts +0 -72
  358. package/src/panels/panel-manager.ts +0 -791
  359. package/src/panels/plugins-panel.ts +0 -371
  360. package/src/panels/scrollable-list-panel.ts +0 -498
  361. package/src/panels/skills-panel.ts +0 -454
  362. package/src/panels/token-budget-panel.ts +0 -688
  363. package/src/panels/types.ts +0 -191
  364. package/src/panels/wrfc-panel-format.ts +0 -133
  365. package/src/renderer/diff-view.ts +0 -134
  366. package/src/renderer/fleet-tab-strip.ts +0 -56
  367. package/src/renderer/footer-tips.ts +0 -53
  368. package/src/renderer/fullscreen-primitives.ts +0 -146
  369. package/src/renderer/fullscreen-workspace.ts +0 -194
  370. package/src/renderer/hint-grammar.ts +0 -52
  371. package/src/renderer/modal-factory.ts +0 -480
  372. package/src/renderer/overlay-box.ts +0 -154
  373. package/src/renderer/overlay-viewport.ts +0 -104
  374. package/src/renderer/panel-composite.ts +0 -145
  375. package/src/renderer/panel-workspace-bar.ts +0 -54
  376. package/src/renderer/process-indicator.ts +0 -163
  377. package/src/renderer/tab-strip.ts +0 -222
  378. package/src/renderer/text-layout.ts +0 -31
  379. package/src/renderer/tool-call.ts +0 -276
  380. package/src/renderer/tool-result-expanded-lines.ts +0 -48
  381. package/src/renderer/tool-result-summary.ts +0 -219
  382. package/src/runtime/diagnostics/panels/index.ts +0 -21
  383. package/src/runtime/diagnostics/panels/ops.ts +0 -156
  384. package/src/runtime/perf/panel-contracts.ts +0 -32
  385. package/src/runtime/store/domains/panels.ts +0 -117
  386. /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
  387. /package/src/{panels → views}/modals/modal-theme.ts +0 -0
  388. /package/src/{panels → views}/provider-health-domains.ts +0 -0
@@ -1,35 +1,18 @@
1
1
  // ---------------------------------------------------------------------------
2
- // ui-primitives.ts, glyph registry + tone-token table.
2
+ // ui-primitives.ts, glyph registry + spinner frames.
3
3
  //
4
- // These four tables (GLYPHS, UI_TONES, DIFF_TONES, SPINNER_FRAMES) are
5
- // no longer minted locally. They are the SDK presentation contract
6
- // (@pellux/goodvibes-sdk/platform/presentation, already
7
- // adopted by the agent), consumed here so the TUI and the agent
8
- // share ONE source (Mike's move-to-SDK ruling, machinery needed by 2+
9
- // surfaces => SDK). The TUI was the reference these values were lifted from
10
- // verbatim, so this swap is byte-identical. See
11
- // docs/decisions/2026-07-05-presentation-contract-sdk-extraction.md in the SDK.
4
+ // GLYPHS and SPINNER_FRAMES are the SDK presentation contract
5
+ // (@pellux/goodvibes-sdk/platform/presentation), shared with the agent.
12
6
  //
13
- // Re-exported under the historical names (GLYPHS, UI_TONES) so every existing
14
- // importer keeps working with no call-site churn. UI_TONES is the dark-mode
15
- // tone table (== resolveTones('dark')); light is resolved via theme.ts's
16
- // activeUiTones() / resolveUiTones(), which composes the mode dimension over
17
- // this dark constant unchanged.
7
+ // Colours no longer live here: the static dark UI_TONES / DIFF_TONES tables
8
+ // were retired when the TUI adopted the SDK theme engine. Every colour is read
9
+ // from the active theme in theme.ts (activeTokens / activeUiTones /
10
+ // activeDiffTones / activeTheme).
18
11
  // ---------------------------------------------------------------------------
19
12
 
20
13
  import {
21
14
  GLYPHS,
22
- TONE_TOKENS,
23
- DIFF_TONES,
24
15
  SPINNER_FRAMES,
25
16
  } from '@pellux/goodvibes-sdk/platform/presentation';
26
17
 
27
- export { GLYPHS, DIFF_TONES, SPINNER_FRAMES };
28
-
29
- /**
30
- * UI_TONES, the single chrome/color-token source for src/renderer and
31
- * src/panels. Concrete (dark-mode) values; resolveUiTones(mode) in theme.ts
32
- * is the single mode-resolved read path, this object is the 'dark' entry
33
- * (== resolveTones('dark') from the SDK presentation contract).
34
- */
35
- export const UI_TONES = TONE_TOKENS;
18
+ export { GLYPHS, SPINNER_FRAMES };
@@ -0,0 +1,353 @@
1
+ /**
2
+ * renderUsageModal, context and cost for this session (replaces the Tokens and
3
+ * Cost views).
4
+ *
5
+ * ✦ Usage this session esc
6
+ *
7
+ * Overview Turns Agents
8
+ *
9
+ * Context 13.2K of 1.0M · 1%
10
+ * ██──────────────────────────────────────────────┃──────────────────
11
+ * compacts at 80% ┘
12
+ *
13
+ * Session Per turn last 8
14
+ * Input 53.0K ██
15
+ * ... ■ input ■ output ■ cache read
16
+ *
17
+ * Overview carries the context bar (with the configured compaction threshold
18
+ * marked), the session totals, the per-turn bars and the fleet split. Turns is
19
+ * the per-turn table (cache writes included); Agents is the per-agent cost
20
+ * ledger with the plan total. Budget and price entry replace the tab content
21
+ * while they are open.
22
+ */
23
+
24
+ import { mixHex } from '@pellux/goodvibes-sdk/platform/presentation';
25
+ import { activeTokens } from './theme.ts';
26
+ import { beginModal, finishModal, clipText, scrollCountText, type KitHint, type ModalFrame, type SurfaceLayer } from './surface-kit.ts';
27
+ import { drawList, type KitRow } from './surface-kit-list.ts';
28
+ import { drawTabRows, drawTextBlock, tabRowCount } from './surface-kit-extra.ts';
29
+ import { abbreviateCount } from '../utils/format-number.ts';
30
+ import { getDisplayWidth } from '../utils/terminal-width.ts';
31
+ import type { UsageAgentEntry, UsageTracker, UsageTurn } from '../runtime/usage-tracker.ts';
32
+
33
+ export type UsageTab = 'overview' | 'turns' | 'agents';
34
+
35
+ export const USAGE_TABS: readonly UsageTab[] = ['overview', 'turns', 'agents'];
36
+
37
+ const TAB_LABELS: Readonly<Record<UsageTab, string>> = { overview: 'Overview', turns: 'Turns', agents: 'Agents' };
38
+
39
+ /** What the renderer reads from the modal. */
40
+ export interface UsageModalView {
41
+ readonly tracker: UsageTracker;
42
+ readonly tab: UsageTab;
43
+ /** Turns hidden at the newest end (0 = showing the latest). */
44
+ readonly turnOffset: number;
45
+ /** Selected agent in the Agents tab. */
46
+ readonly agentIndex: number;
47
+ /** An open budget or price entry, or null. */
48
+ readonly entry: { readonly kind: 'budget' | 'price'; readonly draft: string } | null;
49
+ /** A one-line result of the last action, or null. */
50
+ readonly status: string | null;
51
+ }
52
+
53
+ /** '53.0K', '1.2M'. */
54
+ function formatTokenCount(n: number): string {
55
+ return abbreviateCount(Math.max(0, Math.round(n))).replace(/k$/, 'K');
56
+ }
57
+
58
+ /** '$0.246', '<$0.0001', '$0.00'. */
59
+ export function formatUsd(usd: number): string {
60
+ if (usd === 0) return '$0.00';
61
+ if (usd < 0.0001) return '<$0.0001';
62
+ if (usd < 0.01) return `$${usd.toFixed(4)}`;
63
+ return `$${usd.toFixed(3)}`;
64
+ }
65
+
66
+ function hintsFor(view: UsageModalView): KitHint[] {
67
+ if (view.entry) return [['⏎', 'save'], ['esc', 'cancel']];
68
+ const base: KitHint[] = [['c', 'compact now']];
69
+ if (view.tab === 'agents') base.push(['↑↓', 'select agent']);
70
+ else base.push(['↑↓', 'scroll turns']);
71
+ base.push(['tab', 'next view'], ['b', 'budget'], ['p', 'model price'], ['r', 'refresh']);
72
+ return base;
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Overview
77
+ // ---------------------------------------------------------------------------
78
+
79
+ function drawContext(f: ModalFrame, view: UsageModalView, y: number): number {
80
+ const t = activeTokens();
81
+ const { canvas, l, r } = f;
82
+ const used = view.tracker.contextTokens();
83
+ const window = view.tracker.contextWindow();
84
+ canvas.put(l, y, 'Context', { fg: t.accent, bold: true });
85
+ const pct = window > 0 ? Math.min(100, Math.round((used / window) * 100)) : 0;
86
+ const right = window > 0 ? `${formatTokenCount(used)} of ${formatTokenCount(window)} · ${pct}%` : 'context window unknown';
87
+ if (getDisplayWidth(right) < r - l - 9) canvas.right(r, y, right, { fg: t.textMuted });
88
+ if (window <= 0) return y + 2;
89
+
90
+ const barY = y + 2;
91
+ const width = r - l + 1;
92
+ const fraction = Math.min(1, used / window);
93
+ const threshold = view.tracker.compactThreshold();
94
+ const filled = used > 0 ? Math.max(1, Math.round(width * fraction)) : 0;
95
+ const hot = threshold > 0 && fraction >= threshold ? t.error : fraction >= 0.65 ? t.warning : null;
96
+ const tick = threshold > 0 ? Math.min(width - 1, Math.round(width * threshold)) : -1;
97
+ for (let q = 0; q < width; q++) {
98
+ if (q === tick) { canvas.put(l + q, barY, '┃', { fg: t.warning }); continue; }
99
+ const on = q < filled;
100
+ const fg = on ? (hot ?? (width > 1 ? mixHex(t.brand, t.brandEnd, q / (width - 1)) : t.brand)) : t.border;
101
+ canvas.put(l + q, barY, on ? '█' : '─', { fg });
102
+ }
103
+ const labelY = barY + 1;
104
+ if (tick >= 0) {
105
+ const label = `compacts at ${Math.round(threshold * 100)}% ┘`;
106
+ const end = l + tick;
107
+ const start = Math.max(l, end - getDisplayWidth(label) + 1);
108
+ canvas.put(start, labelY, clipText(label, end - start + 1), { fg: t.textFaint });
109
+ } else {
110
+ canvas.put(l, labelY, 'auto-compaction is off', { fg: t.textFaint });
111
+ }
112
+ return labelY + 2;
113
+ }
114
+
115
+ function drawSessionTable(f: ModalFrame, view: UsageModalView, x: number, xr: number, y: number, maxY: number): number {
116
+ const t = activeTokens();
117
+ const { canvas } = f;
118
+ const u = view.tracker.totals();
119
+ const cost = view.tracker.sessionCost();
120
+ if (y > maxY) return y;
121
+ canvas.put(x, y, 'Session', { fg: t.accent, bold: true });
122
+ let yy = y + 1;
123
+ const rows: Array<[string, string, boolean, string?]> = [
124
+ ['Input', formatTokenCount(u.input), false],
125
+ ['Output', formatTokenCount(u.output), false],
126
+ ['Cache read', formatTokenCount(u.cacheRead), false],
127
+ ['Cache write', formatTokenCount(u.cacheWrite), false],
128
+ ['Total', formatTokenCount(u.input + u.output + u.cacheRead + u.cacheWrite), false],
129
+ ['Cost', cost.priced ? formatUsd(cost.usd) : 'price unknown', true, view.tracker.overBudget() ? t.error : undefined],
130
+ ];
131
+ for (const [label, value, bold, fg] of rows) {
132
+ if (yy > maxY) return yy;
133
+ canvas.put(x, yy, label, { fg: t.textMuted });
134
+ canvas.right(xr, yy, value, { fg: fg ?? t.text, bold });
135
+ yy++;
136
+ }
137
+ const budget = view.tracker.budget();
138
+ if (yy <= maxY) {
139
+ canvas.put(x, yy, 'Budget', { fg: t.textMuted });
140
+ const text = budget > 0 ? `${formatUsd(budget)} · ${Math.round((cost.usd / budget) * 100)}%` : 'none';
141
+ canvas.right(xr, yy, text, { fg: view.tracker.overBudget() ? t.error : budget > 0 ? t.text : t.textFaint });
142
+ yy++;
143
+ }
144
+ const note = !cost.priced ? 'no known price: press p to set one' : cost.source;
145
+ if (note && yy <= maxY) yy = drawTextBlock(canvas, x, yy, xr - x + 1, [{ text: note, style: { fg: t.textFaint } }], maxY);
146
+ if (view.tracker.hasUnpricedSpend() && budget > 0 && yy <= maxY) {
147
+ yy = drawTextBlock(canvas, x, yy, xr - x + 1, [{ text: 'some spend has no known price and is not counted', style: { fg: t.warning } }], maxY);
148
+ }
149
+ return yy;
150
+ }
151
+
152
+ function drawFleetSplit(f: ModalFrame, view: UsageModalView, x: number, xr: number, y: number, maxY: number): number {
153
+ const t = activeTokens();
154
+ if (y + 1 > maxY) return y;
155
+ const you = view.tracker.sessionCost();
156
+ const fleet = view.tracker.fleetCost();
157
+ f.canvas.put(x, y, 'Fleet', { fg: t.accent, bold: true });
158
+ const text = `you ${you.priced ? formatUsd(you.usd) : 'price unknown'} · agents ${fleet === null ? 'none priced' : formatUsd(fleet)}`;
159
+ return drawTextBlock(f.canvas, x, y + 1, xr - x + 1, [{ text, style: { fg: t.textMuted } }], maxY);
160
+ }
161
+
162
+ /** The per-turn stacked bars (cache read, input, output from the bottom). Returns the row after the legend. */
163
+ function drawTurnBars(f: ModalFrame, view: UsageModalView, x: number, xr: number, y: number, maxY: number): number {
164
+ const t = activeTokens();
165
+ const { canvas } = f;
166
+ const turns = view.tracker.turns();
167
+ canvas.put(x, y, 'Per turn', { fg: t.accent, bold: true });
168
+ if (turns.length === 0) {
169
+ return drawTextBlock(canvas, x, y + 2, xr - x + 1, [{ text: 'Token bars appear here after the first completed turn.', style: { fg: t.textMuted } }], maxY);
170
+ }
171
+ const stride = 4;
172
+ const slots = Math.max(1, Math.floor((xr - x + 3) / stride));
173
+ const end = Math.max(0, turns.length - view.turnOffset);
174
+ const start = Math.max(0, end - slots);
175
+ const shown = turns.slice(start, end);
176
+ const countText = view.turnOffset > 0 ? `turns ${start + 1}-${end} of ${turns.length}` : `last ${shown.length}`;
177
+ if (getDisplayWidth(countText) < xr - x - 10) canvas.right(xr, y, countText, { fg: t.textFaint });
178
+
179
+ // Rows: header, bars, a number row, blank, legend.
180
+ const legendY = maxY;
181
+ const numbersY = legendY - 2;
182
+ const barTop = y + 1;
183
+ const barBottom = numbersY - 1;
184
+ const barRows = barBottom - barTop + 1;
185
+ if (barRows < 2) return y + 1;
186
+ const totalOf = (turn: UsageTurn): number => turn.input + turn.output + turn.cacheRead;
187
+ const max = Math.max(1, ...shown.map(totalOf));
188
+ shown.forEach((turn, k) => {
189
+ const bx = x + k * stride;
190
+ const scale = barRows / max;
191
+ const parts: Array<[number, string]> = [[turn.cacheRead, t.info], [turn.input, t.brand], [turn.output, t.brandEnd]];
192
+ let heights = parts.map(([n]) => Math.round(n * scale));
193
+ if (heights.every((h) => h === 0) && totalOf(turn) > 0) {
194
+ const largest = parts.reduce((best, part, i) => (part[0] > parts[best]![0] ? i : best), 0);
195
+ heights = heights.map((h, i) => (i === largest ? 1 : h));
196
+ }
197
+ let row = barBottom;
198
+ parts.forEach(([, color], i) => {
199
+ for (let h = 0; h < heights[i]! && row >= barTop; h++, row--) canvas.put(bx, row, '██', { fg: color });
200
+ });
201
+ canvas.put(bx, numbersY, String(start + k + 1).slice(-3), { fg: t.textFaint });
202
+ });
203
+ let lx = x;
204
+ for (const [name, color] of [['input', t.brand], ['output', t.brandEnd], ['cache read', t.info]] as const) {
205
+ if (lx + 2 + getDisplayWidth(name) > xr) break;
206
+ canvas.put(lx, legendY, '■', { fg: color });
207
+ lx = canvas.put(lx + 2, legendY, name, { fg: t.textMuted }) + 3;
208
+ }
209
+ return legendY + 1;
210
+ }
211
+
212
+ function drawOverview(f: ModalFrame, view: UsageModalView, y: number): void {
213
+ const t = activeTokens();
214
+ const { l, r } = f;
215
+ const bottom = view.status ? f.bottom - 2 : f.bottom;
216
+ let yy = drawContext(f, view, y);
217
+ const inner = r - l + 1;
218
+ if (inner >= 60) {
219
+ const leftW = Math.min(34, Math.floor(inner * 0.4));
220
+ const leftR = l + leftW - 1;
221
+ const gx = leftR + 5;
222
+ const leftEnd = drawSessionTable(f, view, l, leftR, yy, bottom);
223
+ drawFleetSplit(f, view, l, leftR, leftEnd + 1, bottom);
224
+ if (bottom - yy >= 5) drawTurnBars(f, view, gx, r, yy, bottom);
225
+ } else {
226
+ yy = drawSessionTable(f, view, l, r, yy, bottom);
227
+ yy = drawFleetSplit(f, view, l, r, yy + 1, bottom);
228
+ }
229
+ if (view.status) f.canvas.put(l, f.bottom, clipText(view.status, inner), { fg: t.textMuted });
230
+ }
231
+
232
+ // ---------------------------------------------------------------------------
233
+ // Turns and agents
234
+ // ---------------------------------------------------------------------------
235
+
236
+ function turnRows(view: UsageModalView, width: number): KitRow[] {
237
+ const turns = view.tracker.turns();
238
+ const rows: KitRow[] = [];
239
+ const selected = Math.max(0, turns.length - 1 - view.turnOffset);
240
+ turns.forEach((turn, i) => {
241
+ const total = turn.input + turn.output + turn.cacheRead + turn.cacheWrite;
242
+ const cells = [turn.input, turn.output, turn.cacheRead, turn.cacheWrite].map(formatTokenCount);
243
+ const detail = width >= 60
244
+ ? `in ${cells[0]} out ${cells[1]} cache read ${cells[2]} cache write ${cells[3]}`
245
+ : `in ${cells[0]} out ${cells[1]}`;
246
+ rows.push({ label: `Turn ${i + 1}`, desc: detail, right: formatTokenCount(total), selected: i === selected });
247
+ });
248
+ return rows;
249
+ }
250
+
251
+ function drawTurns(f: ModalFrame, view: UsageModalView, y: number): void {
252
+ const t = activeTokens();
253
+ const turns = view.tracker.turns();
254
+ if (turns.length === 0) {
255
+ drawTextBlock(f.canvas, f.l, y, f.r - f.l + 1, [{ text: 'No turns recorded yet. Each completed turn adds a row with its input, output and cache tokens.', style: { fg: t.textMuted } }], f.bottom);
256
+ return;
257
+ }
258
+ const result = drawList(f.canvas, { rows: turnRows(view, f.r - f.l + 1), top: y, bottom: f.bottom, x0: f.l, x1: f.r, scrollKey: { owner: view.tracker, name: 'turns' } });
259
+ f.hintRight = scrollCountText(result.above, result.below);
260
+ }
261
+
262
+ function agentMark(agent: UsageAgentEntry): { mark: string; fg: string } {
263
+ const t = activeTokens();
264
+ if (agent.status === 'running') return { mark: '◐', fg: t.brand };
265
+ if (agent.status === 'failed') return { mark: '✕', fg: t.error };
266
+ return { mark: '✓', fg: t.success };
267
+ }
268
+
269
+ function drawAgents(f: ModalFrame, view: UsageModalView, y: number): void {
270
+ const t = activeTokens();
271
+ const agents = view.tracker.agents();
272
+ const session = view.tracker.sessionCost();
273
+ const planCost = agents.reduce((sum, a) => sum + a.cost, 0) + session.usd;
274
+ const running = agents.filter((a) => a.status === 'running').length;
275
+ const failed = agents.filter((a) => a.status === 'failed').length;
276
+ const parts = [`plan total ${session.priced ? formatUsd(planCost) : 'price unknown'}`, `${agents.length} agent${agents.length === 1 ? '' : 's'}`];
277
+ if (running > 0) parts.push(`${running} running`);
278
+ if (failed > 0) parts.push(`${failed} failed`);
279
+ const yy = drawTextBlock(f.canvas, f.l, y, f.r - f.l + 1, [{ text: parts.join(' · '), style: { fg: t.textMuted } }], f.bottom);
280
+ if (agents.length === 0) {
281
+ drawTextBlock(f.canvas, f.l, yy + 1, f.r - f.l + 1, [{ text: 'No agents spawned this session. Agent costs appear here once delegated or background agents start.', style: { fg: t.textMuted } }], f.bottom);
282
+ return;
283
+ }
284
+ const selected = Math.max(0, Math.min(view.agentIndex, agents.length - 1));
285
+ const rows: KitRow[] = agents.map((agent, i) => {
286
+ const { mark, fg } = agentMark(agent);
287
+ const tokens = agent.inputTokens > 0 ? `${formatTokenCount(agent.inputTokens)} in · ${formatTokenCount(agent.outputTokens)} out` : 'no usage yet';
288
+ const cost = agent.inputTokens > 0 && agent.cost === 0 && agent.model === 'unknown' ? 'unpriced' : agent.cost > 0 ? formatUsd(agent.cost) : '-';
289
+ return {
290
+ label: agent.agentId.slice(0, 8),
291
+ desc: `${agent.task} · ${agent.model} · ${tokens}`,
292
+ right: cost,
293
+ mark,
294
+ markFg: fg,
295
+ selected: i === selected,
296
+ };
297
+ });
298
+ const result = drawList(f.canvas, { rows, top: yy + 1, bottom: f.bottom, x0: f.l, x1: f.r, scrollKey: { owner: view.tracker, name: 'agents' } });
299
+ f.hintRight = scrollCountText(result.above, result.below);
300
+ }
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // Entry
304
+ // ---------------------------------------------------------------------------
305
+
306
+ function drawEntry(f: ModalFrame, view: UsageModalView, y: number): void {
307
+ const t = activeTokens();
308
+ const entry = view.entry!;
309
+ const width = f.r - f.l + 1;
310
+ let yy = y;
311
+ if (entry.kind === 'budget') {
312
+ yy = drawTextBlock(f.canvas, f.l, yy, width, [
313
+ { text: 'Budget alert', style: { fg: t.accent, bold: true } },
314
+ { text: 'Type a USD amount. You are alerted when this session costs more; 0 turns the alert off. The same setting as /cost budget <usd>.', style: { fg: t.textMuted } },
315
+ ], f.bottom);
316
+ yy++;
317
+ const end = f.canvas.put(f.l, yy, `$${entry.draft}`, { fg: t.text });
318
+ f.canvas.put(end, yy, '▏', { fg: t.brand });
319
+ return;
320
+ }
321
+ const model = view.tracker.modelId();
322
+ if (!view.tracker.canSetModelPrice()) {
323
+ drawTextBlock(f.canvas, f.l, yy, width, [
324
+ { text: 'Model price', style: { fg: t.accent, bold: true } },
325
+ { text: `The current model (${model}) has no provider prefix, so a price cannot be stored for it: prices are keyed provider:model.`, style: { fg: t.textMuted } },
326
+ ], f.bottom);
327
+ return;
328
+ }
329
+ yy = drawTextBlock(f.canvas, f.l, yy, width, [
330
+ { text: `Model price for ${model}`, style: { fg: t.accent, bold: true } },
331
+ { text: 'Your price, in USD per 1M tokens, as input,output (for example 3.00,15.00). It wins over provider and catalog prices everywhere.', style: { fg: t.textMuted } },
332
+ ], f.bottom);
333
+ yy++;
334
+ const end = f.canvas.put(f.l, yy, entry.draft, { fg: t.text });
335
+ f.canvas.put(end, yy, '▏', { fg: t.brand });
336
+ }
337
+
338
+ // ---------------------------------------------------------------------------
339
+ // Frame
340
+ // ---------------------------------------------------------------------------
341
+
342
+ export function renderUsageModal(view: UsageModalView, screenWidth: number, screenHeight: number): SurfaceLayer {
343
+ const f = beginModal(screenWidth, screenHeight, { title: 'Usage', sub: 'this session', hints: hintsFor(view) });
344
+ const labels = USAGE_TABS.map((tab) => TAB_LABELS[tab]);
345
+ const tabRows = view.entry ? 0 : tabRowCount(labels, f.l + 1, f.r - 1);
346
+ if (tabRows > 0) drawTabRows(f.canvas, f.l + 1, f.top, labels, USAGE_TABS.indexOf(view.tab), f.r - 1);
347
+ const y = f.top + (tabRows > 0 ? tabRows + 1 : 0);
348
+ if (view.entry) drawEntry(f, view, y);
349
+ else if (view.tab === 'turns') drawTurns(f, view, y);
350
+ else if (view.tab === 'agents') drawAgents(f, view, y);
351
+ else drawOverview(f, view, y);
352
+ return finishModal(f);
353
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * user-message.ts, a user message in the transcript.
3
+ *
4
+ * col 2 ┃ in the bar color on every row, padding rows included
5
+ * cols 3..w-3 the surface fill, full width
6
+ * rows one padding row, the text, one padding row
7
+ * text from column 5, wrapped to width-9
8
+ *
9
+ * The composer is drawn the same way (composer.ts), so what you typed and
10
+ * what you sent look alike.
11
+ */
12
+
13
+ import { type Line, createEmptyLine } from '@pellux/goodvibes-sdk/platform/types';
14
+ import { getDisplayWidth, wrapText } from '../utils/terminal-width.ts';
15
+
16
+ const USER_MESSAGE_BAR_X = 2;
17
+ const USER_MESSAGE_FILL_X = 3;
18
+ const USER_MESSAGE_TEXT_X = 5;
19
+
20
+ export interface UserMessageStyle {
21
+ readonly bar: string;
22
+ readonly bg: string;
23
+ readonly text: string;
24
+ readonly strikethrough?: boolean;
25
+ }
26
+
27
+ function row(width: number, style: UserMessageStyle): Line {
28
+ const line = createEmptyLine(width);
29
+ for (const cell of line) cell.bg = '';
30
+ if (width > USER_MESSAGE_BAR_X) {
31
+ line[USER_MESSAGE_BAR_X] = { char: '┃', fg: style.bar, bg: '', bold: false, dim: false, underline: false, italic: false, strikethrough: false };
32
+ }
33
+ for (let x = USER_MESSAGE_FILL_X; x <= width - 3; x++) {
34
+ line[x] = { char: ' ', fg: style.text, bg: style.bg, bold: false, dim: false, underline: false, italic: false, strikethrough: false };
35
+ }
36
+ return line;
37
+ }
38
+
39
+ export function renderUserMessage(text: string, width: number, style: UserMessageStyle): Line[] {
40
+ const textWidth = Math.max(1, width - 9);
41
+ const wrapped = wrapText(text, textWidth);
42
+ const lines: Line[] = [row(width, style)];
43
+ for (const content of wrapped.length > 0 ? wrapped : ['']) {
44
+ const line = row(width, style);
45
+ let x = USER_MESSAGE_TEXT_X;
46
+ for (const ch of content) {
47
+ const w = getDisplayWidth(ch);
48
+ if (w <= 0) continue;
49
+ if (x + w > width - 4) break;
50
+ line[x] = { char: ch, fg: style.text, bg: style.bg, bold: false, dim: false, underline: false, italic: false, strikethrough: style.strikethrough ?? false };
51
+ if (w === 2) line[x + 1] = { ...line[x]!, char: '' };
52
+ x += w;
53
+ }
54
+ lines.push(line);
55
+ }
56
+ lines.push(row(width, style));
57
+ return lines;
58
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * voice-capture-chip.ts, the live-microphone chip at the left end of the status line.
3
+ *
4
+ * A capture device held open with nothing on screen saying so is the one
5
+ * state a voice feature must never be in, so the chip is never dropped for
6
+ * lack of room. It is short on purpose; the full wording (device, restart
7
+ * delay, latch reason) is in /voice status.
8
+ */
9
+
10
+ import type { StatusChip } from './status-line.ts';
11
+ import type { VoiceCaptureIndicatorState } from '../core/voice-capture-status.ts';
12
+ import { GLYPHS } from './ui-primitives.ts';
13
+ import { activeTokens, activeUiTones } from './theme.ts';
14
+
15
+ const VOICE_CHIP_LABELS: Record<VoiceCaptureIndicatorState['kind'], string> = {
16
+ requesting: 'mic opening',
17
+ recording: 'mic recording',
18
+ transcribing: 'transcribing',
19
+ 'wake-listening': 'mic listening',
20
+ 'wake-capturing': 'mic recording',
21
+ 'wake-restarting': 'mic restarting',
22
+ 'wake-latched': 'wake stopped',
23
+ };
24
+
25
+ const VOICE_CAPTURE_DESCRIPTIONS: Record<VoiceCaptureIndicatorState['kind'], string> = {
26
+ requesting: 'opening the microphone',
27
+ recording: 'recording: press the voice-input key again to stop',
28
+ transcribing: 'transcribing what you said',
29
+ 'wake-listening': 'listening for the wake phrase',
30
+ 'wake-capturing': 'wake heard: recording what follows',
31
+ 'wake-restarting': 'capture stream ended: restarting',
32
+ 'wake-latched': 'wake detection stopped',
33
+ };
34
+
35
+ /** The full wording for /status: state, device and any detail (a restart delay, a latch reason). */
36
+ export function voiceCaptureDescription(state: VoiceCaptureIndicatorState): string {
37
+ const parts = [VOICE_CAPTURE_DESCRIPTIONS[state.kind]];
38
+ if (state.deviceLabel !== null) parts.push(state.deviceLabel);
39
+ if (state.detail !== undefined && state.detail.length > 0) parts.push(state.detail);
40
+ return parts.join(' · ');
41
+ }
42
+
43
+ export function voiceCaptureChip(state: VoiceCaptureIndicatorState): StatusChip {
44
+ const tones = activeUiTones();
45
+ const marker = state.kind === 'wake-latched' ? GLYPHS.status.blocked : GLYPHS.status.active;
46
+ const fg = state.kind === 'wake-latched' || state.kind === 'wake-restarting' ? tones.chrome.warn : tones.accent.control;
47
+ const text = `${marker} ${VOICE_CHIP_LABELS[state.kind]}`;
48
+ // voice.wake.indicator: banner draws the wake chip filled, statusline plain.
49
+ if (state.kind.startsWith('wake-') && state.indicator === 'banner') {
50
+ return { text: ` ${text} `, fg: activeTokens().backgroundElement, bg: fg, bold: true, keep: true };
51
+ }
52
+ return { text, fg, bold: true, keep: true };
53
+ }
@@ -5,7 +5,6 @@ import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
5
5
  import type { MemorySpineClient } from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
6
6
  import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
7
7
  import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
8
- import type { PanelManager } from '../panels/panel-manager.ts';
9
8
  import type { ProviderApi } from '@pellux/goodvibes-sdk/platform/providers';
10
9
  import type { OpsApi } from '@/runtime/index.ts';
11
10
  import type { MutableRuntimeState } from '@/runtime/index.ts';
@@ -124,7 +123,6 @@ export type CreateBootstrapCommandContextOptions = {
124
123
  mcpApi?: McpApi;
125
124
  opsApi?: OpsApi;
126
125
  directTransport?: DirectTransport;
127
- panelManager: PanelManager;
128
126
  worktreeRegistry: WorktreeRegistry;
129
127
  sandboxSessionRegistry: SandboxSessionRegistry;
130
128
  loadSystemPrompt: () => string;
@@ -220,7 +218,6 @@ export function createBootstrapCommandContext(
220
218
  mcpApi,
221
219
  opsApi,
222
220
  directTransport,
223
- panelManager,
224
221
  worktreeRegistry,
225
222
  sandboxSessionRegistry,
226
223
  loadSystemPrompt,
@@ -287,7 +284,6 @@ export function createBootstrapCommandContext(
287
284
  gatewayMethods,
288
285
  workspaceTrustManager,
289
286
  workspaceRegistrationManager,
290
- panelManager,
291
287
  profileManager,
292
288
  bookmarkManager,
293
289
  projectPlanningService,
@@ -317,7 +313,6 @@ export function createBootstrapCommandContext(
317
313
  conversation,
318
314
  runtime,
319
315
  requestRender,
320
- panelManager,
321
316
  loadSystemPrompt,
322
317
  activatePlan,
323
318
  requestPermission,