@deepseek-harness-tui/dsh-tui 0.5.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 (638) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/bin/dsh-tui.js +95 -0
  4. package/cordis.patch.yml +218 -0
  5. package/cordis.yml +185 -0
  6. package/lib/invariant.js +28 -0
  7. package/lib/types/activityPrefs.d.ts +29 -0
  8. package/lib/types/activityPrefs.d.ts.map +1 -0
  9. package/lib/types/activityPrefs.js +60 -0
  10. package/lib/types/bootstrap/state.d.ts +13 -0
  11. package/lib/types/bootstrap/state.d.ts.map +1 -0
  12. package/lib/types/bootstrap/state.js +12 -0
  13. package/lib/types/cc/cliHighlight.d.ts +20 -0
  14. package/lib/types/cc/cliHighlight.d.ts.map +1 -0
  15. package/lib/types/cc/cliHighlight.js +53 -0
  16. package/lib/types/cc/figures.d.ts +59 -0
  17. package/lib/types/cc/figures.d.ts.map +1 -0
  18. package/lib/types/cc/figures.js +69 -0
  19. package/lib/types/cc/format.d.ts +26 -0
  20. package/lib/types/cc/format.d.ts.map +1 -0
  21. package/lib/types/cc/format.js +55 -0
  22. package/lib/types/cc/hash.d.ts +25 -0
  23. package/lib/types/cc/hash.d.ts.map +1 -0
  24. package/lib/types/cc/hash.js +41 -0
  25. package/lib/types/cc/hyperlink.d.ts +21 -0
  26. package/lib/types/cc/hyperlink.d.ts.map +1 -0
  27. package/lib/types/cc/hyperlink.js +31 -0
  28. package/lib/types/cc/markdown.d.ts +43 -0
  29. package/lib/types/cc/markdown.d.ts.map +1 -0
  30. package/lib/types/cc/markdown.js +345 -0
  31. package/lib/types/cc/spinnerVerbs.d.ts +3 -0
  32. package/lib/types/cc/spinnerVerbs.d.ts.map +1 -0
  33. package/lib/types/cc/spinnerVerbs.js +190 -0
  34. package/lib/types/cc/terminal.d.ts +15 -0
  35. package/lib/types/cc/terminal.d.ts.map +1 -0
  36. package/lib/types/cc/terminal.js +84 -0
  37. package/lib/types/channel.d.ts +672 -0
  38. package/lib/types/channel.d.ts.map +1 -0
  39. package/lib/types/channel.js +2427 -0
  40. package/lib/types/childStderr.d.ts +63 -0
  41. package/lib/types/childStderr.d.ts.map +1 -0
  42. package/lib/types/childStderr.js +161 -0
  43. package/lib/types/commands.d.ts +55 -0
  44. package/lib/types/commands.d.ts.map +1 -0
  45. package/lib/types/commands.js +101 -0
  46. package/lib/types/components/ActivityLine.d.ts +29 -0
  47. package/lib/types/components/ActivityLine.d.ts.map +1 -0
  48. package/lib/types/components/ActivityLine.js +47 -0
  49. package/lib/types/components/ActivityPicker.d.ts +13 -0
  50. package/lib/types/components/ActivityPicker.d.ts.map +1 -0
  51. package/lib/types/components/ActivityPicker.js +24 -0
  52. package/lib/types/components/CommandSuggestions.d.ts +14 -0
  53. package/lib/types/components/CommandSuggestions.d.ts.map +1 -0
  54. package/lib/types/components/CommandSuggestions.js +33 -0
  55. package/lib/types/components/FileSuggestions.d.ts +12 -0
  56. package/lib/types/components/FileSuggestions.d.ts.map +1 -0
  57. package/lib/types/components/FileSuggestions.js +26 -0
  58. package/lib/types/components/GoalTodoPanel.d.ts +13 -0
  59. package/lib/types/components/GoalTodoPanel.d.ts.map +1 -0
  60. package/lib/types/components/GoalTodoPanel.js +57 -0
  61. package/lib/types/components/HelpMenu.d.ts +12 -0
  62. package/lib/types/components/HelpMenu.d.ts.map +1 -0
  63. package/lib/types/components/HelpMenu.js +12 -0
  64. package/lib/types/components/HistorySearchDialog.d.ts +16 -0
  65. package/lib/types/components/HistorySearchDialog.d.ts.map +1 -0
  66. package/lib/types/components/HistorySearchDialog.js +31 -0
  67. package/lib/types/components/InterruptedByUser.d.ts +7 -0
  68. package/lib/types/components/InterruptedByUser.d.ts.map +1 -0
  69. package/lib/types/components/InterruptedByUser.js +9 -0
  70. package/lib/types/components/LoadedContextPanel.d.ts +20 -0
  71. package/lib/types/components/LoadedContextPanel.d.ts.map +1 -0
  72. package/lib/types/components/LoadedContextPanel.js +30 -0
  73. package/lib/types/components/LogoV2.d.ts +23 -0
  74. package/lib/types/components/LogoV2.d.ts.map +1 -0
  75. package/lib/types/components/LogoV2.js +95 -0
  76. package/lib/types/components/Markdown.d.ts +24 -0
  77. package/lib/types/components/Markdown.d.ts.map +1 -0
  78. package/lib/types/components/Markdown.js +106 -0
  79. package/lib/types/components/MarkdownTable.d.ts +17 -0
  80. package/lib/types/components/MarkdownTable.d.ts.map +1 -0
  81. package/lib/types/components/MarkdownTable.js +272 -0
  82. package/lib/types/components/MessageList.d.ts +41 -0
  83. package/lib/types/components/MessageList.d.ts.map +1 -0
  84. package/lib/types/components/MessageList.js +303 -0
  85. package/lib/types/components/ModelPicker.d.ts +14 -0
  86. package/lib/types/components/ModelPicker.d.ts.map +1 -0
  87. package/lib/types/components/ModelPicker.js +15 -0
  88. package/lib/types/components/PresetPicker.d.ts +17 -0
  89. package/lib/types/components/PresetPicker.d.ts.map +1 -0
  90. package/lib/types/components/PresetPicker.js +25 -0
  91. package/lib/types/components/PromptInput.d.ts +67 -0
  92. package/lib/types/components/PromptInput.d.ts.map +1 -0
  93. package/lib/types/components/PromptInput.js +778 -0
  94. package/lib/types/components/ResumePicker.d.ts +15 -0
  95. package/lib/types/components/ResumePicker.d.ts.map +1 -0
  96. package/lib/types/components/ResumePicker.js +34 -0
  97. package/lib/types/components/RewindPicker.d.ts +14 -0
  98. package/lib/types/components/RewindPicker.d.ts.map +1 -0
  99. package/lib/types/components/RewindPicker.js +23 -0
  100. package/lib/types/components/SearchBox.d.ts +20 -0
  101. package/lib/types/components/SearchBox.d.ts.map +1 -0
  102. package/lib/types/components/SearchBox.js +33 -0
  103. package/lib/types/components/Select.d.ts +22 -0
  104. package/lib/types/components/Select.d.ts.map +1 -0
  105. package/lib/types/components/Select.js +19 -0
  106. package/lib/types/components/Spinner/GlimmerMessage.d.ts +19 -0
  107. package/lib/types/components/Spinner/GlimmerMessage.d.ts.map +1 -0
  108. package/lib/types/components/Spinner/GlimmerMessage.js +78 -0
  109. package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts +31 -0
  110. package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts.map +1 -0
  111. package/lib/types/components/Spinner/SpinnerAnimationRow.js +162 -0
  112. package/lib/types/components/Spinner/SpinnerGlyph.d.ts +16 -0
  113. package/lib/types/components/Spinner/SpinnerGlyph.d.ts.map +1 -0
  114. package/lib/types/components/Spinner/SpinnerGlyph.js +36 -0
  115. package/lib/types/components/Spinner/spinnerMode.d.ts +6 -0
  116. package/lib/types/components/Spinner/spinnerMode.d.ts.map +1 -0
  117. package/lib/types/components/Spinner/spinnerMode.js +1 -0
  118. package/lib/types/components/Spinner/spinnerUtils.d.ts +41 -0
  119. package/lib/types/components/Spinner/spinnerUtils.d.ts.map +1 -0
  120. package/lib/types/components/Spinner/spinnerUtils.js +104 -0
  121. package/lib/types/components/Spinner/useStalledAnimation.d.ts +16 -0
  122. package/lib/types/components/Spinner/useStalledAnimation.d.ts.map +1 -0
  123. package/lib/types/components/Spinner/useStalledAnimation.js +71 -0
  124. package/lib/types/components/StreamingMarkdown.d.ts +12 -0
  125. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -0
  126. package/lib/types/components/StreamingMarkdown.js +39 -0
  127. package/lib/types/components/ThemePicker.d.ts +22 -0
  128. package/lib/types/components/ThemePicker.d.ts.map +1 -0
  129. package/lib/types/components/ThemePicker.js +51 -0
  130. package/lib/types/components/ThinkingToggle.d.ts +17 -0
  131. package/lib/types/components/ThinkingToggle.d.ts.map +1 -0
  132. package/lib/types/components/ThinkingToggle.js +30 -0
  133. package/lib/types/components/ToolUseLoader.d.ts +13 -0
  134. package/lib/types/components/ToolUseLoader.d.ts.map +1 -0
  135. package/lib/types/components/ToolUseLoader.js +17 -0
  136. package/lib/types/components/Whale.d.ts +22 -0
  137. package/lib/types/components/Whale.d.ts.map +1 -0
  138. package/lib/types/components/Whale.js +78 -0
  139. package/lib/types/components/WorkingSpinner.d.ts +26 -0
  140. package/lib/types/components/WorkingSpinner.d.ts.map +1 -0
  141. package/lib/types/components/WorkingSpinner.js +68 -0
  142. package/lib/types/components/activityFrames.d.ts +31 -0
  143. package/lib/types/components/activityFrames.d.ts.map +1 -0
  144. package/lib/types/components/activityFrames.js +83 -0
  145. package/lib/types/components/bigfont.d.ts +28 -0
  146. package/lib/types/components/bigfont.d.ts.map +1 -0
  147. package/lib/types/components/bigfont.js +91 -0
  148. package/lib/types/components/design-system/Byline.d.ts +14 -0
  149. package/lib/types/components/design-system/Byline.d.ts.map +1 -0
  150. package/lib/types/components/design-system/Byline.js +17 -0
  151. package/lib/types/components/design-system/Divider.d.ts +40 -0
  152. package/lib/types/components/design-system/Divider.d.ts.map +1 -0
  153. package/lib/types/components/design-system/Divider.js +24 -0
  154. package/lib/types/components/design-system/KeyboardShortcutHint.d.ts +19 -0
  155. package/lib/types/components/design-system/KeyboardShortcutHint.d.ts.map +1 -0
  156. package/lib/types/components/design-system/KeyboardShortcutHint.js +14 -0
  157. package/lib/types/components/design-system/ListItem.d.ts +36 -0
  158. package/lib/types/components/design-system/ListItem.d.ts.map +1 -0
  159. package/lib/types/components/design-system/ListItem.js +48 -0
  160. package/lib/types/components/design-system/LoadingState.d.ts +19 -0
  161. package/lib/types/components/design-system/LoadingState.d.ts.map +1 -0
  162. package/lib/types/components/design-system/LoadingState.js +22 -0
  163. package/lib/types/components/design-system/Pane.d.ts +17 -0
  164. package/lib/types/components/design-system/Pane.d.ts.map +1 -0
  165. package/lib/types/components/design-system/Pane.js +15 -0
  166. package/lib/types/components/design-system/ProgressBar.d.ts +22 -0
  167. package/lib/types/components/design-system/ProgressBar.d.ts.map +1 -0
  168. package/lib/types/components/design-system/ProgressBar.js +27 -0
  169. package/lib/types/components/design-system/StatusIcon.d.ts +15 -0
  170. package/lib/types/components/design-system/StatusIcon.d.ts.map +1 -0
  171. package/lib/types/components/design-system/StatusIcon.js +20 -0
  172. package/lib/types/components/design-system/ThemeProvider.d.ts +11 -0
  173. package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -0
  174. package/lib/types/components/design-system/ThemeProvider.js +135 -0
  175. package/lib/types/components/design-system/ThemedBox.d.ts +37 -0
  176. package/lib/types/components/design-system/ThemedBox.d.ts.map +1 -0
  177. package/lib/types/components/design-system/ThemedBox.js +26 -0
  178. package/lib/types/components/design-system/ThemedText.d.ts +56 -0
  179. package/lib/types/components/design-system/ThemedText.d.ts.map +1 -0
  180. package/lib/types/components/design-system/ThemedText.js +45 -0
  181. package/lib/types/components/messages/AssistantTextMessage.d.ts +18 -0
  182. package/lib/types/components/messages/AssistantTextMessage.d.ts.map +1 -0
  183. package/lib/types/components/messages/AssistantTextMessage.js +15 -0
  184. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +23 -0
  185. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -0
  186. package/lib/types/components/messages/AssistantThinkingMessage.js +23 -0
  187. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +23 -0
  188. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -0
  189. package/lib/types/components/messages/AssistantToolUseMessage.js +220 -0
  190. package/lib/types/components/messages/MessageMetadata.d.ts +15 -0
  191. package/lib/types/components/messages/MessageMetadata.d.ts.map +1 -0
  192. package/lib/types/components/messages/MessageMetadata.js +20 -0
  193. package/lib/types/components/messages/UserPromptMessage.d.ts +19 -0
  194. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -0
  195. package/lib/types/components/messages/UserPromptMessage.js +15 -0
  196. package/lib/types/components/questions/AskUserQuestionPanel.d.ts +39 -0
  197. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -0
  198. package/lib/types/components/questions/AskUserQuestionPanel.js +228 -0
  199. package/lib/types/components/shimmer.d.ts +35 -0
  200. package/lib/types/components/shimmer.d.ts.map +1 -0
  201. package/lib/types/components/shimmer.js +43 -0
  202. package/lib/types/components/whaleFrames.d.ts +25 -0
  203. package/lib/types/components/whaleFrames.d.ts.map +1 -0
  204. package/lib/types/components/whaleFrames.js +419 -0
  205. package/lib/types/customTheme.d.ts +104 -0
  206. package/lib/types/customTheme.d.ts.map +1 -0
  207. package/lib/types/customTheme.js +268 -0
  208. package/lib/types/effortPrefs.d.ts +22 -0
  209. package/lib/types/effortPrefs.d.ts.map +1 -0
  210. package/lib/types/effortPrefs.js +45 -0
  211. package/lib/types/history.d.ts +24 -0
  212. package/lib/types/history.d.ts.map +1 -0
  213. package/lib/types/history.js +75 -0
  214. package/lib/types/hooks/useBlink.d.ts +11 -0
  215. package/lib/types/hooks/useBlink.d.ts.map +1 -0
  216. package/lib/types/hooks/useBlink.js +20 -0
  217. package/lib/types/i18n.d.ts +926 -0
  218. package/lib/types/i18n.d.ts.map +1 -0
  219. package/lib/types/i18n.js +356 -0
  220. package/lib/types/index.d.ts +70 -0
  221. package/lib/types/index.d.ts.map +1 -0
  222. package/lib/types/index.js +31 -0
  223. package/lib/types/ink/Ansi.d.ts +11 -0
  224. package/lib/types/ink/Ansi.d.ts.map +1 -0
  225. package/lib/types/ink/Ansi.js +269 -0
  226. package/lib/types/ink/bidi.d.ts +18 -0
  227. package/lib/types/ink/bidi.d.ts.map +1 -0
  228. package/lib/types/ink/bidi.js +119 -0
  229. package/lib/types/ink/clearTerminal.d.ts +22 -0
  230. package/lib/types/ink/clearTerminal.d.ts.map +1 -0
  231. package/lib/types/ink/clearTerminal.js +72 -0
  232. package/lib/types/ink/colorize.d.ts +33 -0
  233. package/lib/types/ink/colorize.d.ts.map +1 -0
  234. package/lib/types/ink/colorize.js +213 -0
  235. package/lib/types/ink/components/AlternateScreen.d.ts +22 -0
  236. package/lib/types/ink/components/AlternateScreen.d.ts.map +1 -0
  237. package/lib/types/ink/components/AlternateScreen.js +74 -0
  238. package/lib/types/ink/components/App.d.ts +73 -0
  239. package/lib/types/ink/components/App.d.ts.map +1 -0
  240. package/lib/types/ink/components/App.js +597 -0
  241. package/lib/types/ink/components/AppContext.d.ts +15 -0
  242. package/lib/types/ink/components/AppContext.d.ts.map +1 -0
  243. package/lib/types/ink/components/AppContext.js +11 -0
  244. package/lib/types/ink/components/Box.d.ts +49 -0
  245. package/lib/types/ink/components/Box.d.ts.map +1 -0
  246. package/lib/types/ink/components/Box.js +153 -0
  247. package/lib/types/ink/components/Button.d.ts +37 -0
  248. package/lib/types/ink/components/Button.d.ts.map +1 -0
  249. package/lib/types/ink/components/Button.js +166 -0
  250. package/lib/types/ink/components/ClockContext.d.ts +10 -0
  251. package/lib/types/ink/components/ClockContext.d.ts.map +1 -0
  252. package/lib/types/ink/components/ClockContext.js +108 -0
  253. package/lib/types/ink/components/CursorDeclarationContext.d.ts +30 -0
  254. package/lib/types/ink/components/CursorDeclarationContext.d.ts.map +1 -0
  255. package/lib/types/ink/components/CursorDeclarationContext.js +6 -0
  256. package/lib/types/ink/components/ErrorOverview.d.ts +7 -0
  257. package/lib/types/ink/components/ErrorOverview.d.ts.map +1 -0
  258. package/lib/types/ink/components/ErrorOverview.js +49 -0
  259. package/lib/types/ink/components/Link.d.ts +8 -0
  260. package/lib/types/ink/components/Link.d.ts.map +1 -0
  261. package/lib/types/ink/components/Link.js +33 -0
  262. package/lib/types/ink/components/Newline.d.ts +13 -0
  263. package/lib/types/ink/components/Newline.d.ts.map +1 -0
  264. package/lib/types/ink/components/Newline.js +29 -0
  265. package/lib/types/ink/components/NoSelect.d.ts +20 -0
  266. package/lib/types/ink/components/NoSelect.d.ts.map +1 -0
  267. package/lib/types/ink/components/NoSelect.js +56 -0
  268. package/lib/types/ink/components/RawAnsi.d.ts +17 -0
  269. package/lib/types/ink/components/RawAnsi.d.ts.map +1 -0
  270. package/lib/types/ink/components/RawAnsi.js +45 -0
  271. package/lib/types/ink/components/ScrollBox.d.ts +78 -0
  272. package/lib/types/ink/components/ScrollBox.d.ts.map +1 -0
  273. package/lib/types/ink/components/ScrollBox.js +174 -0
  274. package/lib/types/ink/components/Spacer.d.ts +6 -0
  275. package/lib/types/ink/components/Spacer.d.ts.map +1 -0
  276. package/lib/types/ink/components/Spacer.js +19 -0
  277. package/lib/types/ink/components/StdinContext.d.ts +32 -0
  278. package/lib/types/ink/components/StdinContext.d.ts.map +1 -0
  279. package/lib/types/ink/components/StdinContext.js +16 -0
  280. package/lib/types/ink/components/TerminalFocusContext.d.ts +11 -0
  281. package/lib/types/ink/components/TerminalFocusContext.d.ts.map +1 -0
  282. package/lib/types/ink/components/TerminalFocusContext.js +45 -0
  283. package/lib/types/ink/components/TerminalSizeContext.d.ts +6 -0
  284. package/lib/types/ink/components/TerminalSizeContext.d.ts.map +1 -0
  285. package/lib/types/ink/components/TerminalSizeContext.js +3 -0
  286. package/lib/types/ink/components/Text.d.ts +56 -0
  287. package/lib/types/ink/components/Text.d.ts.map +1 -0
  288. package/lib/types/ink/components/Text.js +194 -0
  289. package/lib/types/ink/constants.d.ts +3 -0
  290. package/lib/types/ink/constants.d.ts.map +1 -0
  291. package/lib/types/ink/constants.js +2 -0
  292. package/lib/types/ink/cursor.d.ts +11 -0
  293. package/lib/types/ink/cursor.d.ts.map +1 -0
  294. package/lib/types/ink/cursor.js +1 -0
  295. package/lib/types/ink/devtools.d.ts +7 -0
  296. package/lib/types/ink/devtools.d.ts.map +1 -0
  297. package/lib/types/ink/devtools.js +1 -0
  298. package/lib/types/ink/dom.d.ts +178 -0
  299. package/lib/types/ink/dom.d.ts.map +1 -0
  300. package/lib/types/ink/dom.js +424 -0
  301. package/lib/types/ink/events/click-event.d.ts +32 -0
  302. package/lib/types/ink/events/click-event.d.ts.map +1 -0
  303. package/lib/types/ink/events/click-event.js +36 -0
  304. package/lib/types/ink/events/dispatcher.d.ts +63 -0
  305. package/lib/types/ink/events/dispatcher.d.ts.map +1 -0
  306. package/lib/types/ink/events/dispatcher.js +194 -0
  307. package/lib/types/ink/events/emitter.d.ts +20 -0
  308. package/lib/types/ink/events/emitter.d.ts.map +1 -0
  309. package/lib/types/ink/events/emitter.js +43 -0
  310. package/lib/types/ink/events/event-handlers.d.ts +47 -0
  311. package/lib/types/ink/events/event-handlers.d.ts.map +1 -0
  312. package/lib/types/ink/events/event-handlers.js +30 -0
  313. package/lib/types/ink/events/event.d.ts +19 -0
  314. package/lib/types/ink/events/event.d.ts.map +1 -0
  315. package/lib/types/ink/events/event.js +22 -0
  316. package/lib/types/ink/events/focus-event.d.ts +19 -0
  317. package/lib/types/ink/events/focus-event.d.ts.map +1 -0
  318. package/lib/types/ink/events/focus-event.js +21 -0
  319. package/lib/types/ink/events/input-event.d.ts +53 -0
  320. package/lib/types/ink/events/input-event.d.ts.map +1 -0
  321. package/lib/types/ink/events/input-event.js +180 -0
  322. package/lib/types/ink/events/keyboard-event.d.ts +40 -0
  323. package/lib/types/ink/events/keyboard-event.d.ts.map +1 -0
  324. package/lib/types/ink/events/keyboard-event.js +65 -0
  325. package/lib/types/ink/events/paste-event.d.ts +11 -0
  326. package/lib/types/ink/events/paste-event.d.ts.map +1 -0
  327. package/lib/types/ink/events/paste-event.js +13 -0
  328. package/lib/types/ink/events/resize-event.d.ts +13 -0
  329. package/lib/types/ink/events/resize-event.d.ts.map +1 -0
  330. package/lib/types/ink/events/resize-event.js +16 -0
  331. package/lib/types/ink/events/terminal-event.d.ts +112 -0
  332. package/lib/types/ink/events/terminal-event.d.ts.map +1 -0
  333. package/lib/types/ink/events/terminal-event.js +131 -0
  334. package/lib/types/ink/events/terminal-focus-event.d.ts +21 -0
  335. package/lib/types/ink/events/terminal-focus-event.d.ts.map +1 -0
  336. package/lib/types/ink/events/terminal-focus-event.js +18 -0
  337. package/lib/types/ink/focus.d.ts +74 -0
  338. package/lib/types/ink/focus.d.ts.map +1 -0
  339. package/lib/types/ink/focus.js +188 -0
  340. package/lib/types/ink/frame.d.ts +119 -0
  341. package/lib/types/ink/frame.d.ts.map +1 -0
  342. package/lib/types/ink/frame.js +43 -0
  343. package/lib/types/ink/get-max-width.d.ts +20 -0
  344. package/lib/types/ink/get-max-width.d.ts.map +1 -0
  345. package/lib/types/ink/get-max-width.js +25 -0
  346. package/lib/types/ink/hit-test.d.ts +47 -0
  347. package/lib/types/ink/hit-test.d.ts.map +1 -0
  348. package/lib/types/ink/hit-test.js +126 -0
  349. package/lib/types/ink/hooks/use-animation-frame.d.ts +27 -0
  350. package/lib/types/ink/hooks/use-animation-frame.d.ts.map +1 -0
  351. package/lib/types/ink/hooks/use-animation-frame.js +48 -0
  352. package/lib/types/ink/hooks/use-app.d.ts +8 -0
  353. package/lib/types/ink/hooks/use-app.d.ts.map +1 -0
  354. package/lib/types/ink/hooks/use-app.js +8 -0
  355. package/lib/types/ink/hooks/use-copy-on-select.d.ts +22 -0
  356. package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -0
  357. package/lib/types/ink/hooks/use-copy-on-select.js +40 -0
  358. package/lib/types/ink/hooks/use-declared-cursor.d.ts +31 -0
  359. package/lib/types/ink/hooks/use-declared-cursor.d.ts.map +1 -0
  360. package/lib/types/ink/hooks/use-declared-cursor.js +65 -0
  361. package/lib/types/ink/hooks/use-input.d.ts +41 -0
  362. package/lib/types/ink/hooks/use-input.d.ts.map +1 -0
  363. package/lib/types/ink/hooks/use-input.js +73 -0
  364. package/lib/types/ink/hooks/use-interval.d.ts +23 -0
  365. package/lib/types/ink/hooks/use-interval.d.ts.map +1 -0
  366. package/lib/types/ink/hooks/use-interval.js +58 -0
  367. package/lib/types/ink/hooks/use-search-highlight.d.ts +34 -0
  368. package/lib/types/ink/hooks/use-search-highlight.d.ts.map +1 -0
  369. package/lib/types/ink/hooks/use-search-highlight.js +35 -0
  370. package/lib/types/ink/hooks/use-selection.d.ts +44 -0
  371. package/lib/types/ink/hooks/use-selection.d.ts.map +1 -0
  372. package/lib/types/ink/hooks/use-selection.js +64 -0
  373. package/lib/types/ink/hooks/use-stdin.d.ts +8 -0
  374. package/lib/types/ink/hooks/use-stdin.d.ts.map +1 -0
  375. package/lib/types/ink/hooks/use-stdin.js +8 -0
  376. package/lib/types/ink/hooks/use-tab-status.d.ts +18 -0
  377. package/lib/types/ink/hooks/use-tab-status.d.ts.map +1 -0
  378. package/lib/types/ink/hooks/use-tab-status.js +58 -0
  379. package/lib/types/ink/hooks/use-terminal-focus.d.ts +11 -0
  380. package/lib/types/ink/hooks/use-terminal-focus.d.ts.map +1 -0
  381. package/lib/types/ink/hooks/use-terminal-focus.js +15 -0
  382. package/lib/types/ink/hooks/use-terminal-size.d.ts +6 -0
  383. package/lib/types/ink/hooks/use-terminal-size.d.ts.map +1 -0
  384. package/lib/types/ink/hooks/use-terminal-size.js +12 -0
  385. package/lib/types/ink/hooks/use-terminal-title.d.ts +14 -0
  386. package/lib/types/ink/hooks/use-terminal-title.d.ts.map +1 -0
  387. package/lib/types/ink/hooks/use-terminal-title.js +30 -0
  388. package/lib/types/ink/hooks/use-terminal-viewport.d.ts +32 -0
  389. package/lib/types/ink/hooks/use-terminal-viewport.d.ts.map +1 -0
  390. package/lib/types/ink/hooks/use-terminal-viewport.js +79 -0
  391. package/lib/types/ink/ink.d.ts +370 -0
  392. package/lib/types/ink/ink.d.ts.map +1 -0
  393. package/lib/types/ink/ink.js +1665 -0
  394. package/lib/types/ink/instances.d.ts +10 -0
  395. package/lib/types/ink/instances.d.ts.map +1 -0
  396. package/lib/types/ink/instances.js +13 -0
  397. package/lib/types/ink/layout/engine.d.ts +7 -0
  398. package/lib/types/ink/layout/engine.d.ts.map +1 -0
  399. package/lib/types/ink/layout/engine.js +8 -0
  400. package/lib/types/ink/layout/geometry.d.ts +86 -0
  401. package/lib/types/ink/layout/geometry.d.ts.map +1 -0
  402. package/lib/types/ink/layout/geometry.js +95 -0
  403. package/lib/types/ink/layout/node.d.ts +151 -0
  404. package/lib/types/ink/layout/node.d.ts.map +1 -0
  405. package/lib/types/ink/layout/node.js +72 -0
  406. package/lib/types/ink/layout/yoga.d.ts +69 -0
  407. package/lib/types/ink/layout/yoga.d.ts.map +1 -0
  408. package/lib/types/ink/layout/yoga.js +249 -0
  409. package/lib/types/ink/line-width-cache.d.ts +9 -0
  410. package/lib/types/ink/line-width-cache.d.ts.map +1 -0
  411. package/lib/types/ink/line-width-cache.js +45 -0
  412. package/lib/types/ink/log-update.d.ts +57 -0
  413. package/lib/types/ink/log-update.d.ts.map +1 -0
  414. package/lib/types/ink/log-update.js +710 -0
  415. package/lib/types/ink/measure-element.d.ts +19 -0
  416. package/lib/types/ink/measure-element.d.ts.map +1 -0
  417. package/lib/types/ink/measure-element.js +10 -0
  418. package/lib/types/ink/measure-text.d.ts +13 -0
  419. package/lib/types/ink/measure-text.d.ts.map +1 -0
  420. package/lib/types/ink/measure-text.js +38 -0
  421. package/lib/types/ink/node-cache.d.ts +33 -0
  422. package/lib/types/ink/node-cache.d.ts.map +1 -0
  423. package/lib/types/ink/node-cache.js +42 -0
  424. package/lib/types/ink/optimizer.d.ts +18 -0
  425. package/lib/types/ink/optimizer.d.ts.map +1 -0
  426. package/lib/types/ink/optimizer.js +83 -0
  427. package/lib/types/ink/output.d.ts +200 -0
  428. package/lib/types/ink/output.d.ts.map +1 -0
  429. package/lib/types/ink/output.js +663 -0
  430. package/lib/types/ink/parse-keypress.d.ts +132 -0
  431. package/lib/types/ink/parse-keypress.d.ts.map +1 -0
  432. package/lib/types/ink/parse-keypress.js +707 -0
  433. package/lib/types/ink/reconciler.d.ts +43 -0
  434. package/lib/types/ink/reconciler.d.ts.map +1 -0
  435. package/lib/types/ink/reconciler.js +413 -0
  436. package/lib/types/ink/render-border.d.ts +36 -0
  437. package/lib/types/ink/render-border.d.ts.map +1 -0
  438. package/lib/types/ink/render-border.js +143 -0
  439. package/lib/types/ink/render-node-to-output.d.ts +93 -0
  440. package/lib/types/ink/render-node-to-output.d.ts.map +1 -0
  441. package/lib/types/ink/render-node-to-output.js +1290 -0
  442. package/lib/types/ink/render-to-screen.d.ts +65 -0
  443. package/lib/types/ink/render-to-screen.d.ts.map +1 -0
  444. package/lib/types/ink/render-to-screen.js +189 -0
  445. package/lib/types/ink/renderer.d.ts +24 -0
  446. package/lib/types/ink/renderer.d.ts.map +1 -0
  447. package/lib/types/ink/renderer.js +136 -0
  448. package/lib/types/ink/root.d.ts +91 -0
  449. package/lib/types/ink/root.d.ts.map +1 -0
  450. package/lib/types/ink/root.js +93 -0
  451. package/lib/types/ink/screen.d.ts +443 -0
  452. package/lib/types/ink/screen.d.ts.map +1 -0
  453. package/lib/types/ink/screen.js +1295 -0
  454. package/lib/types/ink/searchHighlight.d.ts +25 -0
  455. package/lib/types/ink/searchHighlight.d.ts.map +1 -0
  456. package/lib/types/ink/searchHighlight.js +82 -0
  457. package/lib/types/ink/selection.d.ts +323 -0
  458. package/lib/types/ink/selection.d.ts.map +1 -0
  459. package/lib/types/ink/selection.js +881 -0
  460. package/lib/types/ink/squash-text-nodes.d.ts +30 -0
  461. package/lib/types/ink/squash-text-nodes.d.ts.map +1 -0
  462. package/lib/types/ink/squash-text-nodes.js +63 -0
  463. package/lib/types/ink/stringWidth.d.ts +11 -0
  464. package/lib/types/ink/stringWidth.d.ts.map +1 -0
  465. package/lib/types/ink/stringWidth.js +209 -0
  466. package/lib/types/ink/styles.d.ts +324 -0
  467. package/lib/types/ink/styles.d.ts.map +1 -0
  468. package/lib/types/ink/styles.js +307 -0
  469. package/lib/types/ink/supports-hyperlinks.d.ts +20 -0
  470. package/lib/types/ink/supports-hyperlinks.d.ts.map +1 -0
  471. package/lib/types/ink/supports-hyperlinks.js +44 -0
  472. package/lib/types/ink/tabstops.d.ts +9 -0
  473. package/lib/types/ink/tabstops.d.ts.map +1 -0
  474. package/lib/types/ink/tabstops.js +46 -0
  475. package/lib/types/ink/terminal-focus-state.d.ts +35 -0
  476. package/lib/types/ink/terminal-focus-state.d.ts.map +1 -0
  477. package/lib/types/ink/terminal-focus-state.js +57 -0
  478. package/lib/types/ink/terminal-querier.d.ts +161 -0
  479. package/lib/types/ink/terminal-querier.d.ts.map +1 -0
  480. package/lib/types/ink/terminal-querier.js +204 -0
  481. package/lib/types/ink/terminal.d.ts +80 -0
  482. package/lib/types/ink/terminal.d.ts.map +1 -0
  483. package/lib/types/ink/terminal.js +268 -0
  484. package/lib/types/ink/termio/ansi.d.ts +75 -0
  485. package/lib/types/ink/termio/ansi.d.ts.map +1 -0
  486. package/lib/types/ink/termio/ansi.js +79 -0
  487. package/lib/types/ink/termio/csi.d.ts +253 -0
  488. package/lib/types/ink/termio/csi.d.ts.map +1 -0
  489. package/lib/types/ink/termio/csi.js +347 -0
  490. package/lib/types/ink/termio/dec.d.ts +61 -0
  491. package/lib/types/ink/termio/dec.d.ts.map +1 -0
  492. package/lib/types/ink/termio/dec.js +76 -0
  493. package/lib/types/ink/termio/esc.d.ts +15 -0
  494. package/lib/types/ink/termio/esc.d.ts.map +1 -0
  495. package/lib/types/ink/termio/esc.js +57 -0
  496. package/lib/types/ink/termio/osc.d.ts +208 -0
  497. package/lib/types/ink/termio/osc.d.ts.map +1 -0
  498. package/lib/types/ink/termio/osc.js +466 -0
  499. package/lib/types/ink/termio/parser.d.ts +46 -0
  500. package/lib/types/ink/termio/parser.d.ts.map +1 -0
  501. package/lib/types/ink/termio/parser.js +366 -0
  502. package/lib/types/ink/termio/sgr.d.ts +17 -0
  503. package/lib/types/ink/termio/sgr.d.ts.map +1 -0
  504. package/lib/types/ink/termio/sgr.js +300 -0
  505. package/lib/types/ink/termio/tokenize.d.ts +50 -0
  506. package/lib/types/ink/termio/tokenize.d.ts.map +1 -0
  507. package/lib/types/ink/termio/tokenize.js +266 -0
  508. package/lib/types/ink/termio/types.d.ts +211 -0
  509. package/lib/types/ink/termio/types.d.ts.map +1 -0
  510. package/lib/types/ink/termio/types.js +68 -0
  511. package/lib/types/ink/termio.d.ts +25 -0
  512. package/lib/types/ink/termio.d.ts.map +1 -0
  513. package/lib/types/ink/termio.js +24 -0
  514. package/lib/types/ink/truncateToWidth.d.ts +7 -0
  515. package/lib/types/ink/truncateToWidth.d.ts.map +1 -0
  516. package/lib/types/ink/truncateToWidth.js +18 -0
  517. package/lib/types/ink/useTerminalNotification.d.ts +45 -0
  518. package/lib/types/ink/useTerminalNotification.d.ts.map +1 -0
  519. package/lib/types/ink/useTerminalNotification.js +70 -0
  520. package/lib/types/ink/warn.d.ts +7 -0
  521. package/lib/types/ink/warn.d.ts.map +1 -0
  522. package/lib/types/ink/warn.js +15 -0
  523. package/lib/types/ink/widest-line.d.ts +7 -0
  524. package/lib/types/ink/widest-line.d.ts.map +1 -0
  525. package/lib/types/ink/widest-line.js +19 -0
  526. package/lib/types/ink/wrap-text.d.ts +10 -0
  527. package/lib/types/ink/wrap-text.d.ts.map +1 -0
  528. package/lib/types/ink/wrap-text.js +61 -0
  529. package/lib/types/ink/wrapAnsi.d.ts +18 -0
  530. package/lib/types/ink/wrapAnsi.d.ts.map +1 -0
  531. package/lib/types/ink/wrapAnsi.js +16 -0
  532. package/lib/types/invariant.d.ts +20 -0
  533. package/lib/types/invariant.d.ts.map +1 -0
  534. package/lib/types/invariant.js +26 -0
  535. package/lib/types/modelPrefs.d.ts +37 -0
  536. package/lib/types/modelPrefs.d.ts.map +1 -0
  537. package/lib/types/modelPrefs.js +66 -0
  538. package/lib/types/modelRoute.d.ts +79 -0
  539. package/lib/types/modelRoute.d.ts.map +1 -0
  540. package/lib/types/modelRoute.js +96 -0
  541. package/lib/types/native-ts/yoga-layout/enums.d.ts +183 -0
  542. package/lib/types/native-ts/yoga-layout/enums.d.ts.map +1 -0
  543. package/lib/types/native-ts/yoga-layout/enums.js +150 -0
  544. package/lib/types/native-ts/yoga-layout/index.d.ts +788 -0
  545. package/lib/types/native-ts/yoga-layout/index.d.ts.map +1 -0
  546. package/lib/types/native-ts/yoga-layout/index.js +2571 -0
  547. package/lib/types/packaged-skills.d.ts +10 -0
  548. package/lib/types/packaged-skills.d.ts.map +1 -0
  549. package/lib/types/packaged-skills.js +63 -0
  550. package/lib/types/plugin.d.ts +33 -0
  551. package/lib/types/plugin.d.ts.map +1 -0
  552. package/lib/types/plugin.js +454 -0
  553. package/lib/types/presetPrefs.d.ts +29 -0
  554. package/lib/types/presetPrefs.d.ts.map +1 -0
  555. package/lib/types/presetPrefs.js +61 -0
  556. package/lib/types/presets.d.ts +110 -0
  557. package/lib/types/presets.d.ts.map +1 -0
  558. package/lib/types/presets.js +114 -0
  559. package/lib/types/questions.d.ts +97 -0
  560. package/lib/types/questions.d.ts.map +1 -0
  561. package/lib/types/questions.js +201 -0
  562. package/lib/types/screens/Chat.d.ts +23 -0
  563. package/lib/types/screens/Chat.d.ts.map +1 -0
  564. package/lib/types/screens/Chat.js +1263 -0
  565. package/lib/types/screens/StatusLine.d.ts +16 -0
  566. package/lib/types/screens/StatusLine.d.ts.map +1 -0
  567. package/lib/types/screens/StatusLine.js +105 -0
  568. package/lib/types/screens/StatusMetrics.d.ts +87 -0
  569. package/lib/types/screens/StatusMetrics.d.ts.map +1 -0
  570. package/lib/types/screens/StatusMetrics.js +252 -0
  571. package/lib/types/sessionHistory.d.ts +32 -0
  572. package/lib/types/sessionHistory.d.ts.map +1 -0
  573. package/lib/types/sessionHistory.js +87 -0
  574. package/lib/types/theme.d.ts +115 -0
  575. package/lib/types/theme.d.ts.map +1 -0
  576. package/lib/types/theme.js +300 -0
  577. package/lib/types/themePrefs.d.ts +28 -0
  578. package/lib/types/themePrefs.d.ts.map +1 -0
  579. package/lib/types/themePrefs.js +59 -0
  580. package/lib/types/ui.d.ts +28 -0
  581. package/lib/types/ui.d.ts.map +1 -0
  582. package/lib/types/ui.js +25 -0
  583. package/lib/types/update.d.ts +76 -0
  584. package/lib/types/update.d.ts.map +1 -0
  585. package/lib/types/update.js +203 -0
  586. package/lib/types/utils/clipboard.d.ts +43 -0
  587. package/lib/types/utils/clipboard.d.ts.map +1 -0
  588. package/lib/types/utils/clipboard.js +82 -0
  589. package/lib/types/utils/debug.d.ts +8 -0
  590. package/lib/types/utils/debug.d.ts.map +1 -0
  591. package/lib/types/utils/debug.js +12 -0
  592. package/lib/types/utils/earlyInput.d.ts +7 -0
  593. package/lib/types/utils/earlyInput.d.ts.map +1 -0
  594. package/lib/types/utils/earlyInput.js +6 -0
  595. package/lib/types/utils/env.d.ts +10 -0
  596. package/lib/types/utils/env.d.ts.map +1 -0
  597. package/lib/types/utils/env.js +9 -0
  598. package/lib/types/utils/envUtils.d.ts +8 -0
  599. package/lib/types/utils/envUtils.d.ts.map +1 -0
  600. package/lib/types/utils/envUtils.js +13 -0
  601. package/lib/types/utils/execFileNoThrow.d.ts +34 -0
  602. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -0
  603. package/lib/types/utils/execFileNoThrow.js +31 -0
  604. package/lib/types/utils/fullscreen.d.ts +8 -0
  605. package/lib/types/utils/fullscreen.d.ts.map +1 -0
  606. package/lib/types/utils/fullscreen.js +10 -0
  607. package/lib/types/utils/intl.d.ts +7 -0
  608. package/lib/types/utils/intl.d.ts.map +1 -0
  609. package/lib/types/utils/intl.js +9 -0
  610. package/lib/types/utils/loaded-context.d.ts +20 -0
  611. package/lib/types/utils/loaded-context.d.ts.map +1 -0
  612. package/lib/types/utils/loaded-context.js +36 -0
  613. package/lib/types/utils/log.d.ts +7 -0
  614. package/lib/types/utils/log.d.ts.map +1 -0
  615. package/lib/types/utils/log.js +9 -0
  616. package/lib/types/utils/mentions.d.ts +30 -0
  617. package/lib/types/utils/mentions.d.ts.map +1 -0
  618. package/lib/types/utils/mentions.js +72 -0
  619. package/lib/types/utils/narration.d.ts +11 -0
  620. package/lib/types/utils/narration.d.ts.map +1 -0
  621. package/lib/types/utils/narration.js +16 -0
  622. package/lib/types/utils/semver.d.ts +9 -0
  623. package/lib/types/utils/semver.d.ts.map +1 -0
  624. package/lib/types/utils/semver.js +24 -0
  625. package/lib/types/utils/sliceAnsi.d.ts +12 -0
  626. package/lib/types/utils/sliceAnsi.d.ts.map +1 -0
  627. package/lib/types/utils/sliceAnsi.js +79 -0
  628. package/lib/types/working-activity.d.ts +19 -0
  629. package/lib/types/working-activity.d.ts.map +1 -0
  630. package/lib/types/working-activity.js +18 -0
  631. package/package.json +141 -0
  632. package/skills/audit/SKILL.md +24 -0
  633. package/skills/bug/SKILL.md +27 -0
  634. package/skills/pr-comments/SKILL.md +20 -0
  635. package/skills/practice/SKILL.md +22 -0
  636. package/skills/release-notes/SKILL.md +25 -0
  637. package/skills/review/SKILL.md +25 -0
  638. package/skills/vuln-check/SKILL.md +25 -0
@@ -0,0 +1,1263 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { t, getLang, setLang, isLang, writeLangPref, subscribeLang } from '../i18n.js';
4
+ import { Box, Text, useInput, ScrollBox, useTheme } from '../ui.js';
5
+ import { POINTER } from '../cc/figures.js';
6
+ import { formatTokens } from '../cc/format.js';
7
+ import { AskUserQuestionPanel } from '../components/questions/AskUserQuestionPanel.js';
8
+ import { useSearchHighlight } from '../ink/hooks/use-search-highlight.js';
9
+ import { useTerminalTitle } from '../ink/hooks/use-terminal-title.js';
10
+ import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js';
11
+ import { useCopyOnSelect } from '../ink/hooks/use-copy-on-select.js';
12
+ import { useSelection } from '../ink/hooks/use-selection.js';
13
+ import { NoSelect } from '../ink/components/NoSelect.js';
14
+ import instances from '../ink/instances.js';
15
+ import { LogoHeader, MessageList } from '../components/MessageList.js';
16
+ import { PromptInput } from '../components/PromptInput.js';
17
+ import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
18
+ import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
19
+ import { StatusLine } from './StatusLine.js';
20
+ import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
21
+ import { ActivityLine, contextPressurePct } from '../components/ActivityLine.js';
22
+ import { ModelPicker } from '../components/ModelPicker.js';
23
+ import { ResumePicker } from '../components/ResumePicker.js';
24
+ import { ActivityPicker } from '../components/ActivityPicker.js';
25
+ import { PresetPicker } from '../components/PresetPicker.js';
26
+ import { ThemePicker, getThemeOptions } from '../components/ThemePicker.js';
27
+ import { FRAME_PRESETS, PRESET_NAMES } from '../components/activityFrames.js';
28
+ import { ThinkingToggle } from '../components/ThinkingToggle.js';
29
+ import { HistorySearchDialog } from '../components/HistorySearchDialog.js';
30
+ import { RewindPicker } from '../components/RewindPicker.js';
31
+ import { LoadingState } from '../components/design-system/LoadingState.js';
32
+ import { Pane } from '../components/design-system/Pane.js';
33
+ import { loadHistory } from '../history.js';
34
+ /** Row kinds the message-selection cursor can land on. */
35
+ const SELECTABLE_KINDS = new Set([
36
+ 'user',
37
+ 'assistant',
38
+ 'tool',
39
+ 'reasoning',
40
+ 'interrupt',
41
+ 'local',
42
+ 'local-output',
43
+ 'compact',
44
+ ]);
45
+ /** Shared empty list for mode-gated derived rows (stable reference, so
46
+ * downstream consumers never see a changing prop when the mode is off). */
47
+ const NO_ROWS = [];
48
+ /** `max` → `Max` (effort levels arrive lower-case from the adapter). */
49
+ function capitalize(text) {
50
+ return text.length === 0 ? text : text[0].toUpperCase() + text.slice(1);
51
+ }
52
+ /**
53
+ * CC's built-in skill commands, driven through the DSH skill system: each
54
+ * submits an activation prompt the model resolves via its skill catalog/load
55
+ * tools (the corresponding SKILL.md ships under ~/.dsh/skills with dsh-tui).
56
+ */
57
+ // i18n keys, not resolved strings: module scope evaluates before apply()'s
58
+ // setLang, so t() must run at the call site to follow the active language.
59
+ const SKILL_PROMPTS = {
60
+ audit: 'skill-audit-prompt',
61
+ bug: 'skill-bug-prompt',
62
+ practice: 'skill-practice-prompt',
63
+ review: 'skill-review-prompt',
64
+ pr_comments: 'skill-pr-comments-prompt',
65
+ 'release-notes': 'skill-release-notes-prompt',
66
+ 'vuln-check': 'skill-vuln-check-prompt',
67
+ };
68
+ /** Terminal-title spinner frames (CC's TITLE_ANIMATION_FRAMES). */
69
+ const TITLE_ANIMATION_FRAMES = ['⠂', '⠐'];
70
+ /** Searchable transcript text for one row (`/` incsearch, CC semantics:
71
+ * user text, assistant text, thinking, tool args/results, local output). */
72
+ function searchableText(row) {
73
+ switch (row.kind) {
74
+ case 'tool':
75
+ return row.tool
76
+ ? `${row.tool.name} ${row.tool.argsText} ${row.tool.resultText ?? ''} ${row.tool.errorText ?? ''}`
77
+ : '';
78
+ default:
79
+ return row.text;
80
+ }
81
+ }
82
+ /**
83
+ * Main chat screen in the Claude Code layout: a scrollable transcript
84
+ * (with the current turn's prompt pinned above the viewport while scrolled
85
+ * up), transient notifications, the working spinner, the bordered prompt
86
+ * input (with slash-command overlay) and the status line pinned at the
87
+ * bottom.
88
+ *
89
+ * Ctrl+O toggles expanded detail globally; Shift+↑ enters message-selection
90
+ * mode (↑/↓ move, Enter expands the selected row, Esc exits); Ctrl+C
91
+ * interrupts the running turn, or (when idle) asks for a second Ctrl+C to
92
+ * exit; Enter while scrolled up jumps back to the bottom.
93
+ */
94
+ export function Chat({ channel, questionStore, onExit, onUpdate, }) {
95
+ // Re-render whenever the channel mutates; rows/status are read fresh below.
96
+ React.useSyncExternalStore(channel.subscribe, () => channel.version);
97
+ // Re-render on language switches so the whole UI hot-swaps its strings.
98
+ React.useSyncExternalStore(subscribeLang, getLang);
99
+ // The pending ask-user-question (DSH user-interaction seam): the model's
100
+ // `ask_user_question` tool parks here until the panel is answered.
101
+ const questionSnapshot = React.useSyncExternalStore(listener => questionStore.subscribe(listener), () => questionStore.getSnapshot());
102
+ // When a questionnaire batch completes, fold a Q&A summary into the
103
+ // transcript (the tool card itself is hidden from the message list).
104
+ const questionOpenRef = React.useRef(questionSnapshot !== null);
105
+ React.useEffect(() => {
106
+ const wasOpen = questionOpenRef.current;
107
+ questionOpenRef.current = questionSnapshot !== null;
108
+ if (wasOpen && questionSnapshot === null) {
109
+ for (const summary of questionStore.takeSummaries()) {
110
+ channel.pushLocal(summary.title, summary.lines);
111
+ }
112
+ }
113
+ }, [channel, questionSnapshot, questionStore]);
114
+ const [expanded, setExpanded] = React.useState(false);
115
+ const [helpOpen, setHelpOpen] = React.useState(false);
116
+ const [handle, setHandle] = React.useState(null);
117
+ const [selectionActive, setSelectionActive] = React.useState(false);
118
+ const [selectedId, setSelectedId] = React.useState(null);
119
+ const [expandedRows, setExpandedRows] = React.useState(() => new Set());
120
+ const [modelPickerOpen, setModelPickerOpen] = React.useState(false);
121
+ const [models, setModels] = React.useState([]);
122
+ const [modelIndex, setModelIndex] = React.useState(0);
123
+ const [resumePickerOpen, setResumePickerOpen] = React.useState(false);
124
+ const [resumeSessions, setResumeSessions] = React.useState([]);
125
+ const [resumeIndex, setResumeIndex] = React.useState(0);
126
+ /** `/activity` indicator picker (pi extension's interactive select). */
127
+ const [activityPickerOpen, setActivityPickerOpen] = React.useState(false);
128
+ const [activityIndex, setActivityIndex] = React.useState(0);
129
+ /** `/preset` agent-preset picker (issue #8): roster list loads async. */
130
+ const [presetPickerOpen, setPresetPickerOpen] = React.useState(false);
131
+ const [presetOptions, setPresetOptions] = React.useState([]);
132
+ const [presetIndex, setPresetIndex] = React.useState(0);
133
+ /** `/theme` color-theme picker (built-ins + ~/.dsh-cc/themes user themes). */
134
+ const [themePickerOpen, setThemePickerOpen] = React.useState(false);
135
+ const [themeIndex, setThemeIndex] = React.useState(0);
136
+ const [themeName, setTheme] = useTheme();
137
+ const [showAllMessages, setShowAllMessages] = React.useState(false);
138
+ const [thinkingVisible, setThinkingVisible] = React.useState(true);
139
+ const [thinkingOpen, setThinkingOpen] = React.useState(false);
140
+ const [thinkingFocus, setThinkingFocus] = React.useState(0);
141
+ /** Mid-conversation toggle waiting for Enter confirmation (CC semantics). */
142
+ const [thinkingConfirm, setThinkingConfirm] = React.useState(null);
143
+ /** ctrl+r history search dialog (ported from CC's HistorySearchDialog). */
144
+ const [historyOpen, setHistoryOpen] = React.useState(false);
145
+ const [historyQuery, setHistoryQuery] = React.useState('');
146
+ const [historyCursor, setHistoryCursor] = React.useState(0);
147
+ const [historyFocus, setHistoryFocus] = React.useState(0);
148
+ const [historyEntries, setHistoryEntries] = React.useState([]);
149
+ const [historyFill, setHistoryFill] = React.useState(null);
150
+ /** Double-Esc rewind picker (CC rewind): open state + focused row + confirm. */
151
+ const [rewindOpen, setRewindOpen] = React.useState(false);
152
+ const [rewindIndex, setRewindIndex] = React.useState(0);
153
+ const [rewindConfirm, setRewindConfirm] = React.useState(null);
154
+ /** Startup context panel: expanded by header click or Ctrl+T. */
155
+ const [loadedContextOpen, setLoadedContextOpen] = React.useState(false);
156
+ /** `/` transcript search (less-style incsearch, ported from CC's REPL). */
157
+ const [searchOpen, setSearchOpen] = React.useState(false);
158
+ const [searchQuery, setSearchQuery] = React.useState('');
159
+ const [searchCursor, setSearchCursor] = React.useState(0);
160
+ const [searchCount, setSearchCount] = React.useState(0);
161
+ const [searchCurrent, setSearchCurrent] = React.useState(0);
162
+ const searchAnchorRef = React.useRef(0);
163
+ const rowRefsRef = React.useRef(new Map());
164
+ const { setQuery: setHighlight } = useSearchHighlight();
165
+ // Sticky (pinned-to-bottom) scroll state, subscribed imperatively so
166
+ // wheel events don't re-render React — only the header/pill flip.
167
+ const isSticky = React.useSyncExternalStore(cb => (handle ? handle.subscribe(cb) : () => { }), () => (handle ? handle.isSticky() : true));
168
+ // "N new messages" pill: new rows whose top edge is still BELOW the
169
+ // viewport bottom. The count decrements as the user scrolls down through
170
+ // them and hits 0 (pill hides) once every new row has been on screen —
171
+ // no need to wait for the exact-bottom sticky restore. Chat anchors the
172
+ // "seen up to" point by ROW ID (stable across loadOlder prepends, unlike
173
+ // a rows.length index); MessageList owns the row offsets, so it computes
174
+ // how many rows past that anchor lie below the viewport and reports it.
175
+ const lastSeenRowIdRef = React.useRef(null);
176
+ const [unseenCount, setUnseenCount] = React.useState(0);
177
+ React.useEffect(() => {
178
+ if (isSticky) {
179
+ lastSeenRowIdRef.current = null;
180
+ setUnseenCount(0);
181
+ }
182
+ else if (lastSeenRowIdRef.current === null) {
183
+ lastSeenRowIdRef.current = channel.rows.length
184
+ ? channel.rows[channel.rows.length - 1].id
185
+ : -1;
186
+ }
187
+ }, [isSticky, channel.rows]);
188
+ const showPill = !isSticky && unseenCount > 0;
189
+ // Idle Ctrl+C: first press arms an exit, second press exits (CC's
190
+ // double-press semantics, simplified). Under Windows ConPTY the key
191
+ // arrives as stdin data (key.ctrl && input === 'c') — the useInput
192
+ // branch below is the only path; SIGINT is not emitted.
193
+ const exitPendingRef = React.useRef(false);
194
+ const exitTimerRef = React.useRef(null);
195
+ // Live view into the prompt's text for the Ctrl+C rule (clears text when
196
+ // non-empty; the double-press exit only arms on an empty input).
197
+ const promptControllerRef = React.useRef(null);
198
+ const requestExit = () => {
199
+ if (exitPendingRef.current) {
200
+ onExit();
201
+ }
202
+ else {
203
+ exitPendingRef.current = true;
204
+ channel.notify('Press Ctrl+C again to exit');
205
+ exitTimerRef.current = setTimeout(() => {
206
+ exitPendingRef.current = false;
207
+ }, 3000);
208
+ }
209
+ };
210
+ React.useEffect(() => {
211
+ return () => {
212
+ if (exitTimerRef.current)
213
+ clearTimeout(exitTimerRef.current);
214
+ };
215
+ }, []);
216
+ // Spinner timing refs, fed from channel state each render (the spinner
217
+ // only mounts while working, so values are stable for the mount).
218
+ const responseLengthRef = React.useRef(0);
219
+ const loadingStartTimeRef = React.useRef(0);
220
+ const totalPausedMsRef = React.useRef(0);
221
+ const pauseStartTimeRef = React.useRef(null);
222
+ responseLengthRef.current = channel.responseChars;
223
+ loadingStartTimeRef.current = channel.turnStart;
224
+ const thinkingStatus = useThinkingStatus(channel.spinnerMode === 'thinking');
225
+ // Terminal tab title (ported from CC's AnimatedTerminalTitle): the session
226
+ // title when set, else "dsh-TUI"; a `⠂/⠐` spinner prefix while a turn is
227
+ // working (960ms cadence, only while the terminal is focused), a static
228
+ // `✦` otherwise. dsh-TUI brands the idle prefix with the DeepSeek whale.
229
+ const [titleFrame, setTitleFrame] = React.useState(0);
230
+ const terminalFocused = useTerminalFocus();
231
+ // Mouse text selection auto-copy (CC's copy-on-select): active only in
232
+ // fullscreen (<AlternateScreen> supplies mouse tracking); a no-op
233
+ // subscription in inline mode, where selection belongs to the terminal.
234
+ // The copy clears the highlight and posts a transient notification.
235
+ useCopyOnSelect(text => channel.notify(t('copied-chars', { n: text.length }), { timeoutMs: 1500 }));
236
+ const { clearSelection: clearMouseSelection, hasSelection: hasMouseSelection } = useSelection();
237
+ React.useEffect(() => {
238
+ if (!channel.working || !terminalFocused)
239
+ return;
240
+ const interval = setInterval(() => {
241
+ setTitleFrame(f => (f + 1) % TITLE_ANIMATION_FRAMES.length);
242
+ }, 960);
243
+ return () => { clearInterval(interval); };
244
+ }, [channel.working, terminalFocused]);
245
+ const titlePrefix = channel.working
246
+ ? (TITLE_ANIMATION_FRAMES[titleFrame] ?? '✦')
247
+ : '✦';
248
+ useTerminalTitle(`${titlePrefix} 🐋 ${channel.sessionTitle}`);
249
+ /**
250
+ * Dispatch a slash command; false lets the input flow to the model.
251
+ * Built-in names run the local switch; anything registered by a DSH
252
+ * plugin (plan/goal/…) dispatches through the command registry, whose
253
+ * result text lands as a notification. `rawInput` carries the text after
254
+ * the command name (`/plan off` → ` off`).
255
+ */
256
+ const runCommand = (name, rawInput = '') => {
257
+ switch (name) {
258
+ case 'activity': {
259
+ // Ported from the pi working-activity extension: bare `/activity`
260
+ // opens the interactive indicator picker; `/activity frames <name>`
261
+ // switches directly; `/activity frames` lists presets; `/activity
262
+ // status` shows the current choice. The choice persists to
263
+ // ~/.dsh-cc/working-activity.json and survives restarts.
264
+ const parts = rawInput.trim().split(/\s+/).filter(Boolean);
265
+ if (parts[0] === 'status') {
266
+ setHelpOpen(false);
267
+ channel.pushLocal('/activity', [
268
+ t('activity-current-preset', { name: channel.activityFrames ?? 'claude' }),
269
+ t('activity-switch-hint'),
270
+ t('activity-persist-hint'),
271
+ ]);
272
+ return true;
273
+ }
274
+ if (parts[0] === 'frames') {
275
+ setHelpOpen(false);
276
+ if (parts[1]) {
277
+ channel.setActivityFrames(parts[1].toLowerCase());
278
+ return true;
279
+ }
280
+ const current = channel.activityFrames;
281
+ channel.pushLocal('/activity', [
282
+ t('activity-current-direct', { name: current ?? 'claude' }),
283
+ ...PRESET_NAMES.map(name => `${name.padEnd(10)} ${name === 'random' ? t('activity-random-each') : FRAME_PRESETS[name].frames.slice(0, 5).join(' ')}${name === current ? t('activity-current-marker') : ''}`),
284
+ ]);
285
+ return true;
286
+ }
287
+ if (parts.length > 0) {
288
+ channel.notify(t('activity-usage'), { color: 'warning' });
289
+ return true;
290
+ }
291
+ setHelpOpen(false);
292
+ setActivityIndex(Math.max(0, PRESET_NAMES.indexOf(channel.activityFrames ?? 'random')));
293
+ setActivityPickerOpen(true);
294
+ return true;
295
+ }
296
+ case 'preset': {
297
+ // issue #8: bare `/preset` opens the roster picker (standard/code/
298
+ // minimal/cordis plus any user-authored presets); `/preset <id>`
299
+ // switches directly; `/preset status` shows the current choice. A
300
+ // blank session swaps composition in place (official blank-only
301
+ // rule); a started session is locked and the choice persists as the
302
+ // default for future sessions (~/.dsh-cc/agent-preset.json).
303
+ const parts = rawInput.trim().split(/\s+/).filter(Boolean);
304
+ if (parts[0] === 'status') {
305
+ setHelpOpen(false);
306
+ channel.pushLocal('/preset', [
307
+ t('preset-current', { name: channel.agentPreset ?? t('preset-roster-missing') }),
308
+ t('preset-switch-hint'),
309
+ t('preset-persist-hint'),
310
+ t('preset-lock-hint'),
311
+ ]);
312
+ return true;
313
+ }
314
+ if (parts.length > 0) {
315
+ setHelpOpen(false);
316
+ void channel.switchPreset(parts[0]);
317
+ return true;
318
+ }
319
+ setHelpOpen(false);
320
+ setPresetPickerOpen(true);
321
+ void channel.listPresets().then((list) => {
322
+ if (list.length === 0) {
323
+ setPresetPickerOpen(false);
324
+ channel.notify(t('preset-roster-unmounted'), { color: 'warning' });
325
+ return;
326
+ }
327
+ setPresetOptions(list);
328
+ const index = list.findIndex(preset => preset.id === channel.agentPreset);
329
+ setPresetIndex(index >= 0 ? index : 0);
330
+ });
331
+ return true;
332
+ }
333
+ case 'lang': {
334
+ // `/lang` shows the current UI language, `/lang en|zh` switches
335
+ // (hot-swap, persisted to ~/.dsh-cc/lang.json). Precedence on next
336
+ // launch: CC_TUI_LANG > cordis.yml `lang` > the persisted choice.
337
+ const parts = rawInput.trim().split(/\s+/).filter(Boolean);
338
+ if (parts[0] === 'status') {
339
+ setHelpOpen(false);
340
+ channel.pushLocal('/lang', [
341
+ t('lang-current', { lang: getLang() }),
342
+ t('lang-switch-hint'),
343
+ t('lang-persist-hint'),
344
+ ]);
345
+ return true;
346
+ }
347
+ if (parts.length > 0) {
348
+ setHelpOpen(false);
349
+ if (isLang(parts[0])) {
350
+ const ok = writeLangPref(parts[0]);
351
+ setLang(parts[0]);
352
+ channel.notify(ok ? t('lang-switched', { lang: parts[0] }) : t('lang-switch-failed', { lang: parts[0] }), { color: ok ? 'success' : 'error' });
353
+ }
354
+ else {
355
+ channel.notify(t('lang-unknown', { lang: parts[0] }), { color: 'error' });
356
+ }
357
+ return true;
358
+ }
359
+ setHelpOpen(false);
360
+ channel.pushLocal('/lang', [
361
+ t('lang-current', { lang: getLang() }),
362
+ t('lang-switch-hint'),
363
+ t('lang-persist-hint'),
364
+ ]);
365
+ return true;
366
+ }
367
+ case 'theme': {
368
+ // Bare `/theme` opens the interactive color picker (built-in
369
+ // palettes + user themes from ~/.dsh-cc/themes); `/theme <name>`
370
+ // switches directly; `/theme status` shows the current choice.
371
+ // Selection persists to ~/.dsh-cc/theme.json and hot swaps via the
372
+ // ThemeProvider setter (CC_TUI_THEME still wins on next launch).
373
+ const parts = rawInput.trim().split(/\s+/).filter(Boolean);
374
+ if (parts[0] === 'status') {
375
+ setHelpOpen(false);
376
+ channel.pushLocal('/theme', [
377
+ t('theme-current', { name: themeName }),
378
+ t('theme-switch-hint'),
379
+ t('theme-persist-hint'),
380
+ t('theme-custom-hint'),
381
+ ]);
382
+ return true;
383
+ }
384
+ if (parts.length > 0) {
385
+ setHelpOpen(false);
386
+ const ok = setTheme(parts[0]);
387
+ channel.notify(ok ? t('theme-switched-saved', { name: parts[0] }) : t('theme-unknown', { name: parts[0] }), { color: ok ? 'success' : 'error' });
388
+ return true;
389
+ }
390
+ setHelpOpen(false);
391
+ setThemeIndex(Math.max(0, getThemeOptions().findIndex(option => option.value === themeName)));
392
+ setThemePickerOpen(true);
393
+ return true;
394
+ }
395
+ case 'new': {
396
+ // One-shot `/new` (issue #25): the old session stays persisted and
397
+ // is recoverable via /resume, so discarding the live view is
398
+ // non-destructive — no CC-style "press /new again" confirmation.
399
+ setHelpOpen(false);
400
+ void channel.newSession().then((ok) => {
401
+ if (ok)
402
+ channel.notify('New session started');
403
+ });
404
+ return true;
405
+ }
406
+ case 'clear':
407
+ channel.clear();
408
+ // channel.clear() resets row ids to 0; stale expanded/selection
409
+ // state would mis-highlight fresh rows (known-limitation fix).
410
+ setExpandedRows(new Set());
411
+ setSelectedId(null);
412
+ setSelectionActive(false);
413
+ return true;
414
+ case 'compact':
415
+ channel.compact();
416
+ return true;
417
+ case 'help':
418
+ setHelpOpen(true);
419
+ return true;
420
+ case 'model':
421
+ setHelpOpen(false);
422
+ setModelPickerOpen(true);
423
+ void channel.listModels().then((list) => {
424
+ setModels(list);
425
+ const index = list.findIndex(model => model.provider === channel.provider && model.id === channel.model);
426
+ setModelIndex(index >= 0 ? index : 0);
427
+ });
428
+ return true;
429
+ case 'thinking':
430
+ setHelpOpen(false);
431
+ setThinkingOpen(true);
432
+ setThinkingFocus(thinkingVisible ? 0 : 1);
433
+ return true;
434
+ case 'tokens': {
435
+ const usage = `Tokens: ${formatTokens(channel.tokens.input)} in · ${formatTokens(channel.tokens.output)} out`;
436
+ if (channel.contextWindow === undefined) {
437
+ channel.notify(usage);
438
+ }
439
+ else {
440
+ const percent = Math.max(0, Math.min(100, Math.round((channel.tokens.input / channel.contextWindow) * 100)));
441
+ channel.notify(`${usage} · ${percent}% of context`);
442
+ }
443
+ return true;
444
+ }
445
+ case 'resume': {
446
+ setHelpOpen(false);
447
+ void (async () => {
448
+ const sessions = await channel.listSessions();
449
+ // The current session cannot be resumed into itself (agents.resume
450
+ // rejects a live session), so it is excluded from the picker —
451
+ // otherwise the fresh empty session of this launch always tops the
452
+ // list as an unopenable row.
453
+ const pickable = sessions.filter(session => session.id !== channel.agentId);
454
+ setResumeSessions(pickable);
455
+ if (pickable.length === 0) {
456
+ channel.notify(t('resume-none-in-cwd'));
457
+ return;
458
+ }
459
+ setResumePickerOpen(true);
460
+ setResumeIndex(0);
461
+ })();
462
+ return true;
463
+ }
464
+ case 'rewind':
465
+ // Same picker as PromptInput's double-Esc on an empty input (CC
466
+ // rewind); `openRewind` notifies when there is nothing to rewind.
467
+ setHelpOpen(false);
468
+ openRewind();
469
+ return true;
470
+ case 'exit':
471
+ onExit();
472
+ return true;
473
+ case 'status': {
474
+ const usage = channel.lastUsage;
475
+ const pct = channel.contextWindow === undefined
476
+ ? undefined
477
+ : Math.max(0, Math.min(100, Math.round((channel.tokens.input / channel.contextWindow) * 100)));
478
+ const lines = [
479
+ `${t('status-model', { model: channel.model })}${channel.reasoningEffort ? ` · ${capitalize(channel.reasoningEffort)} effort` : ''}`,
480
+ `${t('status-state', { state: channel.working ? t('status-working') : t('status-idle') })}`,
481
+ `${t('status-session', { id: channel.agentId })}`,
482
+ `${t('status-dir', { cwd: channel.cwd })}${channel.gitBranch ? ` · ${channel.gitBranch}` : ''}`,
483
+ `Tokens ${formatTokens(channel.tokens.input)} in → ${formatTokens(channel.tokens.output)} out`,
484
+ ];
485
+ if (usage !== undefined) {
486
+ const total = usage.input + usage.cacheRead + usage.cacheWrite;
487
+ const rate = total > 0 ? ((usage.cacheRead / total) * 100).toFixed(1) : '0.0';
488
+ lines.push(t('cost-cache-rate', { rate, read: formatTokens(usage.cacheRead), write: formatTokens(usage.cacheWrite) }));
489
+ }
490
+ if (pct !== undefined)
491
+ lines.push(t('cost-context', { pct }));
492
+ if (channel.sessionTitle)
493
+ lines.push(t('status-title', { title: channel.sessionTitle }));
494
+ setHelpOpen(false);
495
+ channel.pushLocal('/status', lines);
496
+ return true;
497
+ }
498
+ case 'cost': {
499
+ const usage = channel.lastUsage;
500
+ const lines = [
501
+ `Tokens ${formatTokens(channel.tokens.input)} in → ${formatTokens(channel.tokens.output)} out`,
502
+ ];
503
+ if (usage !== undefined) {
504
+ const total = usage.input + usage.cacheRead + usage.cacheWrite;
505
+ const rate = total > 0 ? ((usage.cacheRead / total) * 100).toFixed(1) : '0.0';
506
+ lines.push(t('cost-cache-hit-rate', { rate, read: formatTokens(usage.cacheRead), write: formatTokens(usage.cacheWrite) }));
507
+ }
508
+ lines.push(t('cost-note'));
509
+ setHelpOpen(false);
510
+ channel.pushLocal('/cost', lines);
511
+ return true;
512
+ }
513
+ case 'config': {
514
+ const userHome = process.env.USERPROFILE ?? '';
515
+ const lines = [
516
+ t('doctor-example-config', { path: 'dsh --profile dsh-tui' }),
517
+ t('doctor-user-config', { path: `${userHome}/.dsh/profiles/dsh-tui/cordis.patch.yml` }),
518
+ '',
519
+ t('doctor-launch-hint'),
520
+ t('doctor-route-hint'),
521
+ ];
522
+ setHelpOpen(false);
523
+ channel.pushLocal('/config', lines);
524
+ return true;
525
+ }
526
+ case 'doctor':
527
+ setHelpOpen(false);
528
+ channel.pushLocal('/doctor', channel.doctorInfo());
529
+ return true;
530
+ case 'export': {
531
+ const target = channel.exportSession();
532
+ channel.notify(target === null
533
+ ? t('export-failed')
534
+ : t('export-saved', { target }), target === null ? { color: 'error', timeoutMs: 8000 } : { timeoutMs: 8000 });
535
+ return true;
536
+ }
537
+ case 'init': {
538
+ const result = channel.initWorkspace();
539
+ if (result === null)
540
+ channel.notify(t('agentsmd-create-failed'), { color: 'error' });
541
+ else if (result === 'exists')
542
+ channel.notify(t('agentsmd-exists'));
543
+ else
544
+ channel.notify(t('agentsmd-created', { result }));
545
+ return true;
546
+ }
547
+ case 'agents':
548
+ setHelpOpen(false);
549
+ void channel.listSubagents().then((lines) => {
550
+ channel.pushLocal('/agents', lines);
551
+ });
552
+ return true;
553
+ case 'login': {
554
+ const key = process.env.DEEPSEEK_API_KEY;
555
+ const lines = [
556
+ `${t('login-api-key', { key: key ? key.slice(0, 6) + '…' + key.slice(-4) : t('login-key-missing') })}`,
557
+ `${t('login-base-url', { url: process.env.DEEPSEEK_BASE_URL ?? t('login-official-endpoint') })}`,
558
+ t('login-source-hint'),
559
+ ];
560
+ setHelpOpen(false);
561
+ channel.pushLocal('/login', lines);
562
+ return true;
563
+ }
564
+ case 'logout':
565
+ channel.notify(t('login-logout-hint'));
566
+ return true;
567
+ case 'permissions':
568
+ setHelpOpen(false);
569
+ channel.pushLocal('/permissions', [
570
+ t('permissions-policy-hint'),
571
+ t('permissions-approval-hint'),
572
+ ]);
573
+ return true;
574
+ case 'add-dir':
575
+ setHelpOpen(false);
576
+ channel.pushLocal('/add-dir', [
577
+ t('permissions-root-hint', { cwd: channel.cwd }),
578
+ t('permissions-path-hint'),
579
+ ]);
580
+ return true;
581
+ case 'hooks':
582
+ setHelpOpen(false);
583
+ channel.pushLocal('/hooks', [
584
+ t('hooks-not-mounted'),
585
+ t('hooks-mount-hint'),
586
+ ]);
587
+ return true;
588
+ case 'mcp':
589
+ setHelpOpen(false);
590
+ channel.pushLocal('/mcp', channel.mcpStatus());
591
+ return true;
592
+ case 'memory':
593
+ setHelpOpen(false);
594
+ channel.pushLocal('/memory', [
595
+ t('memory-none'),
596
+ t('memory-hint'),
597
+ ]);
598
+ return true;
599
+ case 'update':
600
+ setHelpOpen(false);
601
+ if (onUpdate === undefined) {
602
+ channel.notify(t('update-unavailable'), { color: 'warning' });
603
+ }
604
+ else if (channel.working) {
605
+ channel.notify(t('update-working'), { color: 'warning' });
606
+ }
607
+ else {
608
+ channel.notify(t('update-starting'));
609
+ onUpdate();
610
+ }
611
+ return true;
612
+ case 'vim':
613
+ channel.notify(t('vim-not-implemented'));
614
+ return true;
615
+ case 'terminal-setup':
616
+ setHelpOpen(false);
617
+ channel.pushLocal('/terminal-setup', [
618
+ t('terminal-setup-hint'),
619
+ t('terminal-paste-hint'),
620
+ ]);
621
+ return true;
622
+ case 'connect':
623
+ setHelpOpen(false);
624
+ channel.pushLocal('/connect', [t('connect-none')]);
625
+ return true;
626
+ case 'audit':
627
+ case 'bug':
628
+ case 'practice':
629
+ case 'review':
630
+ case 'pr_comments':
631
+ case 'release-notes':
632
+ case 'vuln-check': {
633
+ // CC's skill commands: drive the DSH skill system by sending the
634
+ // activation prompt to the model (it loads the skill via its skill
635
+ // catalog/load tools when the SKILL.md ships in ~/.dsh/skills).
636
+ const key = SKILL_PROMPTS[name];
637
+ if (key)
638
+ channel.submit(t(key));
639
+ return true;
640
+ }
641
+ default: {
642
+ // Plugin-registered command (DSH command registry): dispatch through
643
+ // the channel, whose execution logs command/run + command/done (the
644
+ // plan-mode projection folds those records, so /plan state stays
645
+ // consistent). Unknown names fall through to the model.
646
+ const external = channel.commandList.find(command => command.external && command.name === name);
647
+ if (external) {
648
+ setHelpOpen(false);
649
+ void channel.runExternalCommand(name, rawInput).then((text) => {
650
+ if (text !== undefined && text !== '') {
651
+ channel.notify(text);
652
+ }
653
+ else if (text === undefined) {
654
+ channel.notify(`/${name}: no such command`, { color: 'error' });
655
+ }
656
+ });
657
+ return true;
658
+ }
659
+ return false;
660
+ }
661
+ }
662
+ };
663
+ // === Message-selection mode (CC's Shift+↑ message actions) ===
664
+ // NOTE: rows is a live in-place array on the channel (no new reference per
665
+ // update), so derived lists must be computed per render — a useMemo keyed
666
+ // on `channel.rows` would freeze at the first empty snapshot forever.
667
+ // Both lists only feed their respective modes; computing them
668
+ // unconditionally cost an O(rows) scan + array allocation per render
669
+ // (every streamed chunk), so they are gated on the consuming mode.
670
+ const selectableRows = selectionActive
671
+ ? channel.rows.filter(row => SELECTABLE_KINDS.has(row.kind))
672
+ : NO_ROWS;
673
+ // ctrl+r history search: substring match on the query, newest first.
674
+ const historyMatches = React.useMemo(() => {
675
+ const q = historyQuery.trim().toLowerCase();
676
+ return q ? historyEntries.filter(e => e.text.toLowerCase().includes(q)) : historyEntries;
677
+ }, [historyEntries, historyQuery]);
678
+ // Double-Esc rewind: the user's own messages, newest first (CC lists the
679
+ // selectable user turns; steering side-questions are excluded). Computed
680
+ // per render while the picker is open — `channel.rows` is a live in-place
681
+ // array (see selectableRows).
682
+ const rewindRows = rewindOpen
683
+ ? channel.rows
684
+ .filter(row => row.kind === 'user' && row.label === undefined)
685
+ .reverse()
686
+ : NO_ROWS;
687
+ /** Open the rewind picker (from PromptInput's double-Esc on an empty input). */
688
+ const openRewind = () => {
689
+ // rewindOpen is still false this render, so rewindRows is empty — scan
690
+ // directly instead of reading the gated list.
691
+ const candidates = channel.rows
692
+ .filter(row => row.kind === 'user' && row.label === undefined)
693
+ .reverse();
694
+ if (candidates.length === 0) {
695
+ channel.notify('Nothing to rewind yet');
696
+ return;
697
+ }
698
+ setRewindIndex(0);
699
+ setRewindConfirm(null);
700
+ setRewindOpen(true);
701
+ };
702
+ /** Execute the confirmed rewind; the message comes back into the input. */
703
+ const performRewind = async (row) => {
704
+ const text = await channel.rewindTo(row);
705
+ if (text !== null) {
706
+ // CC puts the restored message back in the prompt for re-editing.
707
+ setHistoryFill(text);
708
+ channel.notify('Rewound — edit and press Enter to resend');
709
+ }
710
+ };
711
+ // Row seeking under layout virtualization: a mounted row seeks directly;
712
+ // an unmounted one is force-mounted first, then sought by the completion
713
+ // effect below once its ref lands.
714
+ const [forceMountRowId, setForceMountRowId] = React.useState(null);
715
+ const seekRow = (rowId) => {
716
+ const el = rowRefsRef.current.get(rowId);
717
+ if (el) {
718
+ handle?.scrollToElement(el);
719
+ return;
720
+ }
721
+ setForceMountRowId(rowId);
722
+ };
723
+ React.useLayoutEffect(() => {
724
+ if (forceMountRowId === null)
725
+ return;
726
+ const el = rowRefsRef.current.get(forceMountRowId);
727
+ if (el) {
728
+ handle?.scrollToElement(el);
729
+ setForceMountRowId(null);
730
+ }
731
+ });
732
+ // `/` transcript search: rows whose searchable text contains the query.
733
+ // Computed per render — `channel.rows` is a live in-place array (see
734
+ // selectableRows); a useMemo would freeze the match list at mount.
735
+ const searchMatches = (() => {
736
+ const q = searchQuery.toLowerCase();
737
+ if (!q)
738
+ return [];
739
+ return channel.rows
740
+ .map((row, index) => ({ row, index, text: searchableText(row).toLowerCase() }))
741
+ .filter(m => m.text.includes(q));
742
+ })();
743
+ // Incsearch: highlight all matches (screen-space overlay) and keep the
744
+ // current match row in view as the query changes (CC semantics).
745
+ React.useEffect(() => {
746
+ if (!searchOpen)
747
+ return;
748
+ setHighlight(searchQuery);
749
+ const count = searchMatches.length;
750
+ setSearchCount(count);
751
+ const current = Math.min(searchCurrent, Math.max(0, count - 1));
752
+ setSearchCurrent(current);
753
+ const target = searchMatches[current];
754
+ // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty/filtered list
755
+ if (target) {
756
+ seekRow(target.row.id);
757
+ }
758
+ }, [searchQuery, searchOpen]);
759
+ // n/N navigation: move the current match into view.
760
+ React.useEffect(() => {
761
+ if (!searchOpen)
762
+ return;
763
+ const target = searchMatches[searchCurrent];
764
+ // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty/filtered list
765
+ if (target) {
766
+ seekRow(target.row.id);
767
+ }
768
+ }, [searchCurrent]);
769
+ const enterSelection = () => {
770
+ setSelectionActive(true);
771
+ const last = selectableRows[selectableRows.length - 1];
772
+ // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: empty selectable list
773
+ setSelectedId(last ? last.id : null);
774
+ };
775
+ const moveSelection = (delta) => {
776
+ if (selectedId === null)
777
+ return;
778
+ const index = selectableRows.findIndex(row => row.id === selectedId);
779
+ if (index < 0)
780
+ return;
781
+ const next = selectableRows[index + delta];
782
+ // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index
783
+ if (next)
784
+ setSelectedId(next.id);
785
+ };
786
+ // useCallback: these feed MessageList → MemoRow's shallow compare; fresh
787
+ // closures each render would defeat every row's memo.
788
+ const toggleRowExpanded = React.useCallback((rowId) => {
789
+ setExpandedRows((previous) => {
790
+ const next = new Set(previous);
791
+ if (next.has(rowId))
792
+ next.delete(rowId);
793
+ else
794
+ next.add(rowId);
795
+ return next;
796
+ });
797
+ }, []);
798
+ const registerRowRef = React.useCallback((rowId, el) => {
799
+ if (el)
800
+ rowRefsRef.current.set(rowId, el);
801
+ else
802
+ rowRefsRef.current.delete(rowId);
803
+ }, []);
804
+ useInput((input, key, event) => {
805
+ // The questionnaire owns the keyboard while a question is pending (the
806
+ // panel's own useInput handles ↑/↓/Space/Tab/Enter/Esc; the prompt
807
+ // input is unmounted, so nothing else should see these keys).
808
+ if (questionSnapshot !== null)
809
+ return;
810
+ // Mouse wheel scrolls the transcript — in fullscreen there is no
811
+ // terminal scrollback (alt-screen), so this is the only way back.
812
+ // Imperative scrollBy: no React re-render per notch (CC semantics).
813
+ // Events only arrive with mouse tracking on; inline mode never sees
814
+ // them, so this is a no-op there.
815
+ if (key.wheelUp || key.wheelDown) {
816
+ handle?.scrollBy(key.wheelUp ? -3 : 3);
817
+ event.stopImmediatePropagation();
818
+ return;
819
+ }
820
+ // Esc clears a settled mouse selection first (CC precedence), ahead of
821
+ // every other Esc meaning below (close pickers, interrupt the turn).
822
+ // hasSelection() is an imperative read — no subscription needed.
823
+ if (key.escape && hasMouseSelection()) {
824
+ clearMouseSelection();
825
+ event.stopImmediatePropagation();
826
+ return;
827
+ }
828
+ if (searchOpen) {
829
+ // Transcript search bar (less-style): edit the query, Enter commits
830
+ // (query persists for n/N), Esc/ctrl+c cancels back to the anchor.
831
+ if (key.escape || (key.ctrl && input === 'c')) {
832
+ setSearchOpen(false);
833
+ setHighlight('');
834
+ handle?.scrollTo(searchAnchorRef.current);
835
+ }
836
+ else if (key.return) {
837
+ // Enter commits; 0-match junk queries don't persist (CC behavior).
838
+ if (searchCount === 0)
839
+ setSearchQuery('');
840
+ setSearchOpen(false);
841
+ }
842
+ else if (key.backspace) {
843
+ if (searchCursor > 0) {
844
+ setSearchQuery(searchQuery.slice(0, searchCursor - 1) + searchQuery.slice(searchCursor));
845
+ setSearchCursor(searchCursor - 1);
846
+ }
847
+ }
848
+ else if (key.delete) {
849
+ if (searchCursor < searchQuery.length) {
850
+ setSearchQuery(searchQuery.slice(0, searchCursor) + searchQuery.slice(searchCursor + 1));
851
+ }
852
+ }
853
+ else if (key.leftArrow) {
854
+ setSearchCursor(c => Math.max(0, c - 1));
855
+ }
856
+ else if (key.rightArrow) {
857
+ setSearchCursor(c => Math.min(searchQuery.length, c + 1));
858
+ }
859
+ else if (key.home) {
860
+ setSearchCursor(0);
861
+ }
862
+ else if (key.end) {
863
+ setSearchCursor(searchQuery.length);
864
+ }
865
+ else if (!key.ctrl && !key.meta && input) {
866
+ const next = searchQuery.slice(0, searchCursor) + input + searchQuery.slice(searchCursor);
867
+ setSearchQuery(next);
868
+ setSearchCursor(searchCursor + input.length);
869
+ }
870
+ event.stopImmediatePropagation();
871
+ return;
872
+ }
873
+ // After Enter closed the search bar, n/N keep walking the matches
874
+ // (CC: "Query persists across bar open/close so n/N keep working").
875
+ // Transcript mode only — in prompt mode n/N are ordinary input chars.
876
+ if (expanded && input === 'n' && searchQuery && searchCount > 0 && !key.ctrl && !key.meta) {
877
+ setSearchCurrent(i => (i >= searchCount - 1 ? 0 : i + 1));
878
+ event.stopImmediatePropagation();
879
+ return;
880
+ }
881
+ if (expanded && input === 'N' && searchQuery && searchCount > 0 && !key.ctrl && !key.meta) {
882
+ setSearchCurrent(i => (i <= 0 ? searchCount - 1 : i - 1));
883
+ event.stopImmediatePropagation();
884
+ return;
885
+ }
886
+ if (thinkingOpen) {
887
+ if (thinkingConfirm !== null) {
888
+ // Confirmation state: Enter applies, Esc backs out to the select.
889
+ if (key.return) {
890
+ const enabled = thinkingConfirm;
891
+ setThinkingVisible(enabled);
892
+ setThinkingConfirm(null);
893
+ setThinkingOpen(false);
894
+ channel.notify(`Thinking ${enabled ? 'on' : 'off'}`);
895
+ }
896
+ else if (key.escape) {
897
+ setThinkingConfirm(null);
898
+ }
899
+ }
900
+ else if (key.upArrow || key.downArrow) {
901
+ setThinkingFocus(index => (index === 0 ? 1 : 0));
902
+ }
903
+ else if (key.return) {
904
+ const enabled = thinkingFocus === 0;
905
+ const midConversation = channel.rows.some(row => row.kind === 'assistant');
906
+ if (midConversation && enabled !== thinkingVisible) {
907
+ setThinkingConfirm(enabled);
908
+ }
909
+ else {
910
+ setThinkingVisible(enabled);
911
+ setThinkingOpen(false);
912
+ channel.notify(`Thinking ${enabled ? 'on' : 'off'}`);
913
+ }
914
+ }
915
+ else if (key.escape) {
916
+ setThinkingOpen(false);
917
+ }
918
+ return;
919
+ }
920
+ if (resumePickerOpen) {
921
+ if (key.upArrow) {
922
+ setResumeIndex(index => (index <= 0 ? resumeSessions.length - 1 : index - 1));
923
+ }
924
+ else if (key.downArrow) {
925
+ setResumeIndex(index => (index >= resumeSessions.length - 1 ? 0 : index + 1));
926
+ }
927
+ else if (key.return) {
928
+ const session = resumeSessions[resumeIndex];
929
+ // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
930
+ if (session) {
931
+ // Enter switches the live agent to the persisted session right
932
+ // away (the history replays into the transcript); the resume.txt
933
+ // launcher marker is refreshed by resumeTo so `--resume` on the
934
+ // next launch opens the same session.
935
+ setResumePickerOpen(false);
936
+ void channel.resumeTo(session.id).then((ok) => {
937
+ if (ok)
938
+ channel.notify('Session resumed');
939
+ });
940
+ }
941
+ else {
942
+ setResumePickerOpen(false);
943
+ }
944
+ }
945
+ else if (key.escape) {
946
+ setResumePickerOpen(false);
947
+ }
948
+ return;
949
+ }
950
+ if (modelPickerOpen) {
951
+ if (key.upArrow) {
952
+ setModelIndex(index => (index <= 0 ? models.length - 1 : index - 1));
953
+ }
954
+ else if (key.downArrow) {
955
+ setModelIndex(index => (index >= models.length - 1 ? 0 : index + 1));
956
+ }
957
+ else if (key.return) {
958
+ const model = models[modelIndex];
959
+ // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
960
+ if (model) {
961
+ // Enter switches the live model right away: the conversation is
962
+ // forked at its end and continued with an agent routed to the new
963
+ // model (history replays unchanged).
964
+ setModelPickerOpen(false);
965
+ channel.notify(`Switching model to ${model.name}…`);
966
+ void channel.switchModel(model.provider, model.id).then((ok) => {
967
+ if (ok)
968
+ channel.notify(`Model switched to ${model.name}`);
969
+ });
970
+ }
971
+ else {
972
+ setModelPickerOpen(false);
973
+ }
974
+ }
975
+ else if (key.escape) {
976
+ setModelPickerOpen(false);
977
+ }
978
+ return;
979
+ }
980
+ if (activityPickerOpen) {
981
+ if (key.upArrow) {
982
+ setActivityIndex(index => (index <= 0 ? PRESET_NAMES.length - 1 : index - 1));
983
+ }
984
+ else if (key.downArrow) {
985
+ setActivityIndex(index => (index >= PRESET_NAMES.length - 1 ? 0 : index + 1));
986
+ }
987
+ else if (key.return) {
988
+ const name = PRESET_NAMES[activityIndex];
989
+ setActivityPickerOpen(false);
990
+ if (name)
991
+ channel.setActivityFrames(name);
992
+ }
993
+ else if (key.escape) {
994
+ setActivityPickerOpen(false);
995
+ }
996
+ return;
997
+ }
998
+ if (presetPickerOpen) {
999
+ if (key.upArrow) {
1000
+ setPresetIndex(index => (index <= 0 ? presetOptions.length - 1 : index - 1));
1001
+ }
1002
+ else if (key.downArrow) {
1003
+ setPresetIndex(index => (index >= presetOptions.length - 1 ? 0 : index + 1));
1004
+ }
1005
+ else if (key.return) {
1006
+ const option = presetOptions[presetIndex];
1007
+ setPresetPickerOpen(false);
1008
+ if (option)
1009
+ void channel.switchPreset(option.id);
1010
+ }
1011
+ else if (key.escape) {
1012
+ setPresetPickerOpen(false);
1013
+ }
1014
+ return;
1015
+ }
1016
+ if (themePickerOpen) {
1017
+ const options = getThemeOptions();
1018
+ if (key.upArrow) {
1019
+ setThemeIndex(index => (index <= 0 ? options.length - 1 : index - 1));
1020
+ }
1021
+ else if (key.downArrow) {
1022
+ setThemeIndex(index => (index >= options.length - 1 ? 0 : index + 1));
1023
+ }
1024
+ else if (key.return) {
1025
+ setThemePickerOpen(false);
1026
+ const name = options[themeIndex]?.value;
1027
+ if (name !== undefined) {
1028
+ const ok = setTheme(name);
1029
+ channel.notify(ok ? t('theme-switched-saved', { name }) : t('theme-switch-failed', { name }), { color: ok ? 'success' : 'error' });
1030
+ }
1031
+ }
1032
+ else if (key.escape) {
1033
+ setThemePickerOpen(false);
1034
+ }
1035
+ return;
1036
+ }
1037
+ if (historyOpen) {
1038
+ if (key.escape) {
1039
+ setHistoryOpen(false);
1040
+ }
1041
+ else if (key.ctrl && (input === 'c' || input === 'd')) {
1042
+ // CC's history search cancels on ctrl+c/ctrl+d too.
1043
+ setHistoryOpen(false);
1044
+ }
1045
+ else if (key.return) {
1046
+ const entry = historyMatches[historyFocus];
1047
+ // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty match list
1048
+ if (entry) {
1049
+ setHistoryFill(entry.text);
1050
+ setHistoryOpen(false);
1051
+ }
1052
+ }
1053
+ else if (key.upArrow) {
1054
+ setHistoryFocus(index => historyMatches.length === 0 ? 0 : (index <= 0 ? historyMatches.length - 1 : index - 1));
1055
+ }
1056
+ else if (key.downArrow || (key.ctrl && input === 'r')) {
1057
+ // CC's historySearch:next — ↓ and repeat ctrl+r walk to the next match.
1058
+ setHistoryFocus(index => historyMatches.length === 0 ? 0 : (index >= historyMatches.length - 1 ? 0 : index + 1));
1059
+ }
1060
+ else if (key.backspace) {
1061
+ if (historyCursor > 0) {
1062
+ const next = historyQuery.slice(0, historyCursor - 1) + historyQuery.slice(historyCursor);
1063
+ setHistoryQuery(next);
1064
+ setHistoryCursor(historyCursor - 1);
1065
+ setHistoryFocus(0);
1066
+ }
1067
+ }
1068
+ else if (key.delete) {
1069
+ if (historyCursor < historyQuery.length) {
1070
+ setHistoryQuery(historyQuery.slice(0, historyCursor) + historyQuery.slice(historyCursor + 1));
1071
+ setHistoryFocus(0);
1072
+ }
1073
+ }
1074
+ else if (key.leftArrow) {
1075
+ setHistoryCursor(cursor => Math.max(0, cursor - 1));
1076
+ }
1077
+ else if (key.rightArrow) {
1078
+ setHistoryCursor(cursor => Math.min(historyQuery.length, cursor + 1));
1079
+ }
1080
+ else if (key.home) {
1081
+ setHistoryCursor(0);
1082
+ }
1083
+ else if (key.end) {
1084
+ setHistoryCursor(historyQuery.length);
1085
+ }
1086
+ else if (!key.ctrl && !key.meta && input) {
1087
+ const next = historyQuery.slice(0, historyCursor) + input + historyQuery.slice(historyCursor);
1088
+ setHistoryQuery(next);
1089
+ setHistoryCursor(historyCursor + input.length);
1090
+ setHistoryFocus(0);
1091
+ }
1092
+ return;
1093
+ }
1094
+ if (rewindOpen) {
1095
+ if (rewindConfirm !== null) {
1096
+ // Confirmation state: Enter rewinds, Esc backs out to the list.
1097
+ if (key.return) {
1098
+ const row = rewindConfirm;
1099
+ setRewindOpen(false);
1100
+ setRewindConfirm(null);
1101
+ void performRewind(row);
1102
+ }
1103
+ else if (key.escape) {
1104
+ setRewindConfirm(null);
1105
+ }
1106
+ }
1107
+ else if (key.upArrow) {
1108
+ setRewindIndex(index => (index <= 0 ? rewindRows.length - 1 : index - 1));
1109
+ }
1110
+ else if (key.downArrow) {
1111
+ setRewindIndex(index => (index >= rewindRows.length - 1 ? 0 : index + 1));
1112
+ }
1113
+ else if (key.return) {
1114
+ const row = rewindRows[rewindIndex];
1115
+ // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
1116
+ if (row)
1117
+ setRewindConfirm(row);
1118
+ }
1119
+ else if (key.escape) {
1120
+ setRewindOpen(false);
1121
+ setRewindConfirm(null);
1122
+ }
1123
+ return;
1124
+ }
1125
+ if (key.ctrl && input === 't') {
1126
+ // Toggle the startup loaded-context panel (keyboard only — the
1127
+ // ported ink core handles no mouse clicks).
1128
+ setLoadedContextOpen(previous => !previous);
1129
+ }
1130
+ if (key.ctrl && input === 'r' && !helpOpen) {
1131
+ setHistoryQuery('');
1132
+ setHistoryCursor(0);
1133
+ setHistoryFocus(0);
1134
+ setHistoryEntries(loadHistory());
1135
+ setHistoryOpen(true);
1136
+ return;
1137
+ }
1138
+ if (key.shift && key.upArrow && !selectionActive) {
1139
+ enterSelection();
1140
+ }
1141
+ else if (selectionActive) {
1142
+ if (key.upArrow) {
1143
+ moveSelection(-1);
1144
+ }
1145
+ else if (key.downArrow) {
1146
+ moveSelection(1);
1147
+ }
1148
+ else if (key.return && selectedId !== null) {
1149
+ toggleRowExpanded(selectedId);
1150
+ }
1151
+ else if (key.escape) {
1152
+ setSelectionActive(false);
1153
+ setSelectedId(null);
1154
+ }
1155
+ }
1156
+ else if (key.escape && channel.working) {
1157
+ // CC's chat:cancel — esc interrupts a running turn (the prompt input
1158
+ // only sees esc when idle, where it has the double-tap-clear meaning).
1159
+ // With messages queued for delivery, interrupt-and-deliver them right
1160
+ // away (Codex behavior); otherwise a plain interrupt parks the queue.
1161
+ if (channel.pending.length > 0) {
1162
+ const count = channel.interruptAndDeliver(channel.pending.map(item => item.text));
1163
+ if (count > 0) {
1164
+ channel.notify(t('interrupt-delivered', { n: count }), { timeoutMs: 2500 });
1165
+ }
1166
+ }
1167
+ else {
1168
+ channel.cancel();
1169
+ }
1170
+ event.stopImmediatePropagation();
1171
+ }
1172
+ else if (key.ctrl && input === 'o') {
1173
+ // Leaving transcript mode (Ctrl+O) — search was already handled above.
1174
+ setExpanded(previous => !previous);
1175
+ }
1176
+ else if (input === '/' && !key.ctrl && !key.meta) {
1177
+ // `/` in transcript mode (Ctrl+O expanded, CC's REPL semantics:
1178
+ // search is active on the transcript screen where `/` isn't a command).
1179
+ if (expanded) {
1180
+ searchAnchorRef.current = handle?.getScrollTop() ?? 0;
1181
+ setSearchQuery('');
1182
+ setSearchCursor(0);
1183
+ setSearchCurrent(0);
1184
+ setSearchCount(0);
1185
+ setSearchOpen(true);
1186
+ event.stopImmediatePropagation();
1187
+ }
1188
+ }
1189
+ else if (key.ctrl && (input === 'c' || input === 'd')) {
1190
+ // CC's app:exit — ctrl+c interrupts a running turn; idle ctrl+c
1191
+ // CLEARS a non-empty prompt (single press) and only arms the
1192
+ // double-press exit when the input is empty; ctrl+d keeps the
1193
+ // time-based double-press exit regardless.
1194
+ if (channel.working) {
1195
+ channel.cancel();
1196
+ }
1197
+ else if (input === 'c' && promptControllerRef.current?.hasText()) {
1198
+ promptControllerRef.current.clear();
1199
+ // A pending exit arm no longer makes sense once the user is editing.
1200
+ exitPendingRef.current = false;
1201
+ if (exitTimerRef.current)
1202
+ clearTimeout(exitTimerRef.current);
1203
+ }
1204
+ else {
1205
+ requestExit();
1206
+ }
1207
+ }
1208
+ else if (key.ctrl && input === 'l') {
1209
+ // CC's app:redraw — clear the physical terminal and repaint.
1210
+ instances.get(process.stdout)?.forceRedraw();
1211
+ }
1212
+ else if (key.ctrl && input === 'e') {
1213
+ setShowAllMessages(previous => !previous);
1214
+ }
1215
+ else if (key.return && showPill) {
1216
+ handle?.scrollToBottom();
1217
+ }
1218
+ });
1219
+ // Working-activity line (spinner slot): context-pressure prefix shares the
1220
+ // StatusLine thresholds (amber ≥ 80, red ≥ 95).
1221
+ const activityWarnPct = contextPressurePct(channel.lastUsage, channel.contextWindow);
1222
+ /** Prompt input is inert while a modal dialog owns the keyboard. */
1223
+ const promptSelectionActive = selectionActive || modelPickerOpen || resumePickerOpen || activityPickerOpen ||
1224
+ presetPickerOpen || themePickerOpen || thinkingOpen || historyOpen || rewindOpen || searchOpen;
1225
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, width: "100%", children: [!isSticky && channel.lastUserText && (_jsx(StickyPromptHeader, { text: channel.lastUserText, onClick: () => {
1226
+ // Click jumps back to the pinned prompt (CC's StickyPromptHeader).
1227
+ const lastUser = [...channel.rows].reverse().find(row => row.kind === 'user');
1228
+ if (lastUser)
1229
+ seekRow(lastUser.id);
1230
+ else
1231
+ handle?.scrollToBottom();
1232
+ } })), _jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.cwd }), channel.rows.length === 0 && channel.loadedContext !== undefined && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: () => { setLoadedContextOpen(previous => !previous); } })), _jsx(MessageList, { rows: channel.rows, expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, model: channel.model, showAll: showAllMessages, thinkingVisible: thinkingVisible, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
1233
+ (channel.activityEnabled &&
1234
+ channel.workingActivity !== undefined &&
1235
+ channel.workingActivity.line !== '' &&
1236
+ channel.workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: channel.workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95, suffix: ` · ↓ ${channel.responseChars} tokens` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus }))), thinkingOpen && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: thinkingFocus, confirmationPending: thinkingConfirm })), resumePickerOpen && resumeSessions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ResumePicker, { sessions: resumeSessions, focusIndex: resumeIndex, currentSessionId: channel.agentId }) })), modelPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : (_jsx(ModelPicker, { models: models, focusIndex: modelIndex, currentModel: `${channel.provider}/${channel.model}` })) })), activityPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: activityIndex, currentPreset: channel.activityFrames }) })), presetPickerOpen && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: presetIndex, currentPreset: channel.agentPreset }) })), themePickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: themeIndex, currentTheme: themeName }) })), historyOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(HistorySearchDialog, { query: historyQuery, cursorOffset: historyCursor, matches: historyMatches, focusIndex: historyFocus }) })), rewindOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: rewindIndex, confirmRow: rewindConfirm }) })), searchOpen && _jsx(TranscriptSearchBar, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent }), _jsx(GoalTodoPanel, { channel: channel }), questionSnapshot !== null && (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent() }, questionSnapshot.key)), questionSnapshot === null && (_jsx(PromptInput, { channel: channel, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => { setHistoryFill(null); }, onRewindRequest: openRewind, controllerRef: promptControllerRef })), _jsx(StatusLine, { channel: channel, selectionActive: selectionActive, helpOpen: helpOpen })] })] }));
1237
+ }
1238
+ /**
1239
+ * The pinned prompt header shown above the ScrollBox while the user has
1240
+ * scrolled up (ported from the leak's FullscreenLayout.StickyPromptHeader).
1241
+ * Fixed at 1 row so the ScrollBox never shifts when the text changes.
1242
+ */
1243
+ function StickyPromptHeader({ text, onClick, }) {
1244
+ const [hover, setHover] = React.useState(false);
1245
+ return (_jsx(Box, { flexShrink: 0, width: "100%", height: 1, paddingRight: 1, backgroundColor: hover ? 'userMessageBackgroundHover' : 'userMessageBackground', onMouseEnter: () => { setHover(true); }, onMouseLeave: () => { setHover(false); }, onClick: onClick, children: _jsxs(Text, { color: "subtle", wrap: "truncate-end", children: [POINTER, " ", text] }) }));
1246
+ }
1247
+ /** The `↓ N new messages` pill shown while scrolled up with new content. */
1248
+ function NewMessagesPill({ count, onClick, }) {
1249
+ const [hover, setHover] = React.useState(false);
1250
+ return (_jsx(Box, { paddingX: 2, paddingTop: 1, children: _jsx(Box, { backgroundColor: hover ? 'userMessageBackgroundHover' : 'background', onClick: onClick, onMouseEnter: () => { setHover(true); }, onMouseLeave: () => { setHover(false); }, children: _jsxs(Text, { color: "inverseText", bold: true, children: [' ', "\u2193 ", count === 1 ? '1 new message' : `${count} new messages`, ' '] }) }) }));
1251
+ }
1252
+ /** /model while the provider catalog is still loading (CC's LoadingState). */
1253
+ function ModelPickerLoading() {
1254
+ return (_jsx(Pane, { color: "permission", children: _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, color: "permission", children: "Model" }), _jsx(LoadingState, { message: "Loading models", bold: true, subtitle: "Querying the provider\u2026" })] }) }));
1255
+ }
1256
+ /**
1257
+ * The `/` incsearch bar (ported from CC's REPL TranscriptSearchBar): a
1258
+ * single row above the prompt input with the query, a block cursor, and the
1259
+ * match counter (`current/count`) or a red `no matches` when nothing hits.
1260
+ */ function TranscriptSearchBar({ query, cursorOffset, count, current, }) {
1261
+ const cursorChar = cursorOffset < query.length ? query[cursorOffset] : ' ';
1262
+ return (_jsxs(NoSelect, { borderTopDimColor: true, borderBottom: false, borderLeft: false, borderRight: false, borderStyle: "single", marginTop: 1, paddingLeft: 2, width: "100%", children: [_jsx(Text, { children: "/" }), _jsx(Text, { children: query.slice(0, cursorOffset) }), _jsx(Text, { inverse: true, children: cursorChar }), cursorOffset < query.length && _jsx(Text, { children: query.slice(cursorOffset + 1) }), _jsx(Box, { flexGrow: 1 }), query && count === 0 ? (_jsx(Text, { color: "error", children: "no matches " })) : count > 0 ? (_jsxs(Text, { dimColor: true, children: [Math.min(current + 1, count), "/", count, ' '] })) : null] }));
1263
+ }