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

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 (348) hide show
  1. package/CHANGELOG.md +1204 -0
  2. package/LICENSE +661 -0
  3. package/README.md +50 -0
  4. package/dist/node/index.d.ts +1224 -0
  5. package/dist/node/index.d.ts.map +1 -0
  6. package/dist/node/index.js +33 -0
  7. package/dist/node/index.js.map +1 -0
  8. package/package.json +88 -0
  9. package/src/App.tsx +157 -0
  10. package/src/AppPresentation.tsx +190 -0
  11. package/src/AppView.tsx +26 -0
  12. package/src/BackgroundTaskPanel.tsx +73 -0
  13. package/src/CjkTextInput.tsx +336 -0
  14. package/src/ConfirmPrompt.tsx +140 -0
  15. package/src/ContextWarningBanner.tsx +45 -0
  16. package/src/ExecutionWorkspaceDetailPane.tsx +85 -0
  17. package/src/ExecutionWorkspaceSwitcher.tsx +213 -0
  18. package/src/HistorySearchOverlay.tsx +150 -0
  19. package/src/InputArea.tsx +338 -0
  20. package/src/ListPicker.tsx +158 -0
  21. package/src/MenuSelect.tsx +159 -0
  22. package/src/MessageList.tsx +308 -0
  23. package/src/MultiSelectList.tsx +294 -0
  24. package/src/PendingActionPrompt.tsx +121 -0
  25. package/src/PermissionPrompt.tsx +236 -0
  26. package/src/PluginTUI.tsx +258 -0
  27. package/src/RoleLabel.tsx +72 -0
  28. package/src/SafeText.tsx +66 -0
  29. package/src/SessionEventNotices.tsx +27 -0
  30. package/src/SessionPicker.tsx +92 -0
  31. package/src/SessionStatusBar.tsx +80 -0
  32. package/src/SlashAutocomplete.tsx +154 -0
  33. package/src/StatusBar.tsx +308 -0
  34. package/src/StreamingIndicator.tsx +145 -0
  35. package/src/SupervisedSessionView.tsx +872 -0
  36. package/src/TextPrompt.tsx +112 -0
  37. package/src/ThemePicker.tsx +366 -0
  38. package/src/ToolCommandOutput.tsx +48 -0
  39. package/src/ToolDiffBlock.tsx +44 -0
  40. package/src/TransportTUI.tsx +166 -0
  41. package/src/TuiInteractionChannel.ts +530 -0
  42. package/src/UpdateNotice.tsx +18 -0
  43. package/src/UsageSummaryEntry.tsx +49 -0
  44. package/src/WaveText.tsx +70 -0
  45. package/src/__tests__/ListPicker.test.tsx +188 -0
  46. package/src/__tests__/MenuSelect.test.tsx +127 -0
  47. package/src/__tests__/PendingActionPrompt.test.tsx +193 -0
  48. package/src/__tests__/PluginTUI.test.tsx +167 -0
  49. package/src/__tests__/SlashAutocomplete.test.tsx +162 -0
  50. package/src/__tests__/SupervisedSessionView-render.test.tsx +52 -0
  51. package/src/__tests__/SupervisedSessionView.test.tsx +1112 -0
  52. package/src/__tests__/TextPrompt.test.tsx +112 -0
  53. package/src/__tests__/TransportTUI.test.tsx +124 -0
  54. package/src/__tests__/TuiInteractionChannel.askUser.test.ts +124 -0
  55. package/src/__tests__/TuiInteractionChannel.display-contract.test.ts +294 -0
  56. package/src/__tests__/TuiInteractionChannel.lifecycle.test.ts +650 -0
  57. package/src/__tests__/UpdateNotice.test.tsx +15 -0
  58. package/src/__tests__/__fixtures__/korean-text.json +17 -0
  59. package/src/__tests__/abort-after-permission.test.tsx +160 -0
  60. package/src/__tests__/abort-streaming-e2e.test.tsx +183 -0
  61. package/src/__tests__/attached-app-session-switch.test.tsx +317 -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__/checkpoint-store-per-channel.test.ts +81 -0
  69. package/src/__tests__/cjk-defer-submit.test.tsx +68 -0
  70. package/src/__tests__/cjk-fallback-render.test.tsx +89 -0
  71. package/src/__tests__/cjk-text-input-flow.test.ts +187 -0
  72. package/src/__tests__/cjk-text-input.test.ts +196 -0
  73. package/src/__tests__/command-handoff-pty-e2e.test.ts +107 -0
  74. package/src/__tests__/command-output-summary.test.ts +95 -0
  75. package/src/__tests__/command-result-handler.test.ts +171 -0
  76. package/src/__tests__/compact-event-bridge.test.ts +20 -0
  77. package/src/__tests__/confirm-permission-flow.test.ts +149 -0
  78. package/src/__tests__/confirm-prompt.test.tsx +103 -0
  79. package/src/__tests__/contextual-keybindings-input.test.tsx +158 -0
  80. package/src/__tests__/default-tui-cli-adapter-host-settings.test.ts +47 -0
  81. package/src/__tests__/effort-reaches-the-session.test.ts +41 -0
  82. package/src/__tests__/execution-workspace-switcher.test.tsx +147 -0
  83. package/src/__tests__/execution-workspace-view-model.test.ts +94 -0
  84. package/src/__tests__/external-prompt-notice.test.tsx +100 -0
  85. package/src/__tests__/fixtures/command-handoff-driver.tsx +114 -0
  86. package/src/__tests__/fixtures/fake-editor.sh +7 -0
  87. package/src/__tests__/fixtures/terminal-handoff-driver.tsx +90 -0
  88. package/src/__tests__/fork-attach.test.tsx +235 -0
  89. package/src/__tests__/grant-history-reaches-the-session.test.ts +31 -0
  90. package/src/__tests__/helpers/terminal-profiles.ts +152 -0
  91. package/src/__tests__/helpers/vt-cursor-interpreter.ts +175 -0
  92. package/src/__tests__/history-clear-broadcast.test.ts +66 -0
  93. package/src/__tests__/history-search-overlay.test.tsx +329 -0
  94. package/src/__tests__/humanize-tool-name.test.ts +30 -0
  95. package/src/__tests__/input-area-bottom-border.test.tsx +45 -0
  96. package/src/__tests__/input-area-flow.test.ts +199 -0
  97. package/src/__tests__/input-area-focus-handoff.test.tsx +77 -0
  98. package/src/__tests__/interaction-channel-charter.test.ts +13 -0
  99. package/src/__tests__/key-hint-consistency.test.tsx +249 -0
  100. package/src/__tests__/key-hint-footer.test.tsx +69 -0
  101. package/src/__tests__/live-trace-reaches-session.test.ts +14 -0
  102. package/src/__tests__/message-list-rendering.test.tsx +474 -0
  103. package/src/__tests__/numbered-selection-chunk.test.tsx +41 -0
  104. package/src/__tests__/org-policy-reaches-the-channel.test.ts +77 -0
  105. package/src/__tests__/output-style-reaches-session.test.ts +35 -0
  106. package/src/__tests__/palette-consistency.test.ts +329 -0
  107. package/src/__tests__/permission-prompt-arming.test.tsx +153 -0
  108. package/src/__tests__/permission-prompt-availability.test.tsx +23 -0
  109. package/src/__tests__/permission-prompt-peer.test.tsx +34 -0
  110. package/src/__tests__/permission-prompt-requester.test.tsx +34 -0
  111. package/src/__tests__/product-display-name.test.tsx +79 -0
  112. package/src/__tests__/prompt-queue.test.tsx +260 -0
  113. package/src/__tests__/provider-definitions-reach-the-session.test.ts +75 -0
  114. package/src/__tests__/pty/ask-user-question.ptytest.ts +72 -0
  115. package/src/__tests__/pty/background-work-switcher.ptytest.ts +53 -0
  116. package/src/__tests__/pty/behavior-2437-git.ptytest.ts +350 -0
  117. package/src/__tests__/pty/fixtures/ask-user-question.jsonl +3 -0
  118. package/src/__tests__/pty/fixtures/replay-conversation.jsonl +4 -0
  119. package/src/__tests__/pty/fixtures/screen-005-ask-shapes.jsonl +5 -0
  120. package/src/__tests__/pty/fixtures/screen-005-permission.jsonl +3 -0
  121. package/src/__tests__/pty/fixtures/screen-006-colors.jsonl +3 -0
  122. package/src/__tests__/pty/fixtures/screen-2002-themes.jsonl +5 -0
  123. package/src/__tests__/pty/flag-tui.ptytest.ts +56 -0
  124. package/src/__tests__/pty/flow-2006-deep-link.ptytest.ts +320 -0
  125. package/src/__tests__/pty/ime-cursor-tmux.ptytest.ts +211 -0
  126. package/src/__tests__/pty/ime-cursor.ptytest.ts +187 -0
  127. package/src/__tests__/pty/isolated-home.ts +97 -0
  128. package/src/__tests__/pty/org-policy.ptytest.ts +71 -0
  129. package/src/__tests__/pty/provider-setup.ptytest.ts +61 -0
  130. package/src/__tests__/pty/pty-driver.ts +155 -0
  131. package/src/__tests__/pty/replay-conversation.ptytest.ts +68 -0
  132. package/src/__tests__/pty/screen-005-prompt-footers.ptytest.ts +135 -0
  133. package/src/__tests__/pty/screen-006-no-color.ptytest.ts +140 -0
  134. package/src/__tests__/pty/screen-010-scrollback.ptytest.ts +123 -0
  135. package/src/__tests__/pty/screen-1993-scrollback.ptytest.ts +124 -0
  136. package/src/__tests__/pty/screen-2002-themes.ptytest.ts +292 -0
  137. package/src/__tests__/pty/screen-2670-prepark.ptytest.ts +228 -0
  138. package/src/__tests__/pty/screen-reader-mode.ptytest.ts +228 -0
  139. package/src/__tests__/pty/session-attach.ptytest.ts +182 -0
  140. package/src/__tests__/pty/settings-screen.ptytest.ts +49 -0
  141. package/src/__tests__/pty/spawn-pty.test.ts +120 -0
  142. package/src/__tests__/pty/spawn-pty.ts +229 -0
  143. package/src/__tests__/pty/terminal-handoff.ptytest.ts +79 -0
  144. package/src/__tests__/pty/trust-prompt.ptytest.ts +134 -0
  145. package/src/__tests__/pty/tui-pty.ptytest.ts +65 -0
  146. package/src/__tests__/pty-driver-dispose.test.ts +43 -0
  147. package/src/__tests__/real-cursor-positioning.test.tsx +264 -0
  148. package/src/__tests__/rename-broadcast-persistence.test.tsx +140 -0
  149. package/src/__tests__/render-channel-options.test.ts +201 -0
  150. package/src/__tests__/render-lifecycle.test.ts +36 -0
  151. package/src/__tests__/render-markdown.test.ts +164 -0
  152. package/src/__tests__/rendered-markdown-styling.test.tsx +98 -0
  153. package/src/__tests__/safe-text-boundary.test.tsx +75 -0
  154. package/src/__tests__/sandbox-reaches-the-session.test.ts +37 -0
  155. package/src/__tests__/screen-reader-input.test.tsx +106 -0
  156. package/src/__tests__/screen-reader-labels.test.ts +91 -0
  157. package/src/__tests__/screen-reader-menus.test.tsx +200 -0
  158. package/src/__tests__/screen-reader-pacing.test.ts +230 -0
  159. package/src/__tests__/screen-reader-prepark-echo.test.tsx +200 -0
  160. package/src/__tests__/screen-reader-render-options.test.ts +262 -0
  161. package/src/__tests__/screen-reader-stdout.test.ts +322 -0
  162. package/src/__tests__/screen-reader-turn-marks-port.test.tsx +85 -0
  163. package/src/__tests__/screen-reader-turn-signals.test.tsx +125 -0
  164. package/src/__tests__/sec-019-terminal-sanitizer.test.ts +276 -0
  165. package/src/__tests__/sec-019-tool-label-render.test.tsx +207 -0
  166. package/src/__tests__/selection-flow.test.ts +61 -0
  167. package/src/__tests__/session-event-notices.test.tsx +31 -0
  168. package/src/__tests__/session-init-poller.test.ts +102 -0
  169. package/src/__tests__/session-picker.test.tsx +93 -0
  170. package/src/__tests__/session-switch-channel.test.tsx +470 -0
  171. package/src/__tests__/shutdown-signal.test.ts +34 -0
  172. package/src/__tests__/status-activity.test.ts +71 -0
  173. package/src/__tests__/status-bar.test.tsx +290 -0
  174. package/src/__tests__/status-glyph.test.ts +30 -0
  175. package/src/__tests__/streaming-indicator.test.tsx +178 -0
  176. package/src/__tests__/terminal-capabilities.test.ts +153 -0
  177. package/src/__tests__/terminal-handoff-controller.test.ts +173 -0
  178. package/src/__tests__/terminal-handoff-pty-e2e.test.ts +77 -0
  179. package/src/__tests__/text-prompt-flow.test.ts +77 -0
  180. package/src/__tests__/theme-picker.test.tsx +345 -0
  181. package/src/__tests__/transport-boundary.test.ts +11 -0
  182. package/src/__tests__/tui-app-channel-port.test.ts +31 -0
  183. package/src/__tests__/tui-channel-init-failure.test.ts +70 -0
  184. package/src/__tests__/tui-channel-lifecycle-coordinator.test.ts +258 -0
  185. package/src/__tests__/tui-interaction-queues.test.ts +149 -0
  186. package/src/__tests__/tui-preset-capabilities.test.ts +33 -0
  187. package/src/__tests__/tui-state-manager.error-stall.test.ts +169 -0
  188. package/src/__tests__/tui-state-manager.test.ts +482 -0
  189. package/src/__tests__/ui-intent-rendering.test.tsx +291 -0
  190. package/src/__tests__/wave-text.test.tsx +133 -0
  191. package/src/__tests__/wire-tui-channel.test.ts +1457 -0
  192. package/src/__tests__/wire-tui-client-commands.test.ts +243 -0
  193. package/src/app-banner.tsx +42 -0
  194. package/src/app-static-items.ts +35 -0
  195. package/src/app-view-model.ts +140 -0
  196. package/src/attached-session-connection.ts +14 -0
  197. package/src/attention/__tests__/attention-coordinator.test.ts +166 -0
  198. package/src/attention/__tests__/attention-tracker.test.ts +103 -0
  199. package/src/attention/__tests__/countdown.test.ts +15 -0
  200. package/src/attention/__tests__/focus-input-filter.test.ts +93 -0
  201. package/src/attention/__tests__/interval-recap.test.ts +49 -0
  202. package/src/attention/attention-coordinator.ts +71 -0
  203. package/src/attention/attention-tracker.ts +124 -0
  204. package/src/attention/countdown.ts +24 -0
  205. package/src/attention/focus-input-filter.ts +131 -0
  206. package/src/attention/interval-recap.ts +148 -0
  207. package/src/attention/time-units.ts +6 -0
  208. package/src/attention-bell.ts +79 -0
  209. package/src/attributed-user-echo.ts +30 -0
  210. package/src/background-task-row-format.ts +99 -0
  211. package/src/command-interaction.ts +8 -0
  212. package/src/command-output-summary.ts +129 -0
  213. package/src/create-default-tui-cli-adapter.ts +45 -0
  214. package/src/execution-workspace-switcher-selection.ts +118 -0
  215. package/src/execution-workspace-view-model.ts +139 -0
  216. package/src/external-prompt-notice.tsx +38 -0
  217. package/src/flows/__tests__/background-focus-flow.test.ts +48 -0
  218. package/src/flows/__tests__/defer-submit.test.ts +88 -0
  219. package/src/flows/__tests__/real-cursor-flow.test.ts +139 -0
  220. package/src/flows/background-focus-flow.ts +44 -0
  221. package/src/flows/cjk-cursor-motion.ts +75 -0
  222. package/src/flows/cjk-text-deletion.ts +53 -0
  223. package/src/flows/cjk-text-input-flow.ts +248 -0
  224. package/src/flows/cjk-text-input-types.ts +24 -0
  225. package/src/flows/confirm-prompt-flow.ts +90 -0
  226. package/src/flows/defer-submit.ts +67 -0
  227. package/src/flows/fork-attach-flow.ts +62 -0
  228. package/src/flows/input-area-flow.ts +188 -0
  229. package/src/flows/permission-prompt-flow.ts +108 -0
  230. package/src/flows/real-cursor-flow.ts +83 -0
  231. package/src/flows/selection-flow.ts +205 -0
  232. package/src/flows/session-init-poller.ts +76 -0
  233. package/src/flows/text-prompt-flow.ts +93 -0
  234. package/src/history-search/__tests__/history-search-flow.test.ts +86 -0
  235. package/src/history-search/history-search-flow.ts +94 -0
  236. package/src/history-search/useHistorySearch.ts +336 -0
  237. package/src/hooks/__tests__/use-app-detach-key.test.tsx +72 -0
  238. package/src/hooks/__tests__/use-app-loop-escape.test.tsx +87 -0
  239. package/src/hooks/__tests__/use-plugin-callbacks.test.ts +18 -0
  240. package/src/hooks/__tests__/use-tui-channel-stability.test.tsx +100 -0
  241. package/src/hooks/__tests__/useAppThemeState.test.tsx +130 -0
  242. package/src/hooks/command-result-handler.ts +44 -0
  243. package/src/hooks/side-effects-types.ts +45 -0
  244. package/src/hooks/useAppController.ts +271 -0
  245. package/src/hooks/useAppInputBindings.ts +163 -0
  246. package/src/hooks/useAppInteractionState.ts +84 -0
  247. package/src/hooks/useAppLifecycleState.ts +81 -0
  248. package/src/hooks/useAppOverlays.ts +90 -0
  249. package/src/hooks/useAppScreenState.ts +81 -0
  250. package/src/hooks/useAppSubmissionState.ts +45 -0
  251. package/src/hooks/useAppThemeState.ts +157 -0
  252. package/src/hooks/useAppWorkspaceState.ts +149 -0
  253. package/src/hooks/useAppearanceSettings.ts +30 -0
  254. package/src/hooks/useAutocomplete.ts +101 -0
  255. package/src/hooks/useBackgroundPanel.ts +58 -0
  256. package/src/hooks/useCountdownTick.ts +24 -0
  257. package/src/hooks/useExecutionDetailPage.ts +69 -0
  258. package/src/hooks/useForkAttach.ts +60 -0
  259. package/src/hooks/useInputAreaHistorySearch.ts +72 -0
  260. package/src/hooks/useInputAreaKeys.ts +114 -0
  261. package/src/hooks/useNumberedSelection.ts +93 -0
  262. package/src/hooks/usePluginCallbacks.ts +38 -0
  263. package/src/hooks/usePluginScreenData.ts +85 -0
  264. package/src/hooks/useRealCursorPosition.ts +125 -0
  265. package/src/hooks/useRenderMarkdown.ts +38 -0
  266. package/src/hooks/useScreenReaderTurnSignals.ts +113 -0
  267. package/src/hooks/useSideEffects.ts +144 -0
  268. package/src/hooks/useStatusLineSettings.ts +50 -0
  269. package/src/hooks/useTerminalHandoffSuspension.ts +30 -0
  270. package/src/hooks/useTuiChannel.ts +141 -0
  271. package/src/humanize-tool-name.ts +51 -0
  272. package/src/index.ts +72 -0
  273. package/src/input-area-rules.tsx +71 -0
  274. package/src/json-value.ts +18 -0
  275. package/src/key-hint-footer.tsx +54 -0
  276. package/src/keybindings/__tests__/keybinding-registry.test.ts +228 -0
  277. package/src/keybindings/__tests__/node-keybindings-source.test.ts +118 -0
  278. package/src/keybindings/keybinding-catalogue.ts +171 -0
  279. package/src/keybindings/keybinding-registry.ts +303 -0
  280. package/src/keybindings/keybinding-syntax.ts +155 -0
  281. package/src/keybindings/keybindings-context.tsx +124 -0
  282. package/src/keybindings/node-keybindings-source.ts +189 -0
  283. package/src/numbered-list.tsx +113 -0
  284. package/src/plugin-tui-handlers.ts +163 -0
  285. package/src/product-display-name-context.tsx +30 -0
  286. package/src/render-markdown.ts +209 -0
  287. package/src/render.tsx +746 -0
  288. package/src/sanitize-terminal-text.ts +171 -0
  289. package/src/screen-reader-announcement.ts +52 -0
  290. package/src/screen-reader-context.tsx +32 -0
  291. package/src/screen-reader-labels.ts +64 -0
  292. package/src/screen-reader-pacing-context.tsx +47 -0
  293. package/src/screen-reader-pacing.ts +178 -0
  294. package/src/screen-reader-stdout.ts +328 -0
  295. package/src/short-session-id.ts +9 -0
  296. package/src/shutdown-signal.ts +34 -0
  297. package/src/slash-command-input.ts +11 -0
  298. package/src/status-activity.ts +70 -0
  299. package/src/status-glyph.ts +78 -0
  300. package/src/terminal-capabilities-context.tsx +27 -0
  301. package/src/terminal-capabilities.ts +77 -0
  302. package/src/terminal-focus-reporting.ts +50 -0
  303. package/src/terminal-handoff-controller.ts +98 -0
  304. package/src/terminal-marks.ts +63 -0
  305. package/src/theme/__tests__/built-in-themes.test.ts +117 -0
  306. package/src/theme/__tests__/color-vision.test.ts +82 -0
  307. package/src/theme/__tests__/theme-context.test.tsx +146 -0
  308. package/src/theme/__tests__/theme-document.test.ts +222 -0
  309. package/src/theme/__tests__/theme-registry.test.ts +138 -0
  310. package/src/theme/__tests__/theme-styles.test.ts +233 -0
  311. package/src/theme/built-in-themes.ts +342 -0
  312. package/src/theme/color-vision.ts +171 -0
  313. package/src/theme/index.ts +45 -0
  314. package/src/theme/theme-context.tsx +81 -0
  315. package/src/theme/theme-contracts.ts +139 -0
  316. package/src/theme/theme-document.ts +278 -0
  317. package/src/theme/theme-registry.ts +127 -0
  318. package/src/theme/theme-styles.ts +177 -0
  319. package/src/tool-summary-status.ts +59 -0
  320. package/src/tui-app-channel-port.ts +111 -0
  321. package/src/tui-channel-lifecycle-coordinator.ts +149 -0
  322. package/src/tui-channel-options.ts +184 -0
  323. package/src/tui-cli-adapter-context.tsx +13 -0
  324. package/src/tui-cli-adapter.ts +24 -0
  325. package/src/tui-interaction-queues.ts +171 -0
  326. package/src/tui-session-binding.ts +52 -0
  327. package/src/tui-session-event-projector.ts +149 -0
  328. package/src/tui-session-events.ts +89 -0
  329. package/src/tui-session-notice-store.ts +43 -0
  330. package/src/tui-session-options.ts +134 -0
  331. package/src/tui-state-manager.ts +347 -0
  332. package/src/types.ts +21 -0
  333. package/src/use-terminal-title.ts +29 -0
  334. package/src/utils/__tests__/edit-diff.test.ts +426 -0
  335. package/src/utils/__tests__/paste-detection.test.ts +116 -0
  336. package/src/utils/__tests__/paste-labels.test.ts +46 -0
  337. package/src/utils/__tests__/tool-call-extractor.test.ts +227 -0
  338. package/src/utils/__tests__/tool-diff-summary.test.ts +104 -0
  339. package/src/utils/edit-diff.ts +151 -0
  340. package/src/utils/input-top-border.ts +19 -0
  341. package/src/utils/paste-labels.ts +9 -0
  342. package/src/utils/tool-call-extractor.ts +92 -0
  343. package/src/utils/tool-diff-summary.ts +75 -0
  344. package/src/waiting-loop-stop-notice.ts +23 -0
  345. package/src/wire-history-sync.ts +159 -0
  346. package/src/wire-tui-channel.ts +963 -0
  347. package/src/wire-tui-client-commands.ts +142 -0
  348. package/src/wire-tui-projection.ts +136 -0
@@ -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
+ }
@@ -0,0 +1,171 @@
1
+ import type { IPendingPermissionRequest } from './types.js';
2
+ import type { IActionRequest, TActionResponse, TToolArgs } from '@robota-sdk/agent-core';
3
+ import type {
4
+ TPermissionRequester,
5
+ TPermissionResultValue,
6
+ } from '@robota-sdk/agent-interface-session';
7
+
8
+ interface IUserActionQueueEntry {
9
+ presentedRequest: IActionRequest;
10
+ resolve: (response: TActionResponse) => void;
11
+ id?: string;
12
+ }
13
+
14
+ interface IPermissionQueueEntry {
15
+ toolName: string;
16
+ toolArgs: TToolArgs;
17
+ canPersistProjectPermission?: boolean;
18
+ requestedByPeer?: string;
19
+ /** Issue #3288 §1: a background agent's own request, forwarded to the person. */
20
+ requester?: TPermissionRequester;
21
+ resolve: (result: TPermissionResultValue) => void;
22
+ id?: string;
23
+ }
24
+
25
+ /** Serializes action prompts and guarantees every pending promise is settled during teardown. */
26
+ export class TuiUserActionQueue {
27
+ private entries: IUserActionQueueEntry[] = [];
28
+ private processing = false;
29
+
30
+ constructor(private readonly onChange: () => void) {}
31
+
32
+ get current(): IActionRequest | null {
33
+ return this.processing ? (this.entries[0]?.presentedRequest ?? null) : null;
34
+ }
35
+
36
+ enqueue(request: IActionRequest, id?: string): Promise<TActionResponse> {
37
+ return new Promise<TActionResponse>((resolve) => {
38
+ this.entries.push({
39
+ presentedRequest: { ...request },
40
+ resolve,
41
+ ...(id !== undefined ? { id } : {}),
42
+ });
43
+ this.processNext();
44
+ });
45
+ }
46
+
47
+ resolveCurrent(expectedRequest: IActionRequest, response: TActionResponse): void {
48
+ if (this.entries[0]?.presentedRequest !== expectedRequest) return;
49
+ const pending = this.entries.shift();
50
+ if (!pending) return;
51
+ this.processing = false;
52
+ this.onChange();
53
+ pending.resolve(response);
54
+ this.processNext();
55
+ }
56
+
57
+ dismissById(id: string): boolean {
58
+ const dismissed = this.entries.filter((entry) => entry.id === id);
59
+ if (dismissed.length === 0) return false;
60
+ this.entries = this.entries.filter((entry) => entry.id !== id);
61
+ this.processing = false;
62
+ for (const entry of dismissed) entry.resolve({ type: 'cancelled' });
63
+ this.processNext();
64
+ return true;
65
+ }
66
+
67
+ cancelAll(): void {
68
+ const queued = this.entries;
69
+ this.entries = [];
70
+ this.processing = false;
71
+ for (const pending of queued) pending.resolve({ type: 'cancelled' });
72
+ this.onChange();
73
+ }
74
+
75
+ private processNext(): void {
76
+ if (this.processing) return;
77
+ if (!this.entries[0]) {
78
+ this.onChange();
79
+ return;
80
+ }
81
+ this.processing = true;
82
+ this.onChange();
83
+ }
84
+ }
85
+
86
+ /** Serializes permission prompts and denies every pending request during teardown. */
87
+ export class TuiPermissionQueue {
88
+ private entries: IPermissionQueueEntry[] = [];
89
+ private processing = false;
90
+ private currentRequest: IPendingPermissionRequest | null = null;
91
+
92
+ constructor(private readonly onChange: () => void) {}
93
+
94
+ get current(): IPendingPermissionRequest | null {
95
+ return this.currentRequest;
96
+ }
97
+
98
+ enqueue(
99
+ toolName: string,
100
+ toolArgs: TToolArgs,
101
+ id?: string,
102
+ canPersistProjectPermission?: boolean,
103
+ requestedByPeer?: string,
104
+ requester?: TPermissionRequester,
105
+ ): Promise<TPermissionResultValue> {
106
+ return new Promise<TPermissionResultValue>((resolve) => {
107
+ this.entries.push({
108
+ toolName,
109
+ toolArgs,
110
+ resolve,
111
+ ...(id !== undefined ? { id } : {}),
112
+ ...(canPersistProjectPermission !== undefined ? { canPersistProjectPermission } : {}),
113
+ ...(requestedByPeer !== undefined ? { requestedByPeer } : {}),
114
+ ...(requester !== undefined ? { requester } : {}),
115
+ });
116
+ this.processNext();
117
+ });
118
+ }
119
+
120
+ dismissById(id: string): boolean {
121
+ const dismissed = this.entries.filter((entry) => entry.id === id);
122
+ if (dismissed.length === 0) return false;
123
+ const dismissedCurrent = this.entries[0]?.id === id;
124
+ this.entries = this.entries.filter((entry) => entry.id !== id);
125
+ for (const entry of dismissed) entry.resolve(false);
126
+ if (dismissedCurrent) {
127
+ this.processing = false;
128
+ this.currentRequest = null;
129
+ this.processNext();
130
+ }
131
+ return true;
132
+ }
133
+
134
+ cancelAll(): void {
135
+ const queued = this.entries;
136
+ this.entries = [];
137
+ this.processing = false;
138
+ this.currentRequest = null;
139
+ for (const pending of queued) pending.resolve(false);
140
+ this.onChange();
141
+ }
142
+
143
+ private processNext(): void {
144
+ if (this.processing) return;
145
+ const next = this.entries[0];
146
+ if (!next) {
147
+ this.currentRequest = null;
148
+ this.onChange();
149
+ return;
150
+ }
151
+ this.processing = true;
152
+ this.currentRequest = {
153
+ toolName: next.toolName,
154
+ toolArgs: next.toolArgs,
155
+ ...(next.canPersistProjectPermission !== undefined
156
+ ? { canPersistProjectPermission: next.canPersistProjectPermission }
157
+ : {}),
158
+ ...(next.requestedByPeer !== undefined ? { requestedByPeer: next.requestedByPeer } : {}),
159
+ ...(next.requester !== undefined ? { requester: next.requester } : {}),
160
+ resolve: (result) => {
161
+ if (this.entries[0] !== next) return;
162
+ this.entries.shift();
163
+ this.processing = false;
164
+ this.currentRequest = null;
165
+ next.resolve(result);
166
+ setTimeout(() => this.processNext(), 0);
167
+ },
168
+ };
169
+ this.onChange();
170
+ }
171
+ }
@@ -0,0 +1,52 @@
1
+ import type { TuiStateManager } from './tui-state-manager.js';
2
+ import type {
3
+ IInteractiveSession,
4
+ IInteractiveSessionEvents,
5
+ TInteractiveEventName,
6
+ } from '@robota-sdk/agent-interface-session';
7
+
8
+ export interface ITuiSessionEventBinding {
9
+ event: TInteractiveEventName;
10
+ handler: (...args: never[]) => void;
11
+ }
12
+
13
+ export interface ITuiSessionBinder {
14
+ <E extends TInteractiveEventName>(event: E, handler: IInteractiveSessionEvents[E]): void;
15
+ }
16
+
17
+ export function bindTuiSessionNoticeEvents(
18
+ bind: ITuiSessionBinder,
19
+ manager: TuiStateManager,
20
+ ): void {
21
+ bind('plan_event', (payload) => manager.addSessionEventNotice({ event: 'plan_event', payload }));
22
+ bind('context_file_refreshed', (payload) =>
23
+ manager.addSessionEventNotice({ event: 'context_file_refreshed', payload }),
24
+ );
25
+ bind('branch_event', (payload) =>
26
+ manager.addSessionEventNotice({ event: 'branch_event', payload }),
27
+ );
28
+ }
29
+
30
+ /** Isolate a TUI-owned projection failure from the session operation that already committed. */
31
+ export function bindTuiSessionEvent<E extends TInteractiveEventName>(
32
+ session: Pick<IInteractiveSession, 'on'>,
33
+ event: E,
34
+ handler: IInteractiveSessionEvents[E],
35
+ onDeliveryError: (error: Error, event: E) => void,
36
+ bindings: ITuiSessionEventBinding[],
37
+ ): void {
38
+ const ownedHandler = ((...args: never[]): void => {
39
+ try {
40
+ (handler as (...values: never[]) => void)(...args);
41
+ } catch (error) {
42
+ const normalized = error instanceof Error ? error : new Error(String(error));
43
+ try {
44
+ onDeliveryError(normalized, event);
45
+ } catch {
46
+ // Diagnostics cannot make a committed session operation fail.
47
+ }
48
+ }
49
+ }) as IInteractiveSessionEvents[E];
50
+ session.on(event, ownedHandler);
51
+ bindings.push({ event, handler: ownedHandler as (...args: never[]) => void });
52
+ }
@@ -0,0 +1,149 @@
1
+ import { printablePeerDriver } from '@robota-sdk/agent-core';
2
+
3
+ import { attributedUserEcho } from './attributed-user-echo.js';
4
+ import { bindTuiSessionEvent, bindTuiSessionNoticeEvents } from './tui-session-binding.js';
5
+
6
+ import type { AttentionCoordinator } from './attention/attention-coordinator.js';
7
+ import type { ITuiSessionEventBinding } from './tui-session-binding.js';
8
+ import type { TuiStateManager } from './tui-state-manager.js';
9
+ import type { IActionRequest, TActionResponse, TToolArgs } from '@robota-sdk/agent-core';
10
+ import type { InteractiveSession } from '@robota-sdk/agent-framework';
11
+ import type { IExecutionWorkspaceEvent } from '@robota-sdk/agent-interface-execution';
12
+ import type {
13
+ IExecutionResult,
14
+ IInteractiveSessionEvents,
15
+ TInteractiveEventName,
16
+ TPermissionRequester,
17
+ TPermissionResultValue,
18
+ } from '@robota-sdk/agent-interface-session';
19
+
20
+ export interface ITuiSessionEventProjectorOptions {
21
+ session: InteractiveSession;
22
+ manager: TuiStateManager;
23
+ requestPermission: (
24
+ toolName: string,
25
+ toolArgs: TToolArgs,
26
+ id: string,
27
+ canPersistProjectPermission?: boolean,
28
+ requestedByPeer?: string,
29
+ /** Issue #3288 §1: a background agent's own request, forwarded to the person. */
30
+ requester?: TPermissionRequester,
31
+ ) => Promise<TPermissionResultValue>;
32
+ askUser: (request: IActionRequest, id: string) => Promise<TActionResponse>;
33
+ dismissPrompt: (id: string) => void;
34
+ onDeliveryError?: (error: Error, event: TInteractiveEventName) => void;
35
+ /** SCREEN-1992: fed the events the interval recap counts; absent ⇒ nothing is counted. */
36
+ attention?: AttentionCoordinator;
37
+ }
38
+
39
+ /** Projects runtime session events into TUI state and owns the complete listener lifecycle. */
40
+ export class TuiSessionEventProjector {
41
+ private readonly bindings: ITuiSessionEventBinding[] = [];
42
+
43
+ constructor(private readonly options: ITuiSessionEventProjectorOptions) {}
44
+
45
+ wire(): void {
46
+ if (this.bindings.length > 0) return;
47
+ const { session, manager, attention } = this.options;
48
+
49
+ const onUserMessage = (content: string): void => {
50
+ manager.addUserEcho(attributedUserEcho(content, session));
51
+ };
52
+ const syncHistory = (): void => manager.syncHistory(session.getFullHistory());
53
+ const onComplete = (result: IExecutionResult): void => {
54
+ manager.onComplete(result);
55
+ attention?.onComplete();
56
+ syncHistory();
57
+ };
58
+ const onError = (error: Error): void => {
59
+ manager.onError(error);
60
+ attention?.onError();
61
+ syncHistory();
62
+ };
63
+ const onExecutionWorkspaceEvent = (event: IExecutionWorkspaceEvent): void => {
64
+ manager.syncExecutionWorkspaceSnapshot(event.snapshot);
65
+ attention?.onWorkspaceSnapshot(event.snapshot);
66
+ };
67
+
68
+ this.bind('user_message', onUserMessage);
69
+ this.bind('text_delta', manager.onTextDelta);
70
+ this.bind('tool_start', manager.onToolStart);
71
+ this.bind('tool_end', manager.onToolEnd);
72
+ this.bind('thinking', manager.onThinking);
73
+ this.bind('complete', onComplete);
74
+ this.bind('interrupted', manager.onInterrupted);
75
+ this.bind('error', onError);
76
+ this.bind('context_update', manager.onContextUpdate);
77
+ this.bind('compact', syncHistory);
78
+ this.bind('skill_activation', syncHistory);
79
+ this.bind('memory_event', syncHistory);
80
+ this.bind('execution_workspace_event', onExecutionWorkspaceEvent);
81
+ this.bind('history_cleared', () => manager.clearHistory());
82
+ // SCREEN-1992: subscribed regardless, so the channel's subscribed set equals the classification.
83
+ this.bind('turn_source', (source) => attention?.onTurnSource(source));
84
+ bindTuiSessionNoticeEvents(this.bind.bind(this), manager);
85
+
86
+ const onPermissionRequest: IInteractiveSessionEvents['permission_request'] = ({
87
+ id,
88
+ toolName,
89
+ toolArgs,
90
+ canPersistProjectPermission,
91
+ requesterDriverId,
92
+ requester,
93
+ }) => {
94
+ attention?.onNeedsInput();
95
+ // A peer turn's ask names the peer, printed only as a plain identifier.
96
+ const requestedByPeer = requesterDriverId?.startsWith('peer:')
97
+ ? printablePeerDriver(requesterDriverId)
98
+ : undefined;
99
+ void this.options
100
+ .requestPermission(
101
+ toolName,
102
+ toolArgs,
103
+ id,
104
+ canPersistProjectPermission,
105
+ requestedByPeer,
106
+ requester,
107
+ )
108
+ .then((result) => session.resolvePermission(id, result))
109
+ .catch(() => session.resolvePermission(id, false));
110
+ };
111
+ const onAskRequest: IInteractiveSessionEvents['ask_request'] = ({ id, request }) => {
112
+ attention?.onNeedsInput();
113
+ void this.options
114
+ .askUser(request, id)
115
+ .then((response) => session.resolveAsk(id, response))
116
+ .catch(() => session.resolveAsk(id, { type: 'cancelled' }));
117
+ };
118
+ this.bind('permission_request', onPermissionRequest);
119
+ this.bind('ask_request', onAskRequest);
120
+ this.bind('prompt_resolved', ({ id }) => this.options.dismissPrompt(id));
121
+ attention?.wire();
122
+ }
123
+
124
+ unwire(): void {
125
+ const { session } = this.options;
126
+ this.options.attention?.unwire();
127
+ for (const { event, handler } of this.bindings) {
128
+ session.off(event, handler as IInteractiveSessionEvents[typeof event]);
129
+ }
130
+ this.bindings.length = 0;
131
+ }
132
+
133
+ private bind<E extends TInteractiveEventName>(
134
+ event: E,
135
+ handler: IInteractiveSessionEvents[E],
136
+ ): void {
137
+ bindTuiSessionEvent(
138
+ this.options.session,
139
+ event,
140
+ handler,
141
+ (error) => {
142
+ const report = this.options.onDeliveryError;
143
+ if (report) report(error, event);
144
+ else this.options.manager.addSessionEventDeliveryError(error, event);
145
+ },
146
+ this.bindings,
147
+ );
148
+ }
149
+ }
@@ -0,0 +1,89 @@
1
+ import type {
2
+ IBranchEvent,
3
+ IContextFileRefreshedEvent,
4
+ IPlanApprovalEvent,
5
+ TInteractiveEventName,
6
+ } from '@robota-sdk/agent-interface-session';
7
+
8
+ export type TTuiSessionEventClassification = 'channel' | 'react-side-effect' | 'non-surface';
9
+
10
+ /** Exhaustive TUI-owner policy over the shared session-event vocabulary. */
11
+ export const TUI_SESSION_EVENT_CLASSIFICATION = {
12
+ text_delta: 'channel',
13
+ tool_start: 'channel',
14
+ tool_end: 'channel',
15
+ thinking: 'channel',
16
+ thinking_delta: 'non-surface',
17
+ complete: 'channel',
18
+ error: 'channel',
19
+ context_update: 'channel',
20
+ compact: 'channel',
21
+ interrupted: 'channel',
22
+ skill_activation: 'channel',
23
+ background_task_event: 'non-surface',
24
+ background_job_group_event: 'non-surface',
25
+ execution_workspace_event: 'channel',
26
+ user_message: 'channel',
27
+ turn_source: 'channel',
28
+ context_file_refreshed: 'channel',
29
+ memory_event: 'channel',
30
+ goal_event: 'non-surface',
31
+ plan_event: 'channel',
32
+ branch_event: 'channel',
33
+ permission_request: 'channel',
34
+ ask_request: 'channel',
35
+ prompt_resolved: 'channel',
36
+ ui_intent: 'react-side-effect',
37
+ session_renamed: 'react-side-effect',
38
+ history_cleared: 'channel',
39
+ // #3189: a host's session slot emits it on a switch; the TUI switches by remounting its own
40
+ // channel onto the next session, so it never follows a slot.
41
+ session_switched: 'non-surface',
42
+ // A host forwards it to attached clients as `session_status`; this channel owns its session and
43
+ // reads the status from it on each render.
44
+ status_changed: 'non-surface',
45
+ } as const satisfies Record<TInteractiveEventName, TTuiSessionEventClassification>;
46
+
47
+ export interface ITuiSessionEventNotice {
48
+ id: string;
49
+ event:
50
+ | 'plan_event'
51
+ | 'context_file_refreshed'
52
+ | 'branch_event'
53
+ | 'delivery-error'
54
+ /** SCREEN-1992: TUI-originated — the interval recap on return, not a session event. */
55
+ | 'attention-recap';
56
+ message: string;
57
+ }
58
+
59
+ export type TTuiNoticeInput =
60
+ | { event: 'plan_event'; payload: IPlanApprovalEvent }
61
+ | { event: 'context_file_refreshed'; payload: IContextFileRefreshedEvent }
62
+ | { event: 'branch_event'; payload: IBranchEvent };
63
+
64
+ /** Pure deterministic projection from domain event to TUI render state. */
65
+ export function createTuiSessionEventNotice(
66
+ id: string,
67
+ input: TTuiNoticeInput,
68
+ ): ITuiSessionEventNotice {
69
+ switch (input.event) {
70
+ case 'plan_event':
71
+ return {
72
+ id,
73
+ event: input.event,
74
+ message: `Plan ${input.payload.type.replaceAll('_', ' ')}`,
75
+ };
76
+ case 'context_file_refreshed':
77
+ return {
78
+ id,
79
+ event: input.event,
80
+ message: `Context refreshed: ${input.payload.filePath}`,
81
+ };
82
+ case 'branch_event':
83
+ return {
84
+ id,
85
+ event: input.event,
86
+ message: `Branch ${input.payload.kind.replaceAll('_', ' ')}: ${input.payload.branchId} @ ${input.payload.checkpointId}`,
87
+ };
88
+ }
89
+ }
@@ -0,0 +1,43 @@
1
+ import {
2
+ createTuiSessionEventNotice,
3
+ type ITuiSessionEventNotice,
4
+ type TTuiNoticeInput,
5
+ } from './tui-session-events.js';
6
+
7
+ import type { TInteractiveEventName } from '@robota-sdk/agent-interface-session';
8
+
9
+ const NOTICE_LIMIT = 50;
10
+
11
+ /** Bounded append-only render projection, deliberately separate from canonical session history. */
12
+ export class TuiSessionNoticeStore {
13
+ private sequence = 0;
14
+ readonly notices: ITuiSessionEventNotice[] = [];
15
+
16
+ add(input: TTuiNoticeInput): void {
17
+ this.append(createTuiSessionEventNotice(this.nextId(), input));
18
+ }
19
+
20
+ addDeliveryError(error: Error, event: TInteractiveEventName): void {
21
+ this.append({
22
+ id: this.nextId(),
23
+ event: 'delivery-error',
24
+ message: `Session event delivery failed (${event}): ${error.message}`,
25
+ });
26
+ }
27
+
28
+ /** SCREEN-1992: one line for the unattended interval that just ended. */
29
+ addAttentionRecap(line: string): void {
30
+ this.append({ id: this.nextId(), event: 'attention-recap', message: line });
31
+ }
32
+
33
+ private nextId(): string {
34
+ this.sequence += 1;
35
+ return `session-event-${this.sequence}`;
36
+ }
37
+
38
+ private append(notice: ITuiSessionEventNotice): void {
39
+ this.notices.push(notice);
40
+ const overflow = this.notices.length - NOTICE_LIMIT;
41
+ if (overflow > 0) this.notices.splice(0, overflow);
42
+ }
43
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * ARCH-005 — the TUI's session-option mapping, split out of `TuiInteractionChannel`.
3
+ *
4
+ * Pure: it maps the channel's already-resolved options onto the framework's `TInteractiveSessionOptions`.
5
+ * RUNTIME-001 — the channel then builds through the shared `buildRuntimeSession` seam (agent-framework),
6
+ * never a private `new InteractiveSession`: one session-construction SSOT across the TUI, print, and
7
+ * `--serve`.
8
+ */
9
+
10
+ import type { ITuiInteractionChannelOptions } from './tui-channel-options.js';
11
+ import type { TInteractiveSessionOptions } from '@robota-sdk/agent-framework';
12
+
13
+ /** Map the channel's resolved options onto the framework session-construction options. */
14
+ export function buildTuiSessionOptions(
15
+ opts: ITuiInteractionChannelOptions,
16
+ ): TInteractiveSessionOptions {
17
+ return {
18
+ cwd: opts.cwd,
19
+ ...(opts.livePromptTrace ? { livePromptTrace: opts.livePromptTrace } : {}),
20
+ provider: opts.provider,
21
+ ...(opts.providerErrorGuidance !== undefined
22
+ ? { providerErrorGuidance: opts.providerErrorGuidance }
23
+ : {}),
24
+ ...(opts.promptFileReferenceTag !== undefined
25
+ ? { promptFileReferenceTag: opts.promptFileReferenceTag }
26
+ : {}),
27
+ ...(opts.modelCommandToolPrefix !== undefined
28
+ ? { modelCommandToolPrefix: opts.modelCommandToolPrefix }
29
+ : {}),
30
+ ...(opts.subagentHookEnvironmentNames !== undefined
31
+ ? { subagentHookEnvironmentNames: opts.subagentHookEnvironmentNames }
32
+ : {}),
33
+ ...(opts.observerFailureWarningCode !== undefined
34
+ ? { observerFailureWarningCode: opts.observerFailureWarningCode }
35
+ : {}),
36
+ ...(opts.commandHookShell !== undefined ? { commandHookShell: opts.commandHookShell } : {}),
37
+ ...(opts.bare === true ? { bare: true } : {}),
38
+ ...(opts.skipConfiguredHooks === true ? { skipConfiguredHooks: true } : {}),
39
+ ...(opts.orgPolicy !== undefined ? { orgPolicy: opts.orgPolicy } : {}),
40
+ ...(opts.projectAccess !== undefined ? { projectAccess: opts.projectAccess } : {}),
41
+ ...(opts.projectSettingsPaths !== undefined
42
+ ? { projectSettingsPaths: opts.projectSettingsPaths }
43
+ : {}),
44
+ ...(opts.baselinePermissionAllow !== undefined
45
+ ? { baselinePermissionAllow: opts.baselinePermissionAllow }
46
+ : {}),
47
+ ...(opts.taskContext !== undefined ? { taskContext: opts.taskContext } : {}),
48
+ ...(opts.contributionSources !== undefined
49
+ ? { contributionSources: opts.contributionSources }
50
+ : {}),
51
+ ...(opts.skillRoots !== undefined ? { skillRoots: opts.skillRoots } : {}),
52
+ ...(opts.userSettingsSources !== undefined
53
+ ? { userSettingsSources: opts.userSettingsSources }
54
+ : {}),
55
+ ...(opts.environment !== undefined ? { environment: opts.environment } : {}),
56
+ ...(opts.resolveProviderCredential !== undefined
57
+ ? { resolveProviderCredential: opts.resolveProviderCredential }
58
+ : {}),
59
+ ...(opts.editCheckpointStore !== undefined
60
+ ? { editCheckpointStore: opts.editCheckpointStore }
61
+ : {}),
62
+ ...(opts.externalEventVerifierFactory !== undefined
63
+ ? { externalEventVerifierFactory: opts.externalEventVerifierFactory }
64
+ : {}),
65
+ ...(opts.externalEventGrantHistory !== undefined
66
+ ? { externalEventGrantHistory: opts.externalEventGrantHistory }
67
+ : {}),
68
+ // CLI-076: forward the resolved model so `--model` takes effect rather than falling through to the
69
+ // session's config/default model.
70
+ ...(opts.model !== undefined ? { model: opts.model } : {}),
71
+ ...(opts.effort !== undefined ? { effort: opts.effort } : {}),
72
+ ...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}),
73
+ ...(opts.maxOutputTokens !== undefined ? { maxOutputTokens: opts.maxOutputTokens } : {}),
74
+ ...(opts.presetSystemPrompt !== undefined
75
+ ? { presetSystemPrompt: opts.presetSystemPrompt }
76
+ : {}),
77
+ ...(opts.responseFormat !== undefined ? { responseFormat: opts.responseFormat } : {}),
78
+ ...(opts.outputStyle !== undefined ? { outputStyle: opts.outputStyle } : {}),
79
+ permissionMode: opts.permissionMode,
80
+ maxTurns: opts.maxTurns,
81
+ // REMOTE-007: no injected permission/ask handlers — the TUI subscribes to the session's
82
+ // transport-neutral `permission_request`/`ask_request` events (wireSessionEvents) and answers via
83
+ // `resolvePermission`/`resolveAsk`. The local Ink queues + rendering are unchanged.
84
+ sessionStore: opts.sessionStore,
85
+ disableSessionLoops: opts.disableSessionLoops,
86
+ resolveDefaultLoopPrompt: opts.resolveDefaultLoopPrompt,
87
+ resumeSessionId: opts.resumeSessionId,
88
+ forkSession: opts.forkSession,
89
+ ...(opts.workspaceMovedFrom !== undefined
90
+ ? { workspaceMovedFrom: opts.workspaceMovedFrom }
91
+ : {}),
92
+ sessionName: opts.sessionName,
93
+ // The session names itself after its first real turn, unless it already has a name.
94
+ autoName: true,
95
+ backgroundTaskRunners: opts.backgroundTaskRunners,
96
+ ...(opts.toolCallHandoff !== undefined ? { toolCallHandoff: opts.toolCallHandoff } : {}),
97
+ subagentRunnerFactory: opts.subagentRunnerFactory,
98
+ ...(opts.agentDefinitions !== undefined ? { agentDefinitions: opts.agentDefinitions } : {}),
99
+ ...(opts.agentDefinitionRoots !== undefined
100
+ ? { agentDefinitionRoots: opts.agentDefinitionRoots }
101
+ : {}),
102
+ ...(opts.pluginDirectories !== undefined ? { pluginDirectories: opts.pluginDirectories } : {}),
103
+ ...(opts.additionalTools !== undefined ? { additionalTools: opts.additionalTools } : {}),
104
+ ...(opts.defaultTools !== undefined ? { defaultTools: opts.defaultTools } : {}),
105
+ ...(opts.toolExecutionPolicy !== undefined
106
+ ? { toolExecutionPolicy: opts.toolExecutionPolicy }
107
+ : {}),
108
+ ...(opts.sandboxClient !== undefined ? { sandboxClient: opts.sandboxClient } : {}),
109
+ commandModules: opts.commandModules,
110
+ commandHostAdapters: opts.commandHostAdapters,
111
+ shellExec: opts.shellExec,
112
+ ...(opts.remoteCommandPolicy ? { remoteCommandPolicy: opts.remoteCommandPolicy } : {}),
113
+ language: opts.language,
114
+ agentName: opts.agentName,
115
+ activePresetId: opts.activePresetId,
116
+ persona: opts.persona,
117
+ systemPrompt: opts.systemPrompt,
118
+ appendSystemPrompt: opts.appendSystemPrompt,
119
+ allowedTools: opts.allowedTools,
120
+ deniedTools: opts.deniedTools,
121
+ enableParallelSubagents: opts.enableParallelSubagents,
122
+ selfVerification: opts.selfVerification,
123
+ terminalHandoff: opts.terminalHandoff,
124
+ // #1844: the session reads these when `/provider switch` hot-swaps. Absent, the switch throws
125
+ // "Unknown provider: <name>. Currently supported: " with an EMPTY list — measured, not inferred.
126
+ ...(opts.providerDefinitions ? { providerDefinitions: opts.providerDefinitions } : {}),
127
+ // SELFHOST-008 P6: forward the surface-resolved memory fields only when present (absent ⇒ OFF).
128
+ ...(opts.memoryStore ? { memoryStore: opts.memoryStore } : {}),
129
+ ...(opts.automaticMemory ? { automaticMemory: opts.automaticMemory } : {}),
130
+ ...(opts.recallMemory ? { recallMemory: opts.recallMemory } : {}),
131
+ // SCREEN-1993: the owner-prompt append (absent ⇒ the controller records nothing).
132
+ ...(opts.promptHistory ? { promptHistory: opts.promptHistory } : {}),
133
+ };
134
+ }