@pellux/goodvibes-tui 2.0.21 → 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 +58 -0
  2. package/README.md +16 -16
  3. package/docs/README.md +4 -4
  4. package/docs/foundation-artifacts/operator-contract.json +1 -1
  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,98 +1,136 @@
1
1
  import type { Line } from '@pellux/goodvibes-sdk/platform/types';
2
- import { renderProcessIndicator, renderVoiceCaptureIndicator } from './process-indicator.ts';
2
+ import { calcSessionCost, isModelPriced } from '@pellux/goodvibes-sdk/platform/providers';
3
3
  import { UIFactory } from './ui-factory.ts';
4
4
  import { voiceCaptureRowVisible, type VoiceCaptureIndicatorState } from '../core/voice-capture-status.ts';
5
+ import { activeTokens, activeUiTones } from './theme.ts';
6
+ import { permissionModeLabel } from '../core/permission-mode.ts';
7
+ import { SLEEP_DISABLED_CHIP } from '../core/power-status.ts';
8
+ import { renderComposer, COMPOSER_FIXED_ROWS } from './composer.ts';
9
+ import { renderStatusLine, type StatusChip } from './status-line.ts';
10
+ import { renderThrobberLine, type ThrobberState } from './throbber.ts';
11
+ import { renderBackToBottomPill, type BackToBottomState } from './back-to-bottom.ts';
12
+ import { voiceCaptureChip } from './voice-capture-chip.ts';
13
+ import { tagFooterLine } from './footer-targets.ts';
14
+
15
+ /**
16
+ * shell-surface.ts, everything under the transcript, top to bottom:
17
+ *
18
+ * blank row one full empty row under the transcript, drawn only when a
19
+ * text row follows (a half row cannot be drawn between two
20
+ * rows of plain text). An agent or process view's body
21
+ * already ends with one, so views skip it.
22
+ * hint rows the retry affordance, context pressure, the scriptable
23
+ * status line, when they have something to say
24
+ * throbber what main is doing right now, only while it works
25
+ * (throbber.ts)
26
+ * back to bottom the pill, centered, only while the output is scrolled
27
+ * away from its live bottom (back-to-bottom.ts); a fill row,
28
+ * so a full empty row keeps it off the text above it
29
+ * input area ▄ cap, padding, text, padding, ▀ cap (composer.ts): the
30
+ * caps are the half rows between the input area and the rows
31
+ * above and below it
32
+ * status line one row of session state
33
+ *
34
+ * At rest that is 6 rows (input area 5 + status 1), and with the header the
35
+ * resting chrome is 7. While main works, 2 more: the blank row and the
36
+ * throbber; while the output is scrolled back, 2 more: an empty row and the
37
+ * back-to-bottom pill. The input area holds only input (its placeholder,
38
+ * even while the keyboard is in the work tree). Token totals, the per-turn
39
+ * history, tool count, notification mode, the session spine and the web
40
+ * surface address live in the Usage modal and /status, not on the main
41
+ * screen. What stays always visible is safety: the approval mode and the
42
+ * auto-approve warning, the live microphone and the sleep-disabled chip (the
43
+ * left end of the status line), the failover marker (the header, after the
44
+ * model) and the compaction-pressure hint (a hint row above the input area).
45
+ */
46
+
47
+ /**
48
+ * The work tree's keys, shown on the status line while the keyboard is in it.
49
+ * Esc leads: the way back to typing is never dropped for lack of room; the
50
+ * other keys give way from the end (status-line.ts keeps whole keys in order).
51
+ */
52
+ const WORK_TREE_KEYS: ReadonlyArray<readonly [string, string]> = [
53
+ ['esc', 'back to typing'], ['↑↓', 'move between beads'], ['←→', 'fold / unfold'], ['enter', 'open'], ['y', 'copy'],
54
+ ];
55
+
56
+ /** What an agent or process view changes under the transcript. */
57
+ export interface ShellFooterView {
58
+ /** The composer bar: the agent's lane color, or the process color. */
59
+ readonly barColor: string;
60
+ readonly placeholder?: string;
61
+ /** The composer takes no input; this says why. */
62
+ readonly disabledReason?: string;
63
+ /** The view's keys on the status line (esc back to main, ctrl+x stop, …). */
64
+ readonly keys: ReadonlyArray<readonly [string, string]>;
65
+ /** After the keys: main kept in sight (◐ main · working). */
66
+ readonly trail?: { readonly text: string; readonly fg: string } | null;
67
+ /** A notice in place of the keys (press ctrl+x again to stop). */
68
+ readonly notice?: { readonly text: string; readonly tone: 'error' | 'info' } | null;
69
+ /** The process view shows no context bar or cost. */
70
+ readonly noContext?: boolean;
71
+ /** The cost this view states instead of the session's (an agent's own, when priced). */
72
+ readonly cost?: string | null;
73
+ }
5
74
 
6
75
  export interface ShellFooterBuildOptions {
7
76
  readonly width: number;
77
+ /** The keyboard is in the conversation work tree: the status line shows its keys. */
78
+ readonly workTreeFocused?: boolean;
8
79
  readonly promptText: string;
9
80
  readonly promptLineCount: number;
10
81
  readonly promptCursorPos?: number;
11
82
  readonly promptFocused?: boolean;
12
- /**
13
- * True when the panel workspace owns keyboard focus. Only consulted as a
14
- * fallback when `promptFocused` is not supplied, see buildShellFooter's
15
- * default expression. Callers that already compute `promptFocused`
16
- * themselves (main.ts does) do not need to also pass this.
17
- */
18
- readonly panelFocused?: boolean;
19
- readonly usage: { up: number; down: number; fleetCostUsd?: number | null };
83
+ readonly usage: { up: number; down: number; cacheRead?: number; cacheWrite?: number; fleetCostUsd?: number | null };
20
84
  readonly showExitNotice: boolean;
21
85
  readonly lastCopyTime: number;
86
+ /** The model the session cost is priced against; not drawn (the header names the model). */
22
87
  readonly model?: string;
23
- /**
24
- * Divergence marker appended to the model segment while the serving backend
25
- * is not the user's configured selection (core/active-model-identity.ts),
26
- * e.g. "failover from abacusai:route-llm". Absent/empty in the normal case,
27
- * where the context-info line renders exactly as it always has.
28
- */
29
- readonly modelNote?: string;
30
- readonly toolCount?: number;
31
88
  readonly workingDir?: string;
32
- readonly provider?: string;
33
- readonly contextWindow?: number;
89
+ /** The home directory, drawn as ~ at the start of the working directory. */
90
+ readonly homeDirectory?: string;
91
+ /** An agent or process view is showing (session-focus.ts). */
92
+ readonly view?: ShellFooterView | null;
93
+ /** The current branch (and dirty marker), shown beside the directory at rest. */
94
+ readonly branch?: string;
95
+ /** The model's context window; null when it is unknown (the meter says "unknown"). */
96
+ readonly contextWindow?: number | null;
34
97
  readonly compactThreshold?: number;
35
98
  readonly dangerMode?: boolean;
36
99
  readonly lastInputTokens?: number;
37
100
  readonly commandArgsHint?: string;
38
- readonly hitlMode?: string;
39
101
  readonly runningAgentCount: number;
40
102
  readonly runningProcessCount: number;
41
103
  readonly indicatorFocused: boolean;
42
104
  readonly runningAgentProgress?: string;
43
- readonly composerMode?: string;
44
- readonly composerStatus?: string;
45
105
  readonly composerFlags?: readonly string[];
46
106
  readonly composerPendingRisk?: 'none' | 'approval-wait' | 'shell' | 'command' | 'remote';
47
107
  /**
48
108
  * Current session permission mode (config value: 'prompt' | 'allow-all' |
49
- * 'custom' | 'plan' | 'accept-edits'). Rendered as a pill in the composer
50
- * posture block. Cycled by Shift+Tab and toggled by /plan.
109
+ * 'custom' | 'plan' | 'accept-edits'). Named by the status line's mode chip
110
+ * and colors the composer's bar. Cycled by Shift+Tab and toggled by /plan.
51
111
  */
52
112
  readonly permissionMode?: string;
53
- /**
54
- * Passive context pressure hint from buildContextStatusHint.
55
- * Rendered as a dim informational line above the prompt when non-null.
56
- */
113
+ /** Passive context pressure hint from buildContextStatusHint, a row above the composer. */
57
114
  readonly contextStatusHint?: string | null;
58
- /**
59
- * Output of the user's scriptable status line (`statusline.command`).
60
- * Rendered as a dim informational line above the prompt when non-null,
61
- * below the context pressure hint.
62
- */
115
+ /** Output of the user's scriptable status line (`statusline.command`), a row above the composer. */
63
116
  readonly scriptableStatusLine?: string | null;
64
- /**
65
- * The one-key retry/switch-model affordance's transient hint text (see
66
- * retry-affordance.ts). Rendered as its own line ABOVE the context
67
- * pressure hint while armed; present here only means "show it now", the
68
- * caller is responsible for passing null the instant it disarms, so this
69
- * is a time-bounded status line, never a lingering claim.
70
- */
117
+ /** The one-key retry/switch-model affordance's transient hint, topmost while armed. */
71
118
  readonly retryHint?: string | null;
72
- /**
73
- * Compact footer posture for short terminals (~<30 rows). Collapses the
74
- * footer to its essentials (prompt box + token/cost line + help) and drops
75
- * the process indicator, context bar, context-info line and posture block.
76
- */
77
- readonly compact?: boolean;
78
- /**
79
- * Cross-surface session-spine posture for the context-info segment.
80
- * Set ONLY in adopted-daemon mode ('online'/'offline'); left undefined in
81
- * local-only mode so no segment renders.
82
- */
83
- readonly sessionSpineStatus?: 'online' | 'offline';
84
- /** The web surface's reachable URL, when that surface is enabled; a persistent footer segment. */
85
- readonly webSurfaceUrl?: string;
86
119
  /** True while power.keepAwake holds, renders the always-visible "sleep disabled" chip. */
87
120
  readonly powerKeepAwake?: boolean;
121
+ /** Live microphone state; a non-null visible state renders the microphone chip. */
122
+ readonly voiceCapture?: VoiceCaptureIndicatorState | null;
123
+ /** What main is doing while it works (a turn, a compaction): the throbber row. Null at rest. */
124
+ readonly throbber?: ThrobberState | null;
125
+ /** A main turn is running: the status line leads with what Esc does now (interrupt, or clear input). */
126
+ readonly turnRunning?: boolean;
88
127
  /**
89
- * Live microphone state, a push-to-talk recording, or the wake detector for
90
- * as long as it runs. Rendered as a persistent row beside the process
91
- * indicator, because a capture device held open with nothing on screen saying
92
- * so is the one state a voice feature must never be in. Null (or a wake state
93
- * with `voice.wake.indicator: off`) renders nothing.
128
+ * The output is scrolled away from its live bottom: the back-to-bottom pill
129
+ * shows above the input area, and a running turn's esc hint says "back to
130
+ * bottom" (Esc returns there and never interrupts while scrolled). Null or
131
+ * absent at the bottom.
94
132
  */
95
- readonly voiceCapture?: VoiceCaptureIndicatorState | null;
133
+ readonly backToBottom?: BackToBottomState | null;
96
134
  }
97
135
 
98
136
  export interface ShellFooterBuildResult {
@@ -100,35 +138,21 @@ export interface ShellFooterBuildResult {
100
138
  readonly height: number;
101
139
  }
102
140
 
103
- // Fixed rows that createFooter always emits (non-compact): prompt-box top
104
- // border, prompt-box bottom border, token-usage line, context-info line, and
105
- // the help/exit line. The composer posture block and context bar are counted
106
- // separately (they are conditional).
107
- const FOOTER_BASE_ROWS = 5;
108
- const CONTEXT_PROGRESS_ROWS = 1;
109
- const PROCESS_INDICATOR_ROWS = 1;
110
- /** The live-microphone row, when one is showing (see renderVoiceCaptureIndicator). */
111
- const VOICE_CAPTURE_ROWS = 1;
112
- // Compact posture drops the context-info line (and never shows the context
113
- // bar or process indicator), leaving just: prompt-box top border, bottom
114
- // border, token-usage line, and the help/exit line.
115
- const COMPACT_FOOTER_BASE_ROWS = 4;
116
-
117
- // Dim slate foreground shared by the passive footer status lines (context
118
- // pressure hint and the scriptable status line).
119
- const DIM_STATUS_FG = '#64748b';
141
+ /** The status line under the composer. */
142
+ const STATUS_ROWS = 1;
143
+
144
+ function blankRow(width: number): Line {
145
+ const line = UIFactory.stringToLine('', width);
146
+ for (const cell of line) cell.bg = '';
147
+ return line;
148
+ }
120
149
 
121
150
  /**
122
- * Real height of the most recently rendered footer, tagged with the compact
123
- * posture it was rendered under. estimateShellFooterHeight prefers this so
124
- * the pre-prompt viewport math accounts for the composer posture block and
125
- * context hint that the static formula cannot see. Reset to null before any
126
- * footer has rendered (cold start), where the formula is exact for the
127
- * common posture-free, hint-free case. The compact flag is part of the cache
128
- * key: a cached non-compact height must never answer a compact query (and
129
- * vice versa), since the two postures differ by several rows.
151
+ * Real height of the most recently rendered footer. estimateShellFooterHeight
152
+ * prefers it so the pre-render viewport math accounts for the hint rows the
153
+ * static formula cannot see. Null before any footer has rendered.
130
154
  */
131
- let lastRenderedFooterHeight: { compact: boolean; height: number } | null = null;
155
+ let lastRenderedFooterHeight: number | null = null;
132
156
 
133
157
  /**
134
158
  * The wrapped-prompt shape the composer reports each frame, the subset
@@ -142,13 +166,8 @@ export interface WrappedPromptCursorInfo {
142
166
 
143
167
  /**
144
168
  * Flatten a wrapped-prompt cursor position (line + column) into the single
145
- * character offset createFooter's `cursorPos` expects, counting one separator
146
- * per wrapped line. Returns undefined when the cursor is not on a visible line
147
- * (scrolled out of the composer's window), which suppresses the cursor overlay
148
- * rather than drawing it in the wrong place.
149
- *
150
- * Lives here rather than inline at the render site so main.ts (a composition
151
- * root already at the source-line gate) stays a wiring file.
169
+ * character offset the composer's `cursorPos` expects, counting one separator
170
+ * per wrapped line. Undefined when the cursor is not on a visible line.
152
171
  */
153
172
  export function promptCursorOffset(info: WrappedPromptCursorInfo): number | undefined {
154
173
  if (info.visibleCursorLine < 0) return undefined;
@@ -158,92 +177,175 @@ export function promptCursorOffset(info: WrappedPromptCursorInfo): number | unde
158
177
  return precedingChars + info.visibleCursorCol;
159
178
  }
160
179
 
161
- export function estimateShellFooterHeight(
162
- promptLineCount: number,
163
- contextWindow?: number,
164
- compact = false,
165
- voiceCapture: VoiceCaptureIndicatorState | null = null,
166
- ): number {
167
- if (lastRenderedFooterHeight !== null && lastRenderedFooterHeight.compact === compact) {
168
- return lastRenderedFooterHeight.height;
180
+ /** Rows the footer will take for this many prompt rows (exact when no hint rows show). */
181
+ export function estimateShellFooterHeight(promptLineCount: number): number {
182
+ if (lastRenderedFooterHeight !== null) return lastRenderedFooterHeight;
183
+ return COMPOSER_FIXED_ROWS + Math.max(1, promptLineCount) + STATUS_ROWS;
184
+ }
185
+
186
+ /** The composer's bar color: the session mode, or shell while a shell command is typed. */
187
+ function composerBarColor(permissionMode: string | undefined, shell: boolean): string {
188
+ const t = activeTokens();
189
+ if (shell) return t.accent;
190
+ switch (permissionMode) {
191
+ case 'plan': return t.info;
192
+ case 'accept-edits':
193
+ case 'allow-all':
194
+ case 'custom':
195
+ return t.warning;
196
+ default:
197
+ return t.brand;
169
198
  }
170
- const safePromptLines = Math.max(1, promptLineCount);
171
- if (compact) {
172
- return COMPACT_FOOTER_BASE_ROWS + safePromptLines;
199
+ }
200
+
201
+ /**
202
+ * The status line's mode chip: the approval mode (muted; plan in the info
203
+ * color), or "! auto-approve" in the error color while everything is
204
+ * auto-approved. While the warning shows, only plan keeps its name beside it
205
+ * (a read-only posture still worth knowing); "normal" or "auto" next to it
206
+ * would only repeat or contradict it.
207
+ */
208
+ function modeChips(permissionMode: string | undefined, dangerMode: boolean): StatusChip[] {
209
+ const t = activeTokens();
210
+ const chips: StatusChip[] = [];
211
+ if (!dangerMode || permissionMode === 'plan') {
212
+ chips.push({ text: permissionModeLabel(permissionMode), fg: permissionMode === 'plan' ? t.info : t.textMuted, bold: permissionMode === 'plan', keep: true });
173
213
  }
174
- const progressRows = contextWindow && contextWindow > 0 ? CONTEXT_PROGRESS_ROWS : 0;
175
- // Counted on the cold-start path too: a shell launched with the wake detector
176
- // already listening renders that row in its very first frame, and a viewport
177
- // sized one row too tall would draw the transcript's last line under it.
178
- const voiceRows = voiceCaptureRowVisible(voiceCapture) ? VOICE_CAPTURE_ROWS : 0;
179
- return FOOTER_BASE_ROWS + safePromptLines + progressRows + PROCESS_INDICATOR_ROWS + voiceRows;
214
+ if (dangerMode) chips.push({ text: '! auto-approve', fg: t.error, bold: true, keep: true });
215
+ return chips;
216
+ }
217
+
218
+ /** The chips no view hides: auto-approve, the live microphone, sleep disabled. */
219
+ function safetyChips(options: ShellFooterBuildOptions): StatusChip[] {
220
+ const t = activeTokens();
221
+ const chips: StatusChip[] = [];
222
+ if (options.dangerMode) chips.push({ text: '! auto-approve', fg: t.error, bold: true, keep: true });
223
+ const voice = options.voiceCapture ?? null;
224
+ if (voice && voiceCaptureRowVisible(voice)) chips.push(voiceCaptureChip(voice));
225
+ if (options.powerKeepAwake) chips.push({ text: SLEEP_DISABLED_CHIP, fg: t.warning, bold: true, keep: true });
226
+ return chips;
227
+ }
228
+
229
+ /** Format a USD amount with a precision that suits its magnitude. */
230
+ function fmtCost(usd: number): string {
231
+ if (!(usd > 0)) return '0.00';
232
+ if (usd < 0.01) return usd.toFixed(4);
233
+ if (usd < 1) return usd.toFixed(3);
234
+ return usd.toFixed(2);
235
+ }
236
+
237
+ /** "~$0.246", or "you ~$0.25 · fleet ~$0.47" once delegated agents have cost something (estimates, hence the ~). */
238
+ export function statusCostText(usage: ShellFooterBuildOptions['usage'], model: string | undefined): string | null {
239
+ const inp = usage.up;
240
+ const out = usage.down;
241
+ const main = model && isModelPriced(model)
242
+ ? `~$${fmtCost(calcSessionCost(inp, out, usage.cacheRead ?? 0, usage.cacheWrite ?? 0, model))}`
243
+ : null;
244
+ const fleet = usage.fleetCostUsd;
245
+ if (typeof fleet === 'number' && fleet > 0) return `you ${main ?? '~n/a'} · fleet ~$${fmtCost(fleet)}`;
246
+ return main;
247
+ }
248
+
249
+ function displayDirectory(workingDir: string | undefined, homeDirectory: string | undefined): string | undefined {
250
+ if (!workingDir) return undefined;
251
+ const home = homeDirectory ?? (typeof process !== 'undefined' ? process.env.HOME ?? '' : '');
252
+ if (!home) return workingDir;
253
+ if (workingDir === home) return '~';
254
+ return workingDir.startsWith(home.endsWith('/') ? home : `${home}/`) ? '~' + workingDir.slice(home.replace(/\/$/, '').length) : workingDir;
180
255
  }
181
256
 
182
- export function buildShellFooter(
183
- options: ShellFooterBuildOptions,
184
- ): ShellFooterBuildResult {
185
- const lines = UIFactory.createFooter(
186
- options.width,
187
- options.promptText,
188
- options.usage,
189
- options.showExitNotice,
190
- options.lastCopyTime,
191
- options.model,
192
- options.toolCount,
193
- options.promptCursorPos,
194
- options.workingDir,
195
- options.provider,
196
- options.contextWindow,
197
- options.compactThreshold,
198
- options.dangerMode,
199
- options.lastInputTokens,
200
- options.commandArgsHint,
201
- options.hitlMode,
202
- options.promptFocused ?? (!options.indicatorFocused && !options.panelFocused),
203
- options.composerMode,
204
- options.composerStatus,
205
- options.composerFlags,
206
- options.composerPendingRisk,
207
- options.compact ?? false,
208
- options.sessionSpineStatus,
209
- options.permissionMode,
210
- options.webSurfaceUrl,
211
- options.powerKeepAwake,
212
- options.modelNote,
213
- );
214
- // Compact posture drops the process indicator and context hint entirely so
215
- // the footer fits within ~5 rows on short terminals.
216
- if (!options.compact) {
217
- const processIndicator = renderProcessIndicator(
218
- options.width,
219
- options.runningAgentCount,
220
- options.runningProcessCount,
221
- options.indicatorFocused,
222
- options.runningAgentProgress,
223
- );
224
- const inputBoxRows = Math.max(1, options.promptLineCount) + 2;
225
- // The voice row sits directly under the prompt box, ABOVE the process
226
- // indicator: an open microphone is a live condition the user is acting inside,
227
- // while the process indicator is a background summary.
228
- lines.splice(inputBoxRows, 0, ...renderVoiceCaptureIndicator(options.width, options.voiceCapture ?? null), ...processIndicator);
229
- // Scriptable status line, dim informational line above the prompt. Unshifted
230
- // before the context hint so the context hint (if any) sits above it.
231
- if (options.scriptableStatusLine) {
232
- lines.unshift(UIFactory.stringToLine(options.scriptableStatusLine, options.width, { fg: DIM_STATUS_FG }));
233
- }
234
- // Passive context status hint, rendered as a dim informational line before the prompt.
235
- if (options.contextStatusHint) {
236
- const hintLine = UIFactory.stringToLine(options.contextStatusHint, options.width, { fg: DIM_STATUS_FG });
237
- lines.unshift(hintLine);
238
- }
239
- // Retry affordance, topmost of the passive hint lines while armed (an
240
- // actionable, time-bounded prompt outranks the passive status hints
241
- // below it); simply absent the instant it disarms.
242
- if (options.retryHint) {
243
- const retryLine = UIFactory.stringToLine(options.retryHint, options.width, { fg: DIM_STATUS_FG, bold: true });
244
- lines.unshift(retryLine);
245
- }
257
+ export function buildShellFooter(options: ShellFooterBuildOptions): ShellFooterBuildResult {
258
+ const t = activeTokens();
259
+ const lines: Line[] = [];
260
+ const view = options.view ?? null;
261
+ // Passive hint rows, topmost first: the retry affordance (actionable, time
262
+ // bounded), the context pressure hint, then the user's scriptable line.
263
+ const above: Line[] = [];
264
+ if (options.retryHint) above.push(UIFactory.stringToLine(` ${options.retryHint}`, options.width, { fg: t.textMuted, bold: true }));
265
+ if (options.contextStatusHint) above.push(UIFactory.stringToLine(` ${options.contextStatusHint}`, options.width, { fg: t.textMuted }));
266
+ if (options.scriptableStatusLine) above.push(UIFactory.stringToLine(` ${options.scriptableStatusLine}`, options.width, { fg: t.textMuted }));
267
+ // The throbber: main's activity, led by "main" inside a view.
268
+ if (options.throbber) above.push(renderThrobberLine(options.width, view ? { ...options.throbber, owner: 'main' } : options.throbber));
269
+ // The back-to-bottom pill is a fill row: a full empty row keeps it off any text row above it.
270
+ if (options.backToBottom) {
271
+ if (above.length > 0) above.push(blankRow(options.width));
272
+ above.push(renderBackToBottomPill(options.width, options.backToBottom));
273
+ }
274
+ // A full empty row keeps these rows off the transcript (a view's body ends with one already).
275
+ if (above.length > 0 && !view) lines.push(blankRow(options.width));
276
+ lines.push(...above);
277
+
278
+ const focused = options.promptFocused ?? !options.indicatorFocused;
279
+ lines.push(...renderComposer({
280
+ width: options.width,
281
+ promptText: options.promptText,
282
+ cursorPos: options.promptCursorPos,
283
+ focused,
284
+ argsHint: options.commandArgsHint,
285
+ modeColor: view ? view.barColor : composerBarColor(options.permissionMode, options.composerPendingRisk === 'shell'),
286
+ placeholder: view?.placeholder,
287
+ disabledReason: view?.disabledReason,
288
+ }));
289
+
290
+ // The left end of the status line: mode, auto-approve, microphone and
291
+ // sleep are kept at any width; the composer flags are dropped first.
292
+ const chips: StatusChip[] = modeChips(options.permissionMode, options.dangerMode === true);
293
+ const voice = options.voiceCapture ?? null;
294
+ if (voice && voiceCaptureRowVisible(voice)) chips.push(voiceCaptureChip(voice));
295
+ if (options.powerKeepAwake) chips.push({ text: SLEEP_DISABLED_CHIP, fg: t.warning, bold: true, keep: true });
296
+ for (const flag of options.composerFlags ?? []) {
297
+ if (flag === 'shell') continue; // the bar's accent color says shell
298
+ // An orchestration request leaves this terminal: its flag carries the remote color.
299
+ const remote = flag === 'orchestration' && options.composerPendingRisk === 'remote';
300
+ chips.push({ text: flag, fg: remote ? activeUiTones().chrome.remote : t.textFaint, bold: remote });
301
+ }
302
+
303
+ const copied = Date.now() - options.lastCopyTime < 2000;
304
+ if (view) {
305
+ // Inside an agent or process view: its keys (what Esc does first), then main kept in sight.
306
+ lines.push(tagFooterLine(renderStatusLine({
307
+ width: options.width,
308
+ chips: safetyChips(options),
309
+ notice: options.showExitNotice ? { text: 'Press Ctrl+C again to exit', tone: 'error' } : view.notice ?? (copied ? { text: 'Copied', tone: 'info' } : null),
310
+ keys: view.keys,
311
+ // While main works its throbber names what it does; the trail would only repeat "working".
312
+ trail: options.throbber ? null : view.trail ?? null,
313
+ cost: view.noContext ? null : view.cost ?? null,
314
+ context: null,
315
+ }), 'usage'));
316
+ lastRenderedFooterHeight = lines.length;
317
+ return { lines, height: lines.length };
246
318
  }
247
- lastRenderedFooterHeight = { compact: options.compact ?? false, height: lines.length };
319
+ // A click anywhere on the status line (the context bar, the cost) opens Usage.
320
+ lines.push(tagFooterLine(renderStatusLine({
321
+ width: options.width,
322
+ chips,
323
+ notice: options.showExitNotice
324
+ ? { text: 'Press Ctrl+C again to exit', tone: 'error' }
325
+ : copied ? { text: 'Copied', tone: 'info' } : null,
326
+ // With text in the composer the next Esc clears it; scrolled back, it returns to the
327
+ // bottom; only an empty composer's Esc at the bottom interrupts.
328
+ busy: options.turnRunning ? { escAction: options.promptText.trim().length > 0 ? 'clear input' : options.backToBottom ? 'back to bottom' : undefined } : null,
329
+ keys: options.workTreeFocused ? WORK_TREE_KEYS : null,
330
+ directory: displayDirectory(options.workingDir, options.homeDirectory),
331
+ branch: options.branch,
332
+ background: {
333
+ agents: options.runningAgentCount,
334
+ processes: options.runningProcessCount,
335
+ focused: options.indicatorFocused,
336
+ progress: options.runningAgentProgress,
337
+ },
338
+ cost: statusCostText(options.usage, options.model),
339
+ // null: the model's window is unknown, and the meter says so; 0 or
340
+ // undefined: no meter (no model yet).
341
+ context: options.contextWindow === null || (options.contextWindow !== undefined && options.contextWindow > 0)
342
+ ? {
343
+ usedTokens: options.lastInputTokens ?? 0,
344
+ windowTokens: options.contextWindow,
345
+ compactFraction: options.compactThreshold && options.compactThreshold > 0 ? options.compactThreshold : 0.85,
346
+ }
347
+ : null,
348
+ }), 'usage'));
349
+ lastRenderedFooterHeight = lines.length;
248
350
  return { lines, height: lines.length };
249
351
  }