@robota-sdk/agent-ui-terminal 3.0.0-beta.81

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 (328) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +27 -0
  3. package/dist/node/index.cjs +32 -0
  4. package/dist/node/index.d.cts +1066 -0
  5. package/dist/node/index.d.cts.map +1 -0
  6. package/dist/node/index.d.ts +1066 -0
  7. package/dist/node/index.d.ts.map +1 -0
  8. package/dist/node/index.js +33 -0
  9. package/dist/node/index.js.map +1 -0
  10. package/package.json +93 -0
  11. package/src/App.tsx +146 -0
  12. package/src/AppPresentation.tsx +190 -0
  13. package/src/AppView.tsx +26 -0
  14. package/src/BackgroundTaskPanel.tsx +73 -0
  15. package/src/CjkTextInput.tsx +336 -0
  16. package/src/ConfirmPrompt.tsx +140 -0
  17. package/src/ContextWarningBanner.tsx +45 -0
  18. package/src/ExecutionWorkspaceDetailPane.tsx +85 -0
  19. package/src/ExecutionWorkspaceSwitcher.tsx +213 -0
  20. package/src/HistorySearchOverlay.tsx +150 -0
  21. package/src/InputArea.tsx +338 -0
  22. package/src/ListPicker.tsx +158 -0
  23. package/src/MenuSelect.tsx +159 -0
  24. package/src/MessageList.tsx +308 -0
  25. package/src/MultiSelectList.tsx +294 -0
  26. package/src/PendingActionPrompt.tsx +121 -0
  27. package/src/PermissionPrompt.tsx +186 -0
  28. package/src/PluginTUI.tsx +258 -0
  29. package/src/RoleLabel.tsx +72 -0
  30. package/src/SafeText.tsx +66 -0
  31. package/src/SessionEventNotices.tsx +27 -0
  32. package/src/SessionPicker.tsx +72 -0
  33. package/src/SessionStatusBar.tsx +76 -0
  34. package/src/SlashAutocomplete.tsx +154 -0
  35. package/src/StatusBar.tsx +284 -0
  36. package/src/StreamingIndicator.tsx +145 -0
  37. package/src/SupervisedSessionView.tsx +455 -0
  38. package/src/TextPrompt.tsx +112 -0
  39. package/src/ThemePicker.tsx +366 -0
  40. package/src/ToolCommandOutput.tsx +48 -0
  41. package/src/ToolDiffBlock.tsx +44 -0
  42. package/src/TransportTUI.tsx +166 -0
  43. package/src/TuiInteractionChannel.ts +567 -0
  44. package/src/UpdateNotice.tsx +18 -0
  45. package/src/UsageSummaryEntry.tsx +49 -0
  46. package/src/WaveText.tsx +70 -0
  47. package/src/__tests__/ListPicker.test.tsx +188 -0
  48. package/src/__tests__/MenuSelect.test.tsx +127 -0
  49. package/src/__tests__/PendingActionPrompt.test.tsx +193 -0
  50. package/src/__tests__/PluginTUI.test.tsx +167 -0
  51. package/src/__tests__/SlashAutocomplete.test.tsx +162 -0
  52. package/src/__tests__/SupervisedSessionView-render.test.tsx +24 -0
  53. package/src/__tests__/SupervisedSessionView.test.tsx +629 -0
  54. package/src/__tests__/TextPrompt.test.tsx +112 -0
  55. package/src/__tests__/TransportTUI.test.tsx +124 -0
  56. package/src/__tests__/TuiInteractionChannel.askUser.test.ts +124 -0
  57. package/src/__tests__/TuiInteractionChannel.display-contract.test.ts +284 -0
  58. package/src/__tests__/TuiInteractionChannel.lifecycle.test.ts +642 -0
  59. package/src/__tests__/UpdateNotice.test.tsx +15 -0
  60. package/src/__tests__/abort-after-permission.test.tsx +160 -0
  61. package/src/__tests__/abort-streaming-e2e.test.tsx +183 -0
  62. package/src/__tests__/attention-bell-and-marks.test.ts +144 -0
  63. package/src/__tests__/autocomplete-editing-keys.test.tsx +127 -0
  64. package/src/__tests__/background-task-panel-countdown.test.tsx +75 -0
  65. package/src/__tests__/background-task-panel.test.tsx +98 -0
  66. package/src/__tests__/background-task-row-format.test.ts +152 -0
  67. package/src/__tests__/channel-factory-integration.test.ts +150 -0
  68. package/src/__tests__/cjk-defer-submit.test.tsx +67 -0
  69. package/src/__tests__/cjk-fallback-render.test.tsx +78 -0
  70. package/src/__tests__/cjk-text-input-flow.test.ts +186 -0
  71. package/src/__tests__/cjk-text-input.test.ts +191 -0
  72. package/src/__tests__/command-handoff-pty-e2e.test.ts +107 -0
  73. package/src/__tests__/command-output-summary.test.ts +95 -0
  74. package/src/__tests__/command-result-handler.test.ts +171 -0
  75. package/src/__tests__/compact-event-bridge.test.ts +20 -0
  76. package/src/__tests__/confirm-permission-flow.test.ts +149 -0
  77. package/src/__tests__/confirm-prompt.test.tsx +103 -0
  78. package/src/__tests__/contextual-keybindings-input.test.tsx +158 -0
  79. package/src/__tests__/default-tui-cli-adapter-host-settings.test.ts +45 -0
  80. package/src/__tests__/effort-reaches-the-session.test.ts +41 -0
  81. package/src/__tests__/execution-workspace-switcher.test.tsx +147 -0
  82. package/src/__tests__/execution-workspace-view-model.test.ts +94 -0
  83. package/src/__tests__/external-prompt-notice.test.tsx +100 -0
  84. package/src/__tests__/fixtures/command-handoff-driver.tsx +111 -0
  85. package/src/__tests__/fixtures/fake-editor.sh +7 -0
  86. package/src/__tests__/fixtures/terminal-handoff-driver.tsx +90 -0
  87. package/src/__tests__/fork-attach.test.tsx +235 -0
  88. package/src/__tests__/helpers/terminal-profiles.ts +152 -0
  89. package/src/__tests__/helpers/vt-cursor-interpreter.ts +175 -0
  90. package/src/__tests__/history-clear-broadcast.test.ts +66 -0
  91. package/src/__tests__/history-search-overlay.test.tsx +329 -0
  92. package/src/__tests__/humanize-tool-name.test.ts +30 -0
  93. package/src/__tests__/input-area-bottom-border.test.tsx +45 -0
  94. package/src/__tests__/input-area-flow.test.ts +199 -0
  95. package/src/__tests__/input-area-focus-handoff.test.tsx +77 -0
  96. package/src/__tests__/interaction-channel-charter.test.ts +13 -0
  97. package/src/__tests__/key-hint-consistency.test.tsx +249 -0
  98. package/src/__tests__/key-hint-footer.test.tsx +69 -0
  99. package/src/__tests__/live-trace-reaches-session.test.ts +14 -0
  100. package/src/__tests__/message-list-rendering.test.tsx +473 -0
  101. package/src/__tests__/org-policy-reaches-the-channel.test.ts +77 -0
  102. package/src/__tests__/output-style-reaches-session.test.ts +35 -0
  103. package/src/__tests__/palette-consistency.test.ts +330 -0
  104. package/src/__tests__/permission-prompt-availability.test.tsx +23 -0
  105. package/src/__tests__/permission-prompt-peer.test.tsx +34 -0
  106. package/src/__tests__/product-display-name.test.tsx +79 -0
  107. package/src/__tests__/prompt-queue.test.tsx +260 -0
  108. package/src/__tests__/provider-definitions-reach-the-session.test.ts +75 -0
  109. package/src/__tests__/pty/ask-user-question.ptytest.ts +72 -0
  110. package/src/__tests__/pty/background-work-switcher.ptytest.ts +53 -0
  111. package/src/__tests__/pty/behavior-2437-git.ptytest.ts +342 -0
  112. package/src/__tests__/pty/fixtures/ask-user-question.jsonl +3 -0
  113. package/src/__tests__/pty/fixtures/replay-conversation.jsonl +4 -0
  114. package/src/__tests__/pty/fixtures/screen-005-ask-shapes.jsonl +5 -0
  115. package/src/__tests__/pty/fixtures/screen-005-permission.jsonl +3 -0
  116. package/src/__tests__/pty/fixtures/screen-006-colors.jsonl +3 -0
  117. package/src/__tests__/pty/fixtures/screen-2002-themes.jsonl +5 -0
  118. package/src/__tests__/pty/flag-tui.ptytest.ts +56 -0
  119. package/src/__tests__/pty/flow-2006-deep-link.ptytest.ts +291 -0
  120. package/src/__tests__/pty/ime-cursor-tmux.ptytest.ts +165 -0
  121. package/src/__tests__/pty/ime-cursor.ptytest.ts +186 -0
  122. package/src/__tests__/pty/isolated-home.ts +64 -0
  123. package/src/__tests__/pty/org-policy.ptytest.ts +71 -0
  124. package/src/__tests__/pty/provider-setup.ptytest.ts +61 -0
  125. package/src/__tests__/pty/pty-driver.ts +153 -0
  126. package/src/__tests__/pty/replay-conversation.ptytest.ts +68 -0
  127. package/src/__tests__/pty/screen-005-prompt-footers.ptytest.ts +134 -0
  128. package/src/__tests__/pty/screen-006-no-color.ptytest.ts +138 -0
  129. package/src/__tests__/pty/screen-010-scrollback.ptytest.ts +123 -0
  130. package/src/__tests__/pty/screen-1993-scrollback.ptytest.ts +124 -0
  131. package/src/__tests__/pty/screen-2002-themes.ptytest.ts +292 -0
  132. package/src/__tests__/pty/screen-2670-prepark.ptytest.ts +228 -0
  133. package/src/__tests__/pty/screen-reader-mode.ptytest.ts +228 -0
  134. package/src/__tests__/pty/settings-screen.ptytest.ts +49 -0
  135. package/src/__tests__/pty/spawn-pty.test.ts +120 -0
  136. package/src/__tests__/pty/spawn-pty.ts +229 -0
  137. package/src/__tests__/pty/terminal-handoff.ptytest.ts +79 -0
  138. package/src/__tests__/pty/tui-pty.ptytest.ts +65 -0
  139. package/src/__tests__/pty-driver-dispose.test.ts +43 -0
  140. package/src/__tests__/real-cursor-positioning.test.tsx +263 -0
  141. package/src/__tests__/rename-broadcast-persistence.test.tsx +140 -0
  142. package/src/__tests__/render-channel-options.test.ts +201 -0
  143. package/src/__tests__/render-lifecycle.test.ts +36 -0
  144. package/src/__tests__/render-markdown.test.ts +164 -0
  145. package/src/__tests__/rendered-markdown-styling.test.tsx +98 -0
  146. package/src/__tests__/safe-text-boundary.test.tsx +75 -0
  147. package/src/__tests__/screen-reader-input.test.tsx +106 -0
  148. package/src/__tests__/screen-reader-labels.test.ts +91 -0
  149. package/src/__tests__/screen-reader-menus.test.tsx +200 -0
  150. package/src/__tests__/screen-reader-pacing.test.ts +230 -0
  151. package/src/__tests__/screen-reader-prepark-echo.test.tsx +200 -0
  152. package/src/__tests__/screen-reader-render-options.test.ts +262 -0
  153. package/src/__tests__/screen-reader-stdout.test.ts +322 -0
  154. package/src/__tests__/screen-reader-turn-marks-port.test.tsx +85 -0
  155. package/src/__tests__/screen-reader-turn-signals.test.tsx +125 -0
  156. package/src/__tests__/sec-019-terminal-sanitizer.test.ts +276 -0
  157. package/src/__tests__/sec-019-tool-label-render.test.tsx +207 -0
  158. package/src/__tests__/selection-flow.test.ts +61 -0
  159. package/src/__tests__/session-event-notices.test.tsx +31 -0
  160. package/src/__tests__/session-init-poller.test.ts +102 -0
  161. package/src/__tests__/session-switch-channel.test.tsx +470 -0
  162. package/src/__tests__/shutdown-signal.test.ts +34 -0
  163. package/src/__tests__/status-activity.test.ts +71 -0
  164. package/src/__tests__/status-bar.test.tsx +282 -0
  165. package/src/__tests__/status-glyph.test.ts +30 -0
  166. package/src/__tests__/streaming-indicator.test.tsx +177 -0
  167. package/src/__tests__/terminal-capabilities.test.ts +153 -0
  168. package/src/__tests__/terminal-handoff-controller.test.ts +173 -0
  169. package/src/__tests__/terminal-handoff-pty-e2e.test.ts +77 -0
  170. package/src/__tests__/text-prompt-flow.test.ts +77 -0
  171. package/src/__tests__/theme-picker.test.tsx +345 -0
  172. package/src/__tests__/transport-boundary.test.ts +11 -0
  173. package/src/__tests__/tui-app-channel-port.test.ts +31 -0
  174. package/src/__tests__/tui-channel-init-failure.test.ts +62 -0
  175. package/src/__tests__/tui-channel-lifecycle-coordinator.test.ts +258 -0
  176. package/src/__tests__/tui-interaction-queues.test.ts +139 -0
  177. package/src/__tests__/tui-preset-capabilities.test.ts +33 -0
  178. package/src/__tests__/tui-state-manager.error-stall.test.ts +169 -0
  179. package/src/__tests__/tui-state-manager.test.ts +451 -0
  180. package/src/__tests__/ui-intent-rendering.test.tsx +291 -0
  181. package/src/__tests__/wave-text.test.tsx +133 -0
  182. package/src/app-banner.tsx +42 -0
  183. package/src/app-static-items.ts +35 -0
  184. package/src/app-view-model.ts +136 -0
  185. package/src/attention/__tests__/attention-coordinator.test.ts +166 -0
  186. package/src/attention/__tests__/attention-tracker.test.ts +103 -0
  187. package/src/attention/__tests__/countdown.test.ts +15 -0
  188. package/src/attention/__tests__/focus-input-filter.test.ts +92 -0
  189. package/src/attention/__tests__/interval-recap.test.ts +49 -0
  190. package/src/attention/attention-coordinator.ts +71 -0
  191. package/src/attention/attention-tracker.ts +124 -0
  192. package/src/attention/countdown.ts +24 -0
  193. package/src/attention/focus-input-filter.ts +131 -0
  194. package/src/attention/interval-recap.ts +148 -0
  195. package/src/attention/time-units.ts +6 -0
  196. package/src/attention-bell.ts +79 -0
  197. package/src/attributed-user-echo.ts +30 -0
  198. package/src/background-task-row-format.ts +99 -0
  199. package/src/command-interaction.ts +8 -0
  200. package/src/command-output-summary.ts +129 -0
  201. package/src/create-default-tui-cli-adapter.ts +45 -0
  202. package/src/execution-workspace-switcher-selection.ts +118 -0
  203. package/src/execution-workspace-view-model.ts +139 -0
  204. package/src/external-prompt-notice.tsx +38 -0
  205. package/src/flows/__tests__/background-focus-flow.test.ts +48 -0
  206. package/src/flows/__tests__/defer-submit.test.ts +87 -0
  207. package/src/flows/__tests__/real-cursor-flow.test.ts +124 -0
  208. package/src/flows/background-focus-flow.ts +44 -0
  209. package/src/flows/cjk-cursor-motion.ts +75 -0
  210. package/src/flows/cjk-text-deletion.ts +53 -0
  211. package/src/flows/cjk-text-input-flow.ts +248 -0
  212. package/src/flows/cjk-text-input-types.ts +24 -0
  213. package/src/flows/confirm-prompt-flow.ts +90 -0
  214. package/src/flows/defer-submit.ts +67 -0
  215. package/src/flows/fork-attach-flow.ts +62 -0
  216. package/src/flows/input-area-flow.ts +188 -0
  217. package/src/flows/permission-prompt-flow.ts +108 -0
  218. package/src/flows/real-cursor-flow.ts +83 -0
  219. package/src/flows/selection-flow.ts +205 -0
  220. package/src/flows/session-init-poller.ts +76 -0
  221. package/src/flows/text-prompt-flow.ts +93 -0
  222. package/src/history-search/__tests__/history-search-flow.test.ts +86 -0
  223. package/src/history-search/history-search-flow.ts +94 -0
  224. package/src/history-search/useHistorySearch.ts +336 -0
  225. package/src/hooks/__tests__/use-app-loop-escape.test.tsx +74 -0
  226. package/src/hooks/__tests__/use-plugin-callbacks.test.ts +18 -0
  227. package/src/hooks/__tests__/use-tui-channel-stability.test.tsx +100 -0
  228. package/src/hooks/__tests__/useAppThemeState.test.tsx +130 -0
  229. package/src/hooks/command-result-handler.ts +44 -0
  230. package/src/hooks/side-effects-types.ts +45 -0
  231. package/src/hooks/useAppController.ts +267 -0
  232. package/src/hooks/useAppInputBindings.ts +145 -0
  233. package/src/hooks/useAppInteractionState.ts +81 -0
  234. package/src/hooks/useAppLifecycleState.ts +81 -0
  235. package/src/hooks/useAppOverlays.ts +74 -0
  236. package/src/hooks/useAppScreenState.ts +81 -0
  237. package/src/hooks/useAppSubmissionState.ts +45 -0
  238. package/src/hooks/useAppThemeState.ts +157 -0
  239. package/src/hooks/useAppWorkspaceState.ts +149 -0
  240. package/src/hooks/useAppearanceSettings.ts +30 -0
  241. package/src/hooks/useAutocomplete.ts +101 -0
  242. package/src/hooks/useBackgroundPanel.ts +58 -0
  243. package/src/hooks/useCountdownTick.ts +24 -0
  244. package/src/hooks/useExecutionDetailPage.ts +69 -0
  245. package/src/hooks/useForkAttach.ts +60 -0
  246. package/src/hooks/useInputAreaHistorySearch.ts +72 -0
  247. package/src/hooks/useInputAreaKeys.ts +114 -0
  248. package/src/hooks/useNumberedSelection.ts +76 -0
  249. package/src/hooks/usePluginCallbacks.ts +38 -0
  250. package/src/hooks/usePluginScreenData.ts +85 -0
  251. package/src/hooks/useRealCursorPosition.ts +125 -0
  252. package/src/hooks/useRenderMarkdown.ts +38 -0
  253. package/src/hooks/useScreenReaderTurnSignals.ts +113 -0
  254. package/src/hooks/useSideEffects.ts +144 -0
  255. package/src/hooks/useStatusLineSettings.ts +50 -0
  256. package/src/hooks/useTerminalHandoffSuspension.ts +30 -0
  257. package/src/hooks/useTuiChannel.ts +129 -0
  258. package/src/humanize-tool-name.ts +51 -0
  259. package/src/index.ts +56 -0
  260. package/src/input-area-rules.tsx +71 -0
  261. package/src/json-value.ts +18 -0
  262. package/src/key-hint-footer.tsx +54 -0
  263. package/src/keybindings/__tests__/keybinding-registry.test.ts +228 -0
  264. package/src/keybindings/__tests__/node-keybindings-source.test.ts +111 -0
  265. package/src/keybindings/keybinding-catalogue.ts +171 -0
  266. package/src/keybindings/keybinding-registry.ts +303 -0
  267. package/src/keybindings/keybinding-syntax.ts +155 -0
  268. package/src/keybindings/keybindings-context.tsx +124 -0
  269. package/src/keybindings/node-keybindings-source.ts +189 -0
  270. package/src/numbered-list.tsx +113 -0
  271. package/src/plugin-tui-handlers.ts +163 -0
  272. package/src/product-display-name-context.tsx +30 -0
  273. package/src/render-markdown.ts +209 -0
  274. package/src/render.tsx +544 -0
  275. package/src/sanitize-terminal-text.ts +171 -0
  276. package/src/screen-reader-announcement.ts +52 -0
  277. package/src/screen-reader-context.tsx +32 -0
  278. package/src/screen-reader-labels.ts +64 -0
  279. package/src/screen-reader-pacing-context.tsx +47 -0
  280. package/src/screen-reader-pacing.ts +178 -0
  281. package/src/screen-reader-stdout.ts +328 -0
  282. package/src/shutdown-signal.ts +34 -0
  283. package/src/status-activity.ts +70 -0
  284. package/src/status-glyph.ts +78 -0
  285. package/src/terminal-capabilities-context.tsx +27 -0
  286. package/src/terminal-capabilities.ts +77 -0
  287. package/src/terminal-focus-reporting.ts +50 -0
  288. package/src/terminal-handoff-controller.ts +98 -0
  289. package/src/terminal-marks.ts +63 -0
  290. package/src/theme/__tests__/built-in-themes.test.ts +117 -0
  291. package/src/theme/__tests__/color-vision.test.ts +82 -0
  292. package/src/theme/__tests__/theme-context.test.tsx +146 -0
  293. package/src/theme/__tests__/theme-document.test.ts +222 -0
  294. package/src/theme/__tests__/theme-registry.test.ts +138 -0
  295. package/src/theme/__tests__/theme-styles.test.ts +233 -0
  296. package/src/theme/built-in-themes.ts +342 -0
  297. package/src/theme/color-vision.ts +171 -0
  298. package/src/theme/index.ts +45 -0
  299. package/src/theme/theme-context.tsx +81 -0
  300. package/src/theme/theme-contracts.ts +139 -0
  301. package/src/theme/theme-document.ts +278 -0
  302. package/src/theme/theme-registry.ts +127 -0
  303. package/src/theme/theme-styles.ts +177 -0
  304. package/src/tool-summary-status.ts +59 -0
  305. package/src/tui-app-channel-port.ts +83 -0
  306. package/src/tui-channel-lifecycle-coordinator.ts +149 -0
  307. package/src/tui-channel-options.ts +172 -0
  308. package/src/tui-cli-adapter-context.tsx +13 -0
  309. package/src/tui-cli-adapter.ts +24 -0
  310. package/src/tui-interaction-queues.ts +163 -0
  311. package/src/tui-session-binding.ts +52 -0
  312. package/src/tui-session-event-projector.ts +140 -0
  313. package/src/tui-session-events.ts +82 -0
  314. package/src/tui-session-notice-store.ts +43 -0
  315. package/src/tui-session-options.ts +118 -0
  316. package/src/tui-state-manager.ts +327 -0
  317. package/src/types.ts +19 -0
  318. package/src/use-terminal-title.ts +29 -0
  319. package/src/utils/__tests__/edit-diff.test.ts +426 -0
  320. package/src/utils/__tests__/paste-detection.test.ts +116 -0
  321. package/src/utils/__tests__/paste-labels.test.ts +46 -0
  322. package/src/utils/__tests__/tool-call-extractor.test.ts +227 -0
  323. package/src/utils/__tests__/tool-diff-summary.test.ts +104 -0
  324. package/src/utils/edit-diff.ts +151 -0
  325. package/src/utils/input-top-border.ts +19 -0
  326. package/src/utils/paste-labels.ts +9 -0
  327. package/src/utils/tool-call-extractor.ts +92 -0
  328. package/src/utils/tool-diff-summary.ts +75 -0
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Screen-specific selection handlers for PluginTUI.
3
+ * Extracted to keep PluginTUI.tsx under 300 lines.
4
+ */
5
+
6
+ import type { IMenuSelectItem } from './MenuSelect.js';
7
+ import type { ICommandPluginAdapter } from '@robota-sdk/agent-interface-command';
8
+
9
+ interface IConfirmState {
10
+ message: string;
11
+ onConfirm: () => void;
12
+ onCancel: () => void;
13
+ }
14
+
15
+ interface IMenuContext {
16
+ marketplace?: string;
17
+ pluginId?: string;
18
+ }
19
+
20
+ interface INavActions {
21
+ push: (state: { screen: string; context?: IMenuContext }) => void;
22
+ pop: () => void;
23
+ popN: (n: number) => void;
24
+ notify: (content: string) => void;
25
+ setConfirm: (state: IConfirmState | undefined) => void;
26
+ refresh: () => void;
27
+ }
28
+
29
+ export function handleMainSelect(value: string, nav: Pick<INavActions, 'push'>): void {
30
+ if (value === 'marketplace') {
31
+ nav.push({ screen: 'marketplace-list' });
32
+ } else if (value === 'installed') {
33
+ nav.push({ screen: 'installed-list' });
34
+ }
35
+ }
36
+
37
+ export function handleMarketplaceListSelect(value: string, nav: Pick<INavActions, 'push'>): void {
38
+ if (value === '__add__') {
39
+ nav.push({ screen: 'marketplace-add' });
40
+ } else {
41
+ nav.push({ screen: 'marketplace-action', context: { marketplace: value } });
42
+ }
43
+ }
44
+
45
+ export function handleMarketplaceActionSelect(
46
+ value: string,
47
+ marketplace: string,
48
+ callbacks: ICommandPluginAdapter,
49
+ nav: Pick<INavActions, 'push' | 'pop' | 'popN' | 'notify' | 'setConfirm'>,
50
+ ): void {
51
+ if (value === 'browse') {
52
+ nav.push({ screen: 'marketplace-browse', context: { marketplace } });
53
+ } else if (value === 'update') {
54
+ callbacks
55
+ .marketplaceUpdate(marketplace)
56
+ .then(() => {
57
+ nav.notify(`Updated marketplace "${marketplace}".`);
58
+ nav.pop();
59
+ })
60
+ .catch((err) => {
61
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
62
+ });
63
+ } else if (value === 'remove') {
64
+ nav.setConfirm({
65
+ message: `Remove marketplace "${marketplace}" and all its plugins?`,
66
+ onConfirm: () => {
67
+ nav.setConfirm(undefined);
68
+ callbacks
69
+ .marketplaceRemove(marketplace)
70
+ .then(() => {
71
+ nav.notify(`Removed marketplace "${marketplace}".`);
72
+ nav.popN(2);
73
+ })
74
+ .catch((err) => {
75
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
76
+ });
77
+ },
78
+ onCancel: () => nav.setConfirm(undefined),
79
+ });
80
+ }
81
+ }
82
+
83
+ export function handleMarketplaceBrowseSelect(
84
+ value: string,
85
+ marketplace: string,
86
+ items: IMenuSelectItem[],
87
+ nav: Pick<INavActions, 'push'>,
88
+ ): void {
89
+ const fullId = `${value}@${marketplace}`;
90
+ const item = items.find((i) => i.value === value);
91
+ if (item?.hint === 'installed') {
92
+ nav.push({ screen: 'installed-action', context: { pluginId: fullId } });
93
+ } else {
94
+ nav.push({ screen: 'marketplace-install-scope', context: { marketplace, pluginId: fullId } });
95
+ }
96
+ }
97
+
98
+ export function handleInstallScopeSelect(
99
+ value: string,
100
+ pluginId: string,
101
+ callbacks: ICommandPluginAdapter,
102
+ nav: Pick<INavActions, 'popN' | 'notify'>,
103
+ ): void {
104
+ const scope = value as 'user' | 'project';
105
+ callbacks
106
+ .install(pluginId, scope)
107
+ .then(() => {
108
+ nav.notify(`Installed plugin "${pluginId}" (${scope} scope).`);
109
+ nav.popN(2);
110
+ })
111
+ .catch((err) => {
112
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
113
+ });
114
+ }
115
+
116
+ export function handleInstalledListSelect(
117
+ value: string,
118
+ callbacks: ICommandPluginAdapter,
119
+ nav: Pick<INavActions, 'notify' | 'setConfirm' | 'refresh'>,
120
+ ): void {
121
+ nav.setConfirm({
122
+ message: `Uninstall plugin "${value}"?`,
123
+ onConfirm: () => {
124
+ nav.setConfirm(undefined);
125
+ callbacks
126
+ .uninstall(value)
127
+ .then(() => {
128
+ nav.notify(`Uninstalled plugin "${value}".`);
129
+ nav.refresh();
130
+ })
131
+ .catch((err) => {
132
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
133
+ });
134
+ },
135
+ onCancel: () => nav.setConfirm(undefined),
136
+ });
137
+ }
138
+
139
+ export function handleInstalledActionSelect(
140
+ value: string,
141
+ pluginId: string,
142
+ callbacks: ICommandPluginAdapter,
143
+ nav: Pick<INavActions, 'popN' | 'notify' | 'setConfirm'>,
144
+ ): void {
145
+ if (value === 'uninstall') {
146
+ nav.setConfirm({
147
+ message: `Uninstall plugin "${pluginId}"?`,
148
+ onConfirm: () => {
149
+ nav.setConfirm(undefined);
150
+ callbacks
151
+ .uninstall(pluginId)
152
+ .then(() => {
153
+ nav.notify(`Uninstalled plugin "${pluginId}".`);
154
+ nav.popN(2);
155
+ })
156
+ .catch((err) => {
157
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
158
+ });
159
+ },
160
+ onCancel: () => nav.setConfirm(undefined),
161
+ });
162
+ }
163
+ }
@@ -0,0 +1,30 @@
1
+ import React, { createContext, useContext } from 'react';
2
+
3
+ const ProductDisplayNameContext = createContext('Assistant');
4
+ const ModelCommandToolPrefixContext = createContext<string | undefined>(undefined);
5
+
6
+ export function ProductDisplayNameProvider({
7
+ name,
8
+ modelCommandToolPrefix,
9
+ children,
10
+ }: {
11
+ name?: string;
12
+ modelCommandToolPrefix?: string;
13
+ children: React.ReactNode;
14
+ }): React.ReactElement {
15
+ return (
16
+ <ProductDisplayNameContext.Provider value={name?.trim() || 'Assistant'}>
17
+ <ModelCommandToolPrefixContext.Provider value={modelCommandToolPrefix}>
18
+ {children}
19
+ </ModelCommandToolPrefixContext.Provider>
20
+ </ProductDisplayNameContext.Provider>
21
+ );
22
+ }
23
+
24
+ export function useProductDisplayName(): string {
25
+ return useContext(ProductDisplayNameContext);
26
+ }
27
+
28
+ export function useModelCommandToolPrefix(): string | undefined {
29
+ return useContext(ModelCommandToolPrefixContext);
30
+ }
@@ -0,0 +1,209 @@
1
+ import { marked } from 'marked';
2
+ // @ts-expect-error — marked-terminal has no type declarations
3
+ import TerminalRenderer from 'marked-terminal';
4
+
5
+ import { sanitizeTerminalText } from './sanitize-terminal-text.js';
6
+ import { isInteractiveColorTerminal } from './terminal-capabilities.js';
7
+ import {
8
+ diffRowStyles,
9
+ markdownRendererOptions,
10
+ resolveTheme,
11
+ syntaxHighlightTheme,
12
+ } from './theme/index.js';
13
+
14
+ import type { IDiffRowStyles, ITuiTheme } from './theme/index.js';
15
+ import type { Renderer } from 'marked';
16
+
17
+ const CODE_BLOCK_INDENT = ' ';
18
+
19
+ export interface IRenderMarkdownOptions {
20
+ color?: boolean;
21
+ codeBlockWidth?: number;
22
+ /**
23
+ * SCREEN-2002: the resolved theme. Absent ⇒ the `dark` built-in, which is byte-identical to what
24
+ * this renderer produced before themes existed.
25
+ */
26
+ theme?: ITuiTheme;
27
+ /** SCREEN-2002: `false` renders code blocks as plain indented text, with no highlight SGR. */
28
+ syntaxHighlighting?: boolean;
29
+ /**
30
+ * CLI-2004: flatten tables to `Header: value` sentences. A box-drawn grid is unreadable aloud —
31
+ * a reader announces the rules, not the relationship between a heading and its cell.
32
+ */
33
+ screenReader?: boolean;
34
+ }
35
+
36
+ interface IHighlightOptions {
37
+ ignoreIllegals?: boolean;
38
+ /** The cli-highlight theme marked-terminal forwards; absent ⇒ its red/green defaults. */
39
+ theme?: Record<string, (text: string) => string>;
40
+ }
41
+
42
+ type TTerminalRendererConstructor = new (
43
+ /** `marked-terminal`'s style options: one chalk instance per token it colours. */
44
+ options?: Record<string, (text: string) => string>,
45
+ highlightOptions?: IHighlightOptions,
46
+ ) => Renderer;
47
+
48
+ const TerminalRendererConstructor = TerminalRenderer as TTerminalRendererConstructor;
49
+
50
+ function shouldUseColor(option: boolean | undefined): boolean {
51
+ if (option !== undefined) {
52
+ return option;
53
+ }
54
+ return isInteractiveColorTerminal();
55
+ }
56
+
57
+ function isDiffLanguage(language: string | undefined): boolean {
58
+ return language?.trim().toLowerCase() === 'diff';
59
+ }
60
+
61
+ function styleAddedOrRemovedDiffRow(
62
+ line: string,
63
+ rowWidth: number,
64
+ color: boolean,
65
+ styles: IDiffRowStyles,
66
+ ): string {
67
+ const row = `${CODE_BLOCK_INDENT}${line}`.padEnd(rowWidth);
68
+ if (!color) {
69
+ return row.trimEnd();
70
+ }
71
+ if (line.startsWith('+')) return styles.added(row);
72
+ if (line.startsWith('-')) return styles.removed(row);
73
+ return row.trimEnd();
74
+ }
75
+
76
+ function colorizeDiffLine(
77
+ line: string,
78
+ color: boolean,
79
+ rowWidth: number,
80
+ styles: IDiffRowStyles,
81
+ ): string {
82
+ if (line.startsWith('+') || line.startsWith('-')) {
83
+ return styleAddedOrRemovedDiffRow(line, rowWidth, color, styles);
84
+ }
85
+ const row = `${CODE_BLOCK_INDENT}${line}`;
86
+ if (!color) {
87
+ return row;
88
+ }
89
+ if (line.startsWith('@@')) return styles.hunk(row);
90
+ if (line.startsWith('diff ') || line.startsWith('index ')) return styles.meta(row);
91
+ return row;
92
+ }
93
+
94
+ function resolveDiffRowWidth(lines: readonly string[], requestedWidth: number | undefined): number {
95
+ const minimumWidth = lines.reduce(
96
+ (maxWidth, line) => Math.max(maxWidth, CODE_BLOCK_INDENT.length + line.length),
97
+ 0,
98
+ );
99
+ if (requestedWidth === undefined) {
100
+ return minimumWidth;
101
+ }
102
+ return Math.max(minimumWidth, requestedWidth);
103
+ }
104
+
105
+ function renderDiffCodeBlock(
106
+ code: string,
107
+ color: boolean,
108
+ codeBlockWidth: number | undefined,
109
+ styles: IDiffRowStyles,
110
+ ): string {
111
+ const lines = code.split('\n');
112
+ const rowWidth = resolveDiffRowWidth(lines, codeBlockWidth);
113
+ const body = lines.map((line) => colorizeDiffLine(line, color, rowWidth, styles)).join('\n');
114
+ return `${body}\n\n`;
115
+ }
116
+
117
+ /**
118
+ * CLI-2004 — flatten a table to `Header: value` lines.
119
+ *
120
+ * The cell and row hooks tag their output with separators marked's own renderers never emit, so the
121
+ * table hook can recover the grid it was given. Reconstructing it from the RENDERED strings is the
122
+ * alternative, and those already carry the SGR the inline renderers added.
123
+ */
124
+ const CELL_MARK = '\u0000';
125
+ const ROW_MARK = '\u0001';
126
+
127
+ function splitTableCells(row: string): string[] {
128
+ const cells = row.split(ROW_MARK).join('').split(CELL_MARK);
129
+ // The trailing separator leaves one empty tail entry; the cells themselves may legitimately be ''.
130
+ return cells.slice(0, -1);
131
+ }
132
+
133
+ function installTableFlattening(renderer: Renderer): void {
134
+ renderer.tablecell = (content: string): string => `${content}${CELL_MARK}`;
135
+ renderer.tablerow = (content: string): string => `${content}${ROW_MARK}`;
136
+ renderer.table = (header: string, body: string): string => {
137
+ const headers = splitTableCells(header);
138
+ const rows = body
139
+ .split(ROW_MARK)
140
+ .filter((row) => row.length > 0)
141
+ .map((row) => splitTableCells(`${row}${ROW_MARK}`));
142
+ if (headers.length === 0 || rows.length === 0) return '';
143
+ const blocks = rows.map((cells) =>
144
+ headers.map((head, index) => `${head}: ${cells[index] ?? ''}`).join('\n'),
145
+ );
146
+ // A blank line between rows: the row boundary is the only structure left to hear.
147
+ return `${blocks.join('\n\n')}\n\n`;
148
+ };
149
+ }
150
+
151
+ function createTerminalRenderer(
152
+ color: boolean,
153
+ codeBlockWidth: number | undefined,
154
+ screenReader: boolean,
155
+ theme: ITuiTheme,
156
+ syntaxHighlighting: boolean,
157
+ ): Renderer {
158
+ // SCREEN-2002: both dependencies are told what to colour with. Left to their own defaults, a
159
+ // daltonized theme would still get green/red code blocks from cli-highlight's own theme.
160
+ const renderer = new TerminalRendererConstructor(markdownRendererOptions(theme.markdown), {
161
+ ignoreIllegals: true,
162
+ theme: syntaxHighlightTheme(theme.syntax),
163
+ });
164
+ const renderCode = renderer.code.bind(renderer);
165
+ const styles = diffRowStyles(theme);
166
+
167
+ renderer.code = (code: string, language: string | undefined, escaped: boolean): string => {
168
+ if (isDiffLanguage(language)) {
169
+ return renderDiffCodeBlock(code, color, codeBlockWidth, styles);
170
+ }
171
+ // Highlighting off: the block renders as plain indented text, the shape the diff path uses.
172
+ if (!syntaxHighlighting) {
173
+ return `${code
174
+ .split('\n')
175
+ .map((line) => `${CODE_BLOCK_INDENT}${line}`)
176
+ .join('\n')}\n\n`;
177
+ }
178
+ return renderCode(code, language, escaped);
179
+ };
180
+
181
+ if (screenReader) installTableFlattening(renderer);
182
+
183
+ return renderer;
184
+ }
185
+
186
+ /**
187
+ * Render markdown to a terminal-formatted string with colors, bold, etc.
188
+ * Returns the rendered string (may include ANSI escape codes).
189
+ */
190
+ export function renderMarkdown(md: string, options: IRenderMarkdownOptions = {}): string {
191
+ // SEC-019 (issue #2022): the untrusted string is sanitized BEFORE parsing, never after. The
192
+ // renderer ADDS ANSI — colours, bold, code-block framing — so filtering its output would strip the
193
+ // repository's own presentation along with the attacker's. Filtering its input removes what
194
+ // arrived from outside and leaves what is generated after.
195
+ //
196
+ // This is the choke point every render path shares (MessageList, StreamingIndicator,
197
+ // ToolDiffBlock), which is why it is here rather than at each of the three.
198
+ const safe = sanitizeTerminalText(md);
199
+ const result = marked.parse(safe, {
200
+ renderer: createTerminalRenderer(
201
+ shouldUseColor(options.color),
202
+ options.codeBlockWidth,
203
+ options.screenReader === true,
204
+ resolveTheme(options.theme),
205
+ options.syntaxHighlighting !== false,
206
+ ),
207
+ });
208
+ return typeof result === 'string' ? result.trimEnd() : safe;
209
+ }