@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
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "@robota-sdk/agent-ui-terminal",
3
+ "version": "3.0.0-beta.81",
4
+ "description": "Terminal UI package (React + Ink) for the Robota SDK",
5
+ "type": "module",
6
+ "main": "dist/node/index.js",
7
+ "types": "dist/node/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "source": "./src/index.ts",
11
+ "node": {
12
+ "import": {
13
+ "types": "./dist/node/index.d.ts",
14
+ "default": "./dist/node/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/node/index.d.cts",
18
+ "default": "./dist/node/index.cjs"
19
+ }
20
+ },
21
+ "default": {
22
+ "import": {
23
+ "types": "./dist/node/index.d.ts",
24
+ "default": "./dist/node/index.js"
25
+ },
26
+ "require": {
27
+ "types": "./dist/node/index.d.cts",
28
+ "default": "./dist/node/index.cjs"
29
+ }
30
+ }
31
+ }
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/woojubb/robota.git"
36
+ },
37
+ "homepage": "https://robota.io/",
38
+ "bugs": {
39
+ "url": "https://github.com/woojubb/robota/issues"
40
+ },
41
+ "engines": {
42
+ "node": ">=22.0.0"
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "src"
47
+ ],
48
+ "dependencies": {
49
+ "chalk": "^5.6.2",
50
+ "ink": "^7.1.1",
51
+ "marked": "^9.1.6",
52
+ "marked-terminal": "^7.3.0",
53
+ "react": "19.2.8",
54
+ "string-width": "^8.2.2",
55
+ "@robota-sdk/agent-core": "3.0.0-beta.81",
56
+ "@robota-sdk/agent-interface-analytics": "3.0.0-beta.81",
57
+ "@robota-sdk/agent-interface-session": "3.0.0-beta.81",
58
+ "@robota-sdk/agent-framework": "3.0.0-beta.81",
59
+ "@robota-sdk/agent-interface-command": "3.0.0-beta.81",
60
+ "@robota-sdk/agent-interface-execution": "3.0.0-beta.81",
61
+ "@robota-sdk/agent-interface-tui": "3.0.0-beta.81",
62
+ "@robota-sdk/agent-interface-transport": "3.0.0-beta.81"
63
+ },
64
+ "devDependencies": {
65
+ "@homebridge/node-pty-prebuilt-multiarch": "^0.14.1",
66
+ "@types/react": "^19.2.17",
67
+ "ink-testing-library": "^4.0.0",
68
+ "rimraf": "^5.0.10",
69
+ "tsdown": "^0.22.14",
70
+ "tsx": "^4.23.1",
71
+ "typescript": "^6.0.3",
72
+ "vitest": "^3.2.6",
73
+ "@robota-sdk/agent-command": "3.0.0-beta.81"
74
+ },
75
+ "license": "AGPL-3.0-only OR LicenseRef-Commercial",
76
+ "publishConfig": {
77
+ "access": "public"
78
+ },
79
+ "volta": {
80
+ "extends": "../../package.json"
81
+ },
82
+ "scripts": {
83
+ "build": "rimraf dist && tsdown",
84
+ "build:js": "pnpm run build",
85
+ "build:types": "pnpm run build",
86
+ "typecheck": "tsgo --noEmit && tsgo -p tsconfig.examples.json --noEmit",
87
+ "test": "vitest run --passWithNoTests",
88
+ "test:coverage": "vitest run --coverage --passWithNoTests",
89
+ "scenario:verify": "pnpm exec tsx --conditions=source examples/verify-session-event-rendering.ts",
90
+ "test:pty": "vitest run --config vitest.pty.config.ts",
91
+ "clean": "rimraf dist"
92
+ }
93
+ }
package/src/App.tsx ADDED
@@ -0,0 +1,146 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+
3
+ import AppView from './AppView.js';
4
+ import { TuiCliAdapterProvider } from './tui-cli-adapter-context.js';
5
+
6
+ import type { IThemeRegistry } from './theme/theme-registry.js';
7
+ import type { ITuiAppChannelPort } from './tui-app-channel-port.js';
8
+ import type { ITuiCliAdapter } from './tui-cli-adapter.js';
9
+ import type { TPermissionMode } from '@robota-sdk/agent-core';
10
+ import type { TReducedMotionOverride } from '@robota-sdk/agent-interface-command';
11
+ import type { ICommandPluginAdapter } from '@robota-sdk/agent-interface-command';
12
+ import type { IInteractiveSessionStore } from '@robota-sdk/agent-interface-session';
13
+ import type { IPromptHistorySource } from '@robota-sdk/agent-interface-session';
14
+ import type { ITransportRegistryView } from '@robota-sdk/agent-interface-transport';
15
+
16
+ interface IProps {
17
+ cwd: string;
18
+ /** The composition root narrows every concrete channel to this port before React receives it. */
19
+ createChannel: (resumeSessionId?: string) => ITuiAppChannelPort;
20
+ providerOverride?: string | undefined;
21
+ providerType?: string | undefined;
22
+ modelId?: string;
23
+ permissionMode?: TPermissionMode;
24
+ version?: string;
25
+ sessionStore?: IInteractiveSessionStore;
26
+ resumeSessionId?: string;
27
+ showSessionPickerOnStart?: boolean;
28
+ /** FLOW-2006: a deep link's prefill, consumed once by the composer. */
29
+ initialInput?: string;
30
+ initialInputOrigin?: 'external-link';
31
+ startupUpdateNotice?: Promise<string | undefined>;
32
+ transportRegistry?: ITransportRegistryView;
33
+ pluginAdapter?: ICommandPluginAdapter;
34
+ cliAdapter: ITuiCliAdapter;
35
+ /** SCREEN-1993: the stored-prompt source and project key for the input area's search. */
36
+ promptHistorySource?: IPromptHistorySource;
37
+ promptHistoryProject?: string;
38
+ /** SCREEN-2002: the theme catalogue this run renders from; absent ⇒ the built-ins. */
39
+ themeRegistry?: IThemeRegistry;
40
+ /** SCREEN-2002: reduced motion as the product shell resolved it (settings ← env ← flag). */
41
+ reducedMotion?: boolean | undefined;
42
+ /** SCREEN-2002: which tier decided it, when that was not the settings. */
43
+ reducedMotionOverride?: TReducedMotionOverride | undefined;
44
+ }
45
+
46
+ interface IActiveChannel {
47
+ readonly state: { channel: ITuiAppChannelPort; sessionId: string | undefined };
48
+ readonly showPicker: boolean;
49
+ readonly error: string | undefined;
50
+ readonly switching: boolean;
51
+ readonly switchSession: (sessionId: string) => Promise<void>;
52
+ readonly retrySwitch: () => void;
53
+ }
54
+
55
+ function useActiveChannel(props: IProps): IActiveChannel {
56
+ const [state, setState] = useState(() => ({
57
+ channel: props.createChannel(props.resumeSessionId),
58
+ sessionId: props.resumeSessionId,
59
+ }));
60
+ const [showPicker, setShowPicker] = useState(props.showSessionPickerOnStart ?? false);
61
+ const [error, setError] = useState<string | undefined>();
62
+ const [isSwitching, setIsSwitching] = useState(false);
63
+ const [failedTarget, setFailedTarget] = useState<string | undefined>();
64
+ const switching = useRef(false);
65
+ const mounted = useRef(true);
66
+ useEffect(
67
+ () => () => {
68
+ mounted.current = false;
69
+ },
70
+ [],
71
+ );
72
+ const switchSession = useCallback(
73
+ async (sessionId: string): Promise<void> => {
74
+ if (switching.current) return;
75
+ switching.current = true;
76
+ setIsSwitching(true);
77
+ try {
78
+ setShowPicker(false);
79
+ await state.channel.stop();
80
+ if (!mounted.current) return;
81
+ setState({ channel: props.createChannel(sessionId), sessionId });
82
+ setFailedTarget(undefined);
83
+ setError(undefined);
84
+ } catch (cause) {
85
+ const message = cause instanceof Error ? cause.message : String(cause);
86
+ setError(`Session switch failed: ${message}`);
87
+ setFailedTarget(sessionId);
88
+ } finally {
89
+ switching.current = false;
90
+ setIsSwitching(false);
91
+ }
92
+ },
93
+ [props.createChannel, state.channel],
94
+ );
95
+ const retrySwitch = useCallback((): void => {
96
+ if (failedTarget !== undefined) void switchSession(failedTarget);
97
+ }, [failedTarget, switchSession]);
98
+ return { state, showPicker, error, switching: isSwitching, switchSession, retrySwitch };
99
+ }
100
+
101
+ /** React composition shell. Concrete framework objects stay outside the React tree. */
102
+ export default function App(props: IProps): React.ReactElement {
103
+ const active = useActiveChannel(props);
104
+
105
+ return (
106
+ <TuiCliAdapterProvider value={props.cliAdapter}>
107
+ <AppView
108
+ key={active.state.sessionId ?? '__new__'}
109
+ cwd={props.cwd}
110
+ channel={active.state.channel}
111
+ onSessionSwitch={active.switchSession}
112
+ onRetrySessionSwitch={active.retrySwitch}
113
+ sessionSwitchError={active.error}
114
+ sessionSwitchPending={active.switching}
115
+ showSessionPickerOnStart={active.showPicker}
116
+ {...(props.initialInput !== undefined ? { initialInput: props.initialInput } : {})}
117
+ {...(props.initialInputOrigin !== undefined
118
+ ? { initialInputOrigin: props.initialInputOrigin }
119
+ : {})}
120
+ {...(props.providerType !== undefined ? { providerType: props.providerType } : {})}
121
+ {...(props.modelId !== undefined ? { modelId: props.modelId } : {})}
122
+ {...(props.permissionMode !== undefined ? { permissionMode: props.permissionMode } : {})}
123
+ {...(props.version !== undefined ? { version: props.version } : {})}
124
+ {...(props.sessionStore !== undefined ? { sessionStore: props.sessionStore } : {})}
125
+ {...(props.startupUpdateNotice !== undefined
126
+ ? { startupUpdateNotice: props.startupUpdateNotice }
127
+ : {})}
128
+ {...(props.transportRegistry !== undefined
129
+ ? { transportRegistry: props.transportRegistry }
130
+ : {})}
131
+ {...(props.pluginAdapter !== undefined ? { pluginAdapter: props.pluginAdapter } : {})}
132
+ {...(props.promptHistorySource !== undefined
133
+ ? { promptHistorySource: props.promptHistorySource }
134
+ : {})}
135
+ {...(props.promptHistoryProject !== undefined
136
+ ? { promptHistoryProject: props.promptHistoryProject }
137
+ : {})}
138
+ {...(props.themeRegistry !== undefined ? { themeRegistry: props.themeRegistry } : {})}
139
+ {...(props.reducedMotion !== undefined ? { reducedMotion: props.reducedMotion } : {})}
140
+ {...(props.reducedMotionOverride !== undefined
141
+ ? { reducedMotionOverride: props.reducedMotionOverride }
142
+ : {})}
143
+ />
144
+ </TuiCliAdapterProvider>
145
+ );
146
+ }
@@ -0,0 +1,190 @@
1
+ import { Box, Static } from 'ink';
2
+ import React from 'react';
3
+
4
+ import { AppBanner } from './app-banner.js';
5
+ import BackgroundTaskPanel from './BackgroundTaskPanel.js';
6
+ import { ContextWarningBanner } from './ContextWarningBanner.js';
7
+ import ExecutionWorkspaceDetailPane from './ExecutionWorkspaceDetailPane.js';
8
+ import ExecutionWorkspaceSwitcher from './ExecutionWorkspaceSwitcher.js';
9
+ import InputArea from './InputArea.js';
10
+ import { useKeybindings } from './keybindings/keybindings-context.js';
11
+ import { EntryItem } from './MessageList.js';
12
+ import PendingActionPrompt from './PendingActionPrompt.js';
13
+ import PermissionPrompt from './PermissionPrompt.js';
14
+ import PluginTUI from './PluginTUI.js';
15
+ import { Text } from './SafeText.js';
16
+ import SessionEventNotices from './SessionEventNotices.js';
17
+ import SessionPicker from './SessionPicker.js';
18
+ import SessionStatusBar from './SessionStatusBar.js';
19
+ import StreamingIndicator from './StreamingIndicator.js';
20
+ import { usePalette } from './theme/index.js';
21
+ import ThemePicker from './ThemePicker.js';
22
+ import TransportTUI from './TransportTUI.js';
23
+ import UpdateNotice from './UpdateNotice.js';
24
+
25
+ import type { IAppViewModel } from './app-view-model.js';
26
+
27
+ function Transcript({ model }: { model: IAppViewModel }): React.ReactElement {
28
+ return (
29
+ <Static items={model.staticItems}>
30
+ {(item) =>
31
+ item.kind === 'banner' ? (
32
+ <AppBanner key="logo" version={item.version} />
33
+ ) : (
34
+ <EntryItem key={item.entry.id} entry={item.entry} />
35
+ )
36
+ }
37
+ </Static>
38
+ );
39
+ }
40
+
41
+ function Activity({ model }: { model: IAppViewModel }): React.ReactElement {
42
+ const palette = usePalette();
43
+ const { background, stream } = model;
44
+ return (
45
+ <Box flexDirection="column" paddingX={1} flexGrow={1}>
46
+ {background.selectedEntry && background.selectedEntry.kind !== 'main_thread' && (
47
+ <ExecutionWorkspaceDetailPane
48
+ entry={background.selectedEntry}
49
+ page={background.detail.page}
50
+ loading={background.detail.loading}
51
+ error={background.detail.error}
52
+ />
53
+ )}
54
+ {model.isShuttingDown && <Text color={palette.text.warning}>Shutting down...</Text>}
55
+ {(stream.isThinking || stream.activeTools.length > 0) && (
56
+ <Box flexDirection="column" marginBottom={1}>
57
+ <StreamingIndicator
58
+ text={stream.text}
59
+ activeTools={stream.activeTools}
60
+ isThinking={stream.isThinking}
61
+ />
62
+ {stream.isStalled && (
63
+ <Text color={palette.text.warning}>
64
+ ⚠ Still waiting on the provider — the network may be stalled. Esc to interrupt.
65
+ </Text>
66
+ )}
67
+ </Box>
68
+ )}
69
+ {!stream.isThinking && stream.lastErrorMessage && (
70
+ <Text color={palette.text.error}>
71
+ ✖ Last turn failed — the session is alive; type your next prompt when ready.
72
+ </Text>
73
+ )}
74
+ <BackgroundTaskPanel entries={background.entries} focusedIndex={background.focusedIndex} />
75
+ </Box>
76
+ );
77
+ }
78
+
79
+ function Overlays({ model }: { model: IAppViewModel }): React.ReactElement {
80
+ if (model.coordinationError !== undefined || model.coordinationPending) return <></>;
81
+ const { background, plugin, sessionPicker, transport } = model;
82
+ const pendingUserAction = model.pendingUserAction;
83
+ return (
84
+ <>
85
+ {background.switcherVisible && (
86
+ <ExecutionWorkspaceSwitcher
87
+ snapshot={background.snapshot}
88
+ selectedEntryId={background.selectedEntryId}
89
+ onSelect={background.select}
90
+ onClose={background.closeSwitcher}
91
+ onAttach={background.attachToFork}
92
+ />
93
+ )}
94
+ {model.permissionRequest && <PermissionPrompt request={model.permissionRequest} />}
95
+ {pendingUserAction && (
96
+ <PendingActionPrompt
97
+ request={pendingUserAction}
98
+ onAnswer={(response) => model.resolveUserAction(pendingUserAction, response)}
99
+ />
100
+ )}
101
+ {plugin.visible && (
102
+ <PluginTUI
103
+ callbacks={plugin.callbacks}
104
+ onClose={plugin.close}
105
+ addMessage={(message) => plugin.addMessage(message.content)}
106
+ />
107
+ )}
108
+ {transport.visible && transport.registry && (
109
+ <TransportTUI registry={transport.registry} onClose={transport.close} />
110
+ )}
111
+ {sessionPicker.visible && (
112
+ <SessionPicker
113
+ sessions={sessionPicker.sessions}
114
+ onSelect={sessionPicker.select}
115
+ onCancel={sessionPicker.cancel}
116
+ />
117
+ )}
118
+ {model.theme.picker.visible && <ThemePicker picker={model.theme.picker} />}
119
+ </>
120
+ );
121
+ }
122
+
123
+ function PromptAndStatus({ model }: { model: IAppViewModel }): React.ReactElement {
124
+ const { input } = model;
125
+ return (
126
+ <>
127
+ <ContextWarningBanner percentage={model.contextPercentage} />
128
+ <InputArea
129
+ onSubmit={input.submit}
130
+ onCancelQueue={input.cancelQueue}
131
+ isDisabled={input.disabled}
132
+ isQueueCancellationDisabled={input.queueCancellationDisabled}
133
+ isAborting={input.isAborting}
134
+ pendingPrompt={input.pendingPrompt}
135
+ pendingCount={input.pendingCount}
136
+ commandQueryPort={input.commandQueryPort}
137
+ sessionName={input.sessionName}
138
+ history={input.history}
139
+ onRequestFocusBackgroundList={input.focusBackgroundList}
140
+ historySearch={input.historySearch}
141
+ initialValue={input.initialValue}
142
+ consumeInitialValue={input.consumeInitialValue}
143
+ externalPromptOrigin={input.externalPromptOrigin}
144
+ />
145
+ <SessionStatusBar {...model.status} />
146
+ </>
147
+ );
148
+ }
149
+
150
+ /** Pure presentation tree: it receives no channel, session, registry, or mutable state manager. */
151
+ export default function AppPresentation({
152
+ viewModel,
153
+ }: {
154
+ viewModel: IAppViewModel;
155
+ }): React.ReactElement {
156
+ const palette = usePalette();
157
+ const keybindings = useKeybindings();
158
+ return (
159
+ <Box flexDirection="column">
160
+ <Transcript model={viewModel} />
161
+ {!viewModel.handoffSuspended && (
162
+ <>
163
+ {viewModel.updateNotice && <UpdateNotice message={viewModel.updateNotice} />}
164
+ {viewModel.coordinationError && (
165
+ <Text color={palette.text.error}>
166
+ {viewModel.coordinationError} Press Enter to retry.
167
+ </Text>
168
+ )}
169
+ {keybindings.diagnostic && (
170
+ <Text color={palette.text.error}>
171
+ {`Keybindings ${keybindings.diagnostic.file} ${keybindings.diagnostic.path}: ${keybindings.diagnostic.message} Last valid bindings remain active.`}
172
+ </Text>
173
+ )}
174
+ {keybindings.warnings.map((warning) => (
175
+ <Text key={`${warning.path}:${warning.code}`} color={palette.text.warning}>
176
+ {`Keybindings ${warning.path}: ${warning.message}`}
177
+ </Text>
178
+ ))}
179
+ {viewModel.theme.unknownThemeNotice !== undefined && (
180
+ <Text color={palette.text.warning}>{viewModel.theme.unknownThemeNotice}</Text>
181
+ )}
182
+ <SessionEventNotices notices={viewModel.sessionEventNotices} />
183
+ <Activity model={viewModel} />
184
+ <Overlays model={viewModel} />
185
+ <PromptAndStatus model={viewModel} />
186
+ </>
187
+ )}
188
+ </Box>
189
+ );
190
+ }
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+
3
+ import AppPresentation from './AppPresentation.js';
4
+ import { useAppController } from './hooks/useAppController.js';
5
+ import { ThemeProvider } from './theme/index.js';
6
+
7
+ import type { IUseAppControllerOptions } from './hooks/useAppController.js';
8
+
9
+ export type IAppViewProps = IUseAppControllerOptions;
10
+
11
+ /** Controller boundary: the channel terminates here and only a view model reaches presentation. */
12
+ export default function AppView(props: IAppViewProps): React.ReactElement {
13
+ const viewModel = useAppController(props);
14
+ // SCREEN-2002: the provider sits HERE rather than above the controller, because the resolved
15
+ // theme is part of the view model — a provider higher up would have to be fed a theme nobody had
16
+ // resolved yet, and the controller's own hooks read no colours.
17
+ return (
18
+ <ThemeProvider
19
+ theme={viewModel.theme.resolved}
20
+ reducedMotion={viewModel.theme.reducedMotion}
21
+ syntaxHighlighting={viewModel.theme.syntaxHighlighting}
22
+ >
23
+ <AppPresentation viewModel={viewModel} />
24
+ </ThemeProvider>
25
+ );
26
+ }
@@ -0,0 +1,73 @@
1
+ import { Box } from 'ink';
2
+ import React from 'react';
3
+
4
+ import { formatBackgroundTaskRow } from './background-task-row-format.js';
5
+ import { useCountdownTick } from './hooks/useCountdownTick.js';
6
+ import { Text } from './SafeText.js';
7
+ import { useScreenReader } from './screen-reader-context.js';
8
+ import { statusGlyphColor } from './status-glyph.js';
9
+ import { usePalette } from './theme/index.js';
10
+
11
+ import type { IExecutionWorkspaceEntry } from '@robota-sdk/agent-interface-execution';
12
+
13
+ interface IProps {
14
+ entries: IExecutionWorkspaceEntry[];
15
+ /** Index of the keyboard-focused row (SCREEN-014), or null when the prompt input is focused. */
16
+ focusedIndex?: number | null;
17
+ }
18
+
19
+ export default function BackgroundTaskPanel({
20
+ entries,
21
+ focusedIndex = null,
22
+ }: IProps): React.ReactElement | null {
23
+ const palette = usePalette();
24
+ const screenReader = useScreenReader();
25
+ // SCREEN-1992: ticks once a second only while a schedule sleeps, never in screen-reader mode.
26
+ const now = useCountdownTick(
27
+ entries.some((entry) => entry.nextFireAt !== undefined),
28
+ screenReader,
29
+ );
30
+ if (entries.length === 0) return null;
31
+
32
+ // SCREEN-014: the hint is focus-aware — it tells you how to enter the list, then how to move/open.
33
+ const hint =
34
+ focusedIndex !== null ? ' · ↑↓ select · Enter open · Esc back' : ' · ↓ select · Ctrl+B all';
35
+
36
+ return (
37
+ <Box flexDirection="column" marginBottom={1}>
38
+ <Box>
39
+ <Text color={palette.text.accent} bold>
40
+ Background work
41
+ </Text>
42
+ <Text dimColor>{hint}</Text>
43
+ </Box>
44
+ {entries.map((entry, index) => {
45
+ const row = formatBackgroundTaskRow(entry, {
46
+ isLast: index === entries.length - 1,
47
+ screenReader,
48
+ now,
49
+ });
50
+ const isFocused = index === focusedIndex;
51
+ return (
52
+ // SCREEN-011: one truncated line so the connector + glyph lead the row.
53
+ // SCREEN-014: the keyboard-focused row is inverse-highlighted.
54
+ <Text key={entry.id} wrap="truncate-end" inverse={isFocused}>
55
+ {`${row.connector} `}
56
+ <Text
57
+ color={statusGlyphColor(palette, row.statusKind)}
58
+ >{`${row.marker} ${row.state}`}</Text>
59
+ {` ${row.label}`}
60
+ {row.segments.map((segment, segmentIndex) => (
61
+ <Text key={`${segment}-${segmentIndex}`} dimColor>{` · ${segment}`}</Text>
62
+ ))}
63
+ {row.headline ? <Text dimColor>{` · ${row.headline}`}</Text> : null}
64
+ {row.preview ? <Text dimColor>{` · ${row.preview}`}</Text> : null}
65
+ {row.countdown ? (
66
+ <Text color={statusGlyphColor(palette, row.statusKind)}>{` · ${row.countdown}`}</Text>
67
+ ) : null}
68
+ </Text>
69
+ );
70
+ })}
71
+ </Box>
72
+ );
73
+ }