@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,177 @@
1
+ /**
2
+ * SCREEN-2002 — one theme, every encoding.
3
+ *
4
+ * Ink reads `colors` directly. `marked-terminal` wants chalk STYLE FUNCTIONS and `cli-highlight` wants
5
+ * a token→function map, so both are built here from the same values. Ink's own `colorize` is this
6
+ * mapping (`chalk[name]` / `chalk.hex` / `chalk.ansi256` / `chalk.rgb`), which is why deriving them is
7
+ * not the "name→SGR layer" SCREEN-006 refused to invent — chalk is already the layer, and a direct
8
+ * dependency.
9
+ *
10
+ * The STRUCTURE of a markdown or syntax token is fixed here, not themed: a heading is bold, a first
11
+ * heading underlined and bold, a blockquote italic, a deletion dim and struck through, an href
12
+ * underlined, and a syntax `type` dim — exactly what the two dependencies do today. A theme changes
13
+ * the colour and nothing else, which is what keeps the `dark` theme's rendering the one users know.
14
+ */
15
+ import chalk, { Chalk, backgroundColorNames, foregroundColorNames } from 'chalk';
16
+
17
+ import { THEME_SYNTAX_KEYS } from './theme-contracts.js';
18
+
19
+ import type { IThemeMarkdown, IThemeSyntax, ITuiTheme, TThemeColor } from './theme-contracts.js';
20
+ import type { ChalkInstance } from 'chalk';
21
+
22
+ const HEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/iu;
23
+ const ANSI256 = /^ansi256\((\d{1,3})\)$/iu;
24
+ const RGB = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/iu;
25
+ const ANSI256_MAX = 255;
26
+
27
+ /** The grammar, written once: the validator, the refusal below and the file parser all quote it. */
28
+ export const THEME_COLOR_GRAMMAR = '<chalk name> | #rgb | #rrggbb | ansi256(n) | rgb(r,g,b)';
29
+
30
+ /**
31
+ * chalk's own name tables, which it exports; its styles are prototype getters, so a table has to be
32
+ * built from the names rather than from `Object.keys(chalk)`. This is the same set Ink's `colorize`
33
+ * accepts, which is why the grammar needs no list of its own.
34
+ *
35
+ * A `Map`, not an object literal: an object built by `Object.fromEntries` inherits `Object.prototype`,
36
+ * so a lookup of `constructor`, `toString`, `valueOf` or `__proto__` answers TRUTHY and the
37
+ * `undefined` guard below never fires — a value that is not a colour name would be handed back as
38
+ * though it were a style. That value becomes reachable the moment a parsed user theme (work unit 3)
39
+ * reaches this builder.
40
+ */
41
+ const CHALK_STYLES = new Map<string, ChalkInstance>(
42
+ [...foregroundColorNames, ...backgroundColorNames].map((name) => [name, chalk[name]]),
43
+ );
44
+ const FOREGROUND_NAMES = new Set<string>(foregroundColorNames);
45
+
46
+ /** A FOREGROUND chalk name Ink would accept — a `bg…` name is not a token value. */
47
+ function isChalkColorName(value: string): boolean {
48
+ return FOREGROUND_NAMES.has(value);
49
+ }
50
+
51
+ /** Whether a string is a colour in Ink's grammar. The one place the grammar is decided. */
52
+ export function isThemeColor(value: string): boolean {
53
+ if (HEX.test(value)) return true;
54
+ const ansi = ANSI256.exec(value);
55
+ if (ansi) return Number(ansi[1]) <= ANSI256_MAX;
56
+ const rgb = RGB.exec(value);
57
+ if (rgb) return rgb.slice(1).every((part) => Number(part) <= ANSI256_MAX);
58
+ return isChalkColorName(value);
59
+ }
60
+
61
+ /**
62
+ * A value that reached the builder without being a colour is a DEFECT IN THIS PACKAGE, not in a
63
+ * user's file: `parseThemeDocument` refuses a theme file whole, and every built-in value is checked
64
+ * by TC-01. Before work unit 3 this case had two different silent answers — `foreground` handed
65
+ * back its base, `background`'s helper handed back `chalk.reset` — so the same bad value rendered
66
+ * as unstyled text in one place and as a reset in another, and neither said anything. Throwing is
67
+ * the one behaviour that cannot render a theme nobody can see is wrong.
68
+ */
69
+ function refuseThemeColor(value: string): never {
70
+ throw new Error(
71
+ `Theme colour ${JSON.stringify(value)} is not in Ink's colour grammar (${THEME_COLOR_GRAMMAR}).`,
72
+ );
73
+ }
74
+
75
+ /**
76
+ * A foreground style for one theme value, optionally continuing a chain. The base matters: chalk
77
+ * emits its SGR in chain order, so `chalk.dim` + a colour is not the same bytes as a colour + `.dim`,
78
+ * and reproducing a dependency's own default means reproducing its order.
79
+ */
80
+ export function foreground(color: TThemeColor, base: ChalkInstance = chalk): ChalkInstance {
81
+ if (HEX.test(color)) return base.hex(color);
82
+ const ansi = ANSI256.exec(color);
83
+ if (ansi) return base.ansi256(Number(ansi[1]));
84
+ const rgb = RGB.exec(color);
85
+ if (rgb) return base.rgb(Number(rgb[1]), Number(rgb[2]), Number(rgb[3]));
86
+ // Gated on the FOREGROUND names, not on the shared table: the table also holds the `bg…` entries
87
+ // that `background()` needs, so asking it alone would answer `foreground('bgRed')` with a
88
+ // background style — a name `isThemeColor` already refuses as a token value.
89
+ const named = isChalkColorName(color) ? CHALK_STYLES.get(color) : undefined;
90
+ if (named === undefined) refuseThemeColor(color);
91
+ // A named colour continues the chain through chalk's own getter on the base instance.
92
+ const chained = Reflect.get(base, color) as ChalkInstance | undefined;
93
+ return chained ?? named;
94
+ }
95
+
96
+ /** A background style for one theme value. */
97
+ export function background(color: TThemeColor, base: ChalkInstance = chalk): ChalkInstance {
98
+ if (HEX.test(color)) return base.bgHex(color);
99
+ const ansi = ANSI256.exec(color);
100
+ if (ansi) return base.bgAnsi256(Number(ansi[1]));
101
+ const rgb = RGB.exec(color);
102
+ if (rgb) return base.bgRgb(Number(rgb[1]), Number(rgb[2]), Number(rgb[3]));
103
+ const capitalised = `bg${color.charAt(0).toUpperCase()}${color.slice(1)}`;
104
+ const chained = Reflect.get(base, capitalised) as ChalkInstance | undefined;
105
+ return chained ?? refuseThemeColor(color);
106
+ }
107
+
108
+ /**
109
+ * The diff rows are written by this package, not by a dependency, and their caller decides colour
110
+ * with its own `color` flag — so a caller that asked for colour gets it even where chalk's own
111
+ * detection says level 0. That is the behaviour the renderer had when it wrote the escapes by hand,
112
+ * and the reason `renderMarkdown(md, { color: true })` still emits them off a TTY.
113
+ *
114
+ * What is forced is colour, NOT depth. Overriding a DETECTED level would downsample the rest of the
115
+ * frame while these rows alone emit truecolor — visible on a 256-colour terminal with a hex-valued
116
+ * theme, where `#56b4e9` is `ESC[38;5;117m` at level 2 and `ESC[38;2;86;180;233m` at level 3. So the
117
+ * ambient level is kept whenever there is one, and only the OFF case is overridden.
118
+ */
119
+ const TRUECOLOR = 3;
120
+ const FORCED_CHALK = new Chalk({ level: TRUECOLOR });
121
+
122
+ function diffRowBase(): ChalkInstance {
123
+ return chalk.level === 0 ? FORCED_CHALK : chalk;
124
+ }
125
+
126
+ /** The `marked-terminal` constructor options: theme colour + the structure that is not themed. */
127
+ export function markdownRendererOptions(markdown: IThemeMarkdown): Record<string, ChalkInstance> {
128
+ return {
129
+ // Each chain reproduces marked-terminal's own default order, so the `dark` theme is byte-identical.
130
+ heading: foreground(markdown.heading).bold,
131
+ firstHeading: foreground(markdown.firstHeading).underline.bold,
132
+ code: foreground(markdown.code),
133
+ codespan: foreground(markdown.codespan),
134
+ link: foreground(markdown.link),
135
+ href: foreground(markdown.href).underline,
136
+ blockquote: foreground(markdown.blockquote).italic,
137
+ del: foreground(markdown.del, chalk.dim).strikethrough,
138
+ html: foreground(markdown.html),
139
+ };
140
+ }
141
+
142
+ /** The `cli-highlight` theme: every one of its seventeen coloured keys, so none falls back. */
143
+ export function syntaxHighlightTheme(syntax: IThemeSyntax): Record<string, ChalkInstance> {
144
+ const theme: Record<string, ChalkInstance> = {};
145
+ for (const key of THEME_SYNTAX_KEYS) {
146
+ // `type` is `<colour>.dim` in cli-highlight's own theme: the modifier is structure, the colour is
147
+ // the theme's, and the order is the dependency's, so the `dark` theme is byte-identical.
148
+ theme[key] = key === 'type' ? foreground(syntax[key]).dim : foreground(syntax[key]);
149
+ }
150
+ return theme;
151
+ }
152
+
153
+ /** The styles the diff renderer writes around a row: background and foreground composed per row. */
154
+ export interface IDiffRowStyles {
155
+ readonly added: (row: string) => string;
156
+ readonly removed: (row: string) => string;
157
+ readonly hunk: (row: string) => string;
158
+ /** Structure only: dim over the inherited foreground, never a theme colour. */
159
+ readonly meta: (row: string) => string;
160
+ }
161
+
162
+ export function diffRowStyles(theme: ITuiTheme): IDiffRowStyles {
163
+ const { markdown } = theme;
164
+ const base = diffRowBase();
165
+ const addedBackground = background(markdown.diffAddedBackground, base);
166
+ const addedForeground = foreground(markdown.diffAdded, base);
167
+ const removedBackground = background(markdown.diffRemovedBackground, base);
168
+ const removedForeground = foreground(markdown.diffRemoved, base);
169
+ return {
170
+ added: (row) => addedBackground(addedForeground(row)),
171
+ removed: (row) => removedBackground(removedForeground(row)),
172
+ hunk: foreground(markdown.diffHunk, base),
173
+ // Dim over the INHERITED foreground, as this renderer always wrote it — colouring it would be a
174
+ // rendering change dressed as a theme.
175
+ meta: (row) => base.dim(row),
176
+ };
177
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Persisted tool-summary → status-SSOT mapping (SCREEN-006).
3
+ *
4
+ * MessageList used to hand-roll its own glyph/color mapping for tool summaries, which drifted
5
+ * from `status-glyph.ts` (denied rendered plain yellow vs the SSOT's yellowBright). These
6
+ * helpers route the persisted summary items through `toolStateStatusKind` + `STATUS_SYMBOL`
7
+ * so tool status renders one way everywhere.
8
+ */
9
+
10
+ import { humanizeToolArgument, humanizeToolName } from './humanize-tool-name.js';
11
+ import { STATUS_SYMBOL, toolStateStatusKind } from './status-glyph.js';
12
+
13
+ import type { TUiStatusKind } from './status-glyph.js';
14
+ import type { IToolCallSummary } from './utils/tool-call-extractor.js';
15
+ import type { IToolState } from '@robota-sdk/agent-interface-session';
16
+
17
+ /** A tool item as persisted in a `tool-summary` history entry (loosely-typed session data). */
18
+ export type TToolSummaryItem = {
19
+ toolName: string;
20
+ firstArg?: string;
21
+ isRunning?: boolean;
22
+ result?: string;
23
+ diffLines?: IToolCallSummary['diffLines'];
24
+ diffFile?: string;
25
+ toolResultData?: string;
26
+ };
27
+
28
+ /**
29
+ * Map a persisted tool-summary item to the semantic status kind — via the `status-glyph.ts`
30
+ * SSOT. A command-output error (non-zero exit surfaced by `formatCommandOutputSummary`)
31
+ * overrides the recorded result; the caller passes that flag so this module stays free of
32
+ * the command-output dependency.
33
+ */
34
+ export function toolSummaryStatusKind(
35
+ tool: TToolSummaryItem,
36
+ hasCommandOutputError: boolean,
37
+ ): TUiStatusKind {
38
+ if (hasCommandOutputError) return 'error';
39
+ const result: IToolState['result'] =
40
+ tool.result === 'success' || tool.result === 'error' || tool.result === 'denied'
41
+ ? tool.result
42
+ : undefined;
43
+ return toolStateStatusKind({
44
+ toolName: tool.toolName,
45
+ firstArg: tool.firstArg ?? '',
46
+ isRunning: tool.isRunning ?? false,
47
+ result,
48
+ });
49
+ }
50
+
51
+ /** One-line summary label: SSOT glyph + humanized tool name + first argument. */
52
+ export function getToolSummaryLabel(
53
+ tool: TToolSummaryItem,
54
+ kind: TUiStatusKind,
55
+ prefix?: string,
56
+ ): string {
57
+ const argument = humanizeToolArgument(tool.firstArg);
58
+ return `${STATUS_SYMBOL[kind]} ${humanizeToolName(tool.toolName, prefix)}${argument ? `(${argument})` : ''}`;
59
+ }
@@ -0,0 +1,83 @@
1
+ import type { TerminalHandoffController } from './terminal-handoff-controller.js';
2
+ import type { ITuiSessionEventNotice } from './tui-session-events.js';
3
+ import type { IPendingPermissionRequest } from './types.js';
4
+ import type {
5
+ IActionRequest,
6
+ IHistoryEntry,
7
+ TActionResponse,
8
+ TModelEffortSelection,
9
+ TSessionEndReason,
10
+ TPermissionMode,
11
+ } from '@robota-sdk/agent-core';
12
+ import type { ICommand } from '@robota-sdk/agent-interface-command';
13
+ import type {
14
+ IExecutionDetailPage,
15
+ IExecutionWorkspaceSnapshot,
16
+ } from '@robota-sdk/agent-interface-execution';
17
+ import type { IInteractiveSessionEvents, IToolState } from '@robota-sdk/agent-interface-session';
18
+
19
+ export interface ITuiCommandQueryPort {
20
+ getCommands(filter?: string): ICommand[];
21
+ getSubcommands(commandName: string): ICommand[];
22
+ }
23
+
24
+ export type TTuiSessionUiEventName = 'ui_intent' | 'session_renamed';
25
+
26
+ export interface ITuiSessionUiEventPort {
27
+ on<E extends TTuiSessionUiEventName>(event: E, handler: IInteractiveSessionEvents[E]): void;
28
+ off<E extends TTuiSessionUiEventName>(event: E, handler: IInteractiveSessionEvents[E]): void;
29
+ }
30
+
31
+ export interface ITuiRuntimeStatusSnapshot {
32
+ permissionMode: TPermissionMode;
33
+ sessionId: string;
34
+ activePresetId?: string;
35
+ effort?: TModelEffortSelection;
36
+ }
37
+
38
+ export interface ITuiChannelSnapshot {
39
+ history: readonly IHistoryEntry[];
40
+ streamingText: string;
41
+ activeTools: readonly IToolState[];
42
+ isThinking: boolean;
43
+ isAborting: boolean;
44
+ lastErrorMessage: string | null;
45
+ isStalled: boolean;
46
+ sessionEventNotices: readonly ITuiSessionEventNotice[];
47
+ isShuttingDown: boolean;
48
+ pendingPrompt: string | null;
49
+ pendingCount: number;
50
+ executionWorkspaceSnapshot: IExecutionWorkspaceSnapshot | null;
51
+ selectedExecutionEntryId?: string;
52
+ permissionRequest: IPendingPermissionRequest | null;
53
+ pendingUserAction: IActionRequest | null;
54
+ contextState: { percentage: number; usedTokens: number; maxTokens: number };
55
+ }
56
+
57
+ /**
58
+ * Complete channel surface visible below the top-level App composition shell.
59
+ *
60
+ * Deliberately excludes the concrete-session, concrete-registry and mutable-state-manager
61
+ * compatibility escapes retained by TuiInteractionChannel for non-React consumers.
62
+ */
63
+ export interface ITuiAppChannelPort {
64
+ readonly terminalHandoffController: TerminalHandoffController | undefined;
65
+ readonly sessionName: string | undefined;
66
+ start(): Promise<void>;
67
+ stop(): Promise<void>;
68
+ subscribe(onChange: () => void): () => void;
69
+ getSnapshot(): ITuiChannelSnapshot;
70
+ getCommandQueryPort(): ITuiCommandQueryPort;
71
+ getSessionUiEventPort(): ITuiSessionUiEventPort;
72
+ getRuntimeStatusSnapshot(fallbackPermissionMode: TPermissionMode): ITuiRuntimeStatusSnapshot;
73
+ addEntry(entry: IHistoryEntry): void;
74
+ handleInput(input: string): Promise<void>;
75
+ abort(): void;
76
+ cancelQueue(): void;
77
+ stopWaitingSelfPacedLoop(): Promise<void>;
78
+ shutdown(options?: { reason?: TSessionEndReason; timeoutMs?: number }): Promise<void>;
79
+ selectExecutionWorkspaceEntry(entryId: string): void;
80
+ readExecutionWorkspaceDetail(entryId: string): Promise<IExecutionDetailPage>;
81
+ sendAgentJob(taskId: string, input: string): Promise<void>;
82
+ resolveUserAction(request: IActionRequest, response: TActionResponse): void;
83
+ }
@@ -0,0 +1,149 @@
1
+ import type { TSessionEndReason } from '@robota-sdk/agent-core';
2
+
3
+ export interface ITuiChannelShutdownRequest {
4
+ reason: TSessionEndReason;
5
+ message: string;
6
+ }
7
+
8
+ export interface ITuiChannelLifecycleOperations {
9
+ start(): Promise<void>;
10
+ stop(): Promise<void>;
11
+ beginShutdown(): void;
12
+ shutdownSession(request: ITuiChannelShutdownRequest): Promise<void>;
13
+ }
14
+
15
+ /** Marks a startup failure whose rollback also failed, so retrying cannot duplicate live resources. */
16
+ export class TuiChannelStartRollbackError extends AggregateError {}
17
+
18
+ /** Owns channel lifecycle transitions and bounds SDK shutdown without owning presentation state. */
19
+ export class TuiChannelLifecycleCoordinator {
20
+ private started = false;
21
+ private startPromise: Promise<void> | undefined;
22
+ private teardownBegun = false;
23
+ private stopped = false;
24
+ private shuttingDown = false;
25
+ private shutdownPromise: Promise<void> | undefined;
26
+ private stopPromise: Promise<void> | undefined;
27
+
28
+ constructor(
29
+ private readonly operations: ITuiChannelLifecycleOperations,
30
+ private readonly defaultShutdownTimeoutMs: number,
31
+ ) {}
32
+
33
+ get isShuttingDown(): boolean {
34
+ return this.shuttingDown;
35
+ }
36
+
37
+ /** False before startup and immediately when teardown begins, even if teardown later fails. */
38
+ get isActiveForPeerStatus(): boolean {
39
+ return this.started && !this.teardownBegun && !this.shuttingDown;
40
+ }
41
+
42
+ async start(): Promise<void> {
43
+ if (this.teardownBegun) {
44
+ throw new Error('Cannot start a channel after teardown has begun.');
45
+ }
46
+ if (this.started) return;
47
+ const pendingStart = (this.startPromise ??= this.operations.start());
48
+ try {
49
+ await pendingStart;
50
+ this.started = true;
51
+ } catch (error) {
52
+ if (error instanceof TuiChannelStartRollbackError) this.teardownBegun = true;
53
+ throw error;
54
+ } finally {
55
+ if (this.startPromise === pendingStart) this.startPromise = undefined;
56
+ }
57
+ }
58
+
59
+ async stop(): Promise<void> {
60
+ this.teardownBegun = true;
61
+ if (this.stopped) return;
62
+ this.stopPromise ??= this.performStop();
63
+ try {
64
+ await this.stopPromise;
65
+ } catch (error) {
66
+ // A failed teardown is retryable; claiming stopped here would strand owned resources.
67
+ this.stopPromise = undefined;
68
+ throw error;
69
+ }
70
+ }
71
+
72
+ private async performStop(): Promise<void> {
73
+ const pendingStart = this.startPromise;
74
+ let startError: Error | undefined;
75
+ if (pendingStart !== undefined) {
76
+ try {
77
+ await pendingStart;
78
+ } catch (cause) {
79
+ startError = cause instanceof Error ? cause : new Error(String(cause));
80
+ }
81
+ }
82
+ let stopError: Error | undefined;
83
+ try {
84
+ await this.operations.stop();
85
+ } catch (error) {
86
+ stopError = error instanceof Error ? error : new Error(String(error));
87
+ }
88
+ this.shutdownPromise ??= this.shutdownSessionBounded(
89
+ { reason: 'other', message: 'channel stopped' },
90
+ this.defaultShutdownTimeoutMs,
91
+ );
92
+ await this.shutdownPromise;
93
+ if (stopError !== undefined && startError !== undefined) {
94
+ throw new AggregateError(
95
+ [startError, stopError],
96
+ 'TUI channel start and teardown both failed.',
97
+ );
98
+ }
99
+ if (stopError !== undefined) throw stopError;
100
+ this.started = false;
101
+ this.stopped = true;
102
+ if (startError !== undefined) throw startError;
103
+ }
104
+
105
+ async shutdown(options?: { reason?: TSessionEndReason; timeoutMs?: number }): Promise<void> {
106
+ this.teardownBegun = true;
107
+ if (!this.shuttingDown) {
108
+ this.shuttingDown = true;
109
+ this.operations.beginShutdown();
110
+ }
111
+ const pendingStop = this.stopPromise;
112
+ if (pendingStop !== undefined) {
113
+ await pendingStop.catch(() => undefined); // allow-fallback: the stop caller owns teardown failure
114
+ return;
115
+ }
116
+ this.shutdownPromise ??= this.performShutdown(options);
117
+ await this.shutdownPromise;
118
+ }
119
+
120
+ private async performShutdown(options?: {
121
+ reason?: TSessionEndReason;
122
+ timeoutMs?: number;
123
+ }): Promise<void> {
124
+ const pendingStart = this.startPromise;
125
+ if (pendingStart !== undefined) {
126
+ await pendingStart.catch(() => undefined); // allow-fallback: the start caller owns startup failure
127
+ }
128
+ await this.shutdownSessionBounded(
129
+ { reason: options?.reason ?? 'prompt_input_exit', message: 'CLI shutdown' },
130
+ options?.timeoutMs ?? this.defaultShutdownTimeoutMs,
131
+ );
132
+ }
133
+
134
+ private async shutdownSessionBounded(
135
+ request: ITuiChannelShutdownRequest,
136
+ timeoutMs: number,
137
+ ): Promise<void> {
138
+ let timer: ReturnType<typeof setTimeout> | undefined;
139
+ const timeout = new Promise<void>((resolve) => {
140
+ timer = setTimeout(resolve, timeoutMs);
141
+ timer.unref?.();
142
+ });
143
+ await Promise.race([
144
+ this.operations.shutdownSession(request).catch(() => undefined), // allow-fallback: shutdown is best-effort during process exit and remains timeout-bounded
145
+ timeout,
146
+ ]);
147
+ if (timer) clearTimeout(timer);
148
+ }
149
+ }
@@ -0,0 +1,172 @@
1
+ import type { IAttentionSource } from './attention/attention-tracker.js';
2
+ import type { TerminalHandoffController } from './terminal-handoff-controller.js';
3
+ import type {
4
+ IAIProvider,
5
+ IProviderDefinition,
6
+ IToolWithEventService,
7
+ TPermissionMode,
8
+ } from '@robota-sdk/agent-core';
9
+ import type {
10
+ IAgentDefinition,
11
+ IAutomaticMemoryConfig,
12
+ IBackgroundTaskRunner,
13
+ ICommandHostAdapters,
14
+ IOutputStylePrompt,
15
+ IOrgPolicy,
16
+ IProviderErrorGuidance,
17
+ IProjectSettingsPath,
18
+ INodeHostSettingsSource,
19
+ ICommandModule,
20
+ ICreateSessionOptions,
21
+ ILivePromptTracePort,
22
+ EditCheckpointStore,
23
+ IMemoryStore,
24
+ IPromptHistoryOptions,
25
+ IPerTurnRecallConfig,
26
+ IRemoteCommandPolicy,
27
+ IToolCallHandoffPolicy,
28
+ TShellExecFn,
29
+ TSubagentRunnerFactory,
30
+ TWorkspaceProjectAccess,
31
+ } from '@robota-sdk/agent-framework';
32
+ import type { CommandRegistry } from '@robota-sdk/agent-framework';
33
+ import type {
34
+ IInteractiveSession,
35
+ IInteractiveSessionStore,
36
+ TInteractiveEventName,
37
+ } from '@robota-sdk/agent-interface-session';
38
+ import type { ITransportRegistryView } from '@robota-sdk/agent-interface-transport';
39
+
40
+ /**
41
+ * The TUI channel's option surface.
42
+ *
43
+ * ARCH-013: this lived inside the 698-line channel class, beside the code that consumes it. The
44
+ * projection FROM it — `buildTuiSessionOptions` — already had its own module, so the surface and its
45
+ * one projection now sit together and neither is buried in an implementation file. That matters for
46
+ * this item specifically: a field added to the resolved preset has to be added here to reach the
47
+ * interactive surface at all, and a surface nobody can find is a surface people forget.
48
+ */
49
+ export interface ITuiInteractionChannelOptions {
50
+ livePromptTrace?: ILivePromptTracePort;
51
+ /**
52
+ * Provider definitions, forwarded to the session so `/provider switch` can construct the provider
53
+ * it switches TO.
54
+ *
55
+ * Not optional-by-accident: without it the session holds an empty list, and the hot-swap fails with
56
+ * "Unknown provider: <name>. Currently supported: " — an empty supported-list, which is both wrong
57
+ * and unactionable. The definitions live in the composition root; the session cannot discover them.
58
+ */
59
+ providerDefinitions?: readonly IProviderDefinition[];
60
+ cwd: string;
61
+ provider: IAIProvider;
62
+ providerErrorGuidance?: IProviderErrorGuidance;
63
+ promptFileReferenceTag?: string;
64
+ modelCommandToolPrefix?: string;
65
+ subagentHookEnvironmentNames?: ICreateSessionOptions['subagentHookEnvironmentNames'];
66
+ observerFailureWarningCode?: ICreateSessionOptions['observerFailureWarningCode'];
67
+ commandHookShell?: string;
68
+ /** Skip instruction files and plugin discovery (`--safe-mode`). */
69
+ bare?: boolean;
70
+ /** Run no hook the settings layers declare (`--safe-mode`). */
71
+ skipConfiguredHooks?: boolean;
72
+ /** Resolved organization policy forwarded to the interactive session. */
73
+ orgPolicy?: IOrgPolicy;
74
+ projectAccess?: TWorkspaceProjectAccess;
75
+ projectSettingsPaths?: readonly IProjectSettingsPath[];
76
+ baselinePermissionAllow?: readonly string[];
77
+ /** Host-selected task-context root; absent means no framework task-directory scan. */
78
+ taskContext?: { readonly enabled?: boolean; readonly dir?: string };
79
+ contributionSources?: ICreateSessionOptions['contributionSources'];
80
+ skillRoots?: ICreateSessionOptions['skillRoots'];
81
+ userSettingsSources?: readonly INodeHostSettingsSource[];
82
+ /** Explicit authority- and permission-backed edit checkpoint capability. */
83
+ editCheckpointStore?: EditCheckpointStore;
84
+ /**
85
+ * CLI-076: the resolved model id (the same value the status line displays). Forwarded to the session so an
86
+ * explicit `--model` override reaches the provider chat call instead of being silently replaced by the
87
+ * session's default model.
88
+ */
89
+ model?: string;
90
+ /** CLI-1988: resolved provider-neutral response style. */
91
+ outputStyle?: IOutputStylePrompt;
92
+ /** ARCH-013: resolved preset effort, threaded to the session's `effort` seam. */
93
+ effort?: ICreateSessionOptions['effort'];
94
+ temperature?: number;
95
+ maxOutputTokens?: number;
96
+ /** Preset prompt seed, distinct from the replacing `systemPrompt` option. */
97
+ presetSystemPrompt?: string;
98
+ responseFormat?: ICreateSessionOptions['responseFormat'];
99
+ permissionMode?: TPermissionMode;
100
+ maxTurns?: number;
101
+ sessionStore?: IInteractiveSessionStore;
102
+ disableSessionLoops?: boolean;
103
+ resolveDefaultLoopPrompt?: () => string;
104
+ resumeSessionId?: string;
105
+ forkSession?: boolean;
106
+ /** Issue #3081: this channel's session is the target of a `/cd` from this directory. */
107
+ workspaceMovedFrom?: string;
108
+ sessionName?: string;
109
+ onAutoNamed?: (name: string) => void;
110
+ backgroundTaskRunners?: IBackgroundTaskRunner[];
111
+ /** MCP-004: forwarded to `ICreateSessionOptions.toolCallHandoff`. */
112
+ toolCallHandoff?: IToolCallHandoffPolicy;
113
+ subagentRunnerFactory?: TSubagentRunnerFactory;
114
+ /** ARCH-005: composition-root-contributed subagent definitions (merged capability packs). */
115
+ agentDefinitions?: readonly IAgentDefinition[];
116
+ agentDefinitionRoots?: readonly string[];
117
+ pluginDirectories?: { readonly user?: string; readonly project?: string };
118
+ /**
119
+ * ARCH-006: tools contributed by the composition root (the capability packs `assembleProduct` merged)
120
+ * and, when the profile hands the packs the whole tool surface, the suppressed framework default tier
121
+ * (`defaultTools: []`). Forwarded to the session's tool-composition seam; absent ⇒ unchanged.
122
+ */
123
+ additionalTools?: IToolWithEventService[];
124
+ defaultTools?: readonly IToolWithEventService[];
125
+ commandModules?: readonly ICommandModule[];
126
+ commandHostAdapters?: ICommandHostAdapters;
127
+ shellExec?: TShellExecFn;
128
+ /** REMOTE-006: optional remote-command policy (allow-by-default; a transport-origin command runs as a local one). */
129
+ remoteCommandPolicy?: IRemoteCommandPolicy;
130
+ transportRegistry?: ITransportRegistryView;
131
+ /** Bind concrete transports to each newly constructed session before registry startup. */
132
+ bindTransports?: (session: IInteractiveSession) => void | Promise<void>;
133
+ language?: string;
134
+ reloadPluginCommandSource?: (registry: CommandRegistry) => void;
135
+ agentName?: string;
136
+ /** Active preset id selected at startup (PRESET-011 runtime state). Defaults to 'default'. */
137
+ activePresetId?: string;
138
+ /** Preset persona block composed as a `source: 'persona'` system-prompt section (priority 5). */
139
+ persona?: string;
140
+ systemPrompt?: string;
141
+ appendSystemPrompt?: string;
142
+ allowedTools?: readonly string[];
143
+ deniedTools?: readonly string[];
144
+ /** Preset execution capability: activate agent runtime + subagent/background dispatch. */
145
+ enableParallelSubagents?: boolean;
146
+ /** Preset execution capability: run a post-task self-verification step. */
147
+ selfVerification?: boolean;
148
+ /** TERM-002: process-shared terminal-handoff controller (the TUI implementation of ITerminalHandoff). */
149
+ terminalHandoff?: TerminalHandoffController;
150
+ /** SCREEN-1992: the terminal's attention level; absent ⇒ no interval recap. */
151
+ attention?: IAttentionSource;
152
+ /**
153
+ * SELFHOST-008 P6: optional durable-memory store injected by the surface (agent-cli). Forwarded into
154
+ * `buildRuntimeSession`; absent ⇒ memory OFF (today's behavior). Enablement/policy is surface-owned.
155
+ */
156
+ memoryStore?: IMemoryStore;
157
+ /** SELFHOST-008 P6: optional automatic post-turn capture policy (absent ⇒ capture OFF). */
158
+ automaticMemory?: IAutomaticMemoryConfig;
159
+ /** SELFHOST-008 P6: optional per-turn recall policy (absent ⇒ recall OFF, startup-only injection). */
160
+ recallMemory?: IPerTurnRecallConfig;
161
+ /** Observe TUI-owned session-event rendering failures without failing the committed operation. */
162
+ onSessionEventDeliveryError?: (error: Error, event: TInteractiveEventName) => void;
163
+ /**
164
+ * CLI-2004: the resolved screen-reader mode. A PRESENTATION field — it changes nothing about the
165
+ * session and is deliberately NOT projected into `buildTuiSessionOptions`; it is carried here so
166
+ * the render-options → channel-options projection is complete and the ARCH-110 drop hazard is
167
+ * asserted rather than assumed. Absent ⇒ OFF.
168
+ */
169
+ screenReader?: boolean;
170
+ /** SCREEN-1993: the session-side prompt-history writer and project key; absent ⇒ nothing recorded. */
171
+ promptHistory?: IPromptHistoryOptions | undefined;
172
+ }
@@ -0,0 +1,13 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ import type { ITuiCliAdapter } from './tui-cli-adapter.js';
4
+
5
+ const TuiCliAdapterContext = createContext<ITuiCliAdapter | null>(null);
6
+
7
+ export const TuiCliAdapterProvider = TuiCliAdapterContext.Provider;
8
+
9
+ export function useTuiCliAdapter(): ITuiCliAdapter {
10
+ const adapter = useContext(TuiCliAdapterContext);
11
+ if (!adapter) throw new Error('TuiCliAdapterContext not provided');
12
+ return adapter;
13
+ }
@@ -0,0 +1,24 @@
1
+ import type { TUniversalValue } from '@robota-sdk/agent-core';
2
+ import type { CommandRegistry } from '@robota-sdk/agent-framework';
3
+
4
+ /**
5
+ * CLI seam injected into the renderer.
6
+ *
7
+ * CMD-004 Phase 2 Stage C: READ-ONLY toward settings — the renderer re-reads the persisted
8
+ * settings document for display (statusline refresh-on-result) but never writes it. All settings
9
+ * mutations are host actions executed by the session layer via `ICommandHostAdapters`.
10
+ */
11
+ export interface ITuiCliAdapter {
12
+ getUserSettingsPath(): string;
13
+ readSettings(path: string): Record<string, TUniversalValue>;
14
+ reloadPluginCommandSource(registry: CommandRegistry): void;
15
+ applyActiveModelChange(
16
+ cwd: string,
17
+ modelId: string,
18
+ options?: { providerOverride?: string },
19
+ ): { applied: boolean };
20
+ getGitBranch(cwd: string): string | undefined;
21
+ getProviderDisplayName(type: string): string;
22
+ /** Optional host-owned command shown when a completed fork can be reopened outside the TUI. */
23
+ formatResumeCommand?: (sessionId: string) => string;
24
+ }