@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/src/render.tsx ADDED
@@ -0,0 +1,544 @@
1
+ /**
2
+ * Ink render entry point.
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { render } from 'ink';
7
+ import React from 'react';
8
+
9
+ import App from './App.js';
10
+ import { AttentionTracker } from './attention/attention-tracker.js';
11
+ import { FocusReportingStdin } from './attention/focus-input-filter.js';
12
+ import { KeybindingsProvider } from './keybindings/keybindings-context.js';
13
+ import { ProductDisplayNameProvider } from './product-display-name-context.js';
14
+ import { writeScreenReaderAnnouncement } from './screen-reader-announcement.js';
15
+ import { ScreenReaderProvider } from './screen-reader-context.js';
16
+ import { ScreenReaderPacingProvider } from './screen-reader-pacing-context.js';
17
+ import { awaitStartupQuietPeriod, resolvePacing } from './screen-reader-pacing.js';
18
+ import type { IScreenReaderPacingOverrides } from './screen-reader-pacing.js';
19
+ import { createParkedStdout, toPacingPort } from './screen-reader-stdout.js';
20
+ import { isInteractiveColorTerminal, supportsFocusReporting } from './terminal-capabilities.js';
21
+ import { TerminalCapabilitiesProvider } from './terminal-capabilities-context.js';
22
+ import type { ITerminalCapabilityOverrides } from './terminal-capabilities-context.js';
23
+ import { createFocusReportingWriter } from './terminal-focus-reporting.js';
24
+ import { TerminalHandoffController } from './terminal-handoff-controller.js';
25
+ import { TuiInteractionChannel } from './TuiInteractionChannel.js';
26
+
27
+ import type { IKeybindingsSource } from './keybindings/node-keybindings-source.js';
28
+ import type { TScreenReaderChannel } from './screen-reader-announcement.js';
29
+ import type { IThemeRegistry } from './theme/theme-registry.js';
30
+ import type { ITuiAppChannelPort } from './tui-app-channel-port.js';
31
+ import type { ITuiCliAdapter } from './tui-cli-adapter.js';
32
+ import type { ITuiInteractionChannelOptions } from './TuiInteractionChannel.js';
33
+ import type {
34
+ IAIProvider,
35
+ IToolWithEventService,
36
+ IProviderDefinition,
37
+ } from '@robota-sdk/agent-core';
38
+ import type { TPermissionMode } from '@robota-sdk/agent-core';
39
+ import type {
40
+ IBackgroundTaskRunner,
41
+ ICommandHostAdapters,
42
+ IOutputStylePrompt,
43
+ ICommandModule,
44
+ IRemoteCommandPolicy,
45
+ TSubagentRunnerFactory,
46
+ IAgentDefinition,
47
+ TShellExecFn,
48
+ CommandRegistry,
49
+ IMemoryStore,
50
+ IPromptHistoryOptions,
51
+ IAutomaticMemoryConfig,
52
+ IPerTurnRecallConfig,
53
+ TWorkspaceProjectAccess,
54
+ EditCheckpointStore,
55
+ IOrgPolicy,
56
+ IProviderErrorGuidance,
57
+ IProjectSettingsPath,
58
+ IContributionSource,
59
+ ISkillRootDescriptor,
60
+ INodeHostSettingsSource,
61
+ IToolCallHandoffPolicy,
62
+ ICreateSessionOptions,
63
+ ILivePromptTracePort,
64
+ } from '@robota-sdk/agent-framework';
65
+ import type { TReducedMotionOverride } from '@robota-sdk/agent-interface-command';
66
+ import type {
67
+ IInteractiveSessionStore,
68
+ IPromptHistorySource,
69
+ } from '@robota-sdk/agent-interface-session';
70
+ import type { ITransportRegistryView } from '@robota-sdk/agent-interface-transport';
71
+
72
+ export interface IRenderOptions {
73
+ cwd: string;
74
+ livePromptTrace?: ILivePromptTracePort;
75
+ /** Product identity for terminal labels, title, and host-facing copy. */
76
+ productDisplayName?: string;
77
+ modelCommandToolPrefix?: string;
78
+ subagentHookEnvironmentNames?: ICreateSessionOptions['subagentHookEnvironmentNames'];
79
+ observerFailureWarningCode?: ICreateSessionOptions['observerFailureWarningCode'];
80
+ commandHookShell?: string;
81
+ /** Skip instruction files and plugin discovery (`--safe-mode`). */
82
+ bare?: boolean;
83
+ /** Run no hook the settings layers declare (`--safe-mode`). */
84
+ skipConfiguredHooks?: boolean;
85
+ promptFileReferenceTag?: string;
86
+ provider: IAIProvider;
87
+ providerErrorGuidance?: IProviderErrorGuidance;
88
+ projectAccess?: TWorkspaceProjectAccess;
89
+ projectSettingsPaths?: readonly IProjectSettingsPath[];
90
+ baselinePermissionAllow?: readonly string[];
91
+ /** Host-selected task-context root; absent means no framework task-directory scan. */
92
+ taskContext?: { readonly enabled?: boolean; readonly dir?: string };
93
+ contributionSources?: readonly IContributionSource[];
94
+ skillRoots?: readonly ISkillRootDescriptor[];
95
+ userSettingsSources?: readonly INodeHostSettingsSource[];
96
+ /**
97
+ * CLI-083 (issue #2287) — the org policy, forwarded to the session so `blockedCommands` is
98
+ * enforced on the plain `robota` path as well as under `--serve`.
99
+ *
100
+ * DECLARED, not merely spread through. The shell forwards this with
101
+ * `...(orgPolicy === null ? {} : { orgPolicy })`, and a spread bypasses TypeScript's
102
+ * excess-property check — so before this field existed the value compiled, arrived, and was
103
+ * dropped by `toChannelOptions` below, which copies field by field. The idiom that looked safest
104
+ * is what disabled the one check that would have caught the missing declaration.
105
+ */
106
+ orgPolicy?: IOrgPolicy | undefined;
107
+ /** Explicit authority- and permission-backed edit checkpoint capability. */
108
+ editCheckpointStore?: EditCheckpointStore;
109
+ providerOverride?: string | undefined;
110
+ /**
111
+ * #1844: forwarded to the session so `/provider switch` can construct the provider it switches TO.
112
+ *
113
+ * The session cannot discover these — they are assembled at the composition root from the provider
114
+ * packages. Without them the hot-swap throws with an empty supported-list, which is the failure
115
+ * this option exists to prevent rather than a nicety.
116
+ */
117
+ providerDefinitions?: readonly IProviderDefinition[];
118
+ providerType?: string | undefined;
119
+ modelId?: string;
120
+ /** CLI-1988: resolved provider-neutral response style. */
121
+ outputStyle?: IOutputStylePrompt;
122
+ /** ARCH-013: resolved preset effort, forwarded to the session's `effort` seam. */
123
+ effort?: ITuiInteractionChannelOptions['effort'];
124
+ temperature?: number;
125
+ maxOutputTokens?: number;
126
+ /** Preset prompt seed, distinct from the replacing `systemPrompt` option. */
127
+ presetSystemPrompt?: string;
128
+ /** CLI-sourced additive prompt text, composed before the TUI is rendered. */
129
+ appendSystemPrompt?: string;
130
+ responseFormat?: ITuiInteractionChannelOptions['responseFormat'];
131
+ language?: string;
132
+ permissionMode?: TPermissionMode;
133
+ maxTurns?: number;
134
+ allowedTools?: readonly string[];
135
+ deniedTools?: readonly string[];
136
+ version?: string;
137
+ sessionStore?: IInteractiveSessionStore;
138
+ disableSessionLoops?: boolean;
139
+ resolveDefaultLoopPrompt?: () => string;
140
+ resumeSessionId?: string;
141
+ showSessionPickerOnStart?: boolean;
142
+ /** FLOW-2006: text a deep link prefilled into the composer. Never submitted on its own. */
143
+ initialInput?: string;
144
+ /** Where that text came from; `external-link` renders the provenance notice. */
145
+ initialInputOrigin?: 'external-link';
146
+ forkSession?: boolean;
147
+ /** Issue #3081: the startup session is the target of a `/cd` from this directory. */
148
+ workspaceMovedFrom?: string;
149
+ sessionName?: string;
150
+ backgroundTaskRunners?: IBackgroundTaskRunner[];
151
+ /** MCP-004: the tool-call handoff policy the composition root computed for this runtime. */
152
+ toolCallHandoff?: IToolCallHandoffPolicy;
153
+ subagentRunnerFactory?: TSubagentRunnerFactory;
154
+ /**
155
+ * ARCH-005: subagent definitions contributed by the composition root (the capability packs
156
+ * `assembleProduct` merged). Forwarded to the session's `agentDefinitions` seam; absent ⇒ unchanged.
157
+ */
158
+ agentDefinitions?: readonly IAgentDefinition[];
159
+ agentDefinitionRoots?: readonly string[];
160
+ pluginDirectories?: { readonly user?: string; readonly project?: string };
161
+ /**
162
+ * ARCH-006: tools contributed by the composition root (the capability packs `assembleProduct` merged)
163
+ * and, when the profile hands the packs the whole tool surface, the suppressed framework default tier
164
+ * (`defaultTools: []`). Forwarded to the session's tool-composition seam; absent ⇒ unchanged.
165
+ */
166
+ additionalTools?: IToolWithEventService[];
167
+ defaultTools?: readonly IToolWithEventService[];
168
+ commandModules?: readonly ICommandModule[];
169
+ commandHostAdapters?: ICommandHostAdapters;
170
+ shellExec?: TShellExecFn;
171
+ /** REMOTE-006: optional remote-command policy (allow-by-default; local == remote). */
172
+ remoteCommandPolicy?: IRemoteCommandPolicy;
173
+ startupUpdateNotice?: Promise<string | undefined>;
174
+ transportRegistry?: ITransportRegistryView;
175
+ bindTransports?: ITuiInteractionChannelOptions['bindTransports'];
176
+ cliAdapter: ITuiCliAdapter;
177
+ reloadPluginCommandSource?: (registry: CommandRegistry) => void;
178
+ agentName?: string;
179
+ /** Active preset id selected at startup (PRESET-011 runtime state). Defaults to 'default'. */
180
+ activePresetId?: string;
181
+ /** Preset persona block composed as a `source: 'persona'` system-prompt section (priority 5). */
182
+ persona?: string;
183
+ /** Preset execution capability: activate agent runtime + subagent/background dispatch. */
184
+ enableParallelSubagents?: boolean;
185
+ /** Preset execution capability: run a post-task self-verification step. */
186
+ selfVerification?: boolean;
187
+ /**
188
+ * Called with each live channel (including session-switch re-creations). Lets the embedding
189
+ * product wire process-level concerns (ERR-001 G1: error routing into the live session).
190
+ */
191
+ onChannelReady?: (channel: TuiInteractionChannel) => void;
192
+ /**
193
+ * SELFHOST-008 P6: optional durable-memory store injected by the surface (agent-cli). Forwarded to the
194
+ * channel → `buildRuntimeSession`; absent ⇒ memory OFF (today's behavior). Enablement is surface-owned.
195
+ */
196
+ memoryStore?: IMemoryStore;
197
+ /** SELFHOST-008 P6: optional automatic post-turn capture policy (absent ⇒ capture OFF). */
198
+ automaticMemory?: IAutomaticMemoryConfig;
199
+ /** SELFHOST-008 P6: optional per-turn recall policy (absent ⇒ recall OFF, startup-only injection). */
200
+ recallMemory?: IPerTurnRecallConfig;
201
+ /**
202
+ * CLI-2004: the resolved screen-reader mode. DECLARED, not spread through — `toChannelOptions`
203
+ * below copies field by field, so an undeclared field compiles, arrives and is silently dropped
204
+ * (the ARCH-110 containment hazard the `orgPolicy` comment above records). Absent ⇒ mode OFF and
205
+ * today's byte stream is unchanged.
206
+ */
207
+ screenReader?: boolean | undefined;
208
+ /** Host-selected raw timing overrides; absent values use the renderer's defaults. */
209
+ screenReaderPacing?: IScreenReaderPacingOverrides;
210
+ /** Host-selected cursor and turn-mark overrides; absent values use terminal detection. */
211
+ terminalCapabilities?: ITerminalCapabilityOverrides;
212
+ /** CLI-2004: which input turned the mode on — printed in the confirmation line. */
213
+ screenReaderChannel?: TScreenReaderChannel | undefined;
214
+ /** CLI-2004: mode off, but the environment suggests a reader is running ⇒ one advisory line. */
215
+ screenReaderHint?: boolean | undefined;
216
+ /** BEHAVIOR-2003: one watched source shared with the optional `/keybindings` command. */
217
+ keybindingsSource?: IKeybindingsSource;
218
+ /**
219
+ * SCREEN-2002: the theme catalogue this run renders from, shared with the optional `/theme`
220
+ * command. DECLARED, not spread — see the `screenReader` note above. Absent ⇒ the built-ins.
221
+ */
222
+ themeRegistry?: IThemeRegistry;
223
+ /** SCREEN-2002: reduced motion as the product shell resolved it (settings ← env ← flag). */
224
+ reducedMotion?: boolean | undefined;
225
+ /** SCREEN-2002: which tier decided it, when that was not the settings. */
226
+ reducedMotionOverride?: TReducedMotionOverride | undefined;
227
+ /**
228
+ * SCREEN-1992: the focus-reporting override the product shell resolved from its own environment.
229
+ * `true` forces DECSET 1004 on, `false` is the kill switch, absent ⇒ on for an interactive TTY.
230
+ */
231
+ focusReporting?: boolean | undefined;
232
+ /**
233
+ * SCREEN-1993: prompt history, resolved by the product shell. `promptHistory` is the session-side
234
+ * writer (forwarded to the channel like `memoryStore`); `promptHistorySource` and
235
+ * `promptHistoryProject` feed the input area's Ctrl+R search. DECLARED, not spread through — the
236
+ * projection below copies field by field. Absent ⇒ nothing is written and `ctrl+r` is inert.
237
+ */
238
+ promptHistory?: IPromptHistoryOptions;
239
+ promptHistorySource?: IPromptHistorySource;
240
+ promptHistoryProject?: string;
241
+ }
242
+
243
+ /** Map render options to TuiInteractionChannel constructor options. */
244
+ export function toChannelOptions(
245
+ options: IRenderOptions,
246
+ resumeSessionId?: string,
247
+ ): ConstructorParameters<typeof TuiInteractionChannel>[0] {
248
+ return {
249
+ cwd: options.cwd,
250
+ ...(options.livePromptTrace ? { livePromptTrace: options.livePromptTrace } : {}),
251
+ provider: options.provider,
252
+ ...(options.providerErrorGuidance !== undefined
253
+ ? { providerErrorGuidance: options.providerErrorGuidance }
254
+ : {}),
255
+ ...(options.promptFileReferenceTag !== undefined
256
+ ? { promptFileReferenceTag: options.promptFileReferenceTag }
257
+ : {}),
258
+ ...(options.modelCommandToolPrefix !== undefined
259
+ ? { modelCommandToolPrefix: options.modelCommandToolPrefix }
260
+ : {}),
261
+ ...(options.subagentHookEnvironmentNames !== undefined
262
+ ? { subagentHookEnvironmentNames: options.subagentHookEnvironmentNames }
263
+ : {}),
264
+ ...(options.observerFailureWarningCode !== undefined
265
+ ? { observerFailureWarningCode: options.observerFailureWarningCode }
266
+ : {}),
267
+ ...(options.commandHookShell !== undefined
268
+ ? { commandHookShell: options.commandHookShell }
269
+ : {}),
270
+ ...(options.bare === true ? { bare: true } : {}),
271
+ ...(options.skipConfiguredHooks === true ? { skipConfiguredHooks: true } : {}),
272
+ ...(options.projectAccess !== undefined ? { projectAccess: options.projectAccess } : {}),
273
+ ...(options.projectSettingsPaths !== undefined
274
+ ? { projectSettingsPaths: options.projectSettingsPaths }
275
+ : {}),
276
+ ...(options.baselinePermissionAllow !== undefined
277
+ ? { baselinePermissionAllow: options.baselinePermissionAllow }
278
+ : {}),
279
+ ...(options.taskContext !== undefined ? { taskContext: options.taskContext } : {}),
280
+ ...(options.contributionSources !== undefined
281
+ ? { contributionSources: options.contributionSources }
282
+ : {}),
283
+ ...(options.skillRoots !== undefined ? { skillRoots: options.skillRoots } : {}),
284
+ ...(options.userSettingsSources !== undefined
285
+ ? { userSettingsSources: options.userSettingsSources }
286
+ : {}),
287
+ ...(options.orgPolicy !== undefined ? { orgPolicy: options.orgPolicy } : {}),
288
+ ...(options.editCheckpointStore !== undefined
289
+ ? { editCheckpointStore: options.editCheckpointStore }
290
+ : {}),
291
+ ...(options.providerDefinitions ? { providerDefinitions: options.providerDefinitions } : {}),
292
+ // CLI-076: the display model id doubles as the session's model override so `--model` actually reaches
293
+ // the provider chat call (header/status line == the model actually called).
294
+ ...(options.modelId !== undefined ? { model: options.modelId } : {}),
295
+ ...(options.effort !== undefined ? { effort: options.effort } : {}),
296
+ ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),
297
+ ...(options.maxOutputTokens !== undefined ? { maxOutputTokens: options.maxOutputTokens } : {}),
298
+ ...(options.presetSystemPrompt !== undefined
299
+ ? { presetSystemPrompt: options.presetSystemPrompt }
300
+ : {}),
301
+ ...(options.appendSystemPrompt !== undefined
302
+ ? { appendSystemPrompt: options.appendSystemPrompt }
303
+ : {}),
304
+ ...(options.responseFormat !== undefined ? { responseFormat: options.responseFormat } : {}),
305
+ ...(options.outputStyle !== undefined ? { outputStyle: options.outputStyle } : {}),
306
+ permissionMode: options.permissionMode,
307
+ maxTurns: options.maxTurns,
308
+ allowedTools: options.allowedTools,
309
+ deniedTools: options.deniedTools,
310
+ sessionStore: options.sessionStore,
311
+ disableSessionLoops: options.disableSessionLoops,
312
+ resolveDefaultLoopPrompt: options.resolveDefaultLoopPrompt,
313
+ resumeSessionId,
314
+ forkSession: options.forkSession,
315
+ sessionName: options.sessionName,
316
+ backgroundTaskRunners: options.backgroundTaskRunners,
317
+ ...(options.toolCallHandoff !== undefined ? { toolCallHandoff: options.toolCallHandoff } : {}),
318
+ subagentRunnerFactory: options.subagentRunnerFactory,
319
+ ...(options.agentDefinitions !== undefined
320
+ ? { agentDefinitions: options.agentDefinitions }
321
+ : {}),
322
+ ...(options.agentDefinitionRoots !== undefined
323
+ ? { agentDefinitionRoots: options.agentDefinitionRoots }
324
+ : {}),
325
+ ...(options.pluginDirectories !== undefined
326
+ ? { pluginDirectories: options.pluginDirectories }
327
+ : {}),
328
+ ...(options.additionalTools !== undefined ? { additionalTools: options.additionalTools } : {}),
329
+ ...(options.defaultTools !== undefined ? { defaultTools: options.defaultTools } : {}),
330
+ commandModules: options.commandModules,
331
+ commandHostAdapters: options.commandHostAdapters,
332
+ shellExec: options.shellExec,
333
+ remoteCommandPolicy: options.remoteCommandPolicy,
334
+ transportRegistry: options.transportRegistry,
335
+ bindTransports: options.bindTransports,
336
+ language: options.language,
337
+ reloadPluginCommandSource: options.reloadPluginCommandSource,
338
+ agentName: options.agentName,
339
+ activePresetId: options.activePresetId,
340
+ persona: options.persona,
341
+ enableParallelSubagents: options.enableParallelSubagents,
342
+ selfVerification: options.selfVerification,
343
+ // SELFHOST-008 P6: forward the surface-resolved memory fields (absent ⇒ OFF).
344
+ ...(options.memoryStore ? { memoryStore: options.memoryStore } : {}),
345
+ ...(options.automaticMemory ? { automaticMemory: options.automaticMemory } : {}),
346
+ ...(options.recallMemory ? { recallMemory: options.recallMemory } : {}),
347
+ // CLI-2004: declared above AND projected here — the hand-maintained copy is the exact place an
348
+ // undeclared option disappears, so both halves are asserted by one test.
349
+ ...(options.screenReader !== undefined ? { screenReader: options.screenReader } : {}),
350
+ // SCREEN-1993: the session-side writer (absent ⇒ nothing is recorded).
351
+ promptHistory: options.promptHistory,
352
+ };
353
+ }
354
+
355
+ export async function waitForRenderAndStop<TResult>(
356
+ waitUntilExit: () => Promise<TResult>,
357
+ getActiveChannel: () => Pick<ITuiAppChannelPort, 'stop'> | undefined,
358
+ ): Promise<void> {
359
+ let renderFailure: Error | undefined;
360
+ try {
361
+ await waitUntilExit();
362
+ } catch (cause) {
363
+ renderFailure = cause instanceof Error ? cause : new Error(String(cause));
364
+ }
365
+ let stopFailure: Error | undefined;
366
+ try {
367
+ await getActiveChannel()?.stop();
368
+ } catch (cause) {
369
+ stopFailure = cause instanceof Error ? cause : new Error(String(cause));
370
+ }
371
+ if (renderFailure !== undefined && stopFailure !== undefined) {
372
+ throw new AggregateError([renderFailure, stopFailure], 'TUI render and teardown both failed.');
373
+ }
374
+ if (renderFailure !== undefined) throw renderFailure;
375
+ if (stopFailure !== undefined) throw stopFailure;
376
+ }
377
+
378
+ export async function renderApp(options: IRenderOptions): Promise<void> {
379
+ // ERR-001 / Library Neutrality Rule: NO process-level error policy here — process survival
380
+ // is the product assembly's boundary (agent-cli installs the guards via onChannelReady).
381
+ try {
382
+ await options.keybindingsSource?.start();
383
+ await renderStartedApp(options);
384
+ } finally {
385
+ options.keybindingsSource?.dispose();
386
+ }
387
+ }
388
+
389
+ async function renderStartedApp(options: IRenderOptions): Promise<void> {
390
+ // CLI-2004: one resolved boolean drives Ink's own screen-reader support AND the React context
391
+ // every component reads. Both are set here so they can never disagree.
392
+ const screenReader = options.screenReader === true;
393
+ writeScreenReaderAnnouncement({
394
+ enabled: screenReader,
395
+ channel: options.screenReaderChannel,
396
+ hint: options.screenReaderHint,
397
+ });
398
+ const pacing = resolvePacing({ enabled: screenReader, overrides: options.screenReaderPacing });
399
+ // SCREEN-2670: the pre-write park. Constructed only when the mode is on AND the interval is
400
+ // non-zero, so with the mode off `stdout` is not passed at all and Ink defaults to
401
+ // `process.stdout` — the object it keys its instance map by — exactly as today.
402
+ const parked =
403
+ screenReader && pacing.preparkMs > 0
404
+ ? createParkedStdout({ stdout: process.stdout, preparkMs: pacing.preparkMs })
405
+ : undefined;
406
+
407
+ // SCREEN-006: chalk (ink's styling engine) does not implement the NO_COLOR convention itself
408
+ // (verified: chalk 5's vendored supports-color reads only FORCE_COLOR/TTY/TERM), so on a real
409
+ // TTY `NO_COLOR=1` would still color every component. Sync chalk once with the package's single
410
+ // color gate (`terminal-capabilities.ts` — the SSOT that DOES honor NO_COLOR) so gate-off means
411
+ // zero SGR color output. Gate-on changes nothing: chalk's own level detection stays authoritative.
412
+ if (!isInteractiveColorTerminal()) {
413
+ chalk.level = 0;
414
+ }
415
+
416
+ // TERM-002: one terminal-handoff controller per process (one Ink instance / App). Shared across
417
+ // channel re-creations (session switch) so the handoff capability survives a session swap.
418
+ const handoffController = new TerminalHandoffController();
419
+
420
+ // SCREEN-1992: focus reporting is negotiated once for the process; the tracker outlives channels.
421
+ // Screen-reader mode keeps the recap (a plain notice line) — only the row countdown tick is off.
422
+ const focusReportingSupported = supportsFocusReporting({ override: options.focusReporting });
423
+ const focusReporting = createFocusReportingWriter({ supported: () => focusReportingSupported });
424
+ const attention = new AttentionTracker({
425
+ focusReporting: focusReportingSupported,
426
+ now: Date.now,
427
+ });
428
+ handoffController.setTerminalModeHooks({
429
+ preSuspend: async () => {
430
+ focusReporting.disable();
431
+ // SCREEN-2670: nothing parked may land on top of the child's output.
432
+ await parked?.drain();
433
+ },
434
+ postResume: () => focusReporting.enable(),
435
+ });
436
+
437
+ // Concrete framework creation has one composition boundary. React receives only the bounded port;
438
+ // App owns which narrowed channel is active, while each channel owns its own lifecycle.
439
+ let activeChannel: ITuiAppChannelPort | undefined;
440
+ // Issue #3081: the move is announced by the FIRST session only — a later switch to another
441
+ // session (a `/fork` attach) did not move anywhere.
442
+ let pendingWorkspaceMovedFrom = options.workspaceMovedFrom;
443
+ const createChannel = (resumeSessionId?: string): ITuiAppChannelPort => {
444
+ const workspaceMovedFrom = pendingWorkspaceMovedFrom;
445
+ pendingWorkspaceMovedFrom = undefined;
446
+ const channel = new TuiInteractionChannel({
447
+ ...toChannelOptions(options, resumeSessionId),
448
+ ...(workspaceMovedFrom !== undefined ? { workspaceMovedFrom } : {}),
449
+ terminalHandoff: handoffController,
450
+ attention,
451
+ });
452
+ // Expose each live channel (incl. session-switch re-creations) to the embedding product,
453
+ // e.g. for process-level error routing (ERR-001 G1).
454
+ options.onChannelReady?.(channel);
455
+ activeChannel = channel;
456
+ return channel;
457
+ };
458
+
459
+ // The startup quiet period sits between the confirmation line and the first frame, so a reader
460
+ // finishes announcing the mode before the prompt lands. A keypress ends it early; `0` skips it.
461
+ await awaitStartupQuietPeriod(pacing.startupQuietMs, process.stdin);
462
+
463
+ // The filtering proxy is attached only now: the quiet period consumed its own keystroke above, and
464
+ // from here on every byte reaches Ink through the proxy, minus the focus sequences.
465
+ const stdin = new FocusReportingStdin(process.stdin, {
466
+ negotiated: () => focusReporting.negotiated,
467
+ onFocusIn: () => attention.focusIn(),
468
+ onFocusOut: () => attention.focusOut(),
469
+ onKeystroke: () => attention.keystroke(),
470
+ });
471
+ process.stdin.resume();
472
+ // Requested only now, so a focus event the terminal answers with lands in the proxy, never in
473
+ // the quiet period's keypress wait; the tracker's idle source runs until the first one arrives.
474
+ focusReporting.enable();
475
+ attention.start();
476
+
477
+ const pacingPort = parked === undefined ? undefined : toPacingPort(parked);
478
+ const tree = (
479
+ <ProductDisplayNameProvider
480
+ name={options.productDisplayName}
481
+ modelCommandToolPrefix={options.modelCommandToolPrefix}
482
+ >
483
+ <KeybindingsProvider source={options.keybindingsSource}>
484
+ <ScreenReaderProvider enabled={screenReader}>
485
+ <TerminalCapabilitiesProvider overrides={options.terminalCapabilities}>
486
+ <App
487
+ cwd={options.cwd}
488
+ createChannel={createChannel}
489
+ providerOverride={options.providerOverride}
490
+ providerType={options.providerType}
491
+ modelId={options.modelId}
492
+ permissionMode={options.permissionMode}
493
+ version={options.version}
494
+ sessionStore={options.sessionStore}
495
+ resumeSessionId={options.resumeSessionId}
496
+ showSessionPickerOnStart={options.showSessionPickerOnStart}
497
+ initialInput={options.initialInput}
498
+ initialInputOrigin={options.initialInputOrigin}
499
+ startupUpdateNotice={options.startupUpdateNotice}
500
+ transportRegistry={options.transportRegistry}
501
+ pluginAdapter={options.commandHostAdapters?.plugin}
502
+ cliAdapter={options.cliAdapter}
503
+ promptHistorySource={options.promptHistorySource}
504
+ promptHistoryProject={options.promptHistoryProject}
505
+ themeRegistry={options.themeRegistry}
506
+ reducedMotion={options.reducedMotion}
507
+ reducedMotionOverride={options.reducedMotionOverride}
508
+ />
509
+ </TerminalCapabilitiesProvider>
510
+ </ScreenReaderProvider>
511
+ </KeybindingsProvider>
512
+ </ProductDisplayNameProvider>
513
+ );
514
+ const instance = render(
515
+ pacingPort === undefined ? (
516
+ tree
517
+ ) : (
518
+ <ScreenReaderPacingProvider port={pacingPort}>{tree}</ScreenReaderPacingProvider>
519
+ ),
520
+ {
521
+ exitOnCtrlC: false,
522
+ isScreenReaderEnabled: screenReader,
523
+ stdin: stdin.asInkStdin(),
524
+ ...(parked === undefined ? {} : { stdout: parked.asInkStdout() }),
525
+ },
526
+ );
527
+ // The controller needs the Ink instance to clear the frame before a handoff.
528
+ handoffController.setInkInstance(instance);
529
+ try {
530
+ await waitForRenderAndStop(
531
+ () => instance.waitUntilExit(),
532
+ () => activeChannel,
533
+ );
534
+ } finally {
535
+ // The last frame of a session must not be the one the park loses: drain before restoring.
536
+ await parked?.flush();
537
+ // Leave the terminal as it was found: no focus sequences after exit, no reader on stdin.
538
+ handoffController.setTerminalModeHooks(undefined);
539
+ focusReporting.disable();
540
+ attention.dispose();
541
+ stdin.detach();
542
+ process.stdin.pause();
543
+ }
544
+ }