@oh-my-pi/pi-tui 18.2.3 → 18.2.5

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 (750) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +43 -0
  3. package/dist/types/app-keybindings.d.ts +395 -0
  4. package/dist/types/apps/autoresearch-dashboard.d.ts +43 -0
  5. package/dist/types/apps/autoresearch-data.d.ts +15 -0
  6. package/dist/types/apps/cleanse-board.d.ts +66 -0
  7. package/dist/types/apps/cleanse-picker.d.ts +23 -0
  8. package/dist/types/apps/debug/log-formatting.d.ts +6 -0
  9. package/dist/types/apps/debug/log-viewer.d.ts +81 -0
  10. package/dist/types/apps/debug/protocol-probe.d.ts +42 -0
  11. package/dist/types/apps/debug/raw-sse-buffer.d.ts +58 -0
  12. package/dist/types/apps/debug/raw-sse.d.ts +26 -0
  13. package/dist/types/apps/debug/terminal-info.d.ts +34 -0
  14. package/dist/types/apps/debug/viewer-frame.d.ts +52 -0
  15. package/dist/types/apps/git/avatar.d.ts +9 -0
  16. package/dist/types/apps/git/colors.d.ts +49 -0
  17. package/dist/types/apps/git/diff-pane.d.ts +188 -0
  18. package/dist/types/apps/git/git-tui.d.ts +66 -0
  19. package/dist/types/apps/git/sidebar.d.ts +149 -0
  20. package/dist/types/apps/git/state.d.ts +117 -0
  21. package/dist/types/apps/if-bench-board.d.ts +75 -0
  22. package/dist/types/apps/live-visualizer.d.ts +33 -0
  23. package/dist/types/apps/ps-data.d.ts +51 -0
  24. package/dist/types/apps/ps-top.d.ts +33 -0
  25. package/dist/types/apps/session-picker.d.ts +26 -0
  26. package/dist/types/apps/setup-model-picker.d.ts +13 -0
  27. package/dist/types/apps/standalone-picker.d.ts +42 -0
  28. package/dist/types/autocomplete.d.ts +4 -0
  29. package/dist/types/chat/advisor-message.d.ts +11 -0
  30. package/dist/types/chat/assistant-message.d.ts +106 -0
  31. package/dist/types/chat/background-tan-message.d.ts +9 -0
  32. package/dist/types/chat/bash-execution.d.ts +47 -0
  33. package/dist/types/chat/cache-invalidation-marker.d.ts +50 -0
  34. package/dist/types/chat/chat-transcript-builder.d.ts +50 -0
  35. package/dist/types/chat/collab-prompt-message.d.ts +10 -0
  36. package/dist/types/chat/compaction-summary-message.d.ts +54 -0
  37. package/dist/types/chat/custom-message.d.ts +10 -0
  38. package/dist/types/chat/display-preferences.d.ts +13 -0
  39. package/dist/types/chat/eval-execution.d.ts +29 -0
  40. package/dist/types/chat/execution-shared.d.ts +51 -0
  41. package/dist/types/chat/extension-types.d.ts +28 -0
  42. package/dist/types/chat/hook-message.d.ts +10 -0
  43. package/dist/types/chat/image-loading.d.ts +46 -0
  44. package/dist/types/chat/late-diagnostics-message.d.ts +21 -0
  45. package/dist/types/chat/messages.d.ts +189 -0
  46. package/dist/types/chat/reaction.d.ts +30 -0
  47. package/dist/types/chat/read-target.d.ts +4 -0
  48. package/dist/types/chat/read-tool-group.d.ts +67 -0
  49. package/dist/types/chat/served-model-marker.d.ts +47 -0
  50. package/dist/types/chat/skill-message.d.ts +33 -0
  51. package/dist/types/chat/skill-title-input.d.ts +14 -0
  52. package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +12 -0
  53. package/dist/types/chat/thinking-display.d.ts +12 -0
  54. package/dist/types/chat/todo-reminder.d.ts +13 -0
  55. package/dist/types/chat/tool-execution.d.ts +138 -0
  56. package/dist/types/chat/transcript-browser.d.ts +93 -0
  57. package/dist/types/chat/transcript-entry.d.ts +45 -0
  58. package/dist/types/chat/transcript-outline.d.ts +86 -0
  59. package/dist/types/chat/transcript-render-helpers.d.ts +92 -0
  60. package/dist/types/chat/ttsr-notification.d.ts +23 -0
  61. package/dist/types/chat/user-message.d.ts +52 -0
  62. package/dist/types/chrome/chat-block.d.ts +64 -0
  63. package/dist/types/chrome/collab-qrcode.d.ts +28 -0
  64. package/dist/types/chrome/context-thresholds.d.ts +10 -0
  65. package/dist/types/chrome/countdown-timer.d.ts +14 -0
  66. package/dist/types/chrome/diff.d.ts +14 -0
  67. package/dist/types/chrome/dynamic-border.d.ts +16 -0
  68. package/dist/types/chrome/error-block.d.ts +17 -0
  69. package/dist/types/chrome/form-theme.d.ts +3 -0
  70. package/dist/types/chrome/format.d.ts +12 -0
  71. package/dist/types/chrome/index.d.ts +20 -0
  72. package/dist/types/chrome/keybinding-hints.d.ts +40 -0
  73. package/dist/types/chrome/live-board.d.ts +23 -0
  74. package/dist/types/chrome/local-date.d.ts +4 -0
  75. package/dist/types/chrome/message-divider.d.ts +22 -0
  76. package/dist/types/chrome/message-frame.d.ts +52 -0
  77. package/dist/types/chrome/message-notice.d.ts +35 -0
  78. package/dist/types/chrome/overlay-box.d.ts +74 -0
  79. package/dist/types/chrome/qrcode.d.ts +48 -0
  80. package/dist/types/chrome/segment-track.d.ts +27 -0
  81. package/dist/types/chrome/select-list-mouse-routing.d.ts +4 -0
  82. package/dist/types/chrome/selector-helpers.d.ts +52 -0
  83. package/dist/types/chrome/shared.d.ts +5 -0
  84. package/dist/types/chrome/streaming-panel.d.ts +19 -0
  85. package/dist/types/chrome/tool-activity.d.ts +17 -0
  86. package/dist/types/chrome/transcript-container.d.ts +129 -0
  87. package/dist/types/chrome/transcript-status.d.ts +12 -0
  88. package/dist/types/chrome/visual-truncate.d.ts +19 -0
  89. package/dist/types/components/disclosure.d.ts +42 -0
  90. package/dist/types/components/form.d.ts +130 -0
  91. package/dist/types/components/image.d.ts +18 -16
  92. package/dist/types/components/key-value-list.d.ts +24 -0
  93. package/dist/types/components/layout/geometry.d.ts +73 -0
  94. package/dist/types/components/layout/row.d.ts +55 -0
  95. package/dist/types/components/layout/split-pane.d.ts +80 -0
  96. package/dist/types/components/layout/stack.d.ts +50 -0
  97. package/dist/types/components/menu-selection.d.ts +71 -0
  98. package/dist/types/components/metric.d.ts +44 -0
  99. package/dist/types/components/progress-bar.d.ts +36 -0
  100. package/dist/types/components/scroll-view.d.ts +53 -19
  101. package/dist/types/components/scroll-viewport.d.ts +46 -0
  102. package/dist/types/components/section.d.ts +19 -0
  103. package/dist/types/components/select-list.d.ts +46 -2
  104. package/dist/types/components/settings-list.d.ts +20 -0
  105. package/dist/types/components/table.d.ts +32 -0
  106. package/dist/types/components/tree-view.d.ts +130 -0
  107. package/dist/types/components/wizard-step.d.ts +60 -0
  108. package/dist/types/hotkeys-markdown.d.ts +7 -0
  109. package/dist/types/index.d.ts +17 -0
  110. package/dist/types/keybinding-matchers.d.ts +35 -0
  111. package/dist/types/keys.d.ts +2 -2
  112. package/dist/types/lang-from-path.d.ts +9 -0
  113. package/dist/types/overlays/advisor-config.d.ts +114 -0
  114. package/dist/types/overlays/agent-activity.d.ts +41 -0
  115. package/dist/types/overlays/agent-hub-projection.d.ts +33 -0
  116. package/dist/types/overlays/agent-hub-renderer.d.ts +53 -0
  117. package/dist/types/overlays/agent-hub-types.d.ts +99 -0
  118. package/dist/types/overlays/agent-hub.d.ts +126 -0
  119. package/dist/types/overlays/agent-transcript-viewer.d.ts +71 -0
  120. package/dist/types/overlays/agents-hub.d.ts +72 -0
  121. package/dist/types/overlays/ask-dialog.d.ts +88 -0
  122. package/dist/types/overlays/bordered-loader.d.ts +11 -0
  123. package/dist/types/overlays/btw-history-panel.d.ts +29 -0
  124. package/dist/types/overlays/btw-history.d.ts +17 -0
  125. package/dist/types/overlays/btw-panel.d.ts +26 -0
  126. package/dist/types/overlays/cleanse-panel.d.ts +36 -0
  127. package/dist/types/overlays/codex-reset-fireworks.d.ts +52 -0
  128. package/dist/types/overlays/composer-shape-preview.d.ts +47 -0
  129. package/dist/types/overlays/composer-shape-registry.d.ts +50 -0
  130. package/dist/types/overlays/copy-selector.d.ts +48 -0
  131. package/dist/types/overlays/copy-targets.d.ts +63 -0
  132. package/dist/types/overlays/error-banner.d.ts +13 -0
  133. package/dist/types/overlays/extensions/display-text.d.ts +10 -0
  134. package/dist/types/overlays/extensions/extension-dashboard.d.ts +63 -0
  135. package/dist/types/overlays/extensions/extension-list.d.ts +54 -0
  136. package/dist/types/overlays/extensions/inspector-model.d.ts +109 -0
  137. package/dist/types/overlays/extensions/inspector-panel.d.ts +17 -0
  138. package/dist/types/overlays/extensions/live-tool-session.d.ts +28 -0
  139. package/dist/types/overlays/extensions/mcp-runtime.d.ts +107 -0
  140. package/dist/types/overlays/extensions/state-manager.d.ts +42 -0
  141. package/dist/types/overlays/extensions/types.d.ts +165 -0
  142. package/dist/types/overlays/history-search.d.ts +16 -0
  143. package/dist/types/overlays/hook-editor.d.ts +44 -0
  144. package/dist/types/overlays/hook-input.d.ts +20 -0
  145. package/dist/types/overlays/hook-selector.d.ts +71 -0
  146. package/dist/types/overlays/hub-frame.d.ts +77 -0
  147. package/dist/types/overlays/login-dialog.d.ts +43 -0
  148. package/dist/types/overlays/logout-account-selector.d.ts +15 -0
  149. package/dist/types/overlays/mcp-add-wizard.d.ts +87 -0
  150. package/dist/types/overlays/model-browser.d.ts +213 -0
  151. package/dist/types/overlays/model-hub.d.ts +72 -0
  152. package/dist/types/overlays/model-picker.d.ts +74 -0
  153. package/dist/types/overlays/model-selector.d.ts +52 -0
  154. package/dist/types/overlays/move-overlay.d.ts +39 -0
  155. package/dist/types/overlays/oauth-selector.d.ts +41 -0
  156. package/dist/types/overlays/omfg-panel.d.ts +20 -0
  157. package/dist/types/overlays/pause-screen.d.ts +43 -0
  158. package/dist/types/overlays/plan-review-overlay.d.ts +89 -0
  159. package/dist/types/overlays/plan-save-overlay.d.ts +19 -0
  160. package/dist/types/overlays/plan-toc.d.ts +41 -0
  161. package/dist/types/overlays/plugin-selector.d.ts +28 -0
  162. package/dist/types/overlays/plugin-settings.d.ts +180 -0
  163. package/dist/types/overlays/queue-mode-selector.d.ts +11 -0
  164. package/dist/types/overlays/reset-usage-selector.d.ts +23 -0
  165. package/dist/types/overlays/rewind-selector.d.ts +61 -0
  166. package/dist/types/overlays/running-subagent-badge.d.ts +6 -0
  167. package/dist/types/overlays/session-account-selector.d.ts +24 -0
  168. package/dist/types/overlays/session-info-overlay.d.ts +17 -0
  169. package/dist/types/overlays/session-observer-registry.d.ts +77 -0
  170. package/dist/types/overlays/session-selector.d.ts +131 -0
  171. package/dist/types/overlays/settings-defs.d.ts +119 -0
  172. package/dist/types/overlays/settings-selector.d.ts +73 -0
  173. package/dist/types/overlays/show-images-selector.d.ts +11 -0
  174. package/dist/types/overlays/snapcompact-shape-preview.d.ts +31 -0
  175. package/dist/types/overlays/theme-selector.d.ts +12 -0
  176. package/dist/types/overlays/thinking-selector.d.ts +12 -0
  177. package/dist/types/overlays/tiny-title-download-progress.d.ts +21 -0
  178. package/dist/types/overlays/tree-selector.d.ts +92 -0
  179. package/dist/types/overlays/usage-dashboard.d.ts +94 -0
  180. package/dist/types/overlays/usage-display.d.ts +5 -0
  181. package/dist/types/overlays/usage-row.d.ts +17 -0
  182. package/dist/types/prompt/attachment-chips.d.ts +19 -0
  183. package/dist/types/prompt/composer-attachments.d.ts +71 -0
  184. package/dist/types/prompt/composer-cache.d.ts +37 -0
  185. package/dist/types/prompt/composer.d.ts +159 -0
  186. package/dist/types/prompt/custom-editor.d.ts +261 -0
  187. package/dist/types/prompt/editor-top-gap.d.ts +18 -0
  188. package/dist/types/prompt/emoji-autocomplete.d.ts +16 -0
  189. package/dist/types/prompt/github-ref-autocomplete.d.ts +35 -0
  190. package/dist/types/prompt/gradient-highlight.d.ts +32 -0
  191. package/dist/types/prompt/image-format.d.ts +4 -0
  192. package/dist/types/prompt/image-references.d.ts +26 -0
  193. package/dist/types/prompt/interactive-context-helpers.d.ts +43 -0
  194. package/dist/types/prompt/internal-url-autocomplete.d.ts +56 -0
  195. package/dist/types/prompt/macos-spelling.d.ts +44 -0
  196. package/dist/types/prompt/magic-keyword-boundary.d.ts +9 -0
  197. package/dist/types/prompt/magic-keywords.d.ts +26 -0
  198. package/dist/types/prompt/markdown-prose.d.ts +15 -0
  199. package/dist/types/prompt/model-mention-autocomplete.d.ts +25 -0
  200. package/dist/types/prompt/model-mention-syntax.d.ts +24 -0
  201. package/dist/types/prompt/orchestrate.d.ts +13 -0
  202. package/dist/types/prompt/prompt-action-autocomplete.d.ts +56 -0
  203. package/dist/types/prompt/queue-input.d.ts +8 -0
  204. package/dist/types/prompt/skill-tokens.d.ts +13 -0
  205. package/dist/types/prompt/tools-markdown.d.ts +12 -0
  206. package/dist/types/prompt/ultrathink.d.ts +13 -0
  207. package/dist/types/prompt/usage-amounts.d.ts +26 -0
  208. package/dist/types/prompt/video.d.ts +18 -0
  209. package/dist/types/prompt/welcome.d.ts +83 -0
  210. package/dist/types/prompt/workflow.d.ts +13 -0
  211. package/dist/types/render/code-cell.d.ts +51 -0
  212. package/dist/types/render/file-list.d.ts +22 -0
  213. package/dist/types/render/hyperlink.d.ts +80 -0
  214. package/dist/types/render/index.d.ts +16 -0
  215. package/dist/types/render/output-block.d.ts +54 -0
  216. package/dist/types/render/output-pane.d.ts +80 -0
  217. package/dist/types/render/render-utils.d.ts +312 -0
  218. package/dist/types/render/sixel.d.ts +21 -0
  219. package/dist/types/render/status-line.d.ts +23 -0
  220. package/dist/types/render/tool-card.d.ts +71 -0
  221. package/dist/types/render/tree-list.d.ts +29 -0
  222. package/dist/types/render/types.d.ts +17 -0
  223. package/dist/types/render/utils.d.ts +43 -0
  224. package/dist/types/render/width-aware-text.d.ts +23 -0
  225. package/dist/types/setup/lazy.d.ts +3 -0
  226. package/dist/types/setup/scenes/composer.d.ts +3 -0
  227. package/dist/types/setup/scenes/glyph.d.ts +3 -0
  228. package/dist/types/setup/scenes/model.d.ts +3 -0
  229. package/dist/types/setup/scenes/outro.d.ts +2 -0
  230. package/dist/types/setup/scenes/providers.d.ts +3 -0
  231. package/dist/types/setup/scenes/sign-in.d.ts +21 -0
  232. package/dist/types/setup/scenes/splash.d.ts +11 -0
  233. package/dist/types/setup/scenes/theme.d.ts +3 -0
  234. package/dist/types/setup/scenes/types.d.ts +110 -0
  235. package/dist/types/setup/scenes/web-search.d.ts +22 -0
  236. package/dist/types/setup/setup-version.d.ts +11 -0
  237. package/dist/types/setup/startup-splash.d.ts +9 -0
  238. package/dist/types/setup/wizard-overlay.d.ts +16 -0
  239. package/dist/types/setup/wizard.d.ts +25 -0
  240. package/dist/types/status-line/component.d.ts +182 -0
  241. package/dist/types/status-line/context-usage.d.ts +156 -0
  242. package/dist/types/status-line/footer.d.ts +30 -0
  243. package/dist/types/status-line/git-utils.d.ts +22 -0
  244. package/dist/types/status-line/host.d.ts +129 -0
  245. package/dist/types/status-line/index.d.ts +6 -0
  246. package/dist/types/status-line/loop.d.ts +18 -0
  247. package/dist/types/status-line/metrics.d.ts +21 -0
  248. package/dist/types/status-line/presets.d.ts +3 -0
  249. package/dist/types/status-line/schema.d.ts +15 -0
  250. package/dist/types/status-line/segments.d.ts +5 -0
  251. package/dist/types/status-line/separators.d.ts +3 -0
  252. package/dist/types/status-line/types.d.ts +236 -0
  253. package/dist/types/stdin-buffer.d.ts +4 -0
  254. package/dist/types/theme/color.d.ts +24 -0
  255. package/dist/types/theme/defaults/index.d.ts +9489 -0
  256. package/dist/types/theme/index.d.ts +4 -0
  257. package/dist/types/theme/loader.d.ts +23 -0
  258. package/dist/types/theme/mermaid-cache.d.ts +26 -0
  259. package/dist/types/theme/schema-validation.d.ts +3 -0
  260. package/dist/types/theme/schema.d.ts +25 -0
  261. package/dist/types/theme/session-color.d.ts +43 -0
  262. package/dist/types/theme/shimmer.d.ts +44 -0
  263. package/dist/types/theme/symbols.d.ts +34 -0
  264. package/dist/types/theme/theme-class.d.ts +299 -0
  265. package/dist/types/theme/theme.d.ts +111 -0
  266. package/dist/types/theme/tui-adapters.d.ts +30 -0
  267. package/dist/types/thinking.d.ts +165 -0
  268. package/dist/types/tools/agent-tree.d.ts +36 -0
  269. package/dist/types/tools/ask.d.ts +67 -0
  270. package/dist/types/tools/ast-edit.d.ts +53 -0
  271. package/dist/types/tools/ast-grep.d.ts +52 -0
  272. package/dist/types/tools/autoresearch.d.ts +156 -0
  273. package/dist/types/tools/bash-interactive.d.ts +28 -0
  274. package/dist/types/tools/bash.d.ts +103 -0
  275. package/dist/types/tools/conflict-detect.d.ts +43 -0
  276. package/dist/types/tools/debug.d.ts +60 -0
  277. package/dist/types/tools/default-renderer.d.ts +24 -0
  278. package/dist/types/tools/edit.d.ts +158 -0
  279. package/dist/types/tools/eval-format/code-mode-declarations.d.ts +5 -0
  280. package/dist/types/tools/eval-format/index.d.ts +5 -0
  281. package/dist/types/tools/eval-format/javascript.d.ts +2 -0
  282. package/dist/types/tools/eval-format/python.d.ts +2 -0
  283. package/dist/types/tools/eval.d.ts +94 -0
  284. package/dist/types/tools/fetch.d.ts +42 -0
  285. package/dist/types/tools/gh-format.d.ts +8 -0
  286. package/dist/types/tools/github.d.ts +144 -0
  287. package/dist/types/tools/glob.d.ts +44 -0
  288. package/dist/types/tools/goal.d.ts +42 -0
  289. package/dist/types/tools/grep.d.ts +64 -0
  290. package/dist/types/tools/grouped-file-output.d.ts +71 -0
  291. package/dist/types/tools/hashline-format.d.ts +28 -0
  292. package/dist/types/tools/hub.d.ts +340 -0
  293. package/dist/types/tools/index.d.ts +4 -0
  294. package/dist/types/tools/json-tree.d.ts +59 -0
  295. package/dist/types/tools/line-ranges.d.ts +29 -0
  296. package/dist/types/tools/list-limit.d.ts +15 -0
  297. package/dist/types/tools/lsp.d.ts +72 -0
  298. package/dist/types/tools/match-line-format.d.ts +11 -0
  299. package/dist/types/tools/mcp.d.ts +81 -0
  300. package/dist/types/tools/memory.d.ts +67 -0
  301. package/dist/types/tools/output-meta.d.ts +151 -0
  302. package/dist/types/tools/read.d.ts +139 -0
  303. package/dist/types/tools/renderer.d.ts +98 -0
  304. package/dist/types/tools/report-tool-issue.d.ts +8 -0
  305. package/dist/types/tools/resolve.d.ts +47 -0
  306. package/dist/types/tools/streaming-output.d.ts +282 -0
  307. package/dist/types/tools/subprocess.d.ts +24 -0
  308. package/dist/types/tools/task-repair-args.d.ts +53 -0
  309. package/dist/types/tools/task-yield-assembly.d.ts +20 -0
  310. package/dist/types/tools/task.d.ts +425 -0
  311. package/dist/types/tools/terminal-output.d.ts +8 -0
  312. package/dist/types/tools/think.d.ts +13 -0
  313. package/dist/types/tools/todo.d.ts +118 -0
  314. package/dist/types/tools/tool-errors.d.ts +7 -0
  315. package/dist/types/tools/vibe.d.ts +120 -0
  316. package/dist/types/tools/web-search.d.ts +195 -0
  317. package/dist/types/tools/write.d.ts +76 -0
  318. package/dist/types/tools/xd-url.d.ts +13 -0
  319. package/dist/types/tools/xdev.d.ts +52 -0
  320. package/dist/types/tui.d.ts +18 -0
  321. package/dist/types/utils.d.ts +2 -0
  322. package/package.json +37 -3
  323. package/src/app-keybindings.ts +760 -0
  324. package/src/apps/autoresearch-dashboard.ts +500 -0
  325. package/src/apps/autoresearch-data.ts +64 -0
  326. package/src/apps/cleanse-board.ts +341 -0
  327. package/src/apps/cleanse-picker.ts +57 -0
  328. package/src/apps/debug/log-formatting.ts +53 -0
  329. package/src/apps/debug/log-viewer.ts +868 -0
  330. package/src/apps/debug/protocol-probe.ts +267 -0
  331. package/src/apps/debug/raw-sse-buffer.ts +449 -0
  332. package/src/apps/debug/raw-sse.ts +282 -0
  333. package/src/apps/debug/terminal-info.ts +115 -0
  334. package/src/apps/debug/viewer-frame.ts +174 -0
  335. package/src/apps/git/avatar.ts +52 -0
  336. package/src/apps/git/colors.ts +109 -0
  337. package/src/apps/git/diff-pane.ts +1512 -0
  338. package/src/apps/git/git-tui.ts +889 -0
  339. package/src/apps/git/sidebar.ts +1204 -0
  340. package/src/apps/git/state.ts +113 -0
  341. package/src/apps/if-bench-board.ts +324 -0
  342. package/src/apps/live-visualizer.ts +238 -0
  343. package/src/apps/ps-data.ts +116 -0
  344. package/src/apps/ps-top.ts +422 -0
  345. package/src/apps/session-picker.ts +95 -0
  346. package/src/apps/setup-model-picker.ts +21 -0
  347. package/src/apps/standalone-picker.ts +111 -0
  348. package/src/autocomplete.ts +9 -13
  349. package/src/chat/advisor-message.ts +175 -0
  350. package/src/chat/assistant-message.ts +1214 -0
  351. package/src/chat/background-tan-message.ts +35 -0
  352. package/src/chat/bash-execution.ts +394 -0
  353. package/src/chat/cache-invalidation-marker.ts +94 -0
  354. package/src/chat/chat-transcript-builder.ts +593 -0
  355. package/src/chat/collab-prompt-message.ts +34 -0
  356. package/src/chat/compaction-summary-message.ts +261 -0
  357. package/src/chat/custom-message.ts +23 -0
  358. package/src/chat/display-preferences.ts +24 -0
  359. package/src/chat/eval-execution.ts +172 -0
  360. package/src/chat/execution-shared.ts +113 -0
  361. package/src/chat/extension-types.ts +46 -0
  362. package/src/chat/hook-message.ts +20 -0
  363. package/src/chat/image-loading.ts +122 -0
  364. package/src/chat/late-diagnostics-message.ts +92 -0
  365. package/src/chat/messages.ts +305 -0
  366. package/src/chat/reaction.ts +65 -0
  367. package/src/chat/read-target.ts +7 -0
  368. package/src/chat/read-tool-group.ts +873 -0
  369. package/src/chat/served-model-marker.ts +91 -0
  370. package/src/chat/skill-message.ts +220 -0
  371. package/src/chat/skill-title-input.ts +41 -0
  372. package/src/chat/stripped-tool-calls-placeholder.ts +35 -0
  373. package/src/chat/thinking-display.ts +312 -0
  374. package/src/chat/todo-reminder.ts +33 -0
  375. package/src/chat/tool-execution.ts +1395 -0
  376. package/src/chat/transcript-browser.ts +268 -0
  377. package/src/chat/transcript-entry.ts +96 -0
  378. package/src/chat/transcript-outline.ts +260 -0
  379. package/src/chat/transcript-render-helpers.ts +315 -0
  380. package/src/chat/ttsr-notification.ts +123 -0
  381. package/src/chat/user-message.ts +287 -0
  382. package/src/chrome/chat-block.ts +110 -0
  383. package/src/chrome/collab-qrcode.ts +74 -0
  384. package/src/chrome/context-thresholds.ts +85 -0
  385. package/src/chrome/countdown-timer.ts +74 -0
  386. package/src/chrome/diff.ts +266 -0
  387. package/src/chrome/dynamic-border.ts +36 -0
  388. package/src/chrome/error-block.ts +63 -0
  389. package/src/chrome/form-theme.ts +10 -0
  390. package/src/chrome/format.ts +66 -0
  391. package/src/chrome/index.ts +21 -0
  392. package/src/chrome/keybinding-hints.ts +55 -0
  393. package/src/chrome/live-board.ts +123 -0
  394. package/src/chrome/local-date.ts +20 -0
  395. package/src/chrome/message-divider.ts +51 -0
  396. package/src/chrome/message-frame.ts +164 -0
  397. package/src/chrome/message-notice.ts +103 -0
  398. package/src/chrome/overlay-box.ts +301 -0
  399. package/src/chrome/qrcode.ts +539 -0
  400. package/src/chrome/segment-track.ts +86 -0
  401. package/src/chrome/select-list-mouse-routing.ts +11 -0
  402. package/src/chrome/selector-helpers.ts +119 -0
  403. package/src/chrome/shared.ts +27 -0
  404. package/src/chrome/streaming-panel.ts +72 -0
  405. package/src/chrome/tool-activity.ts +44 -0
  406. package/src/chrome/transcript-container.ts +902 -0
  407. package/src/chrome/transcript-status.ts +27 -0
  408. package/src/chrome/visual-truncate.ts +70 -0
  409. package/src/components/disclosure.ts +235 -0
  410. package/src/components/editor.ts +10 -12
  411. package/src/components/form.ts +566 -0
  412. package/src/components/image.ts +80 -53
  413. package/src/components/input.ts +6 -27
  414. package/src/components/key-value-list.ts +88 -0
  415. package/src/components/layout/geometry.ts +202 -0
  416. package/src/components/layout/row.ts +283 -0
  417. package/src/components/layout/split-pane.ts +349 -0
  418. package/src/components/layout/stack.ts +261 -0
  419. package/src/components/markdown.ts +16 -12
  420. package/src/components/menu-selection.ts +248 -0
  421. package/src/components/metric.ts +132 -0
  422. package/src/components/progress-bar.ts +140 -0
  423. package/src/components/scroll-view.ts +372 -70
  424. package/src/components/scroll-viewport.ts +154 -0
  425. package/src/components/section.ts +77 -0
  426. package/src/components/select-list.ts +238 -159
  427. package/src/components/settings-list.ts +103 -80
  428. package/src/components/table.ts +127 -0
  429. package/src/components/tree-view.ts +481 -0
  430. package/src/components/wizard-step.ts +229 -0
  431. package/src/deccara.ts +1 -3
  432. package/src/hotkeys-markdown.ts +100 -0
  433. package/src/index.ts +18 -0
  434. package/src/keybinding-matchers.ts +99 -0
  435. package/src/keys.ts +2 -2
  436. package/src/lang-from-path.ts +252 -0
  437. package/src/overlays/advisor-config.ts +761 -0
  438. package/src/overlays/agent-activity.ts +113 -0
  439. package/src/overlays/agent-hub-projection.ts +250 -0
  440. package/src/overlays/agent-hub-renderer.ts +281 -0
  441. package/src/overlays/agent-hub-types.ts +74 -0
  442. package/src/overlays/agent-hub.ts +1562 -0
  443. package/src/overlays/agent-transcript-viewer.ts +669 -0
  444. package/src/overlays/agents-hub.ts +1203 -0
  445. package/src/overlays/ask-dialog.ts +1202 -0
  446. package/src/overlays/bordered-loader.ts +41 -0
  447. package/src/overlays/btw-history-panel.ts +591 -0
  448. package/src/overlays/btw-history.ts +33 -0
  449. package/src/overlays/btw-panel.ts +167 -0
  450. package/src/overlays/cleanse-panel.ts +167 -0
  451. package/src/overlays/codex-reset-fireworks.ts +375 -0
  452. package/src/overlays/composer-shape-preview.ts +137 -0
  453. package/src/overlays/composer-shape-registry.ts +82 -0
  454. package/src/overlays/copy-selector.ts +618 -0
  455. package/src/overlays/copy-targets.ts +210 -0
  456. package/src/overlays/error-banner.ts +39 -0
  457. package/src/overlays/extensions/display-text.ts +39 -0
  458. package/src/overlays/extensions/extension-dashboard.ts +645 -0
  459. package/src/overlays/extensions/extension-list.ts +667 -0
  460. package/src/overlays/extensions/inspector-model.ts +484 -0
  461. package/src/overlays/extensions/inspector-panel.ts +669 -0
  462. package/src/overlays/extensions/live-tool-session.ts +67 -0
  463. package/src/overlays/extensions/mcp-runtime.ts +289 -0
  464. package/src/overlays/extensions/state-manager.ts +337 -0
  465. package/src/overlays/extensions/types.ts +208 -0
  466. package/src/overlays/history-search.ts +268 -0
  467. package/src/overlays/hook-editor.ts +272 -0
  468. package/src/overlays/hook-input.ts +84 -0
  469. package/src/overlays/hook-selector.ts +635 -0
  470. package/src/overlays/hub-frame.ts +262 -0
  471. package/src/overlays/login-dialog.ts +231 -0
  472. package/src/overlays/logout-account-selector.ts +115 -0
  473. package/src/overlays/mcp-add-wizard.ts +1436 -0
  474. package/src/overlays/model-browser.ts +1176 -0
  475. package/src/overlays/model-hub.ts +2279 -0
  476. package/src/overlays/model-picker.ts +285 -0
  477. package/src/overlays/model-selector.ts +107 -0
  478. package/src/overlays/move-overlay.ts +197 -0
  479. package/src/overlays/oauth-selector.ts +446 -0
  480. package/src/overlays/omfg-panel.ts +136 -0
  481. package/src/overlays/pause-screen.ts +198 -0
  482. package/src/overlays/plan-review-overlay.ts +1227 -0
  483. package/src/overlays/plan-save-overlay.ts +74 -0
  484. package/src/overlays/plan-toc.ts +139 -0
  485. package/src/overlays/plugin-selector.ts +97 -0
  486. package/src/overlays/plugin-settings.ts +839 -0
  487. package/src/overlays/queue-mode-selector.ts +55 -0
  488. package/src/overlays/reset-usage-selector.ts +170 -0
  489. package/src/overlays/rewind-selector.ts +499 -0
  490. package/src/overlays/running-subagent-badge.ts +19 -0
  491. package/src/overlays/session-account-selector.ts +68 -0
  492. package/src/overlays/session-info-overlay.ts +96 -0
  493. package/src/overlays/session-observer-registry.ts +289 -0
  494. package/src/overlays/session-selector.ts +1137 -0
  495. package/src/overlays/settings-defs.ts +315 -0
  496. package/src/overlays/settings-selector.ts +1415 -0
  497. package/src/overlays/show-images-selector.ts +44 -0
  498. package/src/overlays/snapcompact-shape-preview-doc.md +14 -0
  499. package/src/overlays/snapcompact-shape-preview.ts +192 -0
  500. package/src/overlays/theme-selector.ts +62 -0
  501. package/src/overlays/thinking-selector.ts +51 -0
  502. package/src/overlays/tiny-title-download-progress.ts +102 -0
  503. package/src/overlays/tree-selector.ts +1121 -0
  504. package/src/overlays/usage-dashboard.ts +629 -0
  505. package/src/overlays/usage-display.ts +45 -0
  506. package/src/overlays/usage-row.ts +118 -0
  507. package/src/prompt/attachment-chips.ts +201 -0
  508. package/src/prompt/composer-attachments.ts +280 -0
  509. package/src/prompt/composer-cache.ts +304 -0
  510. package/src/prompt/composer.ts +934 -0
  511. package/src/prompt/custom-editor.ts +1392 -0
  512. package/src/prompt/data/emojis.json +1 -0
  513. package/src/prompt/editor-top-gap.ts +28 -0
  514. package/src/prompt/emoji-autocomplete.ts +285 -0
  515. package/src/prompt/github-ref-autocomplete.ts +75 -0
  516. package/src/prompt/gradient-highlight.ts +101 -0
  517. package/src/prompt/image-format.ts +28 -0
  518. package/src/prompt/image-references.ts +102 -0
  519. package/src/prompt/interactive-context-helpers.ts +126 -0
  520. package/src/prompt/internal-url-autocomplete.ts +155 -0
  521. package/src/prompt/macos-spelling.ts +456 -0
  522. package/src/prompt/magic-keyword-boundary.ts +23 -0
  523. package/src/prompt/magic-keywords.ts +42 -0
  524. package/src/prompt/markdown-prose.ts +245 -0
  525. package/src/prompt/model-mention-autocomplete.ts +94 -0
  526. package/src/prompt/model-mention-syntax.ts +41 -0
  527. package/src/prompt/orchestrate.ts +26 -0
  528. package/src/prompt/prompt-action-autocomplete.ts +337 -0
  529. package/src/prompt/queue-input.ts +132 -0
  530. package/src/prompt/skill-tokens.ts +36 -0
  531. package/src/prompt/tips.txt +27 -0
  532. package/src/prompt/tools-markdown.ts +29 -0
  533. package/src/prompt/ultrathink.ts +26 -0
  534. package/src/prompt/usage-amounts.ts +99 -0
  535. package/src/prompt/video.ts +47 -0
  536. package/src/prompt/welcome.ts +605 -0
  537. package/src/prompt/workflow.ts +26 -0
  538. package/src/render/code-cell.ts +244 -0
  539. package/src/render/file-list.ts +58 -0
  540. package/src/render/hyperlink.ts +192 -0
  541. package/src/render/index.ts +17 -0
  542. package/src/render/output-block.ts +267 -0
  543. package/src/render/output-pane.ts +299 -0
  544. package/src/render/render-utils.ts +1182 -0
  545. package/src/render/sixel.ts +69 -0
  546. package/src/render/status-line.ts +55 -0
  547. package/src/render/tool-card.ts +272 -0
  548. package/src/render/tree-list.ts +188 -0
  549. package/src/render/types.ts +19 -0
  550. package/src/render/utils.ts +109 -0
  551. package/src/render/width-aware-text.ts +60 -0
  552. package/src/setup/lazy.ts +17 -0
  553. package/src/setup/scenes/composer.ts +127 -0
  554. package/src/setup/scenes/glyph.ts +120 -0
  555. package/src/setup/scenes/model.ts +138 -0
  556. package/src/setup/scenes/outro.ts +24 -0
  557. package/src/setup/scenes/providers.ts +107 -0
  558. package/src/setup/scenes/sign-in.ts +373 -0
  559. package/src/setup/scenes/splash.ts +192 -0
  560. package/src/setup/scenes/theme.ts +343 -0
  561. package/src/setup/scenes/types.ts +111 -0
  562. package/src/setup/scenes/web-search.ts +165 -0
  563. package/src/setup/setup-version.ts +11 -0
  564. package/src/setup/startup-splash.ts +107 -0
  565. package/src/setup/wizard-overlay.ts +319 -0
  566. package/src/setup/wizard.ts +101 -0
  567. package/src/status-line/component.ts +3003 -0
  568. package/src/status-line/context-usage.ts +688 -0
  569. package/src/status-line/footer.ts +323 -0
  570. package/src/status-line/git-utils.ts +42 -0
  571. package/src/status-line/host.ts +97 -0
  572. package/src/status-line/index.ts +6 -0
  573. package/src/status-line/loop.ts +28 -0
  574. package/src/status-line/metrics.ts +94 -0
  575. package/src/status-line/presets.ts +107 -0
  576. package/src/status-line/schema.ts +57 -0
  577. package/src/status-line/segments.ts +934 -0
  578. package/src/status-line/separators.ts +55 -0
  579. package/src/status-line/types.ts +227 -0
  580. package/src/stdin-buffer.ts +4 -2
  581. package/src/theme/color.ts +95 -0
  582. package/src/theme/dark.json +95 -0
  583. package/src/theme/defaults/alabaster.json +93 -0
  584. package/src/theme/defaults/amethyst.json +96 -0
  585. package/src/theme/defaults/anthracite.json +93 -0
  586. package/src/theme/defaults/basalt.json +91 -0
  587. package/src/theme/defaults/birch.json +95 -0
  588. package/src/theme/defaults/dark-abyss.json +91 -0
  589. package/src/theme/defaults/dark-arctic.json +104 -0
  590. package/src/theme/defaults/dark-aurora.json +95 -0
  591. package/src/theme/defaults/dark-catppuccin.json +107 -0
  592. package/src/theme/defaults/dark-cavern.json +91 -0
  593. package/src/theme/defaults/dark-celestial.json +106 -0
  594. package/src/theme/defaults/dark-copper.json +95 -0
  595. package/src/theme/defaults/dark-cosmos.json +90 -0
  596. package/src/theme/defaults/dark-cyberpunk.json +102 -0
  597. package/src/theme/defaults/dark-dracula.json +98 -0
  598. package/src/theme/defaults/dark-eclipse.json +91 -0
  599. package/src/theme/defaults/dark-ember.json +95 -0
  600. package/src/theme/defaults/dark-equinox.json +90 -0
  601. package/src/theme/defaults/dark-forest.json +96 -0
  602. package/src/theme/defaults/dark-github.json +105 -0
  603. package/src/theme/defaults/dark-gruvbox.json +112 -0
  604. package/src/theme/defaults/dark-lavender.json +95 -0
  605. package/src/theme/defaults/dark-lunar.json +89 -0
  606. package/src/theme/defaults/dark-midnight.json +95 -0
  607. package/src/theme/defaults/dark-monochrome.json +94 -0
  608. package/src/theme/defaults/dark-monokai.json +98 -0
  609. package/src/theme/defaults/dark-nebula.json +90 -0
  610. package/src/theme/defaults/dark-nord.json +98 -0
  611. package/src/theme/defaults/dark-ocean.json +101 -0
  612. package/src/theme/defaults/dark-one.json +100 -0
  613. package/src/theme/defaults/dark-poimandres.json +115 -0
  614. package/src/theme/defaults/dark-rainforest.json +91 -0
  615. package/src/theme/defaults/dark-reef.json +91 -0
  616. package/src/theme/defaults/dark-retro.json +92 -0
  617. package/src/theme/defaults/dark-rose-pine.json +96 -0
  618. package/src/theme/defaults/dark-sakura.json +95 -0
  619. package/src/theme/defaults/dark-slate.json +95 -0
  620. package/src/theme/defaults/dark-solarized.json +97 -0
  621. package/src/theme/defaults/dark-solstice.json +90 -0
  622. package/src/theme/defaults/dark-starfall.json +91 -0
  623. package/src/theme/defaults/dark-sunset.json +99 -0
  624. package/src/theme/defaults/dark-swamp.json +90 -0
  625. package/src/theme/defaults/dark-synthwave.json +103 -0
  626. package/src/theme/defaults/dark-taiga.json +91 -0
  627. package/src/theme/defaults/dark-terminal.json +95 -0
  628. package/src/theme/defaults/dark-tokyo-night.json +101 -0
  629. package/src/theme/defaults/dark-tundra.json +91 -0
  630. package/src/theme/defaults/dark-twilight.json +91 -0
  631. package/src/theme/defaults/dark-volcanic.json +91 -0
  632. package/src/theme/defaults/graphite.json +92 -0
  633. package/src/theme/defaults/index.ts +201 -0
  634. package/src/theme/defaults/light-arctic.json +107 -0
  635. package/src/theme/defaults/light-aurora-day.json +91 -0
  636. package/src/theme/defaults/light-canyon.json +91 -0
  637. package/src/theme/defaults/light-catppuccin.json +106 -0
  638. package/src/theme/defaults/light-cirrus.json +90 -0
  639. package/src/theme/defaults/light-coral.json +95 -0
  640. package/src/theme/defaults/light-cyberpunk.json +96 -0
  641. package/src/theme/defaults/light-dawn.json +90 -0
  642. package/src/theme/defaults/light-dunes.json +91 -0
  643. package/src/theme/defaults/light-eucalyptus.json +95 -0
  644. package/src/theme/defaults/light-forest.json +100 -0
  645. package/src/theme/defaults/light-frost.json +95 -0
  646. package/src/theme/defaults/light-github.json +115 -0
  647. package/src/theme/defaults/light-glacier.json +91 -0
  648. package/src/theme/defaults/light-gruvbox.json +108 -0
  649. package/src/theme/defaults/light-haze.json +90 -0
  650. package/src/theme/defaults/light-honeycomb.json +95 -0
  651. package/src/theme/defaults/light-lagoon.json +91 -0
  652. package/src/theme/defaults/light-lavender.json +95 -0
  653. package/src/theme/defaults/light-meadow.json +91 -0
  654. package/src/theme/defaults/light-mint.json +95 -0
  655. package/src/theme/defaults/light-monochrome.json +101 -0
  656. package/src/theme/defaults/light-ocean.json +99 -0
  657. package/src/theme/defaults/light-one.json +99 -0
  658. package/src/theme/defaults/light-opal.json +91 -0
  659. package/src/theme/defaults/light-orchard.json +91 -0
  660. package/src/theme/defaults/light-paper.json +95 -0
  661. package/src/theme/defaults/light-poimandres.json +115 -0
  662. package/src/theme/defaults/light-prism.json +90 -0
  663. package/src/theme/defaults/light-retro.json +98 -0
  664. package/src/theme/defaults/light-sand.json +95 -0
  665. package/src/theme/defaults/light-savanna.json +91 -0
  666. package/src/theme/defaults/light-solarized.json +102 -0
  667. package/src/theme/defaults/light-soleil.json +90 -0
  668. package/src/theme/defaults/light-sunset.json +99 -0
  669. package/src/theme/defaults/light-synthwave.json +98 -0
  670. package/src/theme/defaults/light-tokyo-night.json +111 -0
  671. package/src/theme/defaults/light-wetland.json +91 -0
  672. package/src/theme/defaults/light-zenith.json +89 -0
  673. package/src/theme/defaults/limestone.json +94 -0
  674. package/src/theme/defaults/mahogany.json +97 -0
  675. package/src/theme/defaults/marble.json +93 -0
  676. package/src/theme/defaults/obsidian.json +91 -0
  677. package/src/theme/defaults/onyx.json +91 -0
  678. package/src/theme/defaults/pearl.json +93 -0
  679. package/src/theme/defaults/porcelain.json +91 -0
  680. package/src/theme/defaults/quartz.json +96 -0
  681. package/src/theme/defaults/sandstone.json +95 -0
  682. package/src/theme/defaults/titanium.json +90 -0
  683. package/src/theme/index.ts +5 -0
  684. package/src/theme/light.json +93 -0
  685. package/src/theme/loader.ts +200 -0
  686. package/src/theme/mermaid-cache.ts +92 -0
  687. package/src/theme/schema-validation.ts +119 -0
  688. package/src/theme/schema.ts +168 -0
  689. package/src/theme/session-color.ts +253 -0
  690. package/src/theme/shimmer.ts +307 -0
  691. package/src/theme/symbols.ts +1433 -0
  692. package/src/theme/theme-class.ts +787 -0
  693. package/src/theme/theme-schema.json +463 -0
  694. package/src/theme/theme.ts +810 -0
  695. package/src/theme/tui-adapters.ts +339 -0
  696. package/src/thinking.ts +380 -0
  697. package/src/tools/agent-tree.ts +128 -0
  698. package/src/tools/ask.ts +482 -0
  699. package/src/tools/ast-edit.ts +210 -0
  700. package/src/tools/ast-grep.ts +171 -0
  701. package/src/tools/autoresearch.ts +287 -0
  702. package/src/tools/bash-interactive.ts +295 -0
  703. package/src/tools/bash.ts +468 -0
  704. package/src/tools/conflict-detect.ts +77 -0
  705. package/src/tools/debug.ts +167 -0
  706. package/src/tools/default-renderer.ts +172 -0
  707. package/src/tools/edit.ts +1258 -0
  708. package/src/tools/eval-format/code-mode-declarations.ts +61 -0
  709. package/src/tools/eval-format/index.ts +16 -0
  710. package/src/tools/eval-format/javascript.ts +952 -0
  711. package/src/tools/eval-format/python.ts +544 -0
  712. package/src/tools/eval.ts +835 -0
  713. package/src/tools/fetch.ts +226 -0
  714. package/src/tools/gh-format.ts +18 -0
  715. package/src/tools/github.ts +686 -0
  716. package/src/tools/glob.ts +213 -0
  717. package/src/tools/goal.ts +153 -0
  718. package/src/tools/grep.ts +385 -0
  719. package/src/tools/grouped-file-output.ts +211 -0
  720. package/src/tools/hashline-format.ts +59 -0
  721. package/src/tools/hub.ts +1488 -0
  722. package/src/tools/index.ts +71 -0
  723. package/src/tools/json-tree.ts +388 -0
  724. package/src/tools/line-ranges.ts +98 -0
  725. package/src/tools/list-limit.ts +43 -0
  726. package/src/tools/lsp.ts +702 -0
  727. package/src/tools/match-line-format.ts +20 -0
  728. package/src/tools/mcp.ts +309 -0
  729. package/src/tools/memory.ts +220 -0
  730. package/src/tools/output-meta.ts +367 -0
  731. package/src/tools/read.ts +628 -0
  732. package/src/tools/renderer.ts +107 -0
  733. package/src/tools/report-tool-issue.ts +13 -0
  734. package/src/tools/resolve.ts +139 -0
  735. package/src/tools/streaming-output.ts +1576 -0
  736. package/src/tools/subprocess.ts +39 -0
  737. package/src/tools/task-repair-args.ts +118 -0
  738. package/src/tools/task-yield-assembly.ts +147 -0
  739. package/src/tools/task.ts +2110 -0
  740. package/src/tools/terminal-output.ts +153 -0
  741. package/src/tools/think.ts +28 -0
  742. package/src/tools/todo.ts +580 -0
  743. package/src/tools/tool-errors.ts +15 -0
  744. package/src/tools/vibe.ts +420 -0
  745. package/src/tools/web-search.ts +409 -0
  746. package/src/tools/write.ts +511 -0
  747. package/src/tools/xd-url.ts +28 -0
  748. package/src/tools/xdev.ts +201 -0
  749. package/src/tui.ts +70 -8
  750. package/src/utils.ts +8 -0
@@ -0,0 +1,934 @@
1
+ import type { EditorTopBorder } from "../components/composer/types";
2
+ import { Spacer } from "../components/spacer";
3
+ import { isInsideTerminalMultiplexer } from "../terminal-multiplexer";
4
+ import { ProcessTerminal, type Terminal } from "../terminal";
5
+ import {
6
+ type Component,
7
+ Container,
8
+ type ResizeScrollbackMode,
9
+ type TerminalFramePlan,
10
+ type TerminalFrameProvider,
11
+ TUI,
12
+ type TUIOptions,
13
+ type ViewportSize,
14
+ } from "../tui";
15
+ import { sliceWithWidth, truncateToWidth, visibleWidth } from "../utils";
16
+ import { postmortem } from "@oh-my-pi/pi-utils";
17
+ import { CustomEditor } from "./custom-editor";
18
+ import { type AnimationFrame, TranscriptContainer } from "../chrome/transcript-container";
19
+ import { type LspServerInfo, type RecentSession, WelcomeComponent } from "./welcome";
20
+ import { ensureThemeSync, getEditorTheme, theme } from "../theme/theme";
21
+
22
+ const DOUBLE_INTERRUPT_MS = 500;
23
+
24
+ /** Live settings that affect the composer before and after session adoption. */
25
+ export interface ComposerPreferences {
26
+ readonly quiet: boolean;
27
+ readonly composerShape: string;
28
+ readonly showHardwareCursor: boolean;
29
+ readonly maxInlineImages: number;
30
+ readonly resizeScrollback: ResizeScrollbackMode;
31
+ readonly imeSafeCursor: boolean;
32
+ readonly autocompleteMaxVisible: number;
33
+ readonly spellingTypoDetection: boolean;
34
+ readonly spellingAutocomplete: boolean;
35
+ readonly spellingAutocorrect: boolean;
36
+ }
37
+
38
+ /** Settings-schema-compatible defaults used when constructing a dependency-free composer. */
39
+ export const COMPOSER_DEFAULTS: ComposerPreferences = {
40
+ quiet: false,
41
+ composerShape: "band",
42
+ showHardwareCursor: true,
43
+ maxInlineImages: 8,
44
+ resizeScrollback: "rebuild",
45
+ imeSafeCursor: false,
46
+ autocompleteMaxVisible: 10,
47
+ spellingTypoDetection: true,
48
+ spellingAutocomplete: true,
49
+ spellingAutocorrect: false,
50
+ };
51
+
52
+ /** Welcome data that can be supplied initially or patched as startup resolves it. */
53
+ export interface ComposerWelcomeUpdate {
54
+ readonly version?: string;
55
+ readonly modelName?: string;
56
+ readonly providerName?: string;
57
+ readonly recentSessions?: readonly RecentSession[];
58
+ readonly lspServers?: readonly LspServerInfo[];
59
+ }
60
+
61
+ /**
62
+ * Placeholder-only status chrome replayed on the next first frame so the
63
+ * status band/border exists before the session-aware status line attaches.
64
+ * Bound to the composer shape it was rendered for; a different shape drops it.
65
+ */
66
+ export interface ComposerStatusSnapshot {
67
+ readonly shape: string;
68
+ /** ANSI wrapper of the editor border at snapshot time (session accent or thinking color). */
69
+ readonly borderColor?: {
70
+ readonly prefix: string;
71
+ readonly suffix: string;
72
+ };
73
+ /** Status content embedded in the editor's top chrome (`top-border`, `top-band`, `top-rule-chip`). */
74
+ readonly topBorder?: {
75
+ readonly content: string;
76
+ readonly width: number;
77
+ };
78
+ /** Standalone bottom-bar rows (`pi`/`claude` shapes), gap row included. */
79
+ readonly bottomLines: readonly string[];
80
+ }
81
+
82
+ /** Optional dependencies and initial state for a standalone composer. */
83
+ export interface ComposerOptions {
84
+ readonly terminal?: Terminal;
85
+ /** Extra TUI construction options (render scheduler injection for tests and `omp render`). */
86
+ readonly tuiOptions?: TUIOptions;
87
+ readonly preferences?: Partial<ComposerPreferences>;
88
+ readonly welcome?: ComposerWelcomeUpdate;
89
+ readonly status?: ComposerStatusSnapshot;
90
+ readonly exit?: (code: number) => void;
91
+ readonly now?: () => number;
92
+ }
93
+
94
+ /** Controls the first terminal paint for a composer that does not already own the terminal. */
95
+ export interface ComposerStartOptions {
96
+ readonly clearScrollback?: boolean;
97
+ readonly playWelcomeIntro?: boolean;
98
+ /**
99
+ * Paint without owning stdin: the tty keeps cooked-mode echo/editing so
100
+ * typing stays visible while startup module loading blocks the event loop.
101
+ * {@link Composer.enableInput} later switches to raw input and replays the
102
+ * kernel-buffered keystrokes into the editor.
103
+ */
104
+ readonly deferInput?: boolean;
105
+ }
106
+
107
+ /**
108
+ * Mount slot for the session-aware status component below the editor. Shows
109
+ * placeholder rows during startup until the real component mounts.
110
+ */
111
+ class StatusHost implements Component {
112
+ #lines: readonly string[] = [];
113
+ #component: Component | undefined;
114
+
115
+ get mounted(): boolean {
116
+ return this.#component !== undefined;
117
+ }
118
+
119
+ setLines(lines: readonly string[]): void {
120
+ this.#lines = lines;
121
+ }
122
+
123
+ setComponent(component: Component): void {
124
+ this.#component = component;
125
+ this.#lines = [];
126
+ }
127
+
128
+ render(width: number): readonly string[] {
129
+ if (this.#component) return this.#component.render(width);
130
+ return this.#lines.map(line => truncateToWidth(line, width));
131
+ }
132
+ }
133
+
134
+ /** One click target's row span within the mutable viewport (half-open `[start, end)`). */
135
+ export interface ViewportClickSpan {
136
+ start: number;
137
+ end: number;
138
+ /** Candidate subagent ids for a span-local row. */
139
+ candidates: (local: number) => string[];
140
+ }
141
+
142
+ /**
143
+ * Row-level click target: maps rendered rows to subagent ids. Implemented by
144
+ * the subagent HUD, whose rows are fixed 1:1 with visible sessions.
145
+ */
146
+ export interface ViewportClickRowTarget {
147
+ getClickAgentAtRow(row: number): string | undefined;
148
+ }
149
+
150
+ /**
151
+ * Candidates under a mutable-viewport line: the first span containing it.
152
+ * Pure seam for tests; the caller intersects with the live registry, which
153
+ * decides focusability and recency.
154
+ */
155
+ export function routeViewportClick(spans: readonly ViewportClickSpan[], index: number): string[] {
156
+ if (!Number.isInteger(index) || index < 0) return [];
157
+ for (const span of spans) {
158
+ if (index < span.start || index >= span.end) continue;
159
+ return span.candidates(index - span.start);
160
+ }
161
+ return [];
162
+ }
163
+
164
+ /**
165
+ * Reserved click-candidate id for the pinned HUD expander row. Checked before
166
+ * any registry lookup: its `@…:…` charset cannot collide with generated agent
167
+ * ids (word names, numeric and `-N` suffixes, dotted nesting).
168
+ */
169
+ export const PINNED_HUD_TOGGLE_ID = "@omp:toggle-pinned-hud";
170
+
171
+ /**
172
+ * Nested background opens inside a hovered row. The band wraps the line, so a
173
+ * surviving nested open would paint over it for every cell it covers; the
174
+ * matching closes stay and become band resumes via bgFill.
175
+ */
176
+ const NESTED_BG_OPEN_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[(?:4[0-7]|10[0-7]|48;[0-9;]*)m`, "g");
177
+
178
+ /**
179
+ * Candidate resolver for a row-level click target, if the component is one.
180
+ * Shared by root and nested-child hit-testing so both stay in lockstep.
181
+ */
182
+ function rowTargetCandidates(target: Component): ((local: number) => string[]) | undefined {
183
+ const rowTarget = target as Partial<ViewportClickRowTarget>;
184
+ if (typeof rowTarget.getClickAgentAtRow !== "function") return undefined;
185
+ return (local: number) => {
186
+ const id = rowTarget.getClickAgentAtRow?.(local);
187
+ return id === undefined ? [] : [id];
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Canonical interactive composer, usable before session/settings exist and updatable in place.
193
+ * It owns the terminal, welcome header, and editor; InteractiveMode later supplies authoritative
194
+ * data and mounts the session-aware runtime children without replacing the visible header.
195
+ */
196
+ export class Composer implements TerminalFrameProvider {
197
+ readonly ui: TUI;
198
+ #editor: CustomEditor;
199
+ readonly #header = new Container();
200
+ readonly #bootstrapInputGap = new Spacer(1);
201
+ readonly #statusHost = new StatusHost();
202
+ readonly #exit: (code: number) => void;
203
+ readonly #now: () => number;
204
+ #preferences: ComposerPreferences;
205
+ #welcome: WelcomeComponent | undefined;
206
+ #version = "";
207
+ #modelName = "";
208
+ #providerName = "";
209
+ #recentSessions: RecentSession[] = [];
210
+ #lspServers: LspServerInfo[] = [];
211
+ #headerBefore: readonly Component[] = [];
212
+ #headerAfter: readonly Component[] = [];
213
+ #runtimeChildren: readonly Component[] = [];
214
+ #statusSnapshot: ComposerStatusSnapshot | undefined;
215
+ #runtimeMounted = false;
216
+ // Composer-owned history id space. Transcript batch ids restart across
217
+ // container clears/swaps; the composer translates them into one monotonic
218
+ // sequence the terminal's accepted-id watermark can trust.
219
+ #nextHistoryId = 1;
220
+ #offeredHistory:
221
+ | {
222
+ id: number;
223
+ rows: readonly string[];
224
+ kind: "append" | "replay";
225
+ source:
226
+ | "header"
227
+ | {
228
+ transcript: TranscriptContainer;
229
+ transcriptId?: number;
230
+ header: "none" | "replay";
231
+ /** Recomposed header rows to accept as the new retired-header bytes. */
232
+ headerRows?: readonly string[];
233
+ };
234
+ }
235
+ | undefined;
236
+ #historyReplayRequested = false;
237
+ #headerReplayPending = false;
238
+ #historyFlush = false;
239
+ // The welcome header retires to terminal history exactly once, after the
240
+ // intro settles; until then it renders as mutable viewport chrome.
241
+ #headerRetired = false;
242
+ // Exact hard rows accepted into native history. Transient resize-alt
243
+ // paints reflow these rows to match the terminal's own rewrap of history
244
+ // it still holds; a settled replay owns every byte it emits, so it
245
+ // recomposes the header at the replay width and refreshes these rows.
246
+ #retiredHeaderRows: readonly string[] | undefined;
247
+ /** Click spans of the last `renderFrame` viewport, in viewport coordinates. */
248
+ #lastClickSpans: ViewportClickSpan[] = [];
249
+ /** Click-candidate id under the pointer, painted with the hover band. Id-anchored so it follows streaming rows. */
250
+ #hoveredClickId: string | undefined;
251
+ // Hard-row prefix currently above the native viewport. The first resize
252
+ // frame may pull part of it down before the normal buffer is borrowed.
253
+ #retiredHeaderStart = 0;
254
+ #resizeRetiredHeaderStart: number | undefined;
255
+ #lastNormalRows = 0;
256
+ // Smallest below-transcript chrome height (editor + status + any transient
257
+ // inline dialog) seen since mount. Retirement is billed against this
258
+ // persistent baseline, never the transient peak, so a dialog or tall editor
259
+ // that later shrinks never leaves committed transcript rows the live viewport
260
+ // cannot reclaim (#11007). The baseline is terminal-height independent — the
261
+ // editor and status floors do not scale with rows — so it is retained across
262
+ // resizes rather than rediscovered from whatever chrome is expanded at the
263
+ // moment the height changes.
264
+ #retirementBelowFloor: number | undefined;
265
+ #lastInterruptAt = 0;
266
+ #started = false;
267
+ #stopped = false;
268
+ #transferred = false;
269
+
270
+ constructor(options: ComposerOptions = {}) {
271
+ ensureThemeSync();
272
+ // Host-owned hard exit: route through postmortem so a double-Ctrl-C during
273
+ // an open extension-load guard window exits cleanly instead of throwing
274
+ // ExtensionExitError through the guarded process.exit (#11789).
275
+ this.#exit = options.exit ?? (code => postmortem.exitProcess(code));
276
+ this.#now = options.now ?? Date.now;
277
+ this.#preferences = { ...COMPOSER_DEFAULTS, ...options.preferences };
278
+ this.#statusSnapshot = options.status;
279
+ this.#applyWelcomeUpdate(options.welcome ?? {});
280
+
281
+ this.ui = new TUI(
282
+ options.terminal ?? new ProcessTerminal(),
283
+ this.#preferences.showHardwareCursor,
284
+ options.tuiOptions,
285
+ );
286
+ this.ui.setFrameProvider(this);
287
+ this.ui.setMaxInlineImages(this.#preferences.maxInlineImages);
288
+ this.ui.setResizeScrollback(this.#preferences.resizeScrollback);
289
+
290
+ this.#editor = new CustomEditor(getEditorTheme());
291
+ this.editor.disableSubmit = true;
292
+ this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
293
+ this.editor.setImeSafeCursorLayout(this.#preferences.imeSafeCursor);
294
+ this.editor.setAutocompleteMaxVisible(this.#preferences.autocompleteMaxVisible);
295
+ this.editor.setSpellingFeatures({
296
+ typoDetection: this.#preferences.spellingTypoDetection,
297
+ autocomplete: this.#preferences.spellingAutocomplete,
298
+ autocorrect: this.#preferences.spellingAutocorrect,
299
+ });
300
+ try {
301
+ this.editor.setBorderStyle(this.#preferences.composerShape);
302
+ } catch {
303
+ // Extension-defined styles arrive with the session; InteractiveMode reapplies them.
304
+ }
305
+ this.#applyStatusSnapshot();
306
+ // Emergency controls stay active until InteractiveMode installs configured bindings.
307
+ // They deliberately mirror the interactive editor's contract so a stalled startup
308
+ // never behaves differently from a healthy one: Ctrl+C clears the draft and a second
309
+ // press exits 130 — the unconditional abort, draft or not; Ctrl+D exits 0 on an
310
+ // empty draft and otherwise forward-deletes (see CustomEditor's app.exit handling),
311
+ // so typing while the session loads cannot be lost to a mistyped delete.
312
+ this.editor.setActionKeys("app.clear", ["ctrl+c"]);
313
+ this.editor.setActionKeys("app.exit", ["ctrl+d"]);
314
+ this.editor.onClear = () => this.#handleInterrupt();
315
+ this.editor.onExit = () => this.#requestExit(0);
316
+ this.editor.setShimmerRepaintHandler(() => this.ui.requestComponentRender(this.editor));
317
+
318
+ if (!this.#preferences.quiet) this.#ensureWelcome();
319
+ this.#rebuildHeader();
320
+ this.ui.addChild(this.#header);
321
+ this.ui.addChild(this.#bootstrapInputGap);
322
+ this.ui.addChild(this.editor);
323
+ this.ui.addChild(this.#statusHost);
324
+ this.ui.setFocus(this.editor);
325
+ }
326
+ /** Compose the bounded mutable viewport and the next ordered history append. */
327
+ renderFrame(viewport: ViewportSize): TerminalFramePlan {
328
+ if (!this.#started || this.#stopped) return { viewport: [] };
329
+ const width = Math.max(1, viewport.columns);
330
+ const rows = Math.max(0, viewport.rows);
331
+ if (this.#resizeRetiredHeaderStart !== undefined) {
332
+ this.#retiredHeaderStart = this.#resizeRetiredHeaderStart;
333
+ this.#resizeRetiredHeaderStart = undefined;
334
+ }
335
+ this.#lastNormalRows = rows;
336
+ const roots = this.#runtimeMounted
337
+ ? [...this.#runtimeChildren, this.#statusHost]
338
+ : [this.#header, this.#bootstrapInputGap, this.editor, this.#statusHost];
339
+ const transcriptIndex = roots.findIndex(root => root instanceof TranscriptContainer);
340
+ if (transcriptIndex < 0) {
341
+ this.#lastClickSpans = [];
342
+ return { viewport: this.#renderRoots(roots, width).slice(-rows) };
343
+ }
344
+ const transcript = roots[transcriptIndex] as TranscriptContainer;
345
+ const preRoots = this.#renderRoots(roots.slice(0, transcriptIndex), width);
346
+ const afterRoots = roots.slice(transcriptIndex + 1);
347
+ const after: string[] = [];
348
+ const afterSpans: ViewportClickSpan[] = [];
349
+ for (const root of afterRoots) {
350
+ const start = after.length;
351
+ // Row targets usually nest one level down: chrome roots are plain
352
+ // containers (the HUD lives inside `subagentContainer`), and
353
+ // `Container.render` is a pure concatenation, so child spans tile
354
+ // the root span exactly. Render those children once and share the
355
+ // rows for composition and measurement — a second render per frame
356
+ // would duplicate render-time side effects (image placement
357
+ // registration). Roots with a custom render keep the composed
358
+ // output as the source of truth and measure up to the last target.
359
+ const plainContainer = root instanceof Container && root.render === Container.prototype.render;
360
+ const targets = root instanceof Container ? root.children : [root];
361
+ const resolves = targets.map(rowTargetCandidates);
362
+ const lastTarget = resolves.findLastIndex(resolve => resolve !== undefined);
363
+ if (plainContainer) {
364
+ let offset = start;
365
+ for (let index = 0; index < targets.length; index++) {
366
+ const childLines = targets[index]!.render(width);
367
+ after.push(...childLines);
368
+ if (index > lastTarget) continue;
369
+ const resolve = resolves[index];
370
+ if (resolve !== undefined && childLines.length > 0) {
371
+ afterSpans.push({ start: offset, end: offset + childLines.length, candidates: resolve });
372
+ }
373
+ offset += childLines.length;
374
+ }
375
+ continue;
376
+ }
377
+ after.push(...root.render(width));
378
+ if (lastTarget === -1) continue;
379
+ let offset = start;
380
+ for (let index = 0; index <= lastTarget; index++) {
381
+ const childLines = targets[index] === root ? after.length - start : targets[index]!.render(width).length;
382
+ const resolve = resolves[index];
383
+ if (resolve !== undefined && childLines > 0) {
384
+ afterSpans.push({ start: offset, end: offset + childLines, candidates: resolve });
385
+ }
386
+ offset += childLines;
387
+ }
388
+ }
389
+ // Offer history under capacity pressure only: blocks stay live (and keep
390
+ // reflowing to the current width) while the screen has room. A batch
391
+ // leaves the mutable viewport in the same frame it is appended, so its
392
+ // rows are never painted twice.
393
+ //
394
+ // Retirement is billed against the persistent below-transcript chrome
395
+ // baseline, not the transient peak: a confirmation dialog or a tall
396
+ // multi-line editor swapped in below the transcript clips the live tail
397
+ // for its lifetime, but must not permanently commit transcript rows to
398
+ // native history — otherwise a later shrink cannot refill the freed rows
399
+ // and the editor drifts up above a band of blank rows (#11007).
400
+ this.#retirementBelowFloor =
401
+ this.#retirementBelowFloor === undefined ? after.length : Math.min(this.#retirementBelowFloor, after.length);
402
+ const belowFloor = this.#retirementBelowFloor;
403
+ const history = this.#offerHistory(transcript, width, rows, preRoots.length + belowFloor);
404
+ const headerVisible = !this.#headerRetired && this.#offeredHistory?.source !== "header";
405
+ const headerRows = headerVisible ? this.#header.render(width) : [];
406
+ const before = [...headerRows, ...preRoots];
407
+ const now = performance.now();
408
+ const frame: AnimationFrame = { now, tick: Math.floor(now / 80) };
409
+ // The live tail is laid out against the same baseline retirement is
410
+ // billed against, so its compaction allocator (one row per block, no
411
+ // inter-block blanks) engages only when a block genuinely cannot retire.
412
+ // Rows the transient chrome peak displaces are clipped from the top by
413
+ // the `drop` slice below, which is what scrollback would have done.
414
+ const active = transcript.renderViewport(width, Math.max(0, rows - before.length - belowFloor), frame);
415
+ const activeSpans: ViewportClickSpan[] = [];
416
+ for (const span of transcript.getLastViewportSpans()) {
417
+ const ids = (span.component as Partial<{ getClickFocusAgentIds(): string[] }>).getClickFocusAgentIds?.();
418
+ if (!ids || ids.length === 0) continue;
419
+ activeSpans.push({ start: span.start, end: span.end, candidates: () => ids });
420
+ }
421
+ const drop = Math.max(0, before.length + active.length + after.length - rows);
422
+ const mutable = [...before, ...active, ...after].slice(drop);
423
+ const viewportLength = mutable.length;
424
+ const spans: ViewportClickSpan[] = [];
425
+ const shift = (span: ViewportClickSpan, base: number): void => {
426
+ const start = span.start + base;
427
+ const end = Math.min(span.end + base, viewportLength);
428
+ const clamped = Math.max(0, start);
429
+ if (end > clamped) {
430
+ // A clipped head must offset the callback: without the skew the
431
+ // first visible row would hit-test as span-local row 0.
432
+ const skew = clamped - start;
433
+ spans.push({ start: clamped, end, candidates: (local: number) => span.candidates(local + skew) });
434
+ }
435
+ };
436
+ for (const span of activeSpans) shift(span, before.length - drop);
437
+ for (const span of afterSpans) shift(span, before.length + active.length - drop);
438
+ this.#lastClickSpans = spans;
439
+ if (history !== undefined && this.#offeredHistory?.source === "header") {
440
+ const visibleHeaderRows = Math.max(0, rows - (mutable.length + drop));
441
+ this.#retiredHeaderStart = Math.max(0, history.rows.length - visibleHeaderRows);
442
+ }
443
+ return { history, viewport: this.#paintHoverBand(mutable, spans) };
444
+ }
445
+
446
+ /**
447
+ * Band the hovered click target's current rows. Id-anchored (not
448
+ * line-anchored) so the band follows an agent whose rows shift while it
449
+ * streams; a retired id matches no span and simply paints nothing. Only
450
+ * the viewport copy is banded — retirement reads unbanded component rows.
451
+ */
452
+ #paintHoverBand(viewport: string[], spans: readonly ViewportClickSpan[]): string[] {
453
+ const hovered = this.#hoveredClickId;
454
+ if (hovered === undefined) return viewport;
455
+ let banded = false;
456
+ const painted = viewport.map((line, index) => {
457
+ for (const span of spans) {
458
+ if (index < span.start || index >= span.end) continue;
459
+ if (!span.candidates(index - span.start).includes(hovered)) continue;
460
+ banded = true;
461
+ // A wrapping band loses to background opens nested inside the row
462
+ // (live card rows carry the pending-tint bg, which would paint over
463
+ // the band for every cell it covers), so drop nested bg opens
464
+ // first; their closes stay and become band resumes via bgFill.
465
+ return theme.bgFill("selectedBg", line.replace(NESTED_BG_OPEN_PATTERN, ""));
466
+ }
467
+ return line;
468
+ });
469
+ return banded ? painted : viewport;
470
+ }
471
+
472
+ /**
473
+ * Candidate subagent ids under a mutable-viewport line, for click-to-focus.
474
+ * Empty when the line has no click target (chrome, separators, retired rows
475
+ * are never in the viewport). Callers intersect with the live registry.
476
+ */
477
+ viewportClickCandidates(index: number): string[] {
478
+ return routeViewportClick(this.#lastClickSpans, index);
479
+ }
480
+
481
+ /**
482
+ * Point the hover band at a click-candidate id (or clear it). Takes effect
483
+ * on the next frame; callers repaint only when the target actually changes.
484
+ */
485
+ setHoveredClickId(id: string | undefined): void {
486
+ this.#hoveredClickId = id;
487
+ }
488
+
489
+ /** Acknowledges one accepted header, replay, or transcript batch. */
490
+ acknowledgeHistory(id: number): void {
491
+ const offered = this.#offeredHistory;
492
+ if (offered === undefined || offered.id !== id) return;
493
+ if (offered.source === "header") {
494
+ this.#headerRetired = true;
495
+ this.#retiredHeaderRows = offered.rows;
496
+ } else {
497
+ if (offered.source.transcriptId !== undefined) {
498
+ offered.source.transcript.acknowledgeFinalizedBatch(offered.source.transcriptId);
499
+ }
500
+ if (offered.source.header === "replay") {
501
+ this.#headerReplayPending = false;
502
+ if (offered.source.headerRows !== undefined) this.#retiredHeaderRows = offered.source.headerRows;
503
+ }
504
+ }
505
+ this.#offeredHistory = undefined;
506
+ if (this.#historyReplayRequested) this.#startHistoryReplay();
507
+ if (offered.kind === "replay") {
508
+ this.ui.requestRender();
509
+ }
510
+ }
511
+
512
+ /** Render the semantic transcript tail while the terminal borrows its resize buffer. */
513
+ renderResizeFrame(viewport: ViewportSize): readonly string[] {
514
+ if (!this.#started || this.#stopped) return [];
515
+ const width = Math.max(1, viewport.columns);
516
+ const rows = Math.max(0, viewport.rows);
517
+ const tail = this.#runtimeMounted
518
+ ? this.#renderResizeTail(width, rows)
519
+ : this.#renderRoots([this.#bootstrapInputGap, this.editor, this.#statusHost], width);
520
+ let header: readonly string[];
521
+ if (this.#headerRetired) {
522
+ this.#resizeRetiredHeaderStart ??= Math.max(
523
+ 0,
524
+ this.#retiredHeaderStart - Math.max(0, rows - this.#lastNormalRows),
525
+ );
526
+ header = this.#reflowRetiredHeader(width, this.#resizeRetiredHeaderStart);
527
+ } else {
528
+ header = this.#header.render(width);
529
+ }
530
+ const rendered = [...header, ...tail];
531
+ return rendered.length <= rows ? rendered : rendered.slice(rendered.length - rows);
532
+ }
533
+
534
+ /** Replays committed presentation without changing logical retirement state. */
535
+ beginHistoryReplay(): void {
536
+ if (this.#offeredHistory !== undefined) {
537
+ this.#historyReplayRequested = true;
538
+ return;
539
+ }
540
+ this.#startHistoryReplay();
541
+ }
542
+
543
+ /** Forces every currently eligible finalized prefix to retire before stop. */
544
+ beginHistoryFlush(): void {
545
+ this.#historyFlush = true;
546
+ // A pending replay would re-render and re-stream the entire committed
547
+ // ledger during shutdown; the terminal already holds that history, so
548
+ // flush emits only genuinely un-retired rows. An already offered batch
549
+ // stays valid and is accepted by the flush loop.
550
+ this.#historyReplayRequested = false;
551
+ this.#headerReplayPending = false;
552
+ for (const child of this.#runtimeChildren) {
553
+ if (child instanceof TranscriptContainer) child.cancelReplay();
554
+ }
555
+ }
556
+
557
+ #startHistoryReplay(): void {
558
+ this.#headerReplayPending = this.#headerRetired && (this.#retiredHeaderRows?.length ?? 0) > 0;
559
+ this.#historyReplayRequested = false;
560
+ for (const child of this.#runtimeChildren) {
561
+ if (child instanceof TranscriptContainer) child.beginReplay();
562
+ }
563
+ }
564
+
565
+ /** Header retires first; replay coalesces it with the complete transcript ledger. */
566
+ #offerHistory(
567
+ transcript: TranscriptContainer,
568
+ width: number,
569
+ rows: number,
570
+ chromeRows: number,
571
+ ): { id: number; rows: readonly string[]; kind: "append" | "replay" } | undefined {
572
+ if (this.#offeredHistory !== undefined) {
573
+ this.#rerenderOfferedHistory(width);
574
+ return {
575
+ id: this.#offeredHistory.id,
576
+ rows: this.#offeredHistory.rows,
577
+ kind: this.#offeredHistory.kind,
578
+ };
579
+ }
580
+ if (this.#headerReplayPending) {
581
+ const transcriptReplay = transcript.peekReplayBatch(width);
582
+ // A replay follows a scrollback clear, so the header recomposes at
583
+ // the new width exactly like transcript entries do. An empty
584
+ // recompose (welcome unmounted after retirement) falls back to the
585
+ // committed rows, hard-wrapped the way the terminal would.
586
+ const recomposed = this.#header.render(width);
587
+ const headerRows = recomposed.length > 0 ? [...recomposed, ""] : this.#reflowRetiredHeader(width, 0);
588
+ this.#offeredHistory = {
589
+ id: this.#nextHistoryId++,
590
+ rows: [...headerRows, ...(transcriptReplay?.rows ?? [])],
591
+ kind: "replay",
592
+ source: {
593
+ transcript,
594
+ transcriptId: transcriptReplay?.id,
595
+ header: "replay",
596
+ headerRows,
597
+ },
598
+ };
599
+ return {
600
+ id: this.#offeredHistory.id,
601
+ rows: this.#offeredHistory.rows,
602
+ kind: this.#offeredHistory.kind,
603
+ };
604
+ }
605
+ if (!this.#headerRetired) {
606
+ const welcome = this.#welcome;
607
+ if (welcome !== undefined && !welcome.isTranscriptBlockFinalized()) return undefined;
608
+ // The header stays live viewport chrome until the screen fills; then it
609
+ // retires first so transcript prefixes can follow in order.
610
+ const renderedHeader = this.#header.render(width);
611
+ if (renderedHeader.length > 0) {
612
+ const liveRows = transcript.liveRowCount(width);
613
+ if (!this.#historyFlush && renderedHeader.length + chromeRows + liveRows <= rows) return undefined;
614
+ this.#offeredHistory = {
615
+ id: this.#nextHistoryId++,
616
+ rows: [...renderedHeader, ""],
617
+ kind: "append",
618
+ source: "header",
619
+ };
620
+ return {
621
+ id: this.#offeredHistory.id,
622
+ rows: this.#offeredHistory.rows,
623
+ kind: this.#offeredHistory.kind,
624
+ };
625
+ }
626
+ this.#headerRetired = true;
627
+ this.#retiredHeaderRows = [];
628
+ }
629
+ const batch = this.#historyFlush
630
+ ? transcript.peekFlushBatch(width)
631
+ : transcript.peekFinalizedBatch(width, Math.max(0, rows - chromeRows));
632
+ if (batch === undefined) return undefined;
633
+ this.#offeredHistory = {
634
+ id: this.#nextHistoryId++,
635
+ rows: batch.rows,
636
+ kind: batch.kind ?? "append",
637
+ source: { transcript, transcriptId: batch.id, header: "none" },
638
+ };
639
+ return {
640
+ id: this.#offeredHistory.id,
641
+ rows: this.#offeredHistory.rows,
642
+ kind: this.#offeredHistory.kind,
643
+ };
644
+ }
645
+
646
+ #rerenderOfferedHistory(width: number): void {
647
+ const offered = this.#offeredHistory;
648
+ if (offered === undefined) return;
649
+ if (offered.source === "header") {
650
+ const rows = this.#header.render(width);
651
+ offered.rows = rows.length > 0 ? [...rows, ""] : [];
652
+ return;
653
+ }
654
+ const transcript = offered.source.transcript.rerenderOfferedBatch(width);
655
+ if (offered.source.header === "none") {
656
+ if (transcript !== undefined) offered.rows = transcript.rows;
657
+ return;
658
+ }
659
+ const recomposed = this.#header.render(width);
660
+ const headerRows = recomposed.length > 0 ? [...recomposed, ""] : this.#reflowRetiredHeader(width, 0);
661
+ offered.source.headerRows = headerRows;
662
+ offered.rows = [...headerRows, ...(transcript?.rows ?? [])];
663
+ }
664
+
665
+ #renderRoots(roots: readonly Component[], width: number): string[] {
666
+ const rows: string[] = [];
667
+ for (const root of roots) rows.push(...root.render(width));
668
+ return rows;
669
+ }
670
+ /**
671
+ * Mounted-runtime rows for the transient resize buffer. Only the trailing
672
+ * viewport can survive the caller's bottom slice, so the transcript renders
673
+ * a bounded tail instead of the full committed ledger, and the chrome above
674
+ * it renders only when that tail underfills the screen.
675
+ */
676
+ #renderResizeTail(width: number, rows: number): string[] {
677
+ const roots = [...this.#runtimeChildren, this.#statusHost];
678
+ const transcriptIndex = roots.findIndex(root => root instanceof TranscriptContainer);
679
+ if (transcriptIndex < 0) return this.#renderRoots(roots, width);
680
+ const transcript = roots[transcriptIndex] as TranscriptContainer;
681
+ const after = this.#renderRoots(roots.slice(transcriptIndex + 1), width);
682
+ const transcriptRows = transcript.renderTail(width, Math.max(0, rows - after.length));
683
+ const pre =
684
+ transcriptRows.length + after.length >= rows ? [] : this.#renderRoots(roots.slice(0, transcriptIndex), width);
685
+ return [...pre, ...transcriptRows, ...after];
686
+ }
687
+
688
+ /** Reflow accepted hard rows exactly as the restored terminal buffer will. */
689
+ #reflowRetiredHeader(width: number, start: number): string[] {
690
+ const lines = this.#retiredHeaderRows;
691
+ if (!lines) return [];
692
+ if (isInsideTerminalMultiplexer()) return lines.slice(start);
693
+ const reflowed: string[] = [];
694
+ const columns = Math.max(1, width);
695
+ for (let index = start; index < lines.length; index++) {
696
+ const line = lines[index]!;
697
+ const lineWidth = visibleWidth(line);
698
+ if (lineWidth === 0) {
699
+ reflowed.push("");
700
+ continue;
701
+ }
702
+ for (let column = 0; column < lineWidth;) {
703
+ let slice = sliceWithWidth(line, column, columns, true);
704
+ if (slice.width === 0) slice = sliceWithWidth(line, column, columns);
705
+ reflowed.push(slice.text);
706
+ column += Math.max(1, slice.width);
707
+ }
708
+ }
709
+ return reflowed;
710
+ }
711
+
712
+ /** Live editor whose draft survives startup and session adoption. */
713
+ get editor(): CustomEditor {
714
+ return this.#editor;
715
+ }
716
+
717
+ /** The welcome component currently mounted in the header, if quiet mode is off. */
718
+ get welcome(): WelcomeComponent | undefined {
719
+ return this.#welcome;
720
+ }
721
+
722
+ /** Whether this composer already owns the terminal render/input loop. */
723
+ get started(): boolean {
724
+ return this.#started && !this.#stopped;
725
+ }
726
+
727
+ /** Start terminal ownership and optionally begin the welcome intro. */
728
+ start(options: ComposerStartOptions = {}): void {
729
+ if (this.#started || this.#stopped) return;
730
+ this.#started = true;
731
+ this.ui.start({ clearScrollback: options.clearScrollback === true, deferInput: options.deferInput === true });
732
+ if (options.playWelcomeIntro !== false) this.playWelcomeIntro();
733
+ // Deferred input identifies the CLI prepaint handoff. Flush the queued
734
+ // forced frame before returning so subsequent dynamic-import evaluation
735
+ // cannot monopolize the event loop ahead of the speculative status chrome.
736
+ if (options.deferInput === true) this.ui.renderNow({ clearScrollback: options.clearScrollback === true });
737
+ }
738
+ /** Take raw-input ownership after a deferred-input start. Idempotent. */
739
+ enableInput(): void {
740
+ if (this.#stopped) return;
741
+ this.ui.enableInput();
742
+ }
743
+
744
+ /** Apply settings changes without replacing the editor or welcome component. */
745
+ setPreferences(update: Partial<ComposerPreferences>): void {
746
+ if (this.#stopped) return;
747
+ const wasQuiet = this.#preferences.quiet;
748
+ this.#preferences = { ...this.#preferences, ...update };
749
+ this.editor.setTheme(getEditorTheme());
750
+ try {
751
+ this.editor.setBorderStyle(this.#preferences.composerShape);
752
+ } catch {
753
+ // Extension-defined styles arrive with the session; InteractiveMode reapplies them.
754
+ }
755
+ this.ui.setShowHardwareCursor(this.#preferences.showHardwareCursor);
756
+ this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
757
+ this.ui.setMaxInlineImages(this.#preferences.maxInlineImages);
758
+ if (update.resizeScrollback !== undefined) this.ui.setResizeScrollback(update.resizeScrollback);
759
+ this.editor.setImeSafeCursorLayout(this.#preferences.imeSafeCursor);
760
+ this.editor.setAutocompleteMaxVisible(this.#preferences.autocompleteMaxVisible);
761
+ this.editor.setSpellingFeatures({
762
+ typoDetection: this.#preferences.spellingTypoDetection,
763
+ autocomplete: this.#preferences.spellingAutocomplete,
764
+ autocorrect: this.#preferences.spellingAutocorrect,
765
+ });
766
+ this.#applyStatusSnapshot();
767
+ if (this.#preferences.quiet) {
768
+ this.#welcome?.stopIntro();
769
+ this.#welcome = undefined;
770
+ } else {
771
+ this.#ensureWelcome();
772
+ this.#welcome?.invalidate();
773
+ if (wasQuiet && this.#started) this.playWelcomeIntro();
774
+ }
775
+ if (wasQuiet !== this.#preferences.quiet) this.#rebuildHeader();
776
+ this.ui.requestRender();
777
+ }
778
+
779
+ /** Patch welcome data in place as model, session, and project discovery complete. */
780
+ updateWelcome(update: ComposerWelcomeUpdate): void {
781
+ if (this.#stopped) return;
782
+ this.#applyWelcomeUpdate(update);
783
+ if (this.#preferences.quiet) return;
784
+ this.#ensureWelcome();
785
+ const welcome = this.#welcome;
786
+ if (!welcome) return;
787
+ if (update.version !== undefined) welcome.setVersion(this.#version);
788
+ if (update.modelName !== undefined || update.providerName !== undefined) {
789
+ welcome.setModel(this.#modelName, this.#providerName);
790
+ }
791
+ if (update.recentSessions !== undefined) welcome.setRecentSessions(this.#recentSessions);
792
+ if (update.lspServers !== undefined) welcome.setLspServers(this.#lspServers);
793
+ this.ui.requestRender();
794
+ }
795
+
796
+ /** Replace optional header content around the stable welcome scene. */
797
+ setHeaderExtras(before: readonly Component[], after: readonly Component[]): void {
798
+ if (this.#stopped) return;
799
+ this.#headerBefore = before;
800
+ this.#headerAfter = after;
801
+ this.#rebuildHeader();
802
+ this.ui.requestRender();
803
+ }
804
+
805
+ /** Update the canonical editor reference after InteractiveMode remounts a custom editor. */
806
+ setEditor(editor: CustomEditor): void {
807
+ this.#editor = editor;
808
+ }
809
+
810
+ /**
811
+ * Mount the session-aware status component into the slot below the editor.
812
+ * Drops the speculative snapshot; the caller installs the real top-border
813
+ * provider through its composer-shape sync.
814
+ */
815
+ setStatusComponent(component: Component): void {
816
+ this.#statusHost.setComponent(component);
817
+ this.#statusSnapshot = undefined;
818
+ this.editor.setTopBorderProvider(undefined);
819
+ }
820
+
821
+ /** Cached placeholder top-border content fitted to the current editor width. */
822
+ #speculativeTopBorder(availableWidth: number): EditorTopBorder | undefined {
823
+ const border = this.#statusSnapshot?.topBorder;
824
+ if (!border) return undefined;
825
+ if (border.width <= availableWidth) return { content: border.content, width: border.width };
826
+ const content = truncateToWidth(border.content, availableWidth);
827
+ return { content, width: visibleWidth(content) };
828
+ }
829
+
830
+ /** Install the cached chrome for the current shape; a shape mismatch clears it. */
831
+ #applyStatusSnapshot(): void {
832
+ if (this.#statusHost.mounted) return;
833
+ const snapshot = this.#statusSnapshot;
834
+ if (!snapshot || snapshot.shape !== this.#preferences.composerShape) {
835
+ this.editor.setTopBorderProvider(undefined);
836
+ this.#statusHost.setLines([]);
837
+ return;
838
+ }
839
+ if (snapshot.borderColor) {
840
+ const { prefix, suffix } = snapshot.borderColor;
841
+ this.editor.borderColor = text => `${prefix}${text}${suffix}`;
842
+ }
843
+ this.editor.setTopBorderProvider(
844
+ snapshot.topBorder ? availableWidth => this.#speculativeTopBorder(availableWidth) : undefined,
845
+ );
846
+ this.#statusHost.setLines(snapshot.bottomLines);
847
+ }
848
+
849
+ /** Mount or replace session-aware root children while preserving the header and status hosts. */
850
+ setRuntimeChildren(children: readonly Component[]): void {
851
+ if (this.#stopped) return;
852
+ this.ui.removeChild(this.#statusHost);
853
+ if (this.#runtimeMounted) {
854
+ for (const child of this.#runtimeChildren) this.ui.removeChild(child);
855
+ } else {
856
+ this.ui.removeChild(this.#bootstrapInputGap);
857
+ this.ui.removeChild(this.editor);
858
+ this.#runtimeMounted = true;
859
+ }
860
+ this.#runtimeChildren = children;
861
+ for (const child of children) this.ui.addChild(child);
862
+ this.ui.addChild(this.#statusHost);
863
+ this.ui.requestRender();
864
+ }
865
+
866
+ /** Play or replay the welcome intro against the stable header render target. */
867
+ playWelcomeIntro(): void {
868
+ this.#welcome?.playIntro(() => this.ui.requestComponentRender(this.#header));
869
+ }
870
+
871
+ /** Transfer terminal ownership to InteractiveMode without stopping the composer. */
872
+ transfer(): void {
873
+ if (!this.#started || this.#stopped || this.#transferred) {
874
+ throw new Error("Composer is not available for transfer");
875
+ }
876
+ this.#transferred = true;
877
+ }
878
+
879
+ /** Stop a composer that has not transferred terminal ownership. */
880
+ stop(): void {
881
+ if (!this.#started || this.#stopped || this.#transferred) return;
882
+ this.#welcome?.stopIntro();
883
+ this.ui.stop();
884
+ this.#stopped = true;
885
+ }
886
+
887
+ #applyWelcomeUpdate(update: ComposerWelcomeUpdate): void {
888
+ if (update.version !== undefined) this.#version = update.version;
889
+ if (update.modelName !== undefined) this.#modelName = update.modelName;
890
+ if (update.providerName !== undefined) this.#providerName = update.providerName;
891
+ if (update.recentSessions !== undefined) this.#recentSessions = [...update.recentSessions];
892
+ if (update.lspServers !== undefined) this.#lspServers = [...update.lspServers];
893
+ }
894
+
895
+ #ensureWelcome(): void {
896
+ this.#welcome ??= new WelcomeComponent(
897
+ this.#version,
898
+ this.#modelName,
899
+ this.#providerName,
900
+ this.#recentSessions,
901
+ this.#lspServers,
902
+ );
903
+ }
904
+
905
+ #rebuildHeader(): void {
906
+ this.#header.clear();
907
+ for (const component of this.#headerBefore) this.#header.addChild(component);
908
+ if (this.#welcome) {
909
+ this.#header.addChild(new Spacer(1));
910
+ this.#header.addChild(this.#welcome);
911
+ this.#header.addChild(new Spacer(1));
912
+ }
913
+ for (const component of this.#headerAfter) this.#header.addChild(component);
914
+ }
915
+
916
+ #handleInterrupt(): void {
917
+ const now = this.#now();
918
+ if (now - this.#lastInterruptAt < DOUBLE_INTERRUPT_MS) {
919
+ this.#requestExit(130);
920
+ return;
921
+ }
922
+ this.editor.setText("");
923
+ this.#lastInterruptAt = now;
924
+ }
925
+
926
+ #requestExit(code: number): void {
927
+ // Remains live after transfer until InteractiveMode installs its configured handlers.
928
+ if (this.#stopped) return;
929
+ this.#welcome?.stopIntro();
930
+ if (this.#started) this.ui.stop();
931
+ this.#stopped = true;
932
+ this.#exit(code);
933
+ }
934
+ }