thaddeus 1.0.18 → 1.0.27

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 (2084) hide show
  1. package/package.json +14 -5
  2. package/src/QueryEngine.js +926 -0
  3. package/src/Task.js +49 -0
  4. package/src/Tool.js +61 -0
  5. package/src/assistant/gate.js +5 -0
  6. package/src/assistant/index.js +106 -0
  7. package/src/assistant/sessionHistory.js +145 -0
  8. package/src/bootstrap/state.js +1163 -0
  9. package/src/bridge/bridgeApi.js +304 -0
  10. package/src/bridge/bridgeConfig.js +39 -0
  11. package/src/bridge/bridgeDebug.js +73 -0
  12. package/src/bridge/bridgeEnabled.js +185 -0
  13. package/src/bridge/bridgeMain.js +2289 -0
  14. package/src/bridge/bridgeMessaging.js +353 -0
  15. package/src/bridge/bridgePermissionCallbacks.js +10 -0
  16. package/src/bridge/bridgePointer.js +175 -0
  17. package/src/bridge/bridgeStatusUtil.js +105 -0
  18. package/src/bridge/bridgeUI.js +411 -0
  19. package/src/bridge/capacityWake.js +35 -0
  20. package/src/bridge/codeSessionApi.js +111 -0
  21. package/src/bridge/createSession.js +273 -0
  22. package/src/bridge/debugUtils.js +115 -0
  23. package/src/bridge/envLessBridgeConfig.js +120 -0
  24. package/src/bridge/flushGate.js +65 -0
  25. package/src/bridge/inboundAttachments.js +152 -0
  26. package/src/bridge/inboundMessages.js +63 -0
  27. package/src/bridge/initReplBridge.js +431 -0
  28. package/src/bridge/jwtUtils.js +185 -0
  29. package/src/bridge/peerSessions.js +5 -0
  30. package/src/bridge/pollConfig.js +85 -0
  31. package/src/bridge/pollConfigDefaults.js +62 -0
  32. package/src/bridge/remoteBridgeCore.js +712 -0
  33. package/src/bridge/replBridge.js +1719 -0
  34. package/src/bridge/replBridgeHandle.js +30 -0
  35. package/src/bridge/replBridgeTransport.js +236 -0
  36. package/src/bridge/sessionIdCompat.js +56 -0
  37. package/src/bridge/sessionRunner.js +421 -0
  38. package/src/bridge/trustedDevice.js +170 -0
  39. package/src/bridge/types.js +9 -0
  40. package/src/bridge/webhookSanitizer.js +6 -0
  41. package/src/bridge/workSecret.js +99 -0
  42. package/src/buddy/CompanionSprite.js +348 -0
  43. package/src/buddy/companion.js +107 -0
  44. package/src/buddy/prompt.js +33 -0
  45. package/src/buddy/sprites.js +488 -0
  46. package/src/buddy/types.js +90 -0
  47. package/src/buddy/useBuddyNotification.js +85 -0
  48. package/src/cli/bg.js +17 -0
  49. package/src/cli/exit.js +30 -0
  50. package/src/cli/handlers/agents.js +55 -0
  51. package/src/cli/handlers/auth.js +249 -0
  52. package/src/cli/handlers/autoMode.js +128 -0
  53. package/src/cli/handlers/mcp.js +335 -0
  54. package/src/cli/handlers/plugins.js +634 -0
  55. package/src/cli/handlers/templateJobs.js +19 -0
  56. package/src/cli/handlers/util.js +76 -0
  57. package/src/cli/ndjsonSafeStringify.js +27 -0
  58. package/src/cli/print.js +4294 -0
  59. package/src/cli/remoteIO.js +208 -0
  60. package/src/cli/structuredIO.js +644 -0
  61. package/src/cli/transports/HybridTransport.js +233 -0
  62. package/src/cli/transports/SSETransport.js +538 -0
  63. package/src/cli/transports/SerialBatchEventUploader.js +224 -0
  64. package/src/cli/transports/WebSocketTransport.js +613 -0
  65. package/src/cli/transports/WorkerStateUploader.js +88 -0
  66. package/src/cli/transports/ccrClient.js +711 -0
  67. package/src/cli/transports/transportUtils.js +39 -0
  68. package/src/cli/update.js +314 -0
  69. package/src/commandCenter/launch.js +39 -0
  70. package/src/commandCenter/phoneApi.js +168 -0
  71. package/src/commandCenter/phoneStore.js +159 -0
  72. package/src/commandCenter/reactorBus.js +130 -0
  73. package/src/commandCenter/server.js +288 -0
  74. package/src/commandCenter/server.ts +42 -7
  75. package/src/commandCenter/tunnel.js +199 -0
  76. package/src/commands/add-dir/add-dir.js +121 -0
  77. package/src/commands/add-dir/index.js +8 -0
  78. package/src/commands/add-dir/validation.js +76 -0
  79. package/src/commands/advisor.js +88 -0
  80. package/src/commands/agents/agents.js +10 -0
  81. package/src/commands/agents/index.js +7 -0
  82. package/src/commands/agents-platform/index.js +2 -0
  83. package/src/commands/assistant/index.js +86 -0
  84. package/src/commands/backup/index.js +31 -0
  85. package/src/commands/branch/branch.js +205 -0
  86. package/src/commands/branch/index.js +11 -0
  87. package/src/commands/bridge/bridge.js +513 -0
  88. package/src/commands/bridge/index.js +22 -0
  89. package/src/commands/bridge-kick.js +179 -0
  90. package/src/commands/brief.js +89 -0
  91. package/src/commands/btw/btw.js +235 -0
  92. package/src/commands/btw/index.js +9 -0
  93. package/src/commands/buddy/buddy.js +100 -0
  94. package/src/commands/buddy/index.js +11 -0
  95. package/src/commands/chrome/chrome.js +291 -0
  96. package/src/commands/chrome/index.js +10 -0
  97. package/src/commands/clear/caches.js +116 -0
  98. package/src/commands/clear/clear.js +5 -0
  99. package/src/commands/clear/conversation.js +189 -0
  100. package/src/commands/clear/index.js +9 -0
  101. package/src/commands/color/color.js +58 -0
  102. package/src/commands/color/index.js +9 -0
  103. package/src/commands/commit-push-pr.js +137 -0
  104. package/src/commands/commit.js +80 -0
  105. package/src/commands/compact/compact.js +194 -0
  106. package/src/commands/compact/index.js +11 -0
  107. package/src/commands/config/config.js +6 -0
  108. package/src/commands/config/index.js +8 -0
  109. package/src/commands/context/context-noninteractive.js +219 -0
  110. package/src/commands/context/context.js +45 -0
  111. package/src/commands/context/index.js +21 -0
  112. package/src/commands/coordinator.js +34 -0
  113. package/src/commands/copy/copy.js +366 -0
  114. package/src/commands/copy/index.js +7 -0
  115. package/src/commands/cost/cost.js +21 -0
  116. package/src/commands/cost/index.js +16 -0
  117. package/src/commands/createMovedToPluginCommand.js +33 -0
  118. package/src/commands/desktop/desktop.js +6 -0
  119. package/src/commands/desktop/index.js +22 -0
  120. package/src/commands/diff/diff.js +6 -0
  121. package/src/commands/diff/index.js +6 -0
  122. package/src/commands/doctor/doctor.js +6 -0
  123. package/src/commands/doctor/index.js +9 -0
  124. package/src/commands/effort/effort.js +166 -0
  125. package/src/commands/effort/index.js +11 -0
  126. package/src/commands/exit/exit.js +32 -0
  127. package/src/commands/exit/index.js +9 -0
  128. package/src/commands/export/export.js +87 -0
  129. package/src/commands/export/index.js +8 -0
  130. package/src/commands/extra-usage/extra-usage-core.js +99 -0
  131. package/src/commands/extra-usage/extra-usage-noninteractive.js +13 -0
  132. package/src/commands/extra-usage/extra-usage.js +15 -0
  133. package/src/commands/extra-usage/index.js +29 -0
  134. package/src/commands/fast/fast.js +276 -0
  135. package/src/commands/fast/index.js +19 -0
  136. package/src/commands/feedback/feedback.js +11 -0
  137. package/src/commands/feedback/index.js +20 -0
  138. package/src/commands/files/files.js +11 -0
  139. package/src/commands/files/index.js +9 -0
  140. package/src/commands/force-snip.js +19 -0
  141. package/src/commands/fork/index.js +67 -0
  142. package/src/commands/heapdump/heapdump.js +14 -0
  143. package/src/commands/heapdump/index.js +9 -0
  144. package/src/commands/help/help.js +6 -0
  145. package/src/commands/help/index.js +7 -0
  146. package/src/commands/hooks/hooks.js +12 -0
  147. package/src/commands/hooks/index.js +8 -0
  148. package/src/commands/ide/ide.js +615 -0
  149. package/src/commands/ide/index.js +8 -0
  150. package/src/commands/init-verifiers.js +258 -0
  151. package/src/commands/init.js +248 -0
  152. package/src/commands/insights.js +2554 -0
  153. package/src/commands/install-github-app/ApiKeyStep.js +230 -0
  154. package/src/commands/install-github-app/CheckExistingSecretStep.js +194 -0
  155. package/src/commands/install-github-app/CheckGitHubStep.js +16 -0
  156. package/src/commands/install-github-app/ChooseRepoStep.js +211 -0
  157. package/src/commands/install-github-app/CreatingStep.js +53 -0
  158. package/src/commands/install-github-app/ErrorStep.js +84 -0
  159. package/src/commands/install-github-app/ExistingWorkflowStep.js +105 -0
  160. package/src/commands/install-github-app/InstallAppStep.js +97 -0
  161. package/src/commands/install-github-app/OAuthFlowStep.js +190 -0
  162. package/src/commands/install-github-app/SuccessStep.js +94 -0
  163. package/src/commands/install-github-app/WarningsStep.js +71 -0
  164. package/src/commands/install-github-app/index.js +10 -0
  165. package/src/commands/install-github-app/install-github-app.js +593 -0
  166. package/src/commands/install-github-app/setupGitHubActions.js +227 -0
  167. package/src/commands/install-slack-app/index.js +9 -0
  168. package/src/commands/install-slack-app/install-slack-app.js +25 -0
  169. package/src/commands/install.js +198 -0
  170. package/src/commands/keybindings/index.js +10 -0
  171. package/src/commands/keybindings/keybindings.js +47 -0
  172. package/src/commands/login/index.js +21 -0
  173. package/src/commands/login/login.js +135 -0
  174. package/src/commands/logout/index.js +11 -0
  175. package/src/commands/logout/logout.js +75 -0
  176. package/src/commands/mcp/addCommand.js +183 -0
  177. package/src/commands/mcp/index.js +9 -0
  178. package/src/commands/mcp/mcp.js +78 -0
  179. package/src/commands/mcp/xaaIdpCommand.js +193 -0
  180. package/src/commands/memories/index.js +9 -0
  181. package/src/commands/memories/index.ts +12 -0
  182. package/src/commands/memories/memories.tsx +950 -0
  183. package/src/commands/memory/index.js +7 -0
  184. package/src/commands/memory/memory.js +71 -0
  185. package/src/commands/mobile/index.js +9 -0
  186. package/src/commands/mobile/mobile.js +279 -0
  187. package/src/commands/model/index.js +14 -0
  188. package/src/commands/model/model.js +284 -0
  189. package/src/commands/output-style/index.js +8 -0
  190. package/src/commands/output-style/output-style.js +6 -0
  191. package/src/commands/passes/index.js +17 -0
  192. package/src/commands/passes/passes.js +23 -0
  193. package/src/commands/peers/index.js +68 -0
  194. package/src/commands/permissions/index.js +8 -0
  195. package/src/commands/permissions/permissions.js +9 -0
  196. package/src/commands/plan/index.js +8 -0
  197. package/src/commands/plan/plan.js +116 -0
  198. package/src/commands/plugin/AddMarketplace.js +96 -0
  199. package/src/commands/plugin/BrowseMarketplace.js +582 -0
  200. package/src/commands/plugin/DiscoverPlugins.js +613 -0
  201. package/src/commands/plugin/ManageMarketplaces.js +583 -0
  202. package/src/commands/plugin/ManagePlugins.js +1783 -0
  203. package/src/commands/plugin/PluginErrors.js +124 -0
  204. package/src/commands/plugin/PluginOptionsDialog.js +367 -0
  205. package/src/commands/plugin/PluginOptionsFlow.js +97 -0
  206. package/src/commands/plugin/PluginSettings.js +1041 -0
  207. package/src/commands/plugin/PluginTrustWarning.js +35 -0
  208. package/src/commands/plugin/UnifiedInstalledCell.js +616 -0
  209. package/src/commands/plugin/ValidatePlugin.js +96 -0
  210. package/src/commands/plugin/index.js +10 -0
  211. package/src/commands/plugin/parseArgs.js +71 -0
  212. package/src/commands/plugin/plugin.js +6 -0
  213. package/src/commands/plugin/pluginDetailsHelpers.js +95 -0
  214. package/src/commands/plugin/usePagination.js +89 -0
  215. package/src/commands/pr_comments/index.js +49 -0
  216. package/src/commands/privacy-settings/index.js +11 -0
  217. package/src/commands/privacy-settings/privacy-settings.js +55 -0
  218. package/src/commands/proactive.js +29 -0
  219. package/src/commands/rate-limit-options/index.js +15 -0
  220. package/src/commands/rate-limit-options/rate-limit-options.js +213 -0
  221. package/src/commands/release-notes/index.js +8 -0
  222. package/src/commands/release-notes/release-notes.js +38 -0
  223. package/src/commands/reload-plugins/index.js +11 -0
  224. package/src/commands/reload-plugins/reload-plugins.js +52 -0
  225. package/src/commands/remote-env/index.js +12 -0
  226. package/src/commands/remote-env/remote-env.js +6 -0
  227. package/src/commands/remote-setup/api.js +155 -0
  228. package/src/commands/remote-setup/index.js +15 -0
  229. package/src/commands/remote-setup/remote-setup.js +150 -0
  230. package/src/commands/remoteControlServer/index.js +58 -0
  231. package/src/commands/rename/generateSessionName.js +58 -0
  232. package/src/commands/rename/index.js +9 -0
  233. package/src/commands/rename/rename.js +52 -0
  234. package/src/commands/resume/index.js +9 -0
  235. package/src/commands/resume/resume.js +239 -0
  236. package/src/commands/review/UltrareviewOverageDialog.js +97 -0
  237. package/src/commands/review/reviewRemote.js +259 -0
  238. package/src/commands/review/ultrareviewCommand.js +58 -0
  239. package/src/commands/review/ultrareviewEnabled.js +10 -0
  240. package/src/commands/review.js +53 -0
  241. package/src/commands/rewind/index.js +10 -0
  242. package/src/commands/rewind/rewind.js +7 -0
  243. package/src/commands/sandbox-toggle/index.js +41 -0
  244. package/src/commands/sandbox-toggle/sandbox-toggle.js +73 -0
  245. package/src/commands/security-review.js +231 -0
  246. package/src/commands/session/index.js +13 -0
  247. package/src/commands/session/session.js +143 -0
  248. package/src/commands/skills/index.js +7 -0
  249. package/src/commands/skills/skills.js +6 -0
  250. package/src/commands/speak.js +21 -0
  251. package/src/commands/start-business.js +1575 -0
  252. package/src/commands/start-business.ts +1581 -0
  253. package/src/commands/stats/index.js +7 -0
  254. package/src/commands/stats/stats.js +6 -0
  255. package/src/commands/status/index.js +8 -0
  256. package/src/commands/status/status.js +6 -0
  257. package/src/commands/statusline.js +22 -0
  258. package/src/commands/stickers/index.js +8 -0
  259. package/src/commands/stickers/stickers.js +14 -0
  260. package/src/commands/subscribe-pr.js +131 -0
  261. package/src/commands/tag/index.js +9 -0
  262. package/src/commands/tag/tag.js +215 -0
  263. package/src/commands/tasks/index.js +8 -0
  264. package/src/commands/tasks/tasks.js +6 -0
  265. package/src/commands/terminalSetup/index.js +18 -0
  266. package/src/commands/terminalSetup/terminalSetup.js +491 -0
  267. package/src/commands/thaddeus-usage/index.js +17 -0
  268. package/src/commands/theme/index.js +7 -0
  269. package/src/commands/theme/theme.js +51 -0
  270. package/src/commands/thinkback/index.js +9 -0
  271. package/src/commands/thinkback/thinkback.js +528 -0
  272. package/src/commands/thinkback-play/index.js +13 -0
  273. package/src/commands/thinkback-play/thinkback-play.js +34 -0
  274. package/src/commands/torch.js +122 -0
  275. package/src/commands/ultraplan.js +416 -0
  276. package/src/commands/upgrade/index.js +12 -0
  277. package/src/commands/upgrade/upgrade.js +38 -0
  278. package/src/commands/usage/index.js +7 -0
  279. package/src/commands/usage/usage.js +6 -0
  280. package/src/commands/version.js +17 -0
  281. package/src/commands/vim/index.js +8 -0
  282. package/src/commands/vim/vim.js +25 -0
  283. package/src/commands/voice/index.js +13 -0
  284. package/src/commands/voice/voice.js +44 -0
  285. package/src/commands/workflows/index.js +123 -0
  286. package/src/commands.js +614 -0
  287. package/src/commands.ts +4 -0
  288. package/src/components/AgentProgressLine.js +112 -0
  289. package/src/components/AntModelSwitchCallout.js +8 -0
  290. package/src/components/App.js +46 -0
  291. package/src/components/ApproveApiKey.js +125 -0
  292. package/src/components/AutoModeOptInDialog.js +140 -0
  293. package/src/components/AutoUpdater.js +156 -0
  294. package/src/components/AutoUpdaterWrapper.js +78 -0
  295. package/src/components/AwsAuthStatusBox.js +88 -0
  296. package/src/components/BaseTextInput.js +105 -0
  297. package/src/components/BashModeProgress.js +49 -0
  298. package/src/components/BridgeDialog.js +415 -0
  299. package/src/components/BypassPermissionsModeDialog.js +87 -0
  300. package/src/components/ChannelDowngradeDialog.js +101 -0
  301. package/src/components/ClaudeInChromeOnboarding.js +126 -0
  302. package/src/components/ClaudeMdExternalIncludesDialog.js +137 -0
  303. package/src/components/ClickableImageRef.js +65 -0
  304. package/src/components/CompactSummary.js +120 -0
  305. package/src/components/ConfigurableShortcutHint.js +35 -0
  306. package/src/components/ConsoleOAuthFlow.js +554 -0
  307. package/src/components/ContextSuggestions.js +44 -0
  308. package/src/components/ContextVisualization.js +482 -0
  309. package/src/components/CoordinatorAgentStatus.js +261 -0
  310. package/src/components/CostThresholdDialog.js +49 -0
  311. package/src/components/CtrlOToExpand.js +50 -0
  312. package/src/components/CustomSelect/SelectMulti.js +150 -0
  313. package/src/components/CustomSelect/index.js +2 -0
  314. package/src/components/CustomSelect/option-map.js +32 -0
  315. package/src/components/CustomSelect/select-input-option.js +426 -0
  316. package/src/components/CustomSelect/select-option.js +24 -0
  317. package/src/components/CustomSelect/select.js +518 -0
  318. package/src/components/CustomSelect/use-multi-select-state.js +214 -0
  319. package/src/components/CustomSelect/use-select-input.js +170 -0
  320. package/src/components/CustomSelect/use-select-navigation.js +366 -0
  321. package/src/components/CustomSelect/use-select-state.js +22 -0
  322. package/src/components/DesktopHandoff.js +195 -0
  323. package/src/components/DesktopUpsell/DesktopUpsellStartup.js +174 -0
  324. package/src/components/DevBar.js +51 -0
  325. package/src/components/DevChannelsDialog.js +104 -0
  326. package/src/components/DiagnosticsDisplay.js +91 -0
  327. package/src/components/EffortCallout.js +264 -0
  328. package/src/components/EffortIndicator.js +28 -0
  329. package/src/components/ExitFlow.js +41 -0
  330. package/src/components/ExportDialog.js +101 -0
  331. package/src/components/FallbackToolUseErrorMessage.js +116 -0
  332. package/src/components/FallbackToolUseRejectedMessage.js +17 -0
  333. package/src/components/FastIcon.js +43 -0
  334. package/src/components/Feedback.js +369 -0
  335. package/src/components/FeedbackSurvey/FeedbackSurvey.js +151 -0
  336. package/src/components/FeedbackSurvey/FeedbackSurveyView.js +104 -0
  337. package/src/components/FeedbackSurvey/TranscriptSharePrompt.js +84 -0
  338. package/src/components/FeedbackSurvey/submitTranscriptShare.js +10 -0
  339. package/src/components/FeedbackSurvey/useDebouncedDigitInput.js +51 -0
  340. package/src/components/FeedbackSurvey/useFeedbackSurvey.js +258 -0
  341. package/src/components/FeedbackSurvey/useFrustrationDetection.js +8 -0
  342. package/src/components/FeedbackSurvey/useMemorySurvey.js +191 -0
  343. package/src/components/FeedbackSurvey/usePostCompactSurvey.js +202 -0
  344. package/src/components/FeedbackSurvey/useSurveyState.js +80 -0
  345. package/src/components/FileEditToolDiff.js +167 -0
  346. package/src/components/FileEditToolUpdatedMessage.js +112 -0
  347. package/src/components/FileEditToolUseRejectedMessage.js +158 -0
  348. package/src/components/FilePathLink.js +35 -0
  349. package/src/components/FullscreenLayout.js +578 -0
  350. package/src/components/GlobalSearchDialog.js +340 -0
  351. package/src/components/HelpV2/Commands.js +66 -0
  352. package/src/components/HelpV2/General.js +25 -0
  353. package/src/components/HelpV2/HelpV2.js +186 -0
  354. package/src/components/HighlightedCode/Fallback.js +193 -0
  355. package/src/components/HighlightedCode.js +185 -0
  356. package/src/components/HistorySearchDialog.js +93 -0
  357. package/src/components/IdeAutoConnectDialog.js +154 -0
  358. package/src/components/IdeOnboardingDialog.js +175 -0
  359. package/src/components/IdeStatusIndicator.js +50 -0
  360. package/src/components/IdleReturnDialog.js +117 -0
  361. package/src/components/InterruptedByUser.js +16 -0
  362. package/src/components/InvalidConfigDialog.js +135 -0
  363. package/src/components/InvalidSettingsDialog.js +85 -0
  364. package/src/components/KeybindingWarnings.js +55 -0
  365. package/src/components/LanguagePicker.js +84 -0
  366. package/src/components/LogSelector.js +1579 -0
  367. package/src/components/LogoV2/AnimatedAsterisk.js +43 -0
  368. package/src/components/LogoV2/AnimatedClawd.js +64 -0
  369. package/src/components/LogoV2/ChannelsNotice.js +262 -0
  370. package/src/components/LogoV2/Clawd.js +33 -0
  371. package/src/components/LogoV2/CondensedLogo.js +160 -0
  372. package/src/components/LogoV2/EmergencyTip.js +48 -0
  373. package/src/components/LogoV2/Feed.js +85 -0
  374. package/src/components/LogoV2/FeedColumn.js +55 -0
  375. package/src/components/LogoV2/GuestPassesUpsell.js +71 -0
  376. package/src/components/LogoV2/LogoV2.js +565 -0
  377. package/src/components/LogoV2/Opus1mMergeNotice.js +57 -0
  378. package/src/components/LogoV2/OverageCreditUpsell.js +161 -0
  379. package/src/components/LogoV2/VoiceModeNotice.js +71 -0
  380. package/src/components/LogoV2/WelcomeV2.js +14 -0
  381. package/src/components/LogoV2/feedConfigs.js +79 -0
  382. package/src/components/LspRecommendation/LspRecommendationMenu.js +46 -0
  383. package/src/components/MCPServerApprovalDialog.js +114 -0
  384. package/src/components/MCPServerDesktopImportDialog.js +206 -0
  385. package/src/components/MCPServerDialogCopy.js +16 -0
  386. package/src/components/MCPServerMultiselectDialog.js +134 -0
  387. package/src/components/ManagedSettingsSecurityDialog/ManagedSettingsSecurityDialog.js +150 -0
  388. package/src/components/ManagedSettingsSecurityDialog/utils.js +105 -0
  389. package/src/components/Markdown.js +233 -0
  390. package/src/components/MarkdownTable.js +280 -0
  391. package/src/components/MemoryUsageIndicator.js +28 -0
  392. package/src/components/Message.js +564 -0
  393. package/src/components/MessageModel.js +37 -0
  394. package/src/components/MessageResponse.js +73 -0
  395. package/src/components/MessageRow.js +346 -0
  396. package/src/components/MessageSelector.js +744 -0
  397. package/src/components/MessageTimestamp.js +58 -0
  398. package/src/components/Messages.js +645 -0
  399. package/src/components/ModelPicker.js +452 -0
  400. package/src/components/NativeAutoUpdater.js +152 -0
  401. package/src/components/NotebookEditToolUseRejectedMessage.js +84 -0
  402. package/src/components/OffscreenFreeze.js +35 -0
  403. package/src/components/Onboarding.js +174 -0
  404. package/src/components/OutputStylePicker.js +103 -0
  405. package/src/components/PackageManagerAutoUpdater.js +99 -0
  406. package/src/components/Passes/Passes.js +114 -0
  407. package/src/components/PrBadge.js +91 -0
  408. package/src/components/PressEnterToContinue.js +16 -0
  409. package/src/components/PromptInput/HistorySearchInput.js +45 -0
  410. package/src/components/PromptInput/IssueFlagBanner.js +8 -0
  411. package/src/components/PromptInput/Notifications.js +220 -0
  412. package/src/components/PromptInput/PromptInput.js +2014 -0
  413. package/src/components/PromptInput/PromptInputFooter.js +85 -0
  414. package/src/components/PromptInput/PromptInputFooterLeftSide.js +408 -0
  415. package/src/components/PromptInput/PromptInputFooterSuggestions.js +281 -0
  416. package/src/components/PromptInput/PromptInputHelpMenu.js +380 -0
  417. package/src/components/PromptInput/PromptInputModeIndicator.js +73 -0
  418. package/src/components/PromptInput/PromptInputQueuedCommands.js +105 -0
  419. package/src/components/PromptInput/PromptInputStashNotice.js +21 -0
  420. package/src/components/PromptInput/SandboxPromptFooterHint.js +66 -0
  421. package/src/components/PromptInput/ShimmeredInput.js +133 -0
  422. package/src/components/PromptInput/VoiceIndicator.js +137 -0
  423. package/src/components/PromptInput/inputModes.js +24 -0
  424. package/src/components/PromptInput/inputPaste.js +62 -0
  425. package/src/components/PromptInput/useMaybeTruncateInput.js +33 -0
  426. package/src/components/PromptInput/usePromptInputPlaceholder.js +53 -0
  427. package/src/components/PromptInput/useShowFastIconHint.js +23 -0
  428. package/src/components/PromptInput/useSwarmBanner.js +112 -0
  429. package/src/components/PromptInput/utils.js +50 -0
  430. package/src/components/QuickOpenDialog.js +244 -0
  431. package/src/components/RemoteCallout.js +53 -0
  432. package/src/components/RemoteEnvironmentDialog.js +346 -0
  433. package/src/components/ResumeTask.js +173 -0
  434. package/src/components/SandboxViolationExpandedView.js +106 -0
  435. package/src/components/SandboxViolationExpandedView.tsx +3 -0
  436. package/src/components/ScrollKeybindingHandler.js +982 -0
  437. package/src/components/SearchBox.js +56 -0
  438. package/src/components/SentryErrorBoundary.js +16 -0
  439. package/src/components/SessionBackgroundHint.js +105 -0
  440. package/src/components/SessionPreview.js +200 -0
  441. package/src/components/Settings/Config.js +1626 -0
  442. package/src/components/Settings/Settings.js +131 -0
  443. package/src/components/Settings/Status.js +230 -0
  444. package/src/components/Settings/Usage.js +341 -0
  445. package/src/components/ShowInIDEPrompt.js +152 -0
  446. package/src/components/SkillImprovementSurvey.js +130 -0
  447. package/src/components/Spinner/FlashingChar.js +52 -0
  448. package/src/components/Spinner/GlimmerMessage.js +329 -0
  449. package/src/components/Spinner/ShimmerChar.js +23 -0
  450. package/src/components/Spinner/SpinnerAnimationRow.js +170 -0
  451. package/src/components/Spinner/SpinnerGlyph.js +70 -0
  452. package/src/components/Spinner/TeammateSpinnerLine.js +171 -0
  453. package/src/components/Spinner/TeammateSpinnerTree.js +269 -0
  454. package/src/components/Spinner/index.js +9 -0
  455. package/src/components/Spinner/teammateSelectHint.js +1 -0
  456. package/src/components/Spinner/useShimmerAnimation.js +22 -0
  457. package/src/components/Spinner/useStalledAnimation.js +63 -0
  458. package/src/components/Spinner/utils.js +78 -0
  459. package/src/components/Spinner.js +474 -0
  460. package/src/components/Stats.js +1000 -0
  461. package/src/components/StatusLine.js +286 -0
  462. package/src/components/StatusNotices.js +50 -0
  463. package/src/components/StructuredDiff/Fallback.js +336 -0
  464. package/src/components/StructuredDiff/colorDiff.js +37 -0
  465. package/src/components/StructuredDiff.js +153 -0
  466. package/src/components/StructuredDiffList.js +9 -0
  467. package/src/components/TagTabs.js +101 -0
  468. package/src/components/TaskListV2.js +333 -0
  469. package/src/components/TeammateViewHeader.js +88 -0
  470. package/src/components/TeleportError.js +191 -0
  471. package/src/components/TeleportProgress.js +131 -0
  472. package/src/components/TeleportRepoMismatchDialog.js +98 -0
  473. package/src/components/TeleportResumeWrapper.js +158 -0
  474. package/src/components/TeleportStash.js +82 -0
  475. package/src/components/TextInput.js +108 -0
  476. package/src/components/ThaddeusHint/PluginHintMenu.js +37 -0
  477. package/src/components/ThemePicker.js +331 -0
  478. package/src/components/ThinkingToggle.js +154 -0
  479. package/src/components/TokenWarning.js +171 -0
  480. package/src/components/ToolUseLoader.js +35 -0
  481. package/src/components/TrustDialog/TrustDialog.js +301 -0
  482. package/src/components/TrustDialog/utils.js +199 -0
  483. package/src/components/UndercoverAutoCallout.js +5 -0
  484. package/src/components/ValidationErrorsList.js +147 -0
  485. package/src/components/VimTextInput.js +136 -0
  486. package/src/components/VirtualMessageList.js +893 -0
  487. package/src/components/WorkflowMultiselectDialog.js +118 -0
  488. package/src/components/WorktreeExitDialog.js +220 -0
  489. package/src/components/agents/AgentDetail.js +227 -0
  490. package/src/components/agents/AgentEditor.js +147 -0
  491. package/src/components/agents/AgentNavigationFooter.js +22 -0
  492. package/src/components/agents/AgentsList.js +436 -0
  493. package/src/components/agents/AgentsMenu.js +849 -0
  494. package/src/components/agents/ColorPicker.js +110 -0
  495. package/src/components/agents/ModelSelector.js +63 -0
  496. package/src/components/agents/SnapshotUpdateDialog.js +14 -0
  497. package/src/components/agents/ToolSelector.js +557 -0
  498. package/src/components/agents/agentFileUtils.js +179 -0
  499. package/src/components/agents/generateAgent.js +161 -0
  500. package/src/components/agents/new-agent-creation/CreateAgentWizard.js +89 -0
  501. package/src/components/agents/new-agent-creation/wizard-steps/ColorStep.js +81 -0
  502. package/src/components/agents/new-agent-creation/wizard-steps/ConfirmStep.js +387 -0
  503. package/src/components/agents/new-agent-creation/wizard-steps/ConfirmStepWrapper.js +63 -0
  504. package/src/components/agents/new-agent-creation/wizard-steps/DescriptionStep.js +126 -0
  505. package/src/components/agents/new-agent-creation/wizard-steps/GenerateStep.js +118 -0
  506. package/src/components/agents/new-agent-creation/wizard-steps/LocationStep.js +80 -0
  507. package/src/components/agents/new-agent-creation/wizard-steps/MemoryStep.js +108 -0
  508. package/src/components/agents/new-agent-creation/wizard-steps/MethodStep.js +80 -0
  509. package/src/components/agents/new-agent-creation/wizard-steps/ModelStep.js +49 -0
  510. package/src/components/agents/new-agent-creation/wizard-steps/PromptStep.js +131 -0
  511. package/src/components/agents/new-agent-creation/wizard-steps/ToolsStep.js +52 -0
  512. package/src/components/agents/new-agent-creation/wizard-steps/TypeStep.js +100 -0
  513. package/src/components/agents/types.js +4 -0
  514. package/src/components/agents/utils.js +14 -0
  515. package/src/components/agents/validateAgent.js +79 -0
  516. package/src/components/design-system/Byline.js +72 -0
  517. package/src/components/design-system/Dialog.js +117 -0
  518. package/src/components/design-system/Divider.js +110 -0
  519. package/src/components/design-system/FuzzyPicker.js +191 -0
  520. package/src/components/design-system/KeyboardShortcutHint.js +68 -0
  521. package/src/components/design-system/ListItem.js +184 -0
  522. package/src/components/design-system/LoadingState.js +69 -0
  523. package/src/components/design-system/Pane.js +69 -0
  524. package/src/components/design-system/ProgressBar.js +63 -0
  525. package/src/components/design-system/Ratchet.js +71 -0
  526. package/src/components/design-system/StatusIcon.js +70 -0
  527. package/src/components/design-system/Tabs.js +269 -0
  528. package/src/components/design-system/ThemeProvider.js +137 -0
  529. package/src/components/design-system/ThemedBox.js +126 -0
  530. package/src/components/design-system/ThemedText.js +60 -0
  531. package/src/components/design-system/color.js +22 -0
  532. package/src/components/diff/DiffDetailView.js +285 -0
  533. package/src/components/diff/DiffDialog.js +387 -0
  534. package/src/components/diff/DiffFileList.js +292 -0
  535. package/src/components/grove/Grove.js +483 -0
  536. package/src/components/hooks/HooksConfigMenu.js +583 -0
  537. package/src/components/hooks/PromptDialog.js +82 -0
  538. package/src/components/hooks/SelectEventMode.js +118 -0
  539. package/src/components/hooks/SelectHookMode.js +101 -0
  540. package/src/components/hooks/SelectMatcherMode.js +131 -0
  541. package/src/components/hooks/ViewHookMode.js +204 -0
  542. package/src/components/mcp/CapabilitiesSection.js +56 -0
  543. package/src/components/mcp/ElicitationDialog.js +945 -0
  544. package/src/components/mcp/MCPAgentServerMenu.js +95 -0
  545. package/src/components/mcp/MCPListPanel.js +505 -0
  546. package/src/components/mcp/MCPReconnect.js +168 -0
  547. package/src/components/mcp/MCPRemoteServerMenu.js +460 -0
  548. package/src/components/mcp/MCPSettings.js +414 -0
  549. package/src/components/mcp/MCPStdioServerMenu.js +95 -0
  550. package/src/components/mcp/MCPToolDetailView.js +219 -0
  551. package/src/components/mcp/MCPToolListView.js +137 -0
  552. package/src/components/mcp/McpParsingWarnings.js +212 -0
  553. package/src/components/mcp/index.js +8 -0
  554. package/src/components/mcp/utils/reconnectHelpers.js +35 -0
  555. package/src/components/memory/MemoryFileSelector.js +454 -0
  556. package/src/components/memory/MemoryUpdateNotification.js +43 -0
  557. package/src/components/messageActions.js +418 -0
  558. package/src/components/messages/AdvisorMessage.js +152 -0
  559. package/src/components/messages/AssistantRedactedThinkingMessage.js +28 -0
  560. package/src/components/messages/AssistantTextMessage.js +287 -0
  561. package/src/components/messages/AssistantThinkingMessage.js +70 -0
  562. package/src/components/messages/AssistantToolUseMessage.js +324 -0
  563. package/src/components/messages/AttachmentMessage.js +418 -0
  564. package/src/components/messages/CollapsedReadSearchContent.js +363 -0
  565. package/src/components/messages/CompactBoundaryMessage.js +19 -0
  566. package/src/components/messages/GroupedToolUseContent.js +37 -0
  567. package/src/components/messages/HighlightedThinkingText.js +165 -0
  568. package/src/components/messages/HookProgressMessage.js +111 -0
  569. package/src/components/messages/PlanApprovalMessage.js +213 -0
  570. package/src/components/messages/RateLimitMessage.js +149 -0
  571. package/src/components/messages/ShutdownMessage.js +124 -0
  572. package/src/components/messages/SnipBoundaryMessage.js +7 -0
  573. package/src/components/messages/SystemAPIErrorMessage.js +136 -0
  574. package/src/components/messages/SystemTextMessage.js +842 -0
  575. package/src/components/messages/TaskAssignmentMessage.js +72 -0
  576. package/src/components/messages/UserAgentNotificationMessage.js +78 -0
  577. package/src/components/messages/UserBashInputMessage.js +52 -0
  578. package/src/components/messages/UserBashOutputMessage.js +55 -0
  579. package/src/components/messages/UserChannelMessage.js +130 -0
  580. package/src/components/messages/UserCommandMessage.js +107 -0
  581. package/src/components/messages/UserCrossSessionMessage.js +11 -0
  582. package/src/components/messages/UserForkBoilerplateMessage.js +11 -0
  583. package/src/components/messages/UserGitHubWebhookMessage.js +12 -0
  584. package/src/components/messages/UserImageMessage.js +54 -0
  585. package/src/components/messages/UserLocalCommandOutputMessage.js +170 -0
  586. package/src/components/messages/UserMemoryInputMessage.js +73 -0
  587. package/src/components/messages/UserPlanMessage.js +38 -0
  588. package/src/components/messages/UserPromptMessage.js +63 -0
  589. package/src/components/messages/UserResourceUpdateMessage.js +102 -0
  590. package/src/components/messages/UserTeammateMessage.js +156 -0
  591. package/src/components/messages/UserTextMessage.js +270 -0
  592. package/src/components/messages/UserToolResultMessage/RejectedPlanMessage.js +28 -0
  593. package/src/components/messages/UserToolResultMessage/RejectedToolUseMessage.js +17 -0
  594. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.js +17 -0
  595. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.js +92 -0
  596. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.js +74 -0
  597. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.js +84 -0
  598. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.js +58 -0
  599. package/src/components/messages/UserToolResultMessage/utils.js +43 -0
  600. package/src/components/messages/nullRenderingAttachments.js +58 -0
  601. package/src/components/messages/teamMemCollapsed.js +142 -0
  602. package/src/components/messages/teamMemSaved.js +16 -0
  603. package/src/components/permissions/AskUserQuestionPermissionRequest/AskUserQuestionPermissionRequest.js +659 -0
  604. package/src/components/permissions/AskUserQuestionPermissionRequest/PreviewBox.js +219 -0
  605. package/src/components/permissions/AskUserQuestionPermissionRequest/PreviewQuestionView.js +227 -0
  606. package/src/components/permissions/AskUserQuestionPermissionRequest/QuestionNavigationBar.js +175 -0
  607. package/src/components/permissions/AskUserQuestionPermissionRequest/QuestionView.js +444 -0
  608. package/src/components/permissions/AskUserQuestionPermissionRequest/SubmitQuestionsView.js +137 -0
  609. package/src/components/permissions/AskUserQuestionPermissionRequest/use-multiple-choice-state.js +100 -0
  610. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.js +404 -0
  611. package/src/components/permissions/BashPermissionRequest/bashToolUseOptions.js +110 -0
  612. package/src/components/permissions/ComputerUseApproval/ComputerUseApproval.js +449 -0
  613. package/src/components/permissions/EnterPlanModePermissionRequest/EnterPlanModePermissionRequest.js +126 -0
  614. package/src/components/permissions/ExitPlanModePermissionRequest/ExitPlanModePermissionRequest.js +653 -0
  615. package/src/components/permissions/FallbackPermissionRequest.js +349 -0
  616. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.js +185 -0
  617. package/src/components/permissions/FilePermissionDialog/FilePermissionDialog.js +108 -0
  618. package/src/components/permissions/FilePermissionDialog/ideDiffConfig.js +13 -0
  619. package/src/components/permissions/FilePermissionDialog/permissionOptions.js +137 -0
  620. package/src/components/permissions/FilePermissionDialog/useFilePermissionDialog.js +131 -0
  621. package/src/components/permissions/FilePermissionDialog/usePermissionHandler.js +86 -0
  622. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.js +164 -0
  623. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.js +79 -0
  624. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.js +113 -0
  625. package/src/components/permissions/MonitorPermissionRequest/MonitorPermissionRequest.js +7 -0
  626. package/src/components/permissions/NotebookEditPermissionRequest/NotebookEditPermissionRequest.js +164 -0
  627. package/src/components/permissions/NotebookEditPermissionRequest/NotebookEditToolDiff.js +218 -0
  628. package/src/components/permissions/PermissionDecisionDebugInfo.js +467 -0
  629. package/src/components/permissions/PermissionDialog.js +55 -0
  630. package/src/components/permissions/PermissionExplanation.js +269 -0
  631. package/src/components/permissions/PermissionPrompt.js +316 -0
  632. package/src/components/permissions/PermissionRequest.js +159 -0
  633. package/src/components/permissions/PermissionRequestTitle.js +58 -0
  634. package/src/components/permissions/PermissionRuleExplanation.js +110 -0
  635. package/src/components/permissions/PowerShellPermissionRequest/PowerShellPermissionRequest.js +178 -0
  636. package/src/components/permissions/PowerShellPermissionRequest/powershellToolUseOptions.js +73 -0
  637. package/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.js +7 -0
  638. package/src/components/permissions/SandboxPermissionRequest.js +162 -0
  639. package/src/components/permissions/SedEditPermissionRequest/SedEditPermissionRequest.js +228 -0
  640. package/src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.js +385 -0
  641. package/src/components/permissions/WebFetchPermissionRequest/WebFetchPermissionRequest.js +259 -0
  642. package/src/components/permissions/WorkerBadge.js +44 -0
  643. package/src/components/permissions/WorkerPendingPermission.js +107 -0
  644. package/src/components/permissions/hooks.js +163 -0
  645. package/src/components/permissions/rules/AddPermissionRules.js +171 -0
  646. package/src/components/permissions/rules/AddWorkspaceDirectory.js +335 -0
  647. package/src/components/permissions/rules/PermissionRuleDescription.js +78 -0
  648. package/src/components/permissions/rules/PermissionRuleInput.js +136 -0
  649. package/src/components/permissions/rules/PermissionRuleList.js +1190 -0
  650. package/src/components/permissions/rules/RecentDenialsTab.js +205 -0
  651. package/src/components/permissions/rules/RemoveWorkspaceDirectory.js +103 -0
  652. package/src/components/permissions/rules/WorkspaceTab.js +133 -0
  653. package/src/components/permissions/shellPermissionHelpers.js +112 -0
  654. package/src/components/permissions/useShellPermissionFeedback.js +108 -0
  655. package/src/components/permissions/utils.js +14 -0
  656. package/src/components/sandbox/SandboxConfigTab.js +48 -0
  657. package/src/components/sandbox/SandboxDependenciesTab.js +123 -0
  658. package/src/components/sandbox/SandboxDoctorSection.js +47 -0
  659. package/src/components/sandbox/SandboxOverridesTab.js +193 -0
  660. package/src/components/sandbox/SandboxSettings.js +297 -0
  661. package/src/components/shell/ExpandShellOutputContext.js +33 -0
  662. package/src/components/shell/OutputLine.js +110 -0
  663. package/src/components/shell/ShellProgressMessage.js +144 -0
  664. package/src/components/shell/ShellTimeDisplay.js +72 -0
  665. package/src/components/skills/SkillsMenu.js +239 -0
  666. package/src/components/tasks/AsyncAgentDetailDialog.js +235 -0
  667. package/src/components/tasks/BackgroundTask.js +364 -0
  668. package/src/components/tasks/BackgroundTaskStatus.js +419 -0
  669. package/src/components/tasks/BackgroundTasksDialog.js +494 -0
  670. package/src/components/tasks/DreamDetailDialog.js +251 -0
  671. package/src/components/tasks/InProcessTeammateDetailDialog.js +275 -0
  672. package/src/components/tasks/MonitorMcpDetailDialog.js +7 -0
  673. package/src/components/tasks/RemoteSessionDetailDialog.js +868 -0
  674. package/src/components/tasks/RemoteSessionProgress.js +249 -0
  675. package/src/components/tasks/ShellDetailDialog.js +403 -0
  676. package/src/components/tasks/ShellProgress.js +77 -0
  677. package/src/components/tasks/WorkflowDetailDialog.js +7 -0
  678. package/src/components/tasks/renderToolActivity.js +29 -0
  679. package/src/components/tasks/taskStatusUtils.js +94 -0
  680. package/src/components/teams/TeamStatus.js +77 -0
  681. package/src/components/teams/TeamsDialog.js +673 -0
  682. package/src/components/ui/OrderedList.js +66 -0
  683. package/src/components/ui/OrderedListItem.js +41 -0
  684. package/src/components/ui/TreeSelect.js +300 -0
  685. package/src/components/wizard/WizardDialogLayout.js +48 -0
  686. package/src/components/wizard/WizardNavigationFooter.js +11 -0
  687. package/src/components/wizard/WizardProvider.js +217 -0
  688. package/src/components/wizard/index.js +4 -0
  689. package/src/components/wizard/useWizard.js +9 -0
  690. package/src/constants/apiLimits.js +81 -0
  691. package/src/constants/betas.js +45 -0
  692. package/src/constants/common.js +29 -0
  693. package/src/constants/cyberRiskInstruction.js +23 -0
  694. package/src/constants/errorIds.js +14 -0
  695. package/src/constants/figures.js +38 -0
  696. package/src/constants/files.js +150 -0
  697. package/src/constants/github-app.js +139 -0
  698. package/src/constants/identity.js +112 -0
  699. package/src/constants/keys.js +10 -0
  700. package/src/constants/messages.js +1 -0
  701. package/src/constants/oauth.js +175 -0
  702. package/src/constants/outputStyles.js +162 -0
  703. package/src/constants/product.js +54 -0
  704. package/src/constants/prompts.js +994 -0
  705. package/src/constants/spinnerVerbs.js +98 -0
  706. package/src/constants/system.js +77 -0
  707. package/src/constants/systemPromptSections.js +39 -0
  708. package/src/constants/toolLimits.js +50 -0
  709. package/src/constants/tools.js +103 -0
  710. package/src/constants/turnCompletionVerbs.js +12 -0
  711. package/src/constants/xml.js +73 -0
  712. package/src/context/QueuedMessageContext.js +51 -0
  713. package/src/context/fpsMetrics.js +22 -0
  714. package/src/context/mailbox.js +35 -0
  715. package/src/context/modalContext.js +34 -0
  716. package/src/context/notifications.js +199 -0
  717. package/src/context/overlayContext.js +149 -0
  718. package/src/context/promptOverlayContext.js +118 -0
  719. package/src/context/stats.js +207 -0
  720. package/src/context/voice.js +74 -0
  721. package/src/context.js +146 -0
  722. package/src/coordinator/coordinatorMode.js +345 -0
  723. package/src/coordinator/workerAgent.js +24 -0
  724. package/src/cost-tracker.js +208 -0
  725. package/src/costHook.js +17 -0
  726. package/src/daemon/main.js +19 -0
  727. package/src/dialogLaunchers.js +77 -0
  728. package/src/entrypoints/agentSdkTypes.js +202 -0
  729. package/src/entrypoints/cli.js +226 -0
  730. package/src/entrypoints/init.js +265 -0
  731. package/src/entrypoints/mcp.js +141 -0
  732. package/src/entrypoints/sandboxTypes.js +112 -0
  733. package/src/entrypoints/sdk/controlSchemas.js +452 -0
  734. package/src/entrypoints/sdk/controlTypes.js +1 -0
  735. package/src/entrypoints/sdk/coreSchemas.js +1331 -0
  736. package/src/entrypoints/sdk/coreTypes.generated.js +3 -0
  737. package/src/entrypoints/sdk/coreTypes.js +49 -0
  738. package/src/entrypoints/sdk/runtimeTypes.js +1 -0
  739. package/src/entrypoints/sdk/sdkUtilityTypes.js +1 -0
  740. package/src/entrypoints/sdk/settingsTypes.generated.js +1 -0
  741. package/src/entrypoints/sdk/toolTypes.js +1 -0
  742. package/src/environment-runner/main.js +8 -0
  743. package/src/history.js +386 -0
  744. package/src/hooks/fileSuggestions.js +635 -0
  745. package/src/hooks/notifs/useAntOrgWarningNotification.js +5 -0
  746. package/src/hooks/notifs/useAutoModeUnavailableNotification.js +47 -0
  747. package/src/hooks/notifs/useCanSwitchToExistingSubscription.js +58 -0
  748. package/src/hooks/notifs/useDeprecationWarningNotification.js +43 -0
  749. package/src/hooks/notifs/useFastModeNotification.js +164 -0
  750. package/src/hooks/notifs/useIDEStatusIndicator.js +174 -0
  751. package/src/hooks/notifs/useInstallMessages.js +27 -0
  752. package/src/hooks/notifs/useLspInitializationNotification.js +144 -0
  753. package/src/hooks/notifs/useMcpConnectivityStatus.js +81 -0
  754. package/src/hooks/notifs/useModelMigrationNotifications.js +53 -0
  755. package/src/hooks/notifs/useNpmDeprecationNotification.js +25 -0
  756. package/src/hooks/notifs/usePluginAutoupdateNotification.js +83 -0
  757. package/src/hooks/notifs/usePluginInstallationStatus.js +128 -0
  758. package/src/hooks/notifs/useRateLimitWarningNotification.js +119 -0
  759. package/src/hooks/notifs/useSettingsErrors.js +64 -0
  760. package/src/hooks/notifs/useStartupNotification.js +33 -0
  761. package/src/hooks/notifs/useTeammateShutdownNotification.js +64 -0
  762. package/src/hooks/renderPlaceholder.js +26 -0
  763. package/src/hooks/toolPermission/PermissionContext.js +211 -0
  764. package/src/hooks/toolPermission/handlers/coordinatorHandler.js +44 -0
  765. package/src/hooks/toolPermission/handlers/interactiveHandler.js +397 -0
  766. package/src/hooks/toolPermission/handlers/swarmWorkerHandler.js +108 -0
  767. package/src/hooks/toolPermission/permissionLogging.js +145 -0
  768. package/src/hooks/unifiedSuggestions.js +130 -0
  769. package/src/hooks/useAfterFirstRender.js +12 -0
  770. package/src/hooks/useApiKeyVerification.js +63 -0
  771. package/src/hooks/useArrowKeyHistory.js +203 -0
  772. package/src/hooks/useAssistantHistory.js +193 -0
  773. package/src/hooks/useAwaySummary.js +105 -0
  774. package/src/hooks/useBackgroundTaskNavigation.js +204 -0
  775. package/src/hooks/useBlink.js +28 -0
  776. package/src/hooks/useCanUseTool.js +193 -0
  777. package/src/hooks/useCancelRequest.js +195 -0
  778. package/src/hooks/useChromeExtensionNotification.js +50 -0
  779. package/src/hooks/useClipboardImageHint.js +59 -0
  780. package/src/hooks/useCommandKeybindings.js +87 -0
  781. package/src/hooks/useCommandQueue.js +10 -0
  782. package/src/hooks/useCopyOnSelect.js +88 -0
  783. package/src/hooks/useDeferredHookMessages.js +43 -0
  784. package/src/hooks/useDiffData.js +69 -0
  785. package/src/hooks/useDiffInIDE.js +252 -0
  786. package/src/hooks/useDirectConnect.js +150 -0
  787. package/src/hooks/useDoublePress.js +44 -0
  788. package/src/hooks/useDynamicConfig.js +17 -0
  789. package/src/hooks/useElapsedTime.js +25 -0
  790. package/src/hooks/useExitOnCtrlCD.js +57 -0
  791. package/src/hooks/useExitOnCtrlCDWithKeybindings.js +17 -0
  792. package/src/hooks/useFileHistorySnapshotInit.js +14 -0
  793. package/src/hooks/useGlobalKeybindings.js +213 -0
  794. package/src/hooks/useHistorySearch.js +241 -0
  795. package/src/hooks/useIDEIntegration.js +56 -0
  796. package/src/hooks/useIdeAtMentioned.js +51 -0
  797. package/src/hooks/useIdeConnectionStatus.js +21 -0
  798. package/src/hooks/useIdeLogging.js +29 -0
  799. package/src/hooks/useIdeSelection.js +106 -0
  800. package/src/hooks/useInboxPoller.js +709 -0
  801. package/src/hooks/useInputBuffer.js +73 -0
  802. package/src/hooks/useIssueFlagBanner.js +115 -0
  803. package/src/hooks/useLogMessages.js +98 -0
  804. package/src/hooks/useLspPluginRecommendation.js +176 -0
  805. package/src/hooks/useMailboxBridge.js +15 -0
  806. package/src/hooks/useMainLoopModel.js +25 -0
  807. package/src/hooks/useManagePlugins.js +261 -0
  808. package/src/hooks/useMemoryUsage.js +28 -0
  809. package/src/hooks/useMergedClients.js +11 -0
  810. package/src/hooks/useMergedCommands.js +10 -0
  811. package/src/hooks/useMergedTools.js +32 -0
  812. package/src/hooks/useMinDisplayTime.js +26 -0
  813. package/src/hooks/useNotifyAfterTimeout.js +51 -0
  814. package/src/hooks/useOfficialMarketplaceNotification.js +47 -0
  815. package/src/hooks/usePasteHandler.js +195 -0
  816. package/src/hooks/usePluginRecommendationBase.js +101 -0
  817. package/src/hooks/usePrStatus.js +91 -0
  818. package/src/hooks/usePromptSuggestion.js +128 -0
  819. package/src/hooks/usePromptsFromClaudeInChrome.js +66 -0
  820. package/src/hooks/useQueueProcessor.js +46 -0
  821. package/src/hooks/useRemoteSession.js +431 -0
  822. package/src/hooks/useReplBridge.js +715 -0
  823. package/src/hooks/useSSHSession.js +167 -0
  824. package/src/hooks/useScheduledTasks.js +104 -0
  825. package/src/hooks/useSearchInput.js +302 -0
  826. package/src/hooks/useSessionBackgrounding.js +132 -0
  827. package/src/hooks/useSettings.js +10 -0
  828. package/src/hooks/useSettingsChange.js +13 -0
  829. package/src/hooks/useSkillImprovementSurvey.js +69 -0
  830. package/src/hooks/useSkillsChange.js +51 -0
  831. package/src/hooks/useSwarmInitialization.js +67 -0
  832. package/src/hooks/useSwarmPermissionPoller.js +215 -0
  833. package/src/hooks/useTaskListWatcher.js +157 -0
  834. package/src/hooks/useTasksV2.js +220 -0
  835. package/src/hooks/useTeammateViewAutoExit.js +55 -0
  836. package/src/hooks/useTeleportResume.js +81 -0
  837. package/src/hooks/useTerminalSize.js +9 -0
  838. package/src/hooks/useTextInput.js +397 -0
  839. package/src/hooks/useThaddeusHintRecommendation.js +117 -0
  840. package/src/hooks/useTimeout.js +10 -0
  841. package/src/hooks/useTurnDiffs.js +160 -0
  842. package/src/hooks/useTypeahead.js +1250 -0
  843. package/src/hooks/useUpdateNotification.js +21 -0
  844. package/src/hooks/useVimInput.js +232 -0
  845. package/src/hooks/useVirtualScroll.js +627 -0
  846. package/src/hooks/useVoice.js +952 -0
  847. package/src/hooks/useVoiceEnabled.js +21 -0
  848. package/src/hooks/useVoiceIntegration.js +629 -0
  849. package/src/infrastructure/audit.js +210 -0
  850. package/src/infrastructure/guardrails.js +513 -0
  851. package/src/infrastructure/index.js +11 -0
  852. package/src/ink/Ansi.js +269 -0
  853. package/src/ink/bidi.js +117 -0
  854. package/src/ink/clearTerminal.js +58 -0
  855. package/src/ink/colorize.js +198 -0
  856. package/src/ink/components/AlternateScreen.js +74 -0
  857. package/src/ink/components/App.js +562 -0
  858. package/src/ink/components/AppContext.js +11 -0
  859. package/src/ink/components/Box.js +155 -0
  860. package/src/ink/components/Button.js +166 -0
  861. package/src/ink/components/ClockContext.js +108 -0
  862. package/src/ink/components/CursorDeclarationContext.js +3 -0
  863. package/src/ink/components/ErrorOverview.js +50 -0
  864. package/src/ink/components/Link.js +34 -0
  865. package/src/ink/components/Newline.js +30 -0
  866. package/src/ink/components/NoSelect.js +57 -0
  867. package/src/ink/components/RawAnsi.js +46 -0
  868. package/src/ink/components/ScrollBox.js +171 -0
  869. package/src/ink/components/Spacer.js +20 -0
  870. package/src/ink/components/StdinContext.js +16 -0
  871. package/src/ink/components/TerminalFocusContext.js +45 -0
  872. package/src/ink/components/TerminalSizeContext.js +3 -0
  873. package/src/ink/components/Text.js +195 -0
  874. package/src/ink/constants.js +2 -0
  875. package/src/ink/dom.js +298 -0
  876. package/src/ink/events/click-event.js +36 -0
  877. package/src/ink/events/dispatcher.js +172 -0
  878. package/src/ink/events/emitter.js +31 -0
  879. package/src/ink/events/event-handlers.js +30 -0
  880. package/src/ink/events/event.js +9 -0
  881. package/src/ink/events/focus-event.js +16 -0
  882. package/src/ink/events/input-event.js +161 -0
  883. package/src/ink/events/keyboard-event.js +46 -0
  884. package/src/ink/events/terminal-event.js +78 -0
  885. package/src/ink/events/terminal-focus-event.js +15 -0
  886. package/src/ink/focus.js +158 -0
  887. package/src/ink/frame.js +30 -0
  888. package/src/ink/get-max-width.js +23 -0
  889. package/src/ink/hit-test.js +113 -0
  890. package/src/ink/hooks/use-animation-frame.js +48 -0
  891. package/src/ink/hooks/use-app.js +7 -0
  892. package/src/ink/hooks/use-declared-cursor.js +60 -0
  893. package/src/ink/hooks/use-input.js +70 -0
  894. package/src/ink/hooks/use-interval.js +54 -0
  895. package/src/ink/hooks/use-search-highlight.js +32 -0
  896. package/src/ink/hooks/use-selection.js +60 -0
  897. package/src/ink/hooks/use-stdin.js +7 -0
  898. package/src/ink/hooks/use-tab-status.js +57 -0
  899. package/src/ink/hooks/use-terminal-focus.js +15 -0
  900. package/src/ink/hooks/use-terminal-title.js +29 -0
  901. package/src/ink/hooks/use-terminal-viewport.js +77 -0
  902. package/src/ink/ink.js +1645 -0
  903. package/src/ink/instances.js +7 -0
  904. package/src/ink/layout/engine.js +4 -0
  905. package/src/ink/layout/geometry.js +61 -0
  906. package/src/ink/layout/node.js +62 -0
  907. package/src/ink/layout/yoga.js +237 -0
  908. package/src/ink/line-width-cache.js +19 -0
  909. package/src/ink/log-update.js +583 -0
  910. package/src/ink/measure-element.js +8 -0
  911. package/src/ink/measure-text.js +35 -0
  912. package/src/ink/node-cache.js +30 -0
  913. package/src/ink/optimizer.js +81 -0
  914. package/src/ink/output.js +556 -0
  915. package/src/ink/parse-keypress.js +695 -0
  916. package/src/ink/reconciler.js +384 -0
  917. package/src/ink/render-border.js +134 -0
  918. package/src/ink/render-node-to-output.js +1216 -0
  919. package/src/ink/render-to-screen.js +171 -0
  920. package/src/ink/renderer.js +129 -0
  921. package/src/ink/root.js +80 -0
  922. package/src/ink/screen.js +1132 -0
  923. package/src/ink/searchHighlight.js +78 -0
  924. package/src/ink/selection.js +792 -0
  925. package/src/ink/squash-text-nodes.js +56 -0
  926. package/src/ink/stringWidth.js +200 -0
  927. package/src/ink/styles.js +299 -0
  928. package/src/ink/supports-hyperlinks.js +40 -0
  929. package/src/ink/tabstops.js +39 -0
  930. package/src/ink/terminal-focus-state.js +35 -0
  931. package/src/ink/terminal-querier.js +173 -0
  932. package/src/ink/terminal.js +208 -0
  933. package/src/ink/termio/ansi.js +70 -0
  934. package/src/ink/termio/csi.js +260 -0
  935. package/src/ink/termio/dec.js +53 -0
  936. package/src/ink/termio/esc.js +55 -0
  937. package/src/ink/termio/osc.js +432 -0
  938. package/src/ink/termio/parser.js +356 -0
  939. package/src/ink/termio/sgr.js +292 -0
  940. package/src/ink/termio/tokenize.js +264 -0
  941. package/src/ink/termio/types.js +55 -0
  942. package/src/ink/termio.js +24 -0
  943. package/src/ink/useTerminalNotification.js +57 -0
  944. package/src/ink/warn.js +10 -0
  945. package/src/ink/widest-line.js +14 -0
  946. package/src/ink/wrap-text.js +54 -0
  947. package/src/ink/wrapAnsi.js +6 -0
  948. package/src/ink.js +50 -0
  949. package/src/integrations/credentialStore.js +176 -0
  950. package/src/integrations/index.js +5 -0
  951. package/src/integrations/integrationManager.js +180 -0
  952. package/src/integrations/providers/BaseProvider.js +180 -0
  953. package/src/integrations/providers/GitHubProvider.js +217 -0
  954. package/src/integrations/providers/GmailProvider.js +204 -0
  955. package/src/integrations/providers/GoogleCalendarProvider.js +113 -0
  956. package/src/integrations/providers/HubSpotProvider.js +159 -0
  957. package/src/integrations/providers/JiraProvider.js +216 -0
  958. package/src/integrations/providers/NotionProvider.js +221 -0
  959. package/src/integrations/providers/QuickBooksProvider.js +176 -0
  960. package/src/integrations/providers/SlackProvider.js +174 -0
  961. package/src/integrations/providers/StripeProvider.js +206 -0
  962. package/src/integrations/providers/TwilioProvider.js +239 -0
  963. package/src/integrations/providers/_template.js +112 -0
  964. package/src/integrations/types.js +7 -0
  965. package/src/interactiveHelpers.js +308 -0
  966. package/src/jobs/classifier.js +6 -0
  967. package/src/keybindings/KeybindingContext.js +184 -0
  968. package/src/keybindings/KeybindingProviderSetup.js +259 -0
  969. package/src/keybindings/defaultBindings.js +333 -0
  970. package/src/keybindings/loadUserBindings.js +393 -0
  971. package/src/keybindings/match.js +111 -0
  972. package/src/keybindings/parser.js +184 -0
  973. package/src/keybindings/reservedShortcuts.js +109 -0
  974. package/src/keybindings/resolver.js +182 -0
  975. package/src/keybindings/schema.js +205 -0
  976. package/src/keybindings/shortcutFormat.js +48 -0
  977. package/src/keybindings/template.js +40 -0
  978. package/src/keybindings/useKeybinding.js +161 -0
  979. package/src/keybindings/useShortcutDisplay.js +43 -0
  980. package/src/keybindings/validate.js +395 -0
  981. package/src/main.js +4128 -0
  982. package/src/memdir/findRelevantMemories.js +99 -0
  983. package/src/memdir/memdir.js +406 -0
  984. package/src/memdir/memoryAge.js +52 -0
  985. package/src/memdir/memoryScan.js +65 -0
  986. package/src/memdir/memoryShapeTelemetry.js +8 -0
  987. package/src/memdir/memoryTypes.js +260 -0
  988. package/src/memdir/paths.js +235 -0
  989. package/src/memdir/teamMemPaths.js +261 -0
  990. package/src/memdir/teamMemPrompts.js +82 -0
  991. package/src/migrations/migrateAutoUpdatesToSettings.js +47 -0
  992. package/src/migrations/migrateBypassPermissionsAcceptedToSettings.js +32 -0
  993. package/src/migrations/migrateEnableAllProjectMcpServersToSettings.js +83 -0
  994. package/src/migrations/migrateFennecToOpus.js +39 -0
  995. package/src/migrations/migrateLegacyOpusToCurrent.js +44 -0
  996. package/src/migrations/migrateOpusToOpus1m.js +31 -0
  997. package/src/migrations/migrateReplBridgeEnabledToRemoteControlAtStartup.js +23 -0
  998. package/src/migrations/migrateSonnet1mToSonnet45.js +38 -0
  999. package/src/migrations/migrateSonnet45ToSonnet46.js +48 -0
  1000. package/src/migrations/resetAutoModeOptInForDefaultOffer.js +47 -0
  1001. package/src/migrations/resetProToOpusDefault.js +46 -0
  1002. package/src/moreright/useMoreRight.js +13 -0
  1003. package/src/native-ts/color-diff/index.js +819 -0
  1004. package/src/native-ts/file-index/index.js +328 -0
  1005. package/src/native-ts/yoga-layout/enums.js +101 -0
  1006. package/src/native-ts/yoga-layout/index.js +2113 -0
  1007. package/src/outputStyles/loadOutputStylesDir.js +71 -0
  1008. package/src/plugins/builtinPlugins.js +132 -0
  1009. package/src/plugins/bundled/index.js +22 -0
  1010. package/src/proactive/index.js +138 -0
  1011. package/src/proactive/useProactive.js +82 -0
  1012. package/src/projectOnboardingState.js +61 -0
  1013. package/src/query/config.js +17 -0
  1014. package/src/query/deps.js +12 -0
  1015. package/src/query/stopHooks.js +332 -0
  1016. package/src/query/tokenBudget.js +49 -0
  1017. package/src/query.js +1264 -0
  1018. package/src/remote/RemoteSessionManager.js +172 -0
  1019. package/src/remote/SessionsWebSocket.js +308 -0
  1020. package/src/remote/remotePermissionBridge.js +70 -0
  1021. package/src/remote/sdkMessageAdapter.js +227 -0
  1022. package/src/replLauncher.js +7 -0
  1023. package/src/schemas/hooks.js +174 -0
  1024. package/src/screens/Doctor.js +580 -0
  1025. package/src/screens/REPL.js +4500 -0
  1026. package/src/screens/ResumeConversation.js +339 -0
  1027. package/src/self-hosted-runner/main.js +8 -0
  1028. package/src/server/backends/dangerousBackend.js +8 -0
  1029. package/src/server/connectHeadless.js +6 -0
  1030. package/src/server/createDirectConnectSession.js +62 -0
  1031. package/src/server/directConnectManager.js +153 -0
  1032. package/src/server/lockfile.js +11 -0
  1033. package/src/server/parseConnectUrl.js +20 -0
  1034. package/src/server/server.js +12 -0
  1035. package/src/server/serverBanner.js +9 -0
  1036. package/src/server/serverLog.js +11 -0
  1037. package/src/server/sessionManager.js +19 -0
  1038. package/src/server/types.js +7 -0
  1039. package/src/services/AgentSummary/agentSummary.js +147 -0
  1040. package/src/services/MagicDocs/magicDocs.js +193 -0
  1041. package/src/services/MagicDocs/prompts.js +110 -0
  1042. package/src/services/PromptSuggestion/promptSuggestion.js +402 -0
  1043. package/src/services/PromptSuggestion/speculation.js +643 -0
  1044. package/src/services/SessionMemory/prompts.js +254 -0
  1045. package/src/services/SessionMemory/sessionMemory.js +358 -0
  1046. package/src/services/SessionMemory/sessionMemoryUtils.js +157 -0
  1047. package/src/services/analytics/config.js +27 -0
  1048. package/src/services/analytics/datadog.js +26 -0
  1049. package/src/services/analytics/firstPartyEventLogger.js +65 -0
  1050. package/src/services/analytics/firstPartyEventLoggingExporter.js +595 -0
  1051. package/src/services/analytics/growthbook.js +103 -0
  1052. package/src/services/analytics/index.js +91 -0
  1053. package/src/services/analytics/metadata.js +696 -0
  1054. package/src/services/analytics/sink.js +19 -0
  1055. package/src/services/analytics/sinkKillswitch.js +19 -0
  1056. package/src/services/api/adminRequests.js +57 -0
  1057. package/src/services/api/bootstrap.js +118 -0
  1058. package/src/services/api/claude.js +2466 -0
  1059. package/src/services/api/client.js +335 -0
  1060. package/src/services/api/dumpPrompts.js +174 -0
  1061. package/src/services/api/emptyUsage.js +20 -0
  1062. package/src/services/api/errorUtils.js +203 -0
  1063. package/src/services/api/errors.js +926 -0
  1064. package/src/services/api/filesApi.js +523 -0
  1065. package/src/services/api/firstTokenDate.js +49 -0
  1066. package/src/services/api/grove.js +44 -0
  1067. package/src/services/api/logging.js +484 -0
  1068. package/src/services/api/metricsOptOut.js +15 -0
  1069. package/src/services/api/overageCreditGrant.js +123 -0
  1070. package/src/services/api/promptCacheBreakDetection.js +510 -0
  1071. package/src/services/api/referral.js +219 -0
  1072. package/src/services/api/sessionIngress.js +358 -0
  1073. package/src/services/api/ultrareviewQuota.js +29 -0
  1074. package/src/services/api/usage.js +31 -0
  1075. package/src/services/api/withRetry.js +587 -0
  1076. package/src/services/api/xai/anthropic-shim.js +885 -0
  1077. package/src/services/api/xai/brightDataSearch.js +161 -0
  1078. package/src/services/api/xai/thaddeus-engine.js +605 -0
  1079. package/src/services/api/xai/xai-client.js +276 -0
  1080. package/src/services/autoDream/autoDream.js +244 -0
  1081. package/src/services/autoDream/config.js +17 -0
  1082. package/src/services/autoDream/consolidationLock.js +122 -0
  1083. package/src/services/autoDream/consolidationPrompt.js +55 -0
  1084. package/src/services/awaySummary.js +61 -0
  1085. package/src/services/claudeAiLimits.js +331 -0
  1086. package/src/services/claudeAiLimitsHook.js +15 -0
  1087. package/src/services/compact/apiMicrocompact.js +97 -0
  1088. package/src/services/compact/autoCompact.js +234 -0
  1089. package/src/services/compact/cachedMCConfig.js +5 -0
  1090. package/src/services/compact/compact.js +1256 -0
  1091. package/src/services/compact/compactWarningHook.js +12 -0
  1092. package/src/services/compact/compactWarningState.js +15 -0
  1093. package/src/services/compact/grouping.js +58 -0
  1094. package/src/services/compact/microCompact.js +414 -0
  1095. package/src/services/compact/postCompactCleanup.js +70 -0
  1096. package/src/services/compact/prompt.js +325 -0
  1097. package/src/services/compact/reactiveCompact.js +20 -0
  1098. package/src/services/compact/sessionMemoryCompact.js +467 -0
  1099. package/src/services/compact/snipCompact.js +23 -0
  1100. package/src/services/compact/snipProjection.js +11 -0
  1101. package/src/services/compact/timeBasedMCConfig.js +11 -0
  1102. package/src/services/contextCollapse/index.js +33 -0
  1103. package/src/services/contextCollapse/operations.js +5 -0
  1104. package/src/services/contextCollapse/persist.js +5 -0
  1105. package/src/services/diagnosticTracking.js +282 -0
  1106. package/src/services/elevenlabsTTS.js +245 -0
  1107. package/src/services/extractMemories/extractMemories.js +442 -0
  1108. package/src/services/extractMemories/prompts.js +129 -0
  1109. package/src/services/internalLogging.js +68 -0
  1110. package/src/services/lsp/LSPClient.js +306 -0
  1111. package/src/services/lsp/LSPDiagnosticRegistry.js +277 -0
  1112. package/src/services/lsp/LSPServerInstance.js +388 -0
  1113. package/src/services/lsp/LSPServerManager.js +305 -0
  1114. package/src/services/lsp/config.js +57 -0
  1115. package/src/services/lsp/manager.js +246 -0
  1116. package/src/services/lsp/passiveFeedback.js +226 -0
  1117. package/src/services/mcp/InProcessTransport.js +54 -0
  1118. package/src/services/mcp/MCPConnectionManager.js +50 -0
  1119. package/src/services/mcp/SdkControlTransport.js +115 -0
  1120. package/src/services/mcp/auth.js +1882 -0
  1121. package/src/services/mcp/channelAllowlist.js +57 -0
  1122. package/src/services/mcp/channelNotification.js +235 -0
  1123. package/src/services/mcp/channelPermissions.js +192 -0
  1124. package/src/services/mcp/claudeai.js +123 -0
  1125. package/src/services/mcp/client.js +2478 -0
  1126. package/src/services/mcp/config.js +1271 -0
  1127. package/src/services/mcp/elicitationHandler.js +192 -0
  1128. package/src/services/mcp/envExpansion.js +30 -0
  1129. package/src/services/mcp/headersHelper.js +93 -0
  1130. package/src/services/mcp/mcpStringUtils.js +85 -0
  1131. package/src/services/mcp/normalization.js +21 -0
  1132. package/src/services/mcp/oauthPort.js +69 -0
  1133. package/src/services/mcp/officialRegistry.js +20 -0
  1134. package/src/services/mcp/types.js +94 -0
  1135. package/src/services/mcp/useManageMCPConnections.js +818 -0
  1136. package/src/services/mcp/utils.js +433 -0
  1137. package/src/services/mcp/vscodeSdkMcp.js +69 -0
  1138. package/src/services/mcp/xaa.js +342 -0
  1139. package/src/services/mcp/xaaIdpLogin.js +377 -0
  1140. package/src/services/mcpServerApproval.js +30 -0
  1141. package/src/services/mockRateLimits.js +666 -0
  1142. package/src/services/notifier.js +114 -0
  1143. package/src/services/oauth/auth-code-listener.js +165 -0
  1144. package/src/services/oauth/client.js +397 -0
  1145. package/src/services/oauth/crypto.js +19 -0
  1146. package/src/services/oauth/getOauthProfile.js +48 -0
  1147. package/src/services/oauth/index.js +133 -0
  1148. package/src/services/plugins/PluginInstallationManager.js +139 -0
  1149. package/src/services/plugins/pluginCliCommands.js +230 -0
  1150. package/src/services/plugins/pluginOperations.js +826 -0
  1151. package/src/services/policyLimits/index.js +547 -0
  1152. package/src/services/policyLimits/types.js +9 -0
  1153. package/src/services/preventSleep.js +143 -0
  1154. package/src/services/rateLimitMessages.js +271 -0
  1155. package/src/services/rateLimitMocking.js +91 -0
  1156. package/src/services/remoteManagedSettings/index.js +534 -0
  1157. package/src/services/remoteManagedSettings/securityCheck.js +60 -0
  1158. package/src/services/remoteManagedSettings/syncCache.js +90 -0
  1159. package/src/services/remoteManagedSettings/syncCacheState.js +89 -0
  1160. package/src/services/remoteManagedSettings/types.js +12 -0
  1161. package/src/services/sessionTranscript/sessionTranscript.js +5 -0
  1162. package/src/services/settingsSync/index.js +478 -0
  1163. package/src/services/settingsSync/types.js +35 -0
  1164. package/src/services/skillSearch/featureCheck.js +8 -0
  1165. package/src/services/skillSearch/localSearch.js +5 -0
  1166. package/src/services/skillSearch/prefetch.js +8 -0
  1167. package/src/services/skillSearch/remoteSkillLoader.js +8 -0
  1168. package/src/services/skillSearch/remoteSkillState.js +11 -0
  1169. package/src/services/skillSearch/signals.js +3 -0
  1170. package/src/services/skillSearch/telemetry.js +8 -0
  1171. package/src/services/teamMemorySync/index.js +976 -0
  1172. package/src/services/teamMemorySync/secretScanner.js +275 -0
  1173. package/src/services/teamMemorySync/teamMemSecretGuard.js +33 -0
  1174. package/src/services/teamMemorySync/types.js +47 -0
  1175. package/src/services/teamMemorySync/watcher.js +326 -0
  1176. package/src/services/thaddeusAuth.js +485 -0
  1177. package/src/services/thaddeusAuthTypes.js +9 -0
  1178. package/src/services/thaddeusLoginFlow.js +236 -0
  1179. package/src/services/tips/tipHistory.js +17 -0
  1180. package/src/services/tips/tipRegistry.js +593 -0
  1181. package/src/services/tips/tipScheduler.js +40 -0
  1182. package/src/services/tokenEstimation.js +365 -0
  1183. package/src/services/toolUseSummary/toolUseSummaryGenerator.js +87 -0
  1184. package/src/services/tools/StreamingToolExecutor.js +413 -0
  1185. package/src/services/tools/toolExecution.js +1309 -0
  1186. package/src/services/tools/toolHooks.js +454 -0
  1187. package/src/services/tools/toolOrchestration.js +110 -0
  1188. package/src/services/vcr.js +291 -0
  1189. package/src/services/voice.js +392 -0
  1190. package/src/services/voiceKeyterms.js +94 -0
  1191. package/src/services/voiceStreamSTT.js +405 -0
  1192. package/src/setup.js +310 -0
  1193. package/src/skills/bundled/batch.js +114 -0
  1194. package/src/skills/bundled/claudeApi.js +145 -0
  1195. package/src/skills/bundled/claudeApiContent.js +71 -0
  1196. package/src/skills/bundled/claudeInChrome.js +27 -0
  1197. package/src/skills/bundled/debug.js +99 -0
  1198. package/src/skills/bundled/dream.js +49 -0
  1199. package/src/skills/bundled/emailSetup.js +196 -0
  1200. package/src/skills/bundled/hunter.js +28 -0
  1201. package/src/skills/bundled/index.js +80 -0
  1202. package/src/skills/bundled/keybindings.js +292 -0
  1203. package/src/skills/bundled/loop.js +81 -0
  1204. package/src/skills/bundled/loremIpsum.js +264 -0
  1205. package/src/skills/bundled/reactor.js +31 -0
  1206. package/src/skills/bundled/remember.js +73 -0
  1207. package/src/skills/bundled/runSkillGenerator.js +12 -0
  1208. package/src/skills/bundled/scheduleRemoteAgents.js +373 -0
  1209. package/src/skills/bundled/simplify.js +66 -0
  1210. package/src/skills/bundled/skillify.js +182 -0
  1211. package/src/skills/bundled/stuck.js +69 -0
  1212. package/src/skills/bundled/updateConfig.js +463 -0
  1213. package/src/skills/bundled/verify.js +23 -0
  1214. package/src/skills/bundled/verifyContent.js +10 -0
  1215. package/src/skills/bundledSkills.js +159 -0
  1216. package/src/skills/loadSkillsDir.js +736 -0
  1217. package/src/skills/mcpSkillBuilders.js +10 -0
  1218. package/src/skills/mcpSkills.js +5 -0
  1219. package/src/state/AppState.js +182 -0
  1220. package/src/state/AppStateStore.js +117 -0
  1221. package/src/state/onChangeAppState.js +132 -0
  1222. package/src/state/selectors.js +51 -0
  1223. package/src/state/store.js +21 -0
  1224. package/src/state/teammateViewHelpers.js +124 -0
  1225. package/src/stubs/ant-chrome-mcp/index.js +4 -0
  1226. package/src/stubs/ant-computer-use-input/index.js +2 -0
  1227. package/src/stubs/ant-computer-use-mcp/index.js +7 -0
  1228. package/src/stubs/ant-computer-use-mcp/sentinelApps.js +2 -0
  1229. package/src/stubs/ant-computer-use-mcp/types.js +3 -0
  1230. package/src/stubs/ant-computer-use-swift/index.js +1 -0
  1231. package/src/stubs/anthropic-sandbox/index.js +34 -0
  1232. package/src/tasks/DreamTask/DreamTask.js +99 -0
  1233. package/src/tasks/InProcessTeammateTask/InProcessTeammateTask.js +116 -0
  1234. package/src/tasks/InProcessTeammateTask/types.js +35 -0
  1235. package/src/tasks/LocalAgentTask/LocalAgentTask.js +507 -0
  1236. package/src/tasks/LocalMainSessionTask.js +338 -0
  1237. package/src/tasks/LocalShellTask/LocalShellTask.js +475 -0
  1238. package/src/tasks/LocalShellTask/guards.js +9 -0
  1239. package/src/tasks/LocalShellTask/killShellTasks.js +59 -0
  1240. package/src/tasks/LocalWorkflowTask/LocalWorkflowTask.js +7 -0
  1241. package/src/tasks/MonitorMcpTask/MonitorMcpTask.js +20 -0
  1242. package/src/tasks/RemoteAgentTask/RemoteAgentTask.js +742 -0
  1243. package/src/tasks/pillLabel.js +69 -0
  1244. package/src/tasks/stopTask.js +67 -0
  1245. package/src/tasks/types.js +18 -0
  1246. package/src/tasks.js +37 -0
  1247. package/src/tools/AIEmployeesTool/AIEmployeesTool.js +674 -0
  1248. package/src/tools/AIEmployeesTool/constants.js +1 -0
  1249. package/src/tools/AIEmployeesTool/prompt.js +56 -0
  1250. package/src/tools/AgentTool/AgentTool.js +1221 -0
  1251. package/src/tools/AgentTool/UI.js +593 -0
  1252. package/src/tools/AgentTool/agentColorManager.js +43 -0
  1253. package/src/tools/AgentTool/agentDisplay.js +72 -0
  1254. package/src/tools/AgentTool/agentMemory.js +125 -0
  1255. package/src/tools/AgentTool/agentMemorySnapshot.js +136 -0
  1256. package/src/tools/AgentTool/agentToolUtils.js +456 -0
  1257. package/src/tools/AgentTool/built-in/exploreAgent.js +76 -0
  1258. package/src/tools/AgentTool/built-in/generalPurposeAgent.js +28 -0
  1259. package/src/tools/AgentTool/built-in/planAgent.js +87 -0
  1260. package/src/tools/AgentTool/built-in/statuslineSetup.js +140 -0
  1261. package/src/tools/AgentTool/built-in/thaddeusGuideAgent.js +174 -0
  1262. package/src/tools/AgentTool/built-in/verificationAgent.js +146 -0
  1263. package/src/tools/AgentTool/builtInAgents.js +56 -0
  1264. package/src/tools/AgentTool/constants.js +11 -0
  1265. package/src/tools/AgentTool/forkSubagent.js +177 -0
  1266. package/src/tools/AgentTool/loadAgentsDir.js +497 -0
  1267. package/src/tools/AgentTool/prompt.js +260 -0
  1268. package/src/tools/AgentTool/resumeAgent.js +182 -0
  1269. package/src/tools/AgentTool/runAgent.js +627 -0
  1270. package/src/tools/AppointmentsTool/AppointmentsTool.js +628 -0
  1271. package/src/tools/AppointmentsTool/constants.js +1 -0
  1272. package/src/tools/AppointmentsTool/prompt.js +15 -0
  1273. package/src/tools/AskUserQuestionTool/AskUserQuestionTool.js +238 -0
  1274. package/src/tools/AskUserQuestionTool/prompt.js +38 -0
  1275. package/src/tools/BashTool/BashTool.js +1009 -0
  1276. package/src/tools/BashTool/BashToolResultMessage.js +169 -0
  1277. package/src/tools/BashTool/UI.js +134 -0
  1278. package/src/tools/BashTool/bashCommandHelpers.js +184 -0
  1279. package/src/tools/BashTool/bashPermissions.js +2023 -0
  1280. package/src/tools/BashTool/bashSecurity.js +2267 -0
  1281. package/src/tools/BashTool/commandSemantics.js +105 -0
  1282. package/src/tools/BashTool/commentLabel.js +14 -0
  1283. package/src/tools/BashTool/destructiveCommandWarning.js +88 -0
  1284. package/src/tools/BashTool/modeValidation.js +86 -0
  1285. package/src/tools/BashTool/pathValidation.js +1079 -0
  1286. package/src/tools/BashTool/prompt.js +333 -0
  1287. package/src/tools/BashTool/readOnlyValidation.js +1794 -0
  1288. package/src/tools/BashTool/sedEditParser.js +282 -0
  1289. package/src/tools/BashTool/sedValidation.js +580 -0
  1290. package/src/tools/BashTool/shouldUseSandbox.js +125 -0
  1291. package/src/tools/BashTool/toolName.js +2 -0
  1292. package/src/tools/BashTool/utils.js +180 -0
  1293. package/src/tools/BriefTool/BriefTool.js +173 -0
  1294. package/src/tools/BriefTool/UI.js +67 -0
  1295. package/src/tools/BriefTool/attachments.js +86 -0
  1296. package/src/tools/BriefTool/prompt.js +19 -0
  1297. package/src/tools/BriefTool/upload.js +136 -0
  1298. package/src/tools/CalendarTool/CalendarTool.js +498 -0
  1299. package/src/tools/CalendarTool/constants.js +1 -0
  1300. package/src/tools/CalendarTool/prompt.js +11 -0
  1301. package/src/tools/ConfigTool/ConfigTool.js +398 -0
  1302. package/src/tools/ConfigTool/UI.js +25 -0
  1303. package/src/tools/ConfigTool/constants.js +1 -0
  1304. package/src/tools/ConfigTool/prompt.js +82 -0
  1305. package/src/tools/ConfigTool/supportedSettings.js +180 -0
  1306. package/src/tools/ContactsTool/ContactsTool.js +648 -0
  1307. package/src/tools/ContactsTool/constants.js +1 -0
  1308. package/src/tools/ContactsTool/prompt.js +15 -0
  1309. package/src/tools/CtxInspectTool/CtxInspectTool.js +44 -0
  1310. package/src/tools/DiscoverSkillsTool/prompt.js +4 -0
  1311. package/src/tools/EmailReadTool/index.js +410 -0
  1312. package/src/tools/EmailSendTool/index.js +178 -0
  1313. package/src/tools/EnterPlanModeTool/EnterPlanModeTool.js +98 -0
  1314. package/src/tools/EnterPlanModeTool/UI.js +14 -0
  1315. package/src/tools/EnterPlanModeTool/constants.js +1 -0
  1316. package/src/tools/EnterPlanModeTool/prompt.js +164 -0
  1317. package/src/tools/EnterWorktreeTool/EnterWorktreeTool.js +104 -0
  1318. package/src/tools/EnterWorktreeTool/UI.js +9 -0
  1319. package/src/tools/EnterWorktreeTool/constants.js +1 -0
  1320. package/src/tools/EnterWorktreeTool/prompt.js +30 -0
  1321. package/src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.js +383 -0
  1322. package/src/tools/ExitPlanModeTool/UI.js +32 -0
  1323. package/src/tools/ExitPlanModeTool/constants.js +2 -0
  1324. package/src/tools/ExitPlanModeTool/prompt.js +27 -0
  1325. package/src/tools/ExitWorktreeTool/ExitWorktreeTool.js +257 -0
  1326. package/src/tools/ExitWorktreeTool/UI.js +10 -0
  1327. package/src/tools/ExitWorktreeTool/constants.js +1 -0
  1328. package/src/tools/ExitWorktreeTool/prompt.js +32 -0
  1329. package/src/tools/FileEditTool/FileEditTool.js +480 -0
  1330. package/src/tools/FileEditTool/UI.js +202 -0
  1331. package/src/tools/FileEditTool/constants.js +7 -0
  1332. package/src/tools/FileEditTool/prompt.js +24 -0
  1333. package/src/tools/FileEditTool/types.js +50 -0
  1334. package/src/tools/FileEditTool/utils.js +579 -0
  1335. package/src/tools/FileReadTool/FileReadTool.js +889 -0
  1336. package/src/tools/FileReadTool/UI.js +126 -0
  1337. package/src/tools/FileReadTool/imageProcessor.js +46 -0
  1338. package/src/tools/FileReadTool/limits.js +70 -0
  1339. package/src/tools/FileReadTool/prompt.js +31 -0
  1340. package/src/tools/FileWriteTool/FileWriteTool.js +341 -0
  1341. package/src/tools/FileWriteTool/UI.js +339 -0
  1342. package/src/tools/FileWriteTool/prompt.js +15 -0
  1343. package/src/tools/GlobTool/GlobTool.js +161 -0
  1344. package/src/tools/GlobTool/UI.js +40 -0
  1345. package/src/tools/GlobTool/prompt.js +6 -0
  1346. package/src/tools/GrepTool/GrepTool.js +439 -0
  1347. package/src/tools/GrepTool/UI.js +155 -0
  1348. package/src/tools/GrepTool/prompt.js +16 -0
  1349. package/src/tools/IntegrationsTool/IntegrationsTool.js +217 -0
  1350. package/src/tools/IntegrationsTool/constants.js +1 -0
  1351. package/src/tools/IntegrationsTool/prompt.js +41 -0
  1352. package/src/tools/InteractionsTool/InteractionsTool.js +525 -0
  1353. package/src/tools/InteractionsTool/constants.js +1 -0
  1354. package/src/tools/InteractionsTool/prompt.js +14 -0
  1355. package/src/tools/InvoicesTool/InvoicesTool.js +581 -0
  1356. package/src/tools/InvoicesTool/constants.js +1 -0
  1357. package/src/tools/InvoicesTool/prompt.js +15 -0
  1358. package/src/tools/LSPTool/LSPTool.js +660 -0
  1359. package/src/tools/LSPTool/UI.js +205 -0
  1360. package/src/tools/LSPTool/formatters.js +445 -0
  1361. package/src/tools/LSPTool/prompt.js +20 -0
  1362. package/src/tools/LSPTool/schemas.js +197 -0
  1363. package/src/tools/LSPTool/symbolContext.js +75 -0
  1364. package/src/tools/LeadScorerTool/LeadScorerTool.js +509 -0
  1365. package/src/tools/LeadScorerTool/constants.js +1 -0
  1366. package/src/tools/LeadScorerTool/prompt.js +11 -0
  1367. package/src/tools/ListMcpResourcesTool/ListMcpResourcesTool.js +100 -0
  1368. package/src/tools/ListMcpResourcesTool/UI.js +17 -0
  1369. package/src/tools/ListMcpResourcesTool/prompt.js +18 -0
  1370. package/src/tools/ListPeersTool/ListPeersTool.js +45 -0
  1371. package/src/tools/MCPTool/MCPTool.js +60 -0
  1372. package/src/tools/MCPTool/UI.js +343 -0
  1373. package/src/tools/MCPTool/classifyForCollapse.js +597 -0
  1374. package/src/tools/MCPTool/prompt.js +3 -0
  1375. package/src/tools/McpAuthTool/McpAuthTool.js +162 -0
  1376. package/src/tools/MonitorTool/MonitorTool.js +55 -0
  1377. package/src/tools/NotebookEditTool/NotebookEditTool.js +421 -0
  1378. package/src/tools/NotebookEditTool/UI.js +41 -0
  1379. package/src/tools/NotebookEditTool/constants.js +2 -0
  1380. package/src/tools/NotebookEditTool/prompt.js +2 -0
  1381. package/src/tools/OverflowTestTool/OverflowTestTool.js +51 -0
  1382. package/src/tools/PhoneBridgeTool/PhoneBridgeTool.js +301 -0
  1383. package/src/tools/PhoneBridgeTool/constants.js +1 -0
  1384. package/src/tools/PhoneBridgeTool/prompt.js +26 -0
  1385. package/src/tools/PowerShellTool/PowerShellTool.js +900 -0
  1386. package/src/tools/PowerShellTool/UI.js +58 -0
  1387. package/src/tools/PowerShellTool/clmTypes.js +207 -0
  1388. package/src/tools/PowerShellTool/commandSemantics.js +115 -0
  1389. package/src/tools/PowerShellTool/commonParameters.js +27 -0
  1390. package/src/tools/PowerShellTool/destructiveCommandWarning.js +92 -0
  1391. package/src/tools/PowerShellTool/gitSafety.js +185 -0
  1392. package/src/tools/PowerShellTool/modeValidation.js +357 -0
  1393. package/src/tools/PowerShellTool/pathValidation.js +1712 -0
  1394. package/src/tools/PowerShellTool/powershellPermissions.js +1351 -0
  1395. package/src/tools/PowerShellTool/powershellSecurity.js +942 -0
  1396. package/src/tools/PowerShellTool/prompt.js +132 -0
  1397. package/src/tools/PowerShellTool/readOnlyValidation.js +1633 -0
  1398. package/src/tools/PowerShellTool/toolName.js +2 -0
  1399. package/src/tools/PushNotificationTool/PushNotificationTool.js +35 -0
  1400. package/src/tools/REPLTool/REPLTool.js +44 -0
  1401. package/src/tools/REPLTool/constants.js +43 -0
  1402. package/src/tools/REPLTool/primitiveTools.js +36 -0
  1403. package/src/tools/ReadMcpResourceTool/ReadMcpResourceTool.js +112 -0
  1404. package/src/tools/ReadMcpResourceTool/UI.js +24 -0
  1405. package/src/tools/ReadMcpResourceTool/prompt.js +15 -0
  1406. package/src/tools/RemoteTriggerTool/RemoteTriggerTool.js +142 -0
  1407. package/src/tools/RemoteTriggerTool/UI.js +12 -0
  1408. package/src/tools/RemoteTriggerTool/prompt.js +12 -0
  1409. package/src/tools/ReviewArtifactTool/ReviewArtifactTool.js +51 -0
  1410. package/src/tools/ScheduleCronTool/CronCreateTool.js +120 -0
  1411. package/src/tools/ScheduleCronTool/CronDeleteTool.js +74 -0
  1412. package/src/tools/ScheduleCronTool/CronListTool.js +77 -0
  1413. package/src/tools/ScheduleCronTool/UI.js +29 -0
  1414. package/src/tools/ScheduleCronTool/prompt.js +115 -0
  1415. package/src/tools/SendMessageTool/SendMessageTool.js +673 -0
  1416. package/src/tools/SendMessageTool/UI.js +24 -0
  1417. package/src/tools/SendMessageTool/constants.js +1 -0
  1418. package/src/tools/SendMessageTool/prompt.js +47 -0
  1419. package/src/tools/SendUserFileTool/SendUserFileTool.js +35 -0
  1420. package/src/tools/SendUserFileTool/prompt.js +5 -0
  1421. package/src/tools/SkillTool/SkillTool.js +825 -0
  1422. package/src/tools/SkillTool/UI.js +61 -0
  1423. package/src/tools/SkillTool/constants.js +1 -0
  1424. package/src/tools/SkillTool/prompt.js +184 -0
  1425. package/src/tools/SleepTool/SleepTool.js +42 -0
  1426. package/src/tools/SleepTool/prompt.js +14 -0
  1427. package/src/tools/SnipTool/SnipTool.js +47 -0
  1428. package/src/tools/SnipTool/prompt.js +5 -0
  1429. package/src/tools/SubscribePRTool/SubscribePRTool.js +49 -0
  1430. package/src/tools/SuggestBackgroundPRTool/SuggestBackgroundPRTool.js +44 -0
  1431. package/src/tools/SyntheticOutputTool/SyntheticOutputTool.js +138 -0
  1432. package/src/tools/SyntheticOutputTool/SyntheticOutputTool.ts +1 -1
  1433. package/src/tools/TaskCreateTool/TaskCreateTool.js +104 -0
  1434. package/src/tools/TaskCreateTool/constants.js +1 -0
  1435. package/src/tools/TaskCreateTool/prompt.js +52 -0
  1436. package/src/tools/TaskGetTool/TaskGetTool.js +106 -0
  1437. package/src/tools/TaskGetTool/constants.js +1 -0
  1438. package/src/tools/TaskGetTool/prompt.js +23 -0
  1439. package/src/tools/TaskListTool/TaskListTool.js +89 -0
  1440. package/src/tools/TaskListTool/constants.js +1 -0
  1441. package/src/tools/TaskListTool/prompt.js +44 -0
  1442. package/src/tools/TaskOutputTool/TaskOutputTool.js +536 -0
  1443. package/src/tools/TaskOutputTool/constants.js +1 -0
  1444. package/src/tools/TaskStopTool/TaskStopTool.js +110 -0
  1445. package/src/tools/TaskStopTool/UI.js +31 -0
  1446. package/src/tools/TaskStopTool/prompt.js +7 -0
  1447. package/src/tools/TaskUpdateTool/TaskUpdateTool.js +301 -0
  1448. package/src/tools/TaskUpdateTool/constants.js +1 -0
  1449. package/src/tools/TaskUpdateTool/prompt.js +76 -0
  1450. package/src/tools/TeamCreateTool/TeamCreateTool.js +177 -0
  1451. package/src/tools/TeamCreateTool/UI.js +4 -0
  1452. package/src/tools/TeamCreateTool/constants.js +1 -0
  1453. package/src/tools/TeamCreateTool/prompt.js +113 -0
  1454. package/src/tools/TeamDeleteTool/TeamDeleteTool.js +102 -0
  1455. package/src/tools/TeamDeleteTool/UI.js +13 -0
  1456. package/src/tools/TeamDeleteTool/constants.js +1 -0
  1457. package/src/tools/TeamDeleteTool/prompt.js +16 -0
  1458. package/src/tools/TerminalCaptureTool/TerminalCaptureTool.js +47 -0
  1459. package/src/tools/TerminalCaptureTool/prompt.js +11 -0
  1460. package/src/tools/TodoWriteTool/TodoWriteTool.js +99 -0
  1461. package/src/tools/TodoWriteTool/constants.js +1 -0
  1462. package/src/tools/TodoWriteTool/prompt.js +181 -0
  1463. package/src/tools/ToolSearchTool/ToolSearchTool.js +357 -0
  1464. package/src/tools/ToolSearchTool/constants.js +1 -0
  1465. package/src/tools/ToolSearchTool/prompt.js +97 -0
  1466. package/src/tools/TungstenTool/TungstenLiveMonitor.js +7 -0
  1467. package/src/tools/TungstenTool/TungstenTool.js +3 -0
  1468. package/src/tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.js +45 -0
  1469. package/src/tools/VerifyPlanExecutionTool/constants.js +2 -0
  1470. package/src/tools/WebBrowserTool/WebBrowserPanel.js +5 -0
  1471. package/src/tools/WebBrowserTool/WebBrowserTool.js +58 -0
  1472. package/src/tools/WebFetchTool/UI.js +31 -0
  1473. package/src/tools/WebFetchTool/WebFetchTool.js +246 -0
  1474. package/src/tools/WebFetchTool/preapproved.js +154 -0
  1475. package/src/tools/WebFetchTool/prompt.js +39 -0
  1476. package/src/tools/WebFetchTool/utils.js +368 -0
  1477. package/src/tools/WebSearchTool/UI.js +67 -0
  1478. package/src/tools/WebSearchTool/WebSearchTool.js +396 -0
  1479. package/src/tools/WebSearchTool/prompt.js +32 -0
  1480. package/src/tools/WorkflowTool/WorkflowPermissionRequest.js +7 -0
  1481. package/src/tools/WorkflowTool/WorkflowTool.js +51 -0
  1482. package/src/tools/WorkflowTool/bundled/index.js +5 -0
  1483. package/src/tools/WorkflowTool/constants.js +1 -0
  1484. package/src/tools/WorkflowTool/createWorkflowCommand.js +5 -0
  1485. package/src/tools/shared/gitOperationTracking.js +220 -0
  1486. package/src/tools/shared/spawnMultiAgent.js +805 -0
  1487. package/src/tools/testing/TestingPermissionTool.js +72 -0
  1488. package/src/tools/utils.js +24 -0
  1489. package/src/tools.js +365 -0
  1490. package/src/types/command.js +8 -0
  1491. package/src/types/connectorText.js +3 -0
  1492. package/src/types/generated/events_mono/claude_code/v1/claude_code_internal_event.js +673 -0
  1493. package/src/types/generated/events_mono/common/v1/auth.js +49 -0
  1494. package/src/types/generated/events_mono/growthbook/v1/growthbook_experiment_event.js +147 -0
  1495. package/src/types/generated/google/protobuf/timestamp.js +38 -0
  1496. package/src/types/hooks.js +153 -0
  1497. package/src/types/ids.js +27 -0
  1498. package/src/types/logs.js +11 -0
  1499. package/src/types/permissions.js +25 -0
  1500. package/src/types/plugin.js +72 -0
  1501. package/src/types/textInputTypes.js +20 -0
  1502. package/src/upstreamproxy/relay.js +346 -0
  1503. package/src/upstreamproxy/upstreamproxy.js +234 -0
  1504. package/src/utils/CircularBuffer.js +75 -0
  1505. package/src/utils/Cursor.js +1229 -0
  1506. package/src/utils/QueryGuard.js +115 -0
  1507. package/src/utils/Shell.js +374 -0
  1508. package/src/utils/ShellCommand.js +336 -0
  1509. package/src/utils/abortController.js +74 -0
  1510. package/src/utils/activityManager.js +127 -0
  1511. package/src/utils/advisor.js +77 -0
  1512. package/src/utils/agentContext.js +91 -0
  1513. package/src/utils/agentId.js +83 -0
  1514. package/src/utils/agentSwarmsEnabled.js +37 -0
  1515. package/src/utils/agenticSessionSearch.js +255 -0
  1516. package/src/utils/analyzeContext.js +846 -0
  1517. package/src/utils/ansiToPng.js +259 -0
  1518. package/src/utils/ansiToSvg.js +207 -0
  1519. package/src/utils/api.js +555 -0
  1520. package/src/utils/apiPreconnect.js +62 -0
  1521. package/src/utils/appleTerminalBackup.js +95 -0
  1522. package/src/utils/argumentSubstitution.js +114 -0
  1523. package/src/utils/array.js +12 -0
  1524. package/src/utils/asciicast.js +200 -0
  1525. package/src/utils/attachments.js +2518 -0
  1526. package/src/utils/attribution.js +308 -0
  1527. package/src/utils/auth.js +1598 -0
  1528. package/src/utils/authFileDescriptor.js +152 -0
  1529. package/src/utils/authPortable.js +14 -0
  1530. package/src/utils/autoModeDenials.js +15 -0
  1531. package/src/utils/autoRunIssue.js +113 -0
  1532. package/src/utils/autoUpdater.js +457 -0
  1533. package/src/utils/aws.js +44 -0
  1534. package/src/utils/awsAuthStatusManager.js +66 -0
  1535. package/src/utils/background/remote/preconditions.js +175 -0
  1536. package/src/utils/background/remote/remoteSession.js +53 -0
  1537. package/src/utils/backgroundHousekeeping.js +64 -0
  1538. package/src/utils/bash/ParsedCommand.js +241 -0
  1539. package/src/utils/bash/ShellSnapshot.js +489 -0
  1540. package/src/utils/bash/ast.js +2590 -0
  1541. package/src/utils/bash/bashParser.js +4355 -0
  1542. package/src/utils/bash/bashPipeCommand.js +249 -0
  1543. package/src/utils/bash/commands.js +1131 -0
  1544. package/src/utils/bash/heredoc.js +647 -0
  1545. package/src/utils/bash/parser.js +195 -0
  1546. package/src/utils/bash/prefix.js +154 -0
  1547. package/src/utils/bash/registry.js +23 -0
  1548. package/src/utils/bash/shellCompletion.js +196 -0
  1549. package/src/utils/bash/shellPrefix.js +25 -0
  1550. package/src/utils/bash/shellQuote.js +253 -0
  1551. package/src/utils/bash/shellQuoting.js +106 -0
  1552. package/src/utils/bash/specs/alias.js +11 -0
  1553. package/src/utils/bash/specs/index.js +16 -0
  1554. package/src/utils/bash/specs/nohup.js +10 -0
  1555. package/src/utils/bash/specs/pyright.js +88 -0
  1556. package/src/utils/bash/specs/sleep.js +10 -0
  1557. package/src/utils/bash/specs/srun.js +28 -0
  1558. package/src/utils/bash/specs/time.js +10 -0
  1559. package/src/utils/bash/specs/timeout.js +17 -0
  1560. package/src/utils/bash/treeSitterAnalysis.js +407 -0
  1561. package/src/utils/betas.js +331 -0
  1562. package/src/utils/billing.js +54 -0
  1563. package/src/utils/binaryCheck.js +40 -0
  1564. package/src/utils/browser.js +58 -0
  1565. package/src/utils/bufferedWriter.js +77 -0
  1566. package/src/utils/bundledMode.js +19 -0
  1567. package/src/utils/businessDb.js +390 -0
  1568. package/src/utils/caCerts.js +91 -0
  1569. package/src/utils/caCertsConfig.js +77 -0
  1570. package/src/utils/cachePaths.js +28 -0
  1571. package/src/utils/classifierApprovals.js +66 -0
  1572. package/src/utils/classifierApprovalsHook.js +10 -0
  1573. package/src/utils/claudeDesktop.js +108 -0
  1574. package/src/utils/claudeInChrome/chromeNativeHost.js +416 -0
  1575. package/src/utils/claudeInChrome/common.js +466 -0
  1576. package/src/utils/claudeInChrome/mcpServer.js +237 -0
  1577. package/src/utils/claudeInChrome/prompt.js +79 -0
  1578. package/src/utils/claudeInChrome/setup.js +304 -0
  1579. package/src/utils/claudeInChrome/setupPortable.js +172 -0
  1580. package/src/utils/claudeInChrome/toolRendering.js +235 -0
  1581. package/src/utils/claudemd.js +1052 -0
  1582. package/src/utils/cleanup.js +514 -0
  1583. package/src/utils/cleanupRegistry.js +22 -0
  1584. package/src/utils/cliArgs.js +53 -0
  1585. package/src/utils/cliHighlight.js +45 -0
  1586. package/src/utils/codeIndexing.js +149 -0
  1587. package/src/utils/collapseBackgroundBashNotifications.js +70 -0
  1588. package/src/utils/collapseHookSummaries.js +48 -0
  1589. package/src/utils/collapseReadSearch.js +869 -0
  1590. package/src/utils/collapseTeammateShutdowns.js +44 -0
  1591. package/src/utils/combinedAbortSignal.js +40 -0
  1592. package/src/utils/commandLifecycle.js +7 -0
  1593. package/src/utils/commitAttribution.js +718 -0
  1594. package/src/utils/completionCache.js +138 -0
  1595. package/src/utils/computerUse/appNames.js +170 -0
  1596. package/src/utils/computerUse/cleanup.js +65 -0
  1597. package/src/utils/computerUse/common.js +56 -0
  1598. package/src/utils/computerUse/computerUseLock.js +183 -0
  1599. package/src/utils/computerUse/drainRunLoop.js +71 -0
  1600. package/src/utils/computerUse/escHotkey.js +53 -0
  1601. package/src/utils/computerUse/executor.js +480 -0
  1602. package/src/utils/computerUse/gates.js +55 -0
  1603. package/src/utils/computerUse/hostAdapter.js +62 -0
  1604. package/src/utils/computerUse/inputLoader.js +25 -0
  1605. package/src/utils/computerUse/mcpServer.js +84 -0
  1606. package/src/utils/computerUse/setup.js +42 -0
  1607. package/src/utils/computerUse/swiftLoader.js +18 -0
  1608. package/src/utils/computerUse/toolRendering.js +101 -0
  1609. package/src/utils/computerUse/wrapper.js +317 -0
  1610. package/src/utils/concurrentSessions.js +179 -0
  1611. package/src/utils/config.js +1078 -0
  1612. package/src/utils/configConstants.js +18 -0
  1613. package/src/utils/contentArray.js +45 -0
  1614. package/src/utils/context.js +185 -0
  1615. package/src/utils/contextAnalysis.js +171 -0
  1616. package/src/utils/contextSuggestions.js +158 -0
  1617. package/src/utils/controlMessageCompat.js +31 -0
  1618. package/src/utils/conversationRecovery.js +434 -0
  1619. package/src/utils/cron.js +260 -0
  1620. package/src/utils/cronJitterConfig.js +62 -0
  1621. package/src/utils/cronScheduler.js +388 -0
  1622. package/src/utils/cronTasks.js +328 -0
  1623. package/src/utils/cronTasksLock.js +159 -0
  1624. package/src/utils/crossProjectResume.js +46 -0
  1625. package/src/utils/crypto.js +13 -0
  1626. package/src/utils/cwd.js +29 -0
  1627. package/src/utils/debug.js +220 -0
  1628. package/src/utils/debugFilter.js +125 -0
  1629. package/src/utils/deepLink/banner.js +103 -0
  1630. package/src/utils/deepLink/parseDeepLink.js +138 -0
  1631. package/src/utils/deepLink/protocolHandler.js +119 -0
  1632. package/src/utils/deepLink/registerProtocol.js +291 -0
  1633. package/src/utils/deepLink/terminalLauncher.js +455 -0
  1634. package/src/utils/deepLink/terminalPreference.js +51 -0
  1635. package/src/utils/desktopDeepLink.js +208 -0
  1636. package/src/utils/detectRepository.js +157 -0
  1637. package/src/utils/diagLogs.js +74 -0
  1638. package/src/utils/diff.js +108 -0
  1639. package/src/utils/directMemberMessage.js +34 -0
  1640. package/src/utils/displayTags.js +46 -0
  1641. package/src/utils/doctorContextWarnings.js +179 -0
  1642. package/src/utils/doctorDiagnostic.js +494 -0
  1643. package/src/utils/dxt/helpers.js +64 -0
  1644. package/src/utils/dxt/zip.js +167 -0
  1645. package/src/utils/earlyInput.js +166 -0
  1646. package/src/utils/editor.js +163 -0
  1647. package/src/utils/effort.js +271 -0
  1648. package/src/utils/embeddedTools.js +26 -0
  1649. package/src/utils/employeeChat.js +271 -0
  1650. package/src/utils/employeeDb.js +326 -0
  1651. package/src/utils/env.js +358 -0
  1652. package/src/utils/envDynamic.js +130 -0
  1653. package/src/utils/envUtils.js +161 -0
  1654. package/src/utils/envValidation.js +26 -0
  1655. package/src/utils/errorLogSink.js +196 -0
  1656. package/src/utils/errors.js +207 -0
  1657. package/src/utils/exampleCommands.js +165 -0
  1658. package/src/utils/execFileNoThrow.js +93 -0
  1659. package/src/utils/execFileNoThrowPortable.js +49 -0
  1660. package/src/utils/execSyncWrapper.js +6 -0
  1661. package/src/utils/exportRenderer.js +71 -0
  1662. package/src/utils/extraUsage.js +19 -0
  1663. package/src/utils/fastMode.js +393 -0
  1664. package/src/utils/file.js +467 -0
  1665. package/src/utils/fileHistory.js +851 -0
  1666. package/src/utils/fileOperationAnalytics.js +45 -0
  1667. package/src/utils/filePersistence/filePersistence.js +212 -0
  1668. package/src/utils/filePersistence/outputsScanner.js +104 -0
  1669. package/src/utils/filePersistence/types.js +4 -0
  1670. package/src/utils/fileRead.js +81 -0
  1671. package/src/utils/fileReadCache.js +78 -0
  1672. package/src/utils/fileStateCache.js +99 -0
  1673. package/src/utils/findExecutable.js +13 -0
  1674. package/src/utils/fingerprint.js +58 -0
  1675. package/src/utils/forkedAgent.js +410 -0
  1676. package/src/utils/format.js +238 -0
  1677. package/src/utils/formatBriefTimestamp.js +72 -0
  1678. package/src/utils/fpsTracker.js +34 -0
  1679. package/src/utils/frontmatterParser.js +260 -0
  1680. package/src/utils/fsOperations.js +555 -0
  1681. package/src/utils/fullscreen.js +194 -0
  1682. package/src/utils/generatedFiles.js +122 -0
  1683. package/src/utils/generators.js +67 -0
  1684. package/src/utils/genericProcessUtils.js +155 -0
  1685. package/src/utils/getWorktreePaths.js +56 -0
  1686. package/src/utils/getWorktreePathsPortable.js +23 -0
  1687. package/src/utils/ghPrStatus.js +71 -0
  1688. package/src/utils/git/gitConfigParser.js +226 -0
  1689. package/src/utils/git/gitFilesystem.js +606 -0
  1690. package/src/utils/git/gitignore.js +84 -0
  1691. package/src/utils/git.js +725 -0
  1692. package/src/utils/gitDiff.js +395 -0
  1693. package/src/utils/gitSettings.js +18 -0
  1694. package/src/utils/github/ghAuthStatus.js +23 -0
  1695. package/src/utils/githubRepoPathMapping.js +135 -0
  1696. package/src/utils/glob.js +90 -0
  1697. package/src/utils/gracefulShutdown.js +447 -0
  1698. package/src/utils/groupToolUses.js +126 -0
  1699. package/src/utils/handlePromptSubmit.js +398 -0
  1700. package/src/utils/hash.js +44 -0
  1701. package/src/utils/headlessProfiler.js +147 -0
  1702. package/src/utils/heapDumpService.js +201 -0
  1703. package/src/utils/heatmap.js +151 -0
  1704. package/src/utils/highlightMatch.js +29 -0
  1705. package/src/utils/hooks/AsyncHookRegistry.js +187 -0
  1706. package/src/utils/hooks/apiQueryHookHelper.js +77 -0
  1707. package/src/utils/hooks/execAgentHook.js +257 -0
  1708. package/src/utils/hooks/execHttpHook.js +184 -0
  1709. package/src/utils/hooks/execPromptHook.js +171 -0
  1710. package/src/utils/hooks/fileChangedWatcher.js +161 -0
  1711. package/src/utils/hooks/hookEvents.js +111 -0
  1712. package/src/utils/hooks/hookHelpers.js +60 -0
  1713. package/src/utils/hooks/hooksConfigManager.js +323 -0
  1714. package/src/utils/hooks/hooksConfigSnapshot.js +114 -0
  1715. package/src/utils/hooks/hooksSettings.js +204 -0
  1716. package/src/utils/hooks/postSamplingHooks.js +39 -0
  1717. package/src/utils/hooks/registerFrontmatterHooks.js +47 -0
  1718. package/src/utils/hooks/registerSkillHooks.js +40 -0
  1719. package/src/utils/hooks/sessionHooks.js +252 -0
  1720. package/src/utils/hooks/skillImprovement.js +211 -0
  1721. package/src/utils/hooks/ssrfGuard.js +258 -0
  1722. package/src/utils/hooks.js +3668 -0
  1723. package/src/utils/horizontalScroll.js +108 -0
  1724. package/src/utils/http.js +120 -0
  1725. package/src/utils/hyperlink.js +28 -0
  1726. package/src/utils/iTermBackup.js +48 -0
  1727. package/src/utils/ide.js +1195 -0
  1728. package/src/utils/idePathConversion.js +66 -0
  1729. package/src/utils/idleTimeout.js +44 -0
  1730. package/src/utils/imagePaste.js +343 -0
  1731. package/src/utils/imageResizer.js +664 -0
  1732. package/src/utils/imageStore.js +150 -0
  1733. package/src/utils/imageValidation.js +92 -0
  1734. package/src/utils/immediateCommand.js +12 -0
  1735. package/src/utils/inProcessTeammateHelpers.js +71 -0
  1736. package/src/utils/ink.js +20 -0
  1737. package/src/utils/intl.js +83 -0
  1738. package/src/utils/jetbrains.js +152 -0
  1739. package/src/utils/json.js +231 -0
  1740. package/src/utils/jsonRead.js +14 -0
  1741. package/src/utils/keyboardShortcuts.js +11 -0
  1742. package/src/utils/lazySchema.js +8 -0
  1743. package/src/utils/listSessionsImpl.js +332 -0
  1744. package/src/utils/localInstaller.js +130 -0
  1745. package/src/utils/lockfile.js +30 -0
  1746. package/src/utils/log.js +280 -0
  1747. package/src/utils/logoV2Utils.js +256 -0
  1748. package/src/utils/mailbox.js +50 -0
  1749. package/src/utils/managedEnv.js +160 -0
  1750. package/src/utils/managedEnvConstants.js +185 -0
  1751. package/src/utils/markdown.js +315 -0
  1752. package/src/utils/markdownConfigLoader.js +480 -0
  1753. package/src/utils/mcp/dateTimeParser.js +102 -0
  1754. package/src/utils/mcp/elicitationValidation.js +259 -0
  1755. package/src/utils/mcpInstructionsDelta.js +97 -0
  1756. package/src/utils/mcpOutputStorage.js +159 -0
  1757. package/src/utils/mcpValidation.js +165 -0
  1758. package/src/utils/mcpWebSocketTransport.js +180 -0
  1759. package/src/utils/memoize.js +205 -0
  1760. package/src/utils/memory/types.js +9 -0
  1761. package/src/utils/memory/versions.js +7 -0
  1762. package/src/utils/memoryFileDetection.js +245 -0
  1763. package/src/utils/messagePredicates.js +6 -0
  1764. package/src/utils/messageQueueManager.js +430 -0
  1765. package/src/utils/messages/mappers.js +240 -0
  1766. package/src/utils/messages/systemInit.js +72 -0
  1767. package/src/utils/messages.js +4286 -0
  1768. package/src/utils/model/agent.js +128 -0
  1769. package/src/utils/model/aliases.js +21 -0
  1770. package/src/utils/model/antModels.js +25 -0
  1771. package/src/utils/model/bedrock.js +220 -0
  1772. package/src/utils/model/check1mAccess.js +64 -0
  1773. package/src/utils/model/configs.js +86 -0
  1774. package/src/utils/model/contextWindowUpgradeCheck.js +41 -0
  1775. package/src/utils/model/deprecation.js +72 -0
  1776. package/src/utils/model/model.js +533 -0
  1777. package/src/utils/model/modelAllowlist.js +148 -0
  1778. package/src/utils/model/modelCapabilities.js +105 -0
  1779. package/src/utils/model/modelOptions.js +450 -0
  1780. package/src/utils/model/modelStrings.js +144 -0
  1781. package/src/utils/model/modelSupportOverrides.js +40 -0
  1782. package/src/utils/model/providers.js +35 -0
  1783. package/src/utils/model/validateModel.js +131 -0
  1784. package/src/utils/modelCost.js +160 -0
  1785. package/src/utils/modifiers.js +39 -0
  1786. package/src/utils/mtls.js +132 -0
  1787. package/src/utils/nativeInstaller/download.js +370 -0
  1788. package/src/utils/nativeInstaller/index.js +8 -0
  1789. package/src/utils/nativeInstaller/installer.js +1395 -0
  1790. package/src/utils/nativeInstaller/packageManagers.js +258 -0
  1791. package/src/utils/nativeInstaller/pidLock.js +347 -0
  1792. package/src/utils/notebook.js +176 -0
  1793. package/src/utils/objectGroupBy.js +15 -0
  1794. package/src/utils/pasteStore.js +93 -0
  1795. package/src/utils/path.js +140 -0
  1796. package/src/utils/pdf.js +236 -0
  1797. package/src/utils/pdfUtils.js +61 -0
  1798. package/src/utils/peerAddress.js +20 -0
  1799. package/src/utils/permissions/PermissionMode.js +95 -0
  1800. package/src/utils/permissions/PermissionPromptToolResultSchema.js +85 -0
  1801. package/src/utils/permissions/PermissionResult.js +11 -0
  1802. package/src/utils/permissions/PermissionRule.js +19 -0
  1803. package/src/utils/permissions/PermissionUpdate.js +268 -0
  1804. package/src/utils/permissions/PermissionUpdateSchema.js +61 -0
  1805. package/src/utils/permissions/autoModeState.js +31 -0
  1806. package/src/utils/permissions/bashClassifier.js +30 -0
  1807. package/src/utils/permissions/bypassPermissionsKillswitch.js +115 -0
  1808. package/src/utils/permissions/classifierDecision.js +86 -0
  1809. package/src/utils/permissions/classifierShared.js +28 -0
  1810. package/src/utils/permissions/dangerousPatterns.js +78 -0
  1811. package/src/utils/permissions/denialTracking.js +34 -0
  1812. package/src/utils/permissions/filesystem.js +1411 -0
  1813. package/src/utils/permissions/getNextPermissionMode.js +74 -0
  1814. package/src/utils/permissions/pathValidation.js +351 -0
  1815. package/src/utils/permissions/permissionExplainer.js +188 -0
  1816. package/src/utils/permissions/permissionRuleParser.js +175 -0
  1817. package/src/utils/permissions/permissionSetup.js +1162 -0
  1818. package/src/utils/permissions/permissions.js +1063 -0
  1819. package/src/utils/permissions/permissionsLoader.js +217 -0
  1820. package/src/utils/permissions/shadowedRuleDetection.js +149 -0
  1821. package/src/utils/permissions/shellRuleMatching.js +174 -0
  1822. package/src/utils/permissions/yoloClassifier.js +1193 -0
  1823. package/src/utils/planModeV2.js +75 -0
  1824. package/src/utils/plans.js +334 -0
  1825. package/src/utils/platform.js +122 -0
  1826. package/src/utils/plugins/addDirPluginSettings.js +53 -0
  1827. package/src/utils/plugins/cacheUtils.js +174 -0
  1828. package/src/utils/plugins/dependencyResolver.js +244 -0
  1829. package/src/utils/plugins/fetchTelemetry.js +108 -0
  1830. package/src/utils/plugins/gitAvailability.js +65 -0
  1831. package/src/utils/plugins/headlessPluginInstall.js +136 -0
  1832. package/src/utils/plugins/hintRecommendation.js +136 -0
  1833. package/src/utils/plugins/installCounts.js +221 -0
  1834. package/src/utils/plugins/installedPluginsManager.js +1003 -0
  1835. package/src/utils/plugins/loadPluginAgents.js +219 -0
  1836. package/src/utils/plugins/loadPluginCommands.js +595 -0
  1837. package/src/utils/plugins/loadPluginHooks.js +239 -0
  1838. package/src/utils/plugins/loadPluginOutputStyles.js +112 -0
  1839. package/src/utils/plugins/lspPluginIntegration.js +293 -0
  1840. package/src/utils/plugins/lspRecommendation.js +278 -0
  1841. package/src/utils/plugins/managedPlugins.js +26 -0
  1842. package/src/utils/plugins/marketplaceHelpers.js +470 -0
  1843. package/src/utils/plugins/marketplaceManager.js +1939 -0
  1844. package/src/utils/plugins/mcpPluginIntegration.js +465 -0
  1845. package/src/utils/plugins/mcpbHandler.js +708 -0
  1846. package/src/utils/plugins/officialMarketplace.js +19 -0
  1847. package/src/utils/plugins/officialMarketplaceGcs.js +202 -0
  1848. package/src/utils/plugins/officialMarketplaceStartupCheck.js +344 -0
  1849. package/src/utils/plugins/orphanedPluginFilter.js +96 -0
  1850. package/src/utils/plugins/parseMarketplaceInput.js +143 -0
  1851. package/src/utils/plugins/performStartupChecks.js +66 -0
  1852. package/src/utils/plugins/pluginAutoupdate.js +210 -0
  1853. package/src/utils/plugins/pluginBlocklist.js +93 -0
  1854. package/src/utils/plugins/pluginDirectories.js +170 -0
  1855. package/src/utils/plugins/pluginFlagging.js +173 -0
  1856. package/src/utils/plugins/pluginIdentifier.js +78 -0
  1857. package/src/utils/plugins/pluginInstallationHelpers.js +400 -0
  1858. package/src/utils/plugins/pluginLoader.js +2426 -0
  1859. package/src/utils/plugins/pluginOptionsStorage.js +311 -0
  1860. package/src/utils/plugins/pluginPolicy.js +18 -0
  1861. package/src/utils/plugins/pluginStartupCheck.js +261 -0
  1862. package/src/utils/plugins/pluginVersioning.js +128 -0
  1863. package/src/utils/plugins/reconciler.js +181 -0
  1864. package/src/utils/plugins/refresh.js +162 -0
  1865. package/src/utils/plugins/schemas.js +1283 -0
  1866. package/src/utils/plugins/validatePlugin.js +765 -0
  1867. package/src/utils/plugins/walkPluginMarkdown.js +49 -0
  1868. package/src/utils/plugins/zipCache.js +346 -0
  1869. package/src/utils/plugins/zipCacheAdapters.js +133 -0
  1870. package/src/utils/powershell/dangerousCmdlets.js +174 -0
  1871. package/src/utils/powershell/parser.js +1357 -0
  1872. package/src/utils/powershell/staticPrefix.js +277 -0
  1873. package/src/utils/preflightChecks.js +147 -0
  1874. package/src/utils/privacyLevel.js +49 -0
  1875. package/src/utils/process.js +56 -0
  1876. package/src/utils/processUserInput/processBashCommand.js +118 -0
  1877. package/src/utils/processUserInput/processSlashCommand.js +845 -0
  1878. package/src/utils/processUserInput/processTextPrompt.js +68 -0
  1879. package/src/utils/processUserInput/processUserInput.js +344 -0
  1880. package/src/utils/profilerBase.js +32 -0
  1881. package/src/utils/promptCategory.js +39 -0
  1882. package/src/utils/promptEditor.js +151 -0
  1883. package/src/utils/promptShellExecution.js +117 -0
  1884. package/src/utils/protectedNamespace.js +4 -0
  1885. package/src/utils/proxy.js +345 -0
  1886. package/src/utils/queryContext.js +110 -0
  1887. package/src/utils/queryHelpers.js +436 -0
  1888. package/src/utils/queryProfiler.js +242 -0
  1889. package/src/utils/queueProcessor.js +70 -0
  1890. package/src/utils/readEditContext.js +176 -0
  1891. package/src/utils/readFileInRange.js +278 -0
  1892. package/src/utils/releaseNotes.js +307 -0
  1893. package/src/utils/renderOptions.js +67 -0
  1894. package/src/utils/ripgrep.js +521 -0
  1895. package/src/utils/sandbox/sandbox-adapter.js +750 -0
  1896. package/src/utils/sandbox/sandbox-ui-utils.js +11 -0
  1897. package/src/utils/sanitization.js +72 -0
  1898. package/src/utils/screenshotClipboard.js +89 -0
  1899. package/src/utils/sdkEventQueue.js +49 -0
  1900. package/src/utils/secureStorage/fallbackStorage.js +59 -0
  1901. package/src/utils/secureStorage/index.js +13 -0
  1902. package/src/utils/secureStorage/keychainPrefetch.js +91 -0
  1903. package/src/utils/secureStorage/macOsKeychainHelpers.js +91 -0
  1904. package/src/utils/secureStorage/macOsKeychainStorage.js +192 -0
  1905. package/src/utils/secureStorage/plainTextStorage.js +81 -0
  1906. package/src/utils/semanticBoolean.js +23 -0
  1907. package/src/utils/semanticNumber.js +34 -0
  1908. package/src/utils/semver.js +51 -0
  1909. package/src/utils/sequential.js +43 -0
  1910. package/src/utils/sessionActivity.js +120 -0
  1911. package/src/utils/sessionEnvVars.js +18 -0
  1912. package/src/utils/sessionEnvironment.js +131 -0
  1913. package/src/utils/sessionFileAccessHooks.js +205 -0
  1914. package/src/utils/sessionIngressAuth.js +113 -0
  1915. package/src/utils/sessionRestore.js +357 -0
  1916. package/src/utils/sessionStart.js +165 -0
  1917. package/src/utils/sessionState.js +76 -0
  1918. package/src/utils/sessionStorage.js +4162 -0
  1919. package/src/utils/sessionStoragePortable.js +665 -0
  1920. package/src/utils/sessionTitle.js +120 -0
  1921. package/src/utils/sessionUrl.js +50 -0
  1922. package/src/utils/set.js +50 -0
  1923. package/src/utils/settings/allErrors.js +29 -0
  1924. package/src/utils/settings/applySettingsChange.js +65 -0
  1925. package/src/utils/settings/changeDetector.js +409 -0
  1926. package/src/utils/settings/constants.js +166 -0
  1927. package/src/utils/settings/internalWrites.js +33 -0
  1928. package/src/utils/settings/managedPath.js +29 -0
  1929. package/src/utils/settings/mdm/constants.js +62 -0
  1930. package/src/utils/settings/mdm/rawRead.js +97 -0
  1931. package/src/utils/settings/mdm/settings.js +254 -0
  1932. package/src/utils/settings/permissionValidation.js +224 -0
  1933. package/src/utils/settings/pluginOnlyPolicy.js +53 -0
  1934. package/src/utils/settings/schemaOutput.js +7 -0
  1935. package/src/utils/settings/settings.js +791 -0
  1936. package/src/utils/settings/settingsCache.js +47 -0
  1937. package/src/utils/settings/toolValidationConfig.js +76 -0
  1938. package/src/utils/settings/types.js +846 -0
  1939. package/src/utils/settings/validateEditTool.js +34 -0
  1940. package/src/utils/settings/validation.js +192 -0
  1941. package/src/utils/settings/validationTips.js +111 -0
  1942. package/src/utils/shell/bashProvider.js +202 -0
  1943. package/src/utils/shell/outputLimits.js +7 -0
  1944. package/src/utils/shell/powershellDetection.js +96 -0
  1945. package/src/utils/shell/powershellProvider.js +104 -0
  1946. package/src/utils/shell/prefix.js +246 -0
  1947. package/src/utils/shell/readOnlyCommandValidation.js +1776 -0
  1948. package/src/utils/shell/resolveDefaultShell.js +13 -0
  1949. package/src/utils/shell/shellProvider.js +2 -0
  1950. package/src/utils/shell/shellToolUtils.js +21 -0
  1951. package/src/utils/shell/specPrefix.js +198 -0
  1952. package/src/utils/shellConfig.js +136 -0
  1953. package/src/utils/sideQuery.js +134 -0
  1954. package/src/utils/sideQuestion.js +121 -0
  1955. package/src/utils/signal.js +34 -0
  1956. package/src/utils/sinks.js +15 -0
  1957. package/src/utils/skills/skillChangeDetector.js +264 -0
  1958. package/src/utils/slashCommandParsing.js +46 -0
  1959. package/src/utils/sleep.js +72 -0
  1960. package/src/utils/sliceAnsi.js +74 -0
  1961. package/src/utils/slowOperations.js +216 -0
  1962. package/src/utils/standaloneAgent.js +20 -0
  1963. package/src/utils/startupProfiler.js +149 -0
  1964. package/src/utils/staticRender.js +104 -0
  1965. package/src/utils/stats.js +802 -0
  1966. package/src/utils/statsCache.js +330 -0
  1967. package/src/utils/status.js +359 -0
  1968. package/src/utils/statusNoticeDefinitions.js +123 -0
  1969. package/src/utils/statusNoticeHelpers.js +15 -0
  1970. package/src/utils/stream.js +73 -0
  1971. package/src/utils/streamJsonStdoutGuard.js +107 -0
  1972. package/src/utils/streamlinedTransform.js +162 -0
  1973. package/src/utils/stringUtils.js +202 -0
  1974. package/src/utils/subprocessEnv.js +87 -0
  1975. package/src/utils/suggestions/commandSuggestions.js +458 -0
  1976. package/src/utils/suggestions/directoryCompletion.js +191 -0
  1977. package/src/utils/suggestions/shellHistoryCompletion.js +95 -0
  1978. package/src/utils/suggestions/skillUsageTracking.js +50 -0
  1979. package/src/utils/suggestions/slackChannelSuggestions.js +169 -0
  1980. package/src/utils/swarm/It2SetupPrompt.js +386 -0
  1981. package/src/utils/swarm/backends/ITermBackend.js +276 -0
  1982. package/src/utils/swarm/backends/InProcessBackend.js +237 -0
  1983. package/src/utils/swarm/backends/PaneBackendExecutor.js +250 -0
  1984. package/src/utils/swarm/backends/TmuxBackend.js +574 -0
  1985. package/src/utils/swarm/backends/detection.js +112 -0
  1986. package/src/utils/swarm/backends/it2Setup.js +185 -0
  1987. package/src/utils/swarm/backends/registry.js +369 -0
  1988. package/src/utils/swarm/backends/teammateModeSnapshot.js +68 -0
  1989. package/src/utils/swarm/backends/types.js +9 -0
  1990. package/src/utils/swarm/constants.js +29 -0
  1991. package/src/utils/swarm/inProcessRunner.js +1021 -0
  1992. package/src/utils/swarm/leaderPermissionBridge.js +31 -0
  1993. package/src/utils/swarm/permissionSync.js +667 -0
  1994. package/src/utils/swarm/reconnection.js +82 -0
  1995. package/src/utils/swarm/spawnInProcess.js +218 -0
  1996. package/src/utils/swarm/spawnUtils.js +123 -0
  1997. package/src/utils/swarm/teamHelpers.js +484 -0
  1998. package/src/utils/swarm/teammateInit.js +87 -0
  1999. package/src/utils/swarm/teammateLayoutManager.js +82 -0
  2000. package/src/utils/swarm/teammateModel.js +9 -0
  2001. package/src/utils/swarm/teammatePromptAddendum.js +17 -0
  2002. package/src/utils/systemDirectories.js +51 -0
  2003. package/src/utils/systemPrompt.js +88 -0
  2004. package/src/utils/systemPromptType.js +9 -0
  2005. package/src/utils/systemTheme.js +108 -0
  2006. package/src/utils/taggedId.js +49 -0
  2007. package/src/utils/task/TaskOutput.js +320 -0
  2008. package/src/utils/task/diskOutput.js +387 -0
  2009. package/src/utils/task/framework.js +236 -0
  2010. package/src/utils/task/outputFormatting.js +24 -0
  2011. package/src/utils/task/sdkProgress.js +24 -0
  2012. package/src/utils/taskSummary.js +3 -0
  2013. package/src/utils/tasks.js +672 -0
  2014. package/src/utils/teamDiscovery.js +48 -0
  2015. package/src/utils/teamMemoryOps.js +67 -0
  2016. package/src/utils/teammate.js +237 -0
  2017. package/src/utils/teammateContext.js +56 -0
  2018. package/src/utils/teammateMailbox.js +793 -0
  2019. package/src/utils/telemetry/betaSessionTracing.js +25 -0
  2020. package/src/utils/telemetry/bigqueryExporter.js +17 -0
  2021. package/src/utils/telemetry/events.js +7 -0
  2022. package/src/utils/telemetry/instrumentation.js +16 -0
  2023. package/src/utils/telemetry/logger.js +25 -0
  2024. package/src/utils/telemetry/perfettoTracing.js +882 -0
  2025. package/src/utils/telemetry/pluginTelemetry.js +76 -0
  2026. package/src/utils/telemetry/sessionTracing.js +62 -0
  2027. package/src/utils/telemetry/skillLoadedEvent.js +4 -0
  2028. package/src/utils/telemetryAttributes.js +56 -0
  2029. package/src/utils/teleport/api.js +299 -0
  2030. package/src/utils/teleport/environmentSelection.js +55 -0
  2031. package/src/utils/teleport/environments.js +84 -0
  2032. package/src/utils/teleport/gitBundle.js +192 -0
  2033. package/src/utils/teleport.js +1047 -0
  2034. package/src/utils/tempfile.js +26 -0
  2035. package/src/utils/terminal.js +105 -0
  2036. package/src/utils/terminalPanel.js +155 -0
  2037. package/src/utils/textHighlighting.js +113 -0
  2038. package/src/utils/thaddeusHints.js +142 -0
  2039. package/src/utils/theme.js +525 -0
  2040. package/src/utils/thinking.js +130 -0
  2041. package/src/utils/timeouts.js +35 -0
  2042. package/src/utils/tmuxSocket.js +373 -0
  2043. package/src/utils/todo/types.js +9 -0
  2044. package/src/utils/tokenBudget.js +62 -0
  2045. package/src/utils/tokens.js +223 -0
  2046. package/src/utils/toolErrors.js +101 -0
  2047. package/src/utils/toolPool.js +61 -0
  2048. package/src/utils/toolResultStorage.js +768 -0
  2049. package/src/utils/toolSchemaCache.js +7 -0
  2050. package/src/utils/toolSearch.js +551 -0
  2051. package/src/utils/transcriptSearch.js +200 -0
  2052. package/src/utils/treeify.js +111 -0
  2053. package/src/utils/truncate.js +164 -0
  2054. package/src/utils/udsClient.js +5 -0
  2055. package/src/utils/udsMessaging.js +23 -0
  2056. package/src/utils/ultraplan/ccrSession.js +264 -0
  2057. package/src/utils/ultraplan/keyword.js +122 -0
  2058. package/src/utils/unaryLogging.js +16 -0
  2059. package/src/utils/undercover.js +89 -0
  2060. package/src/utils/user.js +137 -0
  2061. package/src/utils/userAgent.js +9 -0
  2062. package/src/utils/userPromptKeywords.js +21 -0
  2063. package/src/utils/uuid.js +22 -0
  2064. package/src/utils/warningHandler.js +97 -0
  2065. package/src/utils/which.js +75 -0
  2066. package/src/utils/windowsPaths.js +146 -0
  2067. package/src/utils/withResolvers.js +13 -0
  2068. package/src/utils/words.js +793 -0
  2069. package/src/utils/workforceIntent.js +192 -0
  2070. package/src/utils/workloadContext.js +42 -0
  2071. package/src/utils/worktree.js +1142 -0
  2072. package/src/utils/worktreeModeEnabled.js +11 -0
  2073. package/src/utils/xdg.js +52 -0
  2074. package/src/utils/xml.js +15 -0
  2075. package/src/utils/yaml.js +14 -0
  2076. package/src/utils/zodToJsonSchema.js +19 -0
  2077. package/src/vim/motions.js +73 -0
  2078. package/src/vim/operators.js +401 -0
  2079. package/src/vim/textObjects.js +153 -0
  2080. package/src/vim/transitions.js +340 -0
  2081. package/src/vim/types.js +93 -0
  2082. package/src/voice/voiceModeEnabled.js +20 -0
  2083. package/thaddeus-terminal.ts +401 -3
  2084. package/thaddeus.command +1 -1
@@ -0,0 +1,4500 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { c as _c } from "react/compiler-runtime";
3
+ // biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
4
+ import { feature } from 'bun:bundle';
5
+ import { spawnSync } from 'child_process';
6
+ import { snapshotOutputTokensForTurn, getCurrentTurnTokenBudget, getTurnOutputTokens, getBudgetContinuationCount, getTotalInputTokens } from '../bootstrap/state.js';
7
+ import { parseTokenBudget } from '../utils/tokenBudget.js';
8
+ import { count } from '../utils/array.js';
9
+ import { dirname, join } from 'path';
10
+ import { tmpdir } from 'os';
11
+ import figures from 'figures';
12
+ // eslint-disable-next-line custom-rules/prefer-use-keybindings -- / n N Esc [ v are bare letters in transcript modal context, same class as g/G/j/k in ScrollKeybindingHandler
13
+ import { useInput } from '../ink.js';
14
+ import { useSearchInput } from '../hooks/useSearchInput.js';
15
+ import { useTerminalSize } from '../hooks/useTerminalSize.js';
16
+ import { useSearchHighlight } from '../ink/hooks/use-search-highlight.js';
17
+ import { renderMessagesToPlainText } from '../utils/exportRenderer.js';
18
+ import { openFileInExternalEditor } from '../utils/editor.js';
19
+ import { writeFile } from 'fs/promises';
20
+ import { Box, Text, useStdin, useTheme, useTerminalFocus, useTerminalTitle, useTabStatus } from '../ink.js';
21
+ import { CostThresholdDialog } from '../components/CostThresholdDialog.js';
22
+ import { IdleReturnDialog } from '../components/IdleReturnDialog.js';
23
+ import * as React from 'react';
24
+ import { useEffect, useMemo, useRef, useState, useCallback, useDeferredValue, useLayoutEffect } from 'react';
25
+ import { useNotifications } from '../context/notifications.js';
26
+ import { sendNotification } from '../services/notifier.js';
27
+ import { startPreventSleep, stopPreventSleep } from '../services/preventSleep.js';
28
+ import { useTerminalNotification } from '../ink/useTerminalNotification.js';
29
+ import { hasCursorUpViewportYankBug } from '../ink/terminal.js';
30
+ import { createFileStateCacheWithSizeLimit, mergeFileStateCaches, READ_FILE_STATE_CACHE_SIZE } from '../utils/fileStateCache.js';
31
+ import { updateLastInteractionTime, getLastInteractionTime, getOriginalCwd, getProjectRoot, getSessionId, switchSession, setCostStateForRestore, getTurnHookDurationMs, getTurnHookCount, resetTurnHookDuration, getTurnToolDurationMs, getTurnToolCount, resetTurnToolDuration, getTurnClassifierDurationMs, getTurnClassifierCount, resetTurnClassifierDuration } from '../bootstrap/state.js';
32
+ import { asSessionId, asAgentId } from '../types/ids.js';
33
+ import { logForDebugging } from '../utils/debug.js';
34
+ import { QueryGuard } from '../utils/QueryGuard.js';
35
+ import { isEnvTruthy } from '../utils/envUtils.js';
36
+ import { formatTokens, truncateToWidth } from '../utils/format.js';
37
+ import { consumeEarlyInput } from '../utils/earlyInput.js';
38
+ import { setMemberActive } from '../utils/swarm/teamHelpers.js';
39
+ import { isSwarmWorker, generateSandboxRequestId, sendSandboxPermissionRequestViaMailbox, sendSandboxPermissionResponseViaMailbox } from '../utils/swarm/permissionSync.js';
40
+ import { registerSandboxPermissionCallback } from '../hooks/useSwarmPermissionPoller.js';
41
+ import { getTeamName, getAgentName } from '../utils/teammate.js';
42
+ import { WorkerPendingPermission } from '../components/permissions/WorkerPendingPermission.js';
43
+ import { injectUserMessageToTeammate, getAllInProcessTeammateTasks } from '../tasks/InProcessTeammateTask/InProcessTeammateTask.js';
44
+ import { isLocalAgentTask, queuePendingMessage, appendMessageToLocalAgent } from '../tasks/LocalAgentTask/LocalAgentTask.js';
45
+ import { registerLeaderToolUseConfirmQueue, unregisterLeaderToolUseConfirmQueue, registerLeaderSetToolPermissionContext, unregisterLeaderSetToolPermissionContext } from '../utils/swarm/leaderPermissionBridge.js';
46
+ import { endInteractionSpan } from '../utils/telemetry/sessionTracing.js';
47
+ import { useLogMessages } from '../hooks/useLogMessages.js';
48
+ import { useReplBridge } from '../hooks/useReplBridge.js';
49
+ import { getCommandName, isCommandEnabled } from '../commands.js';
50
+ import { MessageSelector, selectableUserMessagesFilter, messagesAfterAreOnlySynthetic } from '../components/MessageSelector.js';
51
+ import { useIdeLogging } from '../hooks/useIdeLogging.js';
52
+ import { PermissionRequest } from '../components/permissions/PermissionRequest.js';
53
+ import { ElicitationDialog } from '../components/mcp/ElicitationDialog.js';
54
+ import { PromptDialog } from '../components/hooks/PromptDialog.js';
55
+ import PromptInput from '../components/PromptInput/PromptInput.js';
56
+ import { PromptInputQueuedCommands } from '../components/PromptInput/PromptInputQueuedCommands.js';
57
+ import { useRemoteSession } from '../hooks/useRemoteSession.js';
58
+ import { useDirectConnect } from '../hooks/useDirectConnect.js';
59
+ import { useSSHSession } from '../hooks/useSSHSession.js';
60
+ import { useAssistantHistory } from '../hooks/useAssistantHistory.js';
61
+ import { SkillImprovementSurvey } from '../components/SkillImprovementSurvey.js';
62
+ import { useSkillImprovementSurvey } from '../hooks/useSkillImprovementSurvey.js';
63
+ import { useMoreRight } from '../moreright/useMoreRight.js';
64
+ import { SpinnerWithVerb, BriefIdleStatus } from '../components/Spinner.js';
65
+ import { getSystemPrompt } from '../constants/prompts.js';
66
+ import { buildEffectiveSystemPrompt } from '../utils/systemPrompt.js';
67
+ import { getSystemContext, getUserContext } from '../context.js';
68
+ import { getMemoryFiles } from '../utils/claudemd.js';
69
+ import { startBackgroundHousekeeping } from '../utils/backgroundHousekeeping.js';
70
+ import { getTotalCost, saveCurrentSessionCosts, resetCostState, getStoredSessionCosts } from '../cost-tracker.js';
71
+ import { useCostSummary } from '../costHook.js';
72
+ import { useFpsMetrics } from '../context/fpsMetrics.js';
73
+ import { useAfterFirstRender } from '../hooks/useAfterFirstRender.js';
74
+ import { useDeferredHookMessages } from '../hooks/useDeferredHookMessages.js';
75
+ import { addToHistory, removeLastFromHistory, expandPastedTextRefs, parseReferences } from '../history.js';
76
+ import { prependModeCharacterToInput } from '../components/PromptInput/inputModes.js';
77
+ import { prependToShellHistoryCache } from '../utils/suggestions/shellHistoryCompletion.js';
78
+ import { useApiKeyVerification } from '../hooks/useApiKeyVerification.js';
79
+ import { GlobalKeybindingHandlers } from '../hooks/useGlobalKeybindings.js';
80
+ import { CommandKeybindingHandlers } from '../hooks/useCommandKeybindings.js';
81
+ import { KeybindingSetup } from '../keybindings/KeybindingProviderSetup.js';
82
+ import { useShortcutDisplay } from '../keybindings/useShortcutDisplay.js';
83
+ import { getShortcutDisplay } from '../keybindings/shortcutFormat.js';
84
+ import { CancelRequestHandler } from '../hooks/useCancelRequest.js';
85
+ import { useBackgroundTaskNavigation } from '../hooks/useBackgroundTaskNavigation.js';
86
+ import { useSwarmInitialization } from '../hooks/useSwarmInitialization.js';
87
+ import { useTeammateViewAutoExit } from '../hooks/useTeammateViewAutoExit.js';
88
+ import { errorMessage } from '../utils/errors.js';
89
+ import { isHumanTurn } from '../utils/messagePredicates.js';
90
+ import { logError } from '../utils/log.js';
91
+ // Dead code elimination: conditional imports
92
+ /* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
93
+ const useVoiceIntegration = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').useVoiceIntegration : () => ({
94
+ stripTrailing: () => 0,
95
+ handleKeyEvent: () => { },
96
+ resetAnchor: () => { }
97
+ });
98
+ const VoiceKeybindingHandler = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').VoiceKeybindingHandler : () => null;
99
+ // Frustration detection is ant-only (dogfooding). Conditional require so external
100
+ // builds eliminate the module entirely (including its two O(n) useMemos that run
101
+ // on every messages change, plus the GrowthBook fetch).
102
+ const useFrustrationDetection = "external" === 'ant' ? require('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection : () => ({
103
+ state: 'closed',
104
+ handleTranscriptSelect: () => { }
105
+ });
106
+ // Ant-only org warning. Conditional require so the org UUID list is
107
+ // eliminated from external builds (one UUID is on excluded-strings).
108
+ const useAntOrgWarningNotification = "external" === 'ant' ? require('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification : () => { };
109
+ // Dead code elimination: conditional import for coordinator mode
110
+ const getCoordinatorUserContext = feature('COORDINATOR_MODE') ? require('../coordinator/coordinatorMode.js').getCoordinatorUserContext : () => ({});
111
+ /* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
112
+ import useCanUseTool from '../hooks/useCanUseTool.js';
113
+ import { applyPermissionUpdate, applyPermissionUpdates, persistPermissionUpdate } from '../utils/permissions/PermissionUpdate.js';
114
+ import { buildPermissionUpdates } from '../components/permissions/ExitPlanModePermissionRequest/ExitPlanModePermissionRequest.js';
115
+ import { stripDangerousPermissionsForAutoMode } from '../utils/permissions/permissionSetup.js';
116
+ import { getScratchpadDir, isScratchpadEnabled } from '../utils/permissions/filesystem.js';
117
+ import { WEB_FETCH_TOOL_NAME } from '../tools/WebFetchTool/prompt.js';
118
+ import { SLEEP_TOOL_NAME } from '../tools/SleepTool/prompt.js';
119
+ import { clearSpeculativeChecks } from '../tools/BashTool/bashPermissions.js';
120
+ import { getGlobalConfig, saveGlobalConfig, getGlobalConfigWriteCount } from '../utils/config.js';
121
+ import { hasConsoleBillingAccess } from '../utils/billing.js';
122
+ import { logEvent } from 'src/services/analytics/index.js';
123
+ import { getFeatureValue_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js';
124
+ import { textForResubmit, handleMessageFromStream, isCompactBoundaryMessage, getMessagesAfterCompactBoundary, getContentText, createUserMessage, createAssistantMessage, createTurnDurationMessage, createAgentsKilledMessage, createApiMetricsMessage, createSystemMessage, createCommandInputMessage, formatCommandInputTags } from '../utils/messages.js';
125
+ import { generateSessionTitle } from '../utils/sessionTitle.js';
126
+ import { BASH_INPUT_TAG, COMMAND_MESSAGE_TAG, COMMAND_NAME_TAG, LOCAL_COMMAND_STDOUT_TAG } from '../constants/xml.js';
127
+ import { escapeXml } from '../utils/xml.js';
128
+ import { gracefulShutdownSync } from '../utils/gracefulShutdown.js';
129
+ import { handlePromptSubmit } from '../utils/handlePromptSubmit.js';
130
+ import { useQueueProcessor } from '../hooks/useQueueProcessor.js';
131
+ import { useMailboxBridge } from '../hooks/useMailboxBridge.js';
132
+ import { queryCheckpoint, logQueryProfileReport } from '../utils/queryProfiler.js';
133
+ import { query } from '../query.js';
134
+ import { mergeClients, useMergedClients } from '../hooks/useMergedClients.js';
135
+ import { getQuerySourceForREPL } from '../utils/promptCategory.js';
136
+ import { useMergedTools } from '../hooks/useMergedTools.js';
137
+ import { mergeAndFilterTools } from '../utils/toolPool.js';
138
+ import { useMergedCommands } from '../hooks/useMergedCommands.js';
139
+ import { useSkillsChange } from '../hooks/useSkillsChange.js';
140
+ import { useManagePlugins } from '../hooks/useManagePlugins.js';
141
+ import { Messages } from '../components/Messages.js';
142
+ import { TaskListV2 } from '../components/TaskListV2.js';
143
+ import { TeammateViewHeader } from '../components/TeammateViewHeader.js';
144
+ import { useTasksV2WithCollapseEffect } from '../hooks/useTasksV2.js';
145
+ import { maybeMarkProjectOnboardingComplete } from '../projectOnboardingState.js';
146
+ import { randomUUID } from 'crypto';
147
+ import { processSessionStartHooks } from '../utils/sessionStart.js';
148
+ import { executeSessionEndHooks, getSessionEndHookTimeoutMs } from '../utils/hooks.js';
149
+ import { useIdeSelection } from '../hooks/useIdeSelection.js';
150
+ import { getTools, assembleToolPool } from '../tools.js';
151
+ import { resolveAgentTools } from '../tools/AgentTool/agentToolUtils.js';
152
+ import { resumeAgentBackground } from '../tools/AgentTool/resumeAgent.js';
153
+ import { useMainLoopModel } from '../hooks/useMainLoopModel.js';
154
+ import { useAppState, useSetAppState, useAppStateStore } from '../state/AppState.js';
155
+ import { copyPlanForFork, copyPlanForResume, getPlanSlug, setPlanSlug } from '../utils/plans.js';
156
+ import { clearSessionMetadata, resetSessionFilePointer, adoptResumedSessionFile, removeTranscriptMessage, restoreSessionMetadata, getCurrentSessionTitle, isEphemeralToolProgress, isLoggableMessage, saveWorktreeState, getAgentTranscript } from '../utils/sessionStorage.js';
157
+ import { deserializeMessages } from '../utils/conversationRecovery.js';
158
+ import { extractReadFilesFromMessages, extractBashToolsFromMessages } from '../utils/queryHelpers.js';
159
+ import { resetMicrocompactState } from '../services/compact/microCompact.js';
160
+ import { runPostCompactCleanup } from '../services/compact/postCompactCleanup.js';
161
+ import { provisionContentReplacementState, reconstructContentReplacementState } from '../utils/toolResultStorage.js';
162
+ import { partialCompactConversation } from '../services/compact/compact.js';
163
+ import { fileHistoryMakeSnapshot, fileHistoryRewind, copyFileHistoryForResume, fileHistoryEnabled, fileHistoryHasAnyChanges } from '../utils/fileHistory.js';
164
+ import { incrementPromptCount } from '../utils/commitAttribution.js';
165
+ import { recordAttributionSnapshot } from '../utils/sessionStorage.js';
166
+ import { computeStandaloneAgentContext, restoreAgentFromSession, restoreSessionStateFromLog, restoreWorktreeForResume, exitRestoredWorktree } from '../utils/sessionRestore.js';
167
+ import { isBgSession, updateSessionName, updateSessionActivity } from '../utils/concurrentSessions.js';
168
+ import { isInProcessTeammateTask } from '../tasks/InProcessTeammateTask/types.js';
169
+ import { restoreRemoteAgentTasks } from '../tasks/RemoteAgentTask/RemoteAgentTask.js';
170
+ import { useInboxPoller } from '../hooks/useInboxPoller.js';
171
+ // Dead code elimination: conditional import for loop mode
172
+ /* eslint-disable @typescript-eslint/no-require-imports */
173
+ const proactiveModule = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/index.js') : null;
174
+ const PROACTIVE_NO_OP_SUBSCRIBE = (_cb) => () => { };
175
+ const PROACTIVE_FALSE = () => false;
176
+ const SUGGEST_BG_PR_NOOP = (_p, _n) => false;
177
+ const useProactive = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/useProactive.js').useProactive : null;
178
+ const useScheduledTasks = feature('AGENT_TRIGGERS') ? require('../hooks/useScheduledTasks.js').useScheduledTasks : null;
179
+ /* eslint-enable @typescript-eslint/no-require-imports */
180
+ import { isAgentSwarmsEnabled } from '../utils/agentSwarmsEnabled.js';
181
+ import { useTaskListWatcher } from '../hooks/useTaskListWatcher.js';
182
+ import { closeOpenDiffs, getConnectedIdeClient } from '../utils/ide.js';
183
+ import { useIDEIntegration } from '../hooks/useIDEIntegration.js';
184
+ import exit from '../commands/exit/index.js';
185
+ import { ExitFlow } from '../components/ExitFlow.js';
186
+ import { getCurrentWorktreeSession } from '../utils/worktree.js';
187
+ import { popAllEditable, enqueue, getCommandQueue, getCommandQueueLength, removeByFilter } from '../utils/messageQueueManager.js';
188
+ import { useCommandQueue } from '../hooks/useCommandQueue.js';
189
+ import { SessionBackgroundHint } from '../components/SessionBackgroundHint.js';
190
+ import { startBackgroundSession } from '../tasks/LocalMainSessionTask.js';
191
+ import { useSessionBackgrounding } from '../hooks/useSessionBackgrounding.js';
192
+ import { diagnosticTracker } from '../services/diagnosticTracking.js';
193
+ import { handleSpeculationAccept } from '../services/PromptSuggestion/speculation.js';
194
+ import { IdeOnboardingDialog } from '../components/IdeOnboardingDialog.js';
195
+ import { EffortCallout, shouldShowEffortCallout } from '../components/EffortCallout.js';
196
+ import { RemoteCallout } from '../components/RemoteCallout.js';
197
+ /* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
198
+ const AntModelSwitchCallout = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').AntModelSwitchCallout : null;
199
+ const shouldShowAntModelSwitch = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').shouldShowModelSwitchCallout : () => false;
200
+ const UndercoverAutoCallout = "external" === 'ant' ? require('../components/UndercoverAutoCallout.js').UndercoverAutoCallout : null;
201
+ /* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
202
+ import { activityManager } from '../utils/activityManager.js';
203
+ import { createAbortController } from '../utils/abortController.js';
204
+ import { MCPConnectionManager } from 'src/services/mcp/MCPConnectionManager.js';
205
+ import { useFeedbackSurvey } from 'src/components/FeedbackSurvey/useFeedbackSurvey.js';
206
+ import { useMemorySurvey } from 'src/components/FeedbackSurvey/useMemorySurvey.js';
207
+ import { usePostCompactSurvey } from 'src/components/FeedbackSurvey/usePostCompactSurvey.js';
208
+ import { FeedbackSurvey } from 'src/components/FeedbackSurvey/FeedbackSurvey.js';
209
+ import { useInstallMessages } from 'src/hooks/notifs/useInstallMessages.js';
210
+ import { useAwaySummary } from 'src/hooks/useAwaySummary.js';
211
+ import { useChromeExtensionNotification } from 'src/hooks/useChromeExtensionNotification.js';
212
+ import { useOfficialMarketplaceNotification } from 'src/hooks/useOfficialMarketplaceNotification.js';
213
+ import { usePromptsFromClaudeInChrome } from 'src/hooks/usePromptsFromClaudeInChrome.js';
214
+ import { getTipToShowOnSpinner, recordShownTip } from 'src/services/tips/tipScheduler.js';
215
+ import { checkAndDisableBypassPermissionsIfNeeded, checkAndDisableAutoModeIfNeeded, useKickOffCheckAndDisableBypassPermissionsIfNeeded, useKickOffCheckAndDisableAutoModeIfNeeded } from 'src/utils/permissions/bypassPermissionsKillswitch.js';
216
+ import { SandboxManager } from 'src/utils/sandbox/sandbox-adapter.js';
217
+ import { SANDBOX_NETWORK_ACCESS_TOOL_NAME } from 'src/cli/structuredIO.js';
218
+ import { useFileHistorySnapshotInit } from 'src/hooks/useFileHistorySnapshotInit.js';
219
+ import { SandboxPermissionRequest } from 'src/components/permissions/SandboxPermissionRequest.js';
220
+ import { SandboxViolationExpandedView } from 'src/components/SandboxViolationExpandedView.js';
221
+ import { useSettingsErrors } from 'src/hooks/notifs/useSettingsErrors.js';
222
+ import { useMcpConnectivityStatus } from 'src/hooks/notifs/useMcpConnectivityStatus.js';
223
+ import { useAutoModeUnavailableNotification } from 'src/hooks/notifs/useAutoModeUnavailableNotification.js';
224
+ import { AUTO_MODE_DESCRIPTION } from 'src/components/AutoModeOptInDialog.js';
225
+ import { useLspInitializationNotification } from 'src/hooks/notifs/useLspInitializationNotification.js';
226
+ import { useLspPluginRecommendation } from 'src/hooks/useLspPluginRecommendation.js';
227
+ import { LspRecommendationMenu } from 'src/components/LspRecommendation/LspRecommendationMenu.js';
228
+ import { useThaddeusHintRecommendation } from 'src/hooks/useThaddeusHintRecommendation.js';
229
+ import { PluginHintMenu } from 'src/components/ThaddeusHint/PluginHintMenu.js';
230
+ import { DesktopUpsellStartup, shouldShowDesktopUpsellStartup } from 'src/components/DesktopUpsell/DesktopUpsellStartup.js';
231
+ import { usePluginInstallationStatus } from 'src/hooks/notifs/usePluginInstallationStatus.js';
232
+ import { usePluginAutoupdateNotification } from 'src/hooks/notifs/usePluginAutoupdateNotification.js';
233
+ import { performStartupChecks } from 'src/utils/plugins/performStartupChecks.js';
234
+ import { UserTextMessage } from 'src/components/messages/UserTextMessage.js';
235
+ import { AwsAuthStatusBox } from '../components/AwsAuthStatusBox.js';
236
+ import { useRateLimitWarningNotification } from 'src/hooks/notifs/useRateLimitWarningNotification.js';
237
+ import { useDeprecationWarningNotification } from 'src/hooks/notifs/useDeprecationWarningNotification.js';
238
+ import { useNpmDeprecationNotification } from 'src/hooks/notifs/useNpmDeprecationNotification.js';
239
+ import { useIDEStatusIndicator } from 'src/hooks/notifs/useIDEStatusIndicator.js';
240
+ import { useModelMigrationNotifications } from 'src/hooks/notifs/useModelMigrationNotifications.js';
241
+ import { useCanSwitchToExistingSubscription } from 'src/hooks/notifs/useCanSwitchToExistingSubscription.js';
242
+ import { useTeammateLifecycleNotification } from 'src/hooks/notifs/useTeammateShutdownNotification.js';
243
+ import { useFastModeNotification } from 'src/hooks/notifs/useFastModeNotification.js';
244
+ import { AutoRunIssueNotification, shouldAutoRunIssue, getAutoRunIssueReasonText, getAutoRunCommand } from '../utils/autoRunIssue.js';
245
+ import { TungstenLiveMonitor } from '../tools/TungstenTool/TungstenLiveMonitor.js';
246
+ /* eslint-disable @typescript-eslint/no-require-imports */
247
+ const WebBrowserPanelModule = feature('WEB_BROWSER_TOOL') ? require('../tools/WebBrowserTool/WebBrowserPanel.js') : null;
248
+ /* eslint-enable @typescript-eslint/no-require-imports */
249
+ import { IssueFlagBanner } from '../components/PromptInput/IssueFlagBanner.js';
250
+ import { useIssueFlagBanner } from '../hooks/useIssueFlagBanner.js';
251
+ import { CompanionSprite, CompanionFloatingBubble, MIN_COLS_FOR_FULL_SPRITE } from '../buddy/CompanionSprite.js';
252
+ import { DevBar } from '../components/DevBar.js';
253
+ import { REMOTE_SAFE_COMMANDS } from '../commands.js';
254
+ import { FullscreenLayout, useUnseenDivider, computeUnseenDivider } from '../components/FullscreenLayout.js';
255
+ import { isFullscreenEnvEnabled, maybeGetTmuxMouseHint, isMouseTrackingEnabled } from '../utils/fullscreen.js';
256
+ import { AlternateScreen } from '../ink/components/AlternateScreen.js';
257
+ import { ScrollKeybindingHandler } from '../components/ScrollKeybindingHandler.js';
258
+ import { useMessageActions, MessageActionsKeybindings, MessageActionsBar } from '../components/messageActions.js';
259
+ import { setClipboard } from '../ink/termio/osc.js';
260
+ import { createAttachmentMessage, getQueuedCommandAttachments } from '../utils/attachments.js';
261
+ // Stable empty array for hooks that accept MCPServerConnection[] — avoids
262
+ // creating a new [] literal on every render in remote mode, which would
263
+ // cause useEffect dependency changes and infinite re-render loops.
264
+ const EMPTY_MCP_CLIENTS = [];
265
+ // Stable stub for useAssistantHistory's non-KAIROS branch — avoids a new
266
+ // function identity each render, which would break composedOnScroll's memo.
267
+ const HISTORY_STUB = {
268
+ maybeLoadOlder: (_) => { }
269
+ };
270
+ // Window after a user-initiated scroll during which type-into-empty does NOT
271
+ // repin to bottom. Josh Rosen's workflow: Claude emits long output → scroll
272
+ // up to read the start → start typing → before this fix, snapped to bottom.
273
+ // https://anthropic.slack.com/archives/C07VBSHV7EV/p1773545449871739
274
+ const RECENT_SCROLL_REPIN_WINDOW_MS = 3000;
275
+ // Use LRU cache to prevent unbounded memory growth
276
+ // 100 files should be sufficient for most coding sessions while preventing
277
+ // memory issues when working across many files in large projects
278
+ function median(values) {
279
+ const sorted = [...values].sort((a, b) => a - b);
280
+ const mid = Math.floor(sorted.length / 2);
281
+ return sorted.length % 2 === 0 ? Math.round((sorted[mid - 1] + sorted[mid]) / 2) : sorted[mid];
282
+ }
283
+ /**
284
+ * Small component to display transcript mode footer with dynamic keybinding.
285
+ * Must be rendered inside KeybindingSetup to access keybinding context.
286
+ */
287
+ function TranscriptModeFooter(t0) {
288
+ const $ = _c(9);
289
+ const { showAllInTranscript, virtualScroll, searchBadge, suppressShowAll: t1, status } = t0;
290
+ const suppressShowAll = t1 === undefined ? false : t1;
291
+ const toggleShortcut = useShortcutDisplay("app:toggleTranscript", "Global", "ctrl+o");
292
+ const showAllShortcut = useShortcutDisplay("transcript:toggleShowAll", "Transcript", "ctrl+e");
293
+ const t2 = searchBadge ? " \xB7 n/N to navigate" : virtualScroll ? ` · ${figures.arrowUp}${figures.arrowDown} scroll · home/end top/bottom` : suppressShowAll ? "" : ` · ${showAllShortcut} to ${showAllInTranscript ? "collapse" : "show all"}`;
294
+ let t3;
295
+ if ($[0] !== t2 || $[1] !== toggleShortcut) {
296
+ t3 = _jsxs(Text, { dimColor: true, children: ["Showing detailed transcript \u00B7 ", toggleShortcut, " to toggle", t2] });
297
+ $[0] = t2;
298
+ $[1] = toggleShortcut;
299
+ $[2] = t3;
300
+ }
301
+ else {
302
+ t3 = $[2];
303
+ }
304
+ let t4;
305
+ if ($[3] !== searchBadge || $[4] !== status) {
306
+ t4 = status ? _jsxs(_Fragment, { children: [_jsx(Box, { flexGrow: 1 }), _jsxs(Text, { children: [status, " "] })] }) : searchBadge ? _jsxs(_Fragment, { children: [_jsx(Box, { flexGrow: 1 }), _jsxs(Text, { dimColor: true, children: [searchBadge.current, "/", searchBadge.count, " "] })] }) : null;
307
+ $[3] = searchBadge;
308
+ $[4] = status;
309
+ $[5] = t4;
310
+ }
311
+ else {
312
+ t4 = $[5];
313
+ }
314
+ let t5;
315
+ if ($[6] !== t3 || $[7] !== t4) {
316
+ t5 = _jsxs(Box, { noSelect: true, alignItems: "center", alignSelf: "center", borderTopDimColor: true, borderBottom: false, borderLeft: false, borderRight: false, borderStyle: "single", marginTop: 1, paddingLeft: 2, width: "100%", children: [t3, t4] });
317
+ $[6] = t3;
318
+ $[7] = t4;
319
+ $[8] = t5;
320
+ }
321
+ else {
322
+ t5 = $[8];
323
+ }
324
+ return t5;
325
+ }
326
+ /** less-style / bar. 1-row, same border-top styling as TranscriptModeFooter
327
+ * so swapping them in the bottom slot doesn't shift ScrollBox height.
328
+ * useSearchInput handles readline editing; we report query changes and
329
+ * render the counter. Incremental — re-search + highlight per keystroke. */
330
+ function TranscriptSearchBar({ jumpRef, count, current, onClose, onCancel, setHighlight, initialQuery }) {
331
+ const { query, cursorOffset } = useSearchInput({
332
+ isActive: true,
333
+ initialQuery,
334
+ onExit: () => onClose(query),
335
+ onCancel
336
+ });
337
+ // Index warm-up runs before the query effect so it measures the real
338
+ // cost — otherwise setSearchQuery fills the cache first and warm
339
+ // reports ~0ms while the user felt the actual lag.
340
+ // First / in a transcript session pays the extractSearchText cost.
341
+ // Subsequent / return 0 immediately (indexWarmed ref in VML).
342
+ // Transcript is frozen at ctrl+o so the cache stays valid.
343
+ // Initial 'building' so warmDone is false on mount — the [query] effect
344
+ // waits for the warm effect's first resolve instead of racing it. With
345
+ // null initial, warmDone would be true on mount → [query] fires →
346
+ // setSearchQuery fills cache → warm reports ~0ms while the user felt
347
+ // the real lag.
348
+ const [indexStatus, setIndexStatus] = React.useState('building');
349
+ React.useEffect(() => {
350
+ let alive = true;
351
+ const warm = jumpRef.current?.warmSearchIndex;
352
+ if (!warm) {
353
+ setIndexStatus(null); // VML not mounted yet — rare, skip indicator
354
+ return;
355
+ }
356
+ setIndexStatus('building');
357
+ warm().then(ms => {
358
+ if (!alive)
359
+ return;
360
+ // <20ms = imperceptible. No point showing "indexed in 3ms".
361
+ if (ms < 20) {
362
+ setIndexStatus(null);
363
+ }
364
+ else {
365
+ setIndexStatus({
366
+ ms
367
+ });
368
+ setTimeout(() => alive && setIndexStatus(null), 2000);
369
+ }
370
+ });
371
+ return () => {
372
+ alive = false;
373
+ };
374
+ // eslint-disable-next-line react-hooks/exhaustive-deps
375
+ }, []); // mount-only: bar opens once per /
376
+ // Gate the query effect on warm completion. setHighlight stays instant
377
+ // (screen-space overlay, no indexing). setSearchQuery (the scan) waits.
378
+ const warmDone = indexStatus !== 'building';
379
+ useEffect(() => {
380
+ if (!warmDone)
381
+ return;
382
+ jumpRef.current?.setSearchQuery(query);
383
+ setHighlight(query);
384
+ // eslint-disable-next-line react-hooks/exhaustive-deps
385
+ }, [query, warmDone]);
386
+ const off = cursorOffset;
387
+ const cursorChar = off < query.length ? query[off] : ' ';
388
+ return _jsxs(Box, { borderTopDimColor: true, borderBottom: false, borderLeft: false, borderRight: false, borderStyle: "single", marginTop: 1, paddingLeft: 2, width: "100%",
389
+ // applySearchHighlight scans the whole screen buffer. The query
390
+ // text rendered here IS on screen — /foo matches its own 'foo' in
391
+ // the bar. With no content matches that's the ONLY visible match →
392
+ // gets CURRENT → underlined. noSelect makes searchHighlight.ts:76
393
+ // skip these cells (same exclusion as gutters). You can't text-
394
+ // select the bar either; it's transient chrome, fine.
395
+ noSelect: true, children: [_jsx(Text, { children: "/" }), _jsx(Text, { children: query.slice(0, off) }), _jsx(Text, { inverse: true, children: cursorChar }), off < query.length && _jsx(Text, { children: query.slice(off + 1) }), _jsx(Box, { flexGrow: 1 }), indexStatus === 'building' ? _jsx(Text, { dimColor: true, children: "indexing\u2026 " }) : indexStatus ? _jsxs(Text, { dimColor: true, children: ["indexed in ", indexStatus.ms, "ms "] }) : count === 0 && query ? _jsx(Text, { color: "error", children: "no matches " }) : count > 0 ?
396
+ // Engine-counted (indexOf on extractSearchText). May drift from
397
+ // render-count for ghost/phantom messages — badge is a rough
398
+ // location hint. scanElement gives exact per-message positions
399
+ // but counting ALL would cost ~1-3ms × matched-messages.
400
+ _jsxs(Text, { dimColor: true, children: [current, "/", count, ' '] }) : null] });
401
+ }
402
+ const TITLE_ANIMATION_FRAMES = ['⠂', '⠐'];
403
+ const TITLE_STATIC_PREFIX = '✳';
404
+ const TITLE_ANIMATION_INTERVAL_MS = 960;
405
+ /**
406
+ * Sets the terminal tab title, with an animated prefix glyph while a query
407
+ * is running. Isolated from REPL so the 960ms animation tick re-renders only
408
+ * this leaf component (which returns null — pure side-effect) instead of the
409
+ * entire REPL tree. Before extraction, the tick was ~1 REPL render/sec for
410
+ * the duration of every turn, dragging PromptInput and friends along.
411
+ */
412
+ function AnimatedTerminalTitle(t0) {
413
+ const $ = _c(6);
414
+ const { isAnimating, title, disabled, noPrefix } = t0;
415
+ const terminalFocused = useTerminalFocus();
416
+ const [frame, setFrame] = useState(0);
417
+ let t1;
418
+ let t2;
419
+ if ($[0] !== disabled || $[1] !== isAnimating || $[2] !== noPrefix || $[3] !== terminalFocused) {
420
+ t1 = () => {
421
+ if (disabled || noPrefix || !isAnimating || !terminalFocused) {
422
+ return;
423
+ }
424
+ const interval = setInterval(_temp2, TITLE_ANIMATION_INTERVAL_MS, setFrame);
425
+ return () => clearInterval(interval);
426
+ };
427
+ t2 = [disabled, noPrefix, isAnimating, terminalFocused];
428
+ $[0] = disabled;
429
+ $[1] = isAnimating;
430
+ $[2] = noPrefix;
431
+ $[3] = terminalFocused;
432
+ $[4] = t1;
433
+ $[5] = t2;
434
+ }
435
+ else {
436
+ t1 = $[4];
437
+ t2 = $[5];
438
+ }
439
+ useEffect(t1, t2);
440
+ const prefix = isAnimating ? TITLE_ANIMATION_FRAMES[frame] ?? TITLE_STATIC_PREFIX : TITLE_STATIC_PREFIX;
441
+ useTerminalTitle(disabled ? null : noPrefix ? title : `${prefix} ${title}`);
442
+ return null;
443
+ }
444
+ function _temp2(setFrame_0) {
445
+ return setFrame_0(_temp);
446
+ }
447
+ function _temp(f) {
448
+ return (f + 1) % TITLE_ANIMATION_FRAMES.length;
449
+ }
450
+ export function REPL({ commands: initialCommands, debug, initialTools, initialMessages, pendingHookMessages, initialFileHistorySnapshots, initialContentReplacements, initialAgentName, initialAgentColor, mcpClients: initialMcpClients, dynamicMcpConfig: initialDynamicMcpConfig, autoConnectIdeFlag, strictMcpConfig = false, systemPrompt: customSystemPrompt, appendSystemPrompt, onBeforeQuery, onTurnComplete, disabled = false, mainThreadAgentDefinition: initialMainThreadAgentDefinition, disableSlashCommands = false, taskListId, remoteSessionConfig, directConnectConfig, sshSession, thinkingConfig }) {
451
+ const isRemoteSession = !!remoteSessionConfig;
452
+ // Env-var gates hoisted to mount-time — isEnvTruthy does toLowerCase+trim+
453
+ // includes, and these were on the render path (hot during PageUp spam).
454
+ const titleDisabled = useMemo(() => isEnvTruthy(process.env.THADDEUS_DISABLE_TERMINAL_TITLE), []);
455
+ const moreRightEnabled = useMemo(() => "external" === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []);
456
+ const disableVirtualScroll = useMemo(() => isEnvTruthy(process.env.THADDEUS_DISABLE_VIRTUAL_SCROLL), []);
457
+ const disableMessageActions = feature('MESSAGE_ACTIONS') ?
458
+ // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
459
+ useMemo(() => isEnvTruthy(process.env.THADDEUS_DISABLE_MESSAGE_ACTIONS), []) : false;
460
+ // Log REPL mount/unmount lifecycle
461
+ useEffect(() => {
462
+ logForDebugging(`[REPL:mount] REPL mounted, disabled=${disabled}`);
463
+ return () => logForDebugging(`[REPL:unmount] REPL unmounting`);
464
+ }, [disabled]);
465
+ // Auto-init ElevenLabs TTS if voiceEnabled was set in a previous session
466
+ useEffect(() => {
467
+ try {
468
+ const settings = require('../utils/settings/settings.js').getInitialSettings();
469
+ if (settings.voiceEnabled === true && process.env.ELEVENLABS_API_KEY) {
470
+ const tts = require('../services/elevenlabsTTS.js');
471
+ tts.setTTSEnabled(true);
472
+ }
473
+ }
474
+ catch { }
475
+ }, []);
476
+ // Agent definition is state so /resume can update it mid-session
477
+ const [mainThreadAgentDefinition, setMainThreadAgentDefinition] = useState(initialMainThreadAgentDefinition);
478
+ const toolPermissionContext = useAppState(s => s.toolPermissionContext);
479
+ const verbose = useAppState(s => s.verbose);
480
+ const mcp = useAppState(s => s.mcp);
481
+ const plugins = useAppState(s => s.plugins);
482
+ const agentDefinitions = useAppState(s => s.agentDefinitions);
483
+ const fileHistory = useAppState(s => s.fileHistory);
484
+ const initialMessage = useAppState(s => s.initialMessage);
485
+ const queuedCommands = useCommandQueue();
486
+ // feature() is a build-time constant — dead code elimination removes the hook
487
+ // call entirely in external builds, so this is safe despite looking conditional.
488
+ // These fields contain excluded strings that must not appear in external builds.
489
+ const spinnerTip = useAppState(s => s.spinnerTip);
490
+ const showExpandedTodos = useAppState(s => s.expandedView) === 'tasks';
491
+ const pendingWorkerRequest = useAppState(s => s.pendingWorkerRequest);
492
+ const pendingSandboxRequest = useAppState(s => s.pendingSandboxRequest);
493
+ const teamContext = useAppState(s => s.teamContext);
494
+ const tasks = useAppState(s => s.tasks);
495
+ const workerSandboxPermissions = useAppState(s => s.workerSandboxPermissions);
496
+ const elicitation = useAppState(s => s.elicitation);
497
+ const ultraplanPendingChoice = useAppState(s => s.ultraplanPendingChoice);
498
+ const ultraplanLaunchPending = useAppState(s => s.ultraplanLaunchPending);
499
+ const viewingAgentTaskId = useAppState(s => s.viewingAgentTaskId);
500
+ const setAppState = useSetAppState();
501
+ // Bootstrap: retained local_agent that hasn't loaded disk yet → read
502
+ // sidechain JSONL and UUID-merge with whatever stream has appended so far.
503
+ // Stream appends immediately on retain (no defer); bootstrap fills the
504
+ // prefix. Disk-write-before-yield means live is always a suffix of disk.
505
+ const viewedLocalAgent = viewingAgentTaskId ? tasks[viewingAgentTaskId] : undefined;
506
+ const needsBootstrap = isLocalAgentTask(viewedLocalAgent) && viewedLocalAgent.retain && !viewedLocalAgent.diskLoaded;
507
+ useEffect(() => {
508
+ if (!viewingAgentTaskId || !needsBootstrap)
509
+ return;
510
+ const taskId = viewingAgentTaskId;
511
+ void getAgentTranscript(asAgentId(taskId)).then(result => {
512
+ setAppState(prev => {
513
+ const t = prev.tasks[taskId];
514
+ if (!isLocalAgentTask(t) || t.diskLoaded || !t.retain)
515
+ return prev;
516
+ const live = t.messages ?? [];
517
+ const liveUuids = new Set(live.map(m => m.uuid));
518
+ const diskOnly = result ? result.messages.filter(m => !liveUuids.has(m.uuid)) : [];
519
+ return {
520
+ ...prev,
521
+ tasks: {
522
+ ...prev.tasks,
523
+ [taskId]: {
524
+ ...t,
525
+ messages: [...diskOnly, ...live],
526
+ diskLoaded: true
527
+ }
528
+ }
529
+ };
530
+ });
531
+ });
532
+ }, [viewingAgentTaskId, needsBootstrap, setAppState]);
533
+ const store = useAppStateStore();
534
+ const terminal = useTerminalNotification();
535
+ const mainLoopModel = useMainLoopModel();
536
+ // Note: standaloneAgentContext is initialized in main.tsx (via initialState) or
537
+ // ResumeConversation.tsx (via setAppState before rendering REPL) to avoid
538
+ // useEffect-based state initialization on mount (per THADDEUS.md guidelines)
539
+ // Local state for commands (hot-reloadable when skill files change)
540
+ const [localCommands, setLocalCommands] = useState(initialCommands);
541
+ // Watch for skill file changes and reload all commands
542
+ useSkillsChange(isRemoteSession ? undefined : getProjectRoot(), setLocalCommands);
543
+ // Track proactive mode for tools dependency - SleepTool filters by proactive state
544
+ const proactiveActive = React.useSyncExternalStore(proactiveModule?.subscribeToProactiveChanges ?? PROACTIVE_NO_OP_SUBSCRIBE, proactiveModule?.isProactiveActive ?? PROACTIVE_FALSE);
545
+ // BriefTool.isEnabled() reads getUserMsgOptIn() from bootstrap state, which
546
+ // /brief flips mid-session alongside isBriefOnly. The memo below needs a
547
+ // React-visible dep to re-run getTools() when that happens; isBriefOnly is
548
+ // the AppState mirror that triggers the re-render. Without this, toggling
549
+ // /brief mid-session leaves the stale tool list (no SendUserMessage) and
550
+ // the model emits plain text the brief filter hides.
551
+ const isBriefOnly = useAppState(s => s.isBriefOnly);
552
+ const localTools = useMemo(() => getTools(toolPermissionContext), [toolPermissionContext, proactiveActive, isBriefOnly]);
553
+ useKickOffCheckAndDisableBypassPermissionsIfNeeded();
554
+ useKickOffCheckAndDisableAutoModeIfNeeded();
555
+ const [dynamicMcpConfig, setDynamicMcpConfig] = useState(initialDynamicMcpConfig);
556
+ const onChangeDynamicMcpConfig = useCallback((config) => {
557
+ setDynamicMcpConfig(config);
558
+ }, [setDynamicMcpConfig]);
559
+ const [screen, setScreen] = useState('prompt');
560
+ const [showAllInTranscript, setShowAllInTranscript] = useState(false);
561
+ // [ forces the dump-to-scrollback path inside transcript mode. Separate
562
+ // from THADDEUS_NO_FLICKER=0 (which is process-lifetime) — this is
563
+ // ephemeral, reset on transcript exit. Diagnostic escape hatch so
564
+ // terminal/tmux native cmd-F can search the full flat render.
565
+ const [dumpMode, setDumpMode] = useState(false);
566
+ // v-for-editor render progress. Inline in the footer — notifications
567
+ // render inside PromptInput which isn't mounted in transcript.
568
+ const [editorStatus, setEditorStatus] = useState('');
569
+ // Incremented on transcript exit. Async v-render captures this at start;
570
+ // each status write no-ops if stale (user left transcript mid-render —
571
+ // the stable setState would otherwise stamp a ghost toast into the next
572
+ // session). Also clears any pending 4s auto-clear.
573
+ const editorGenRef = useRef(0);
574
+ const editorTimerRef = useRef(undefined);
575
+ const editorRenderingRef = useRef(false);
576
+ const { addNotification, removeNotification } = useNotifications();
577
+ // eslint-disable-next-line prefer-const
578
+ let trySuggestBgPRIntercept = SUGGEST_BG_PR_NOOP;
579
+ const mcpClients = useMergedClients(initialMcpClients, mcp.clients);
580
+ // IDE integration
581
+ const [ideSelection, setIDESelection] = useState(undefined);
582
+ const [ideToInstallExtension, setIDEToInstallExtension] = useState(null);
583
+ const [ideInstallationStatus, setIDEInstallationStatus] = useState(null);
584
+ const [showIdeOnboarding, setShowIdeOnboarding] = useState(false);
585
+ // Dead code elimination: model switch callout state (ant-only)
586
+ const [showModelSwitchCallout, setShowModelSwitchCallout] = useState(() => {
587
+ if ("external" === 'ant') {
588
+ return shouldShowAntModelSwitch();
589
+ }
590
+ return false;
591
+ });
592
+ const [showEffortCallout, setShowEffortCallout] = useState(() => shouldShowEffortCallout(mainLoopModel));
593
+ const showRemoteCallout = useAppState(s => s.showRemoteCallout);
594
+ const [showDesktopUpsellStartup, setShowDesktopUpsellStartup] = useState(() => shouldShowDesktopUpsellStartup());
595
+ // notifications
596
+ useModelMigrationNotifications();
597
+ useCanSwitchToExistingSubscription();
598
+ useIDEStatusIndicator({
599
+ ideSelection,
600
+ mcpClients,
601
+ ideInstallationStatus
602
+ });
603
+ useMcpConnectivityStatus({
604
+ mcpClients
605
+ });
606
+ useAutoModeUnavailableNotification();
607
+ usePluginInstallationStatus();
608
+ usePluginAutoupdateNotification();
609
+ useSettingsErrors();
610
+ useRateLimitWarningNotification(mainLoopModel);
611
+ useFastModeNotification();
612
+ useDeprecationWarningNotification(mainLoopModel);
613
+ useNpmDeprecationNotification();
614
+ useAntOrgWarningNotification();
615
+ useInstallMessages();
616
+ useChromeExtensionNotification();
617
+ useOfficialMarketplaceNotification();
618
+ useLspInitializationNotification();
619
+ useTeammateLifecycleNotification();
620
+ const { recommendation: lspRecommendation, handleResponse: handleLspResponse } = useLspPluginRecommendation();
621
+ const { recommendation: hintRecommendation, handleResponse: handleHintResponse } = useThaddeusHintRecommendation();
622
+ // Memoize the combined initial tools array to prevent reference changes
623
+ const combinedInitialTools = useMemo(() => {
624
+ return [...localTools, ...initialTools];
625
+ }, [localTools, initialTools]);
626
+ // Initialize plugin management
627
+ useManagePlugins({
628
+ enabled: !isRemoteSession
629
+ });
630
+ const tasksV2 = useTasksV2WithCollapseEffect();
631
+ // Start background plugin installations
632
+ // SECURITY: This code is guaranteed to run ONLY after the "trust this folder" dialog
633
+ // has been confirmed by the user. The trust dialog is shown in cli.tsx (line ~387)
634
+ // before the REPL component is rendered. The dialog blocks execution until the user
635
+ // accepts, and only then is the REPL component mounted and this effect runs.
636
+ // This ensures that plugin installations from repository and user settings only
637
+ // happen after explicit user consent to trust the current working directory.
638
+ useEffect(() => {
639
+ if (isRemoteSession)
640
+ return;
641
+ void performStartupChecks(setAppState);
642
+ }, [setAppState, isRemoteSession]);
643
+ // Allow Claude in Chrome MCP to send prompts through MCP notifications
644
+ // and sync permission mode changes to the Chrome extension
645
+ usePromptsFromClaudeInChrome(isRemoteSession ? EMPTY_MCP_CLIENTS : mcpClients, toolPermissionContext.mode);
646
+ // Initialize swarm features: teammate hooks and context
647
+ // Handles both fresh spawns and resumed teammate sessions
648
+ useSwarmInitialization(setAppState, initialMessages, {
649
+ enabled: !isRemoteSession
650
+ });
651
+ const mergedTools = useMergedTools(combinedInitialTools, mcp.tools, toolPermissionContext);
652
+ // Apply agent tool restrictions if mainThreadAgentDefinition is set
653
+ const { tools, allowedAgentTypes } = useMemo(() => {
654
+ if (!mainThreadAgentDefinition) {
655
+ return {
656
+ tools: mergedTools,
657
+ allowedAgentTypes: undefined
658
+ };
659
+ }
660
+ const resolved = resolveAgentTools(mainThreadAgentDefinition, mergedTools, false, true);
661
+ return {
662
+ tools: resolved.resolvedTools,
663
+ allowedAgentTypes: resolved.allowedAgentTypes
664
+ };
665
+ }, [mainThreadAgentDefinition, mergedTools]);
666
+ // Merge commands from local state, plugins, and MCP
667
+ const commandsWithPlugins = useMergedCommands(localCommands, plugins.commands);
668
+ const mergedCommands = useMergedCommands(commandsWithPlugins, mcp.commands);
669
+ // Filter out all commands if disableSlashCommands is true
670
+ const commands = useMemo(() => disableSlashCommands ? [] : mergedCommands, [disableSlashCommands, mergedCommands]);
671
+ useIdeLogging(isRemoteSession ? EMPTY_MCP_CLIENTS : mcp.clients);
672
+ useIdeSelection(isRemoteSession ? EMPTY_MCP_CLIENTS : mcp.clients, setIDESelection);
673
+ const [streamMode, setStreamMode] = useState('responding');
674
+ // Ref mirror so onSubmit can read the latest value without adding
675
+ // streamMode to its deps. streamMode flips between
676
+ // requesting/responding/tool-use ~10x per turn during streaming; having it
677
+ // in onSubmit's deps was recreating onSubmit on every flip, which
678
+ // cascaded into PromptInput prop churn and downstream useCallback/useMemo
679
+ // invalidation. The only consumers inside callbacks are debug logging and
680
+ // telemetry (handlePromptSubmit.ts), so a stale-by-one-render value is
681
+ // harmless — but ref mirrors sync on every render anyway so it's fresh.
682
+ const streamModeRef = useRef(streamMode);
683
+ streamModeRef.current = streamMode;
684
+ const [streamingToolUses, setStreamingToolUses] = useState([]);
685
+ const [streamingThinking, setStreamingThinking] = useState(null);
686
+ // Auto-hide streaming thinking after 30 seconds of being completed
687
+ useEffect(() => {
688
+ if (streamingThinking && !streamingThinking.isStreaming && streamingThinking.streamingEndedAt) {
689
+ const elapsed = Date.now() - streamingThinking.streamingEndedAt;
690
+ const remaining = 30000 - elapsed;
691
+ if (remaining > 0) {
692
+ const timer = setTimeout(setStreamingThinking, remaining, null);
693
+ return () => clearTimeout(timer);
694
+ }
695
+ else {
696
+ setStreamingThinking(null);
697
+ }
698
+ }
699
+ }, [streamingThinking]);
700
+ const [abortController, setAbortController] = useState(null);
701
+ // Ref that always points to the current abort controller, used by the
702
+ // REPL bridge to abort the active query when a remote interrupt arrives.
703
+ const abortControllerRef = useRef(null);
704
+ abortControllerRef.current = abortController;
705
+ // Ref for the bridge result callback — set after useReplBridge initializes,
706
+ // read in the onQuery finally block to notify mobile clients that a turn ended.
707
+ const sendBridgeResultRef = useRef(() => { });
708
+ // Ref for the synchronous restore callback — set after restoreMessageSync is
709
+ // defined, read in the onQuery finally block for auto-restore on interrupt.
710
+ const restoreMessageSyncRef = useRef(() => { });
711
+ // Ref to the fullscreen layout's scroll box for keyboard scrolling.
712
+ // Null when fullscreen mode is disabled (ref never attached).
713
+ const scrollRef = useRef(null);
714
+ // Separate ref for the modal slot's inner ScrollBox — passed through
715
+ // FullscreenLayout → ModalContext so Tabs can attach it to its own
716
+ // ScrollBox for tall content (e.g. /status's MCP-server list). NOT
717
+ // keyboard-driven — ScrollKeybindingHandler stays on the outer ref so
718
+ // PgUp/PgDn/wheel always scroll the transcript behind the modal.
719
+ // Plumbing kept for future modal-scroll wiring.
720
+ const modalScrollRef = useRef(null);
721
+ // Timestamp of the last user-initiated scroll (wheel, PgUp/PgDn, ctrl+u,
722
+ // End/Home, G, drag-to-scroll). Stamped in composedOnScroll — the single
723
+ // chokepoint ScrollKeybindingHandler calls for every user scroll action.
724
+ // Programmatic scrolls (repinScroll's scrollToBottom, sticky auto-follow)
725
+ // do NOT go through composedOnScroll, so they don't stamp this. Ref not
726
+ // state: no re-render on every wheel tick.
727
+ const lastUserScrollTsRef = useRef(0);
728
+ // Synchronous state machine for the query lifecycle. Replaces the
729
+ // error-prone dual-state pattern where isLoading (React state, async
730
+ // batched) and isQueryRunning (ref, sync) could desync. See QueryGuard.ts.
731
+ const queryGuard = React.useRef(new QueryGuard()).current;
732
+ // Subscribe to the guard — true during dispatching or running.
733
+ // This is the single source of truth for "is a local query in flight".
734
+ const isQueryActive = React.useSyncExternalStore(queryGuard.subscribe, queryGuard.getSnapshot);
735
+ // Separate loading flag for operations outside the local query guard:
736
+ // remote sessions (useRemoteSession / useDirectConnect) and foregrounded
737
+ // background tasks (useSessionBackgrounding). These don't route through
738
+ // onQuery / queryGuard, so they need their own spinner-visibility state.
739
+ // Initialize true if remote mode with initial prompt (CCR processing it).
740
+ const [isExternalLoading, setIsExternalLoadingRaw] = React.useState(remoteSessionConfig?.hasInitialPrompt ?? false);
741
+ // Derived: any loading source active. Read-only — no setter. Local query
742
+ // loading is driven by queryGuard (reserve/tryStart/end/cancelReservation),
743
+ // external loading by setIsExternalLoading.
744
+ const isLoading = isQueryActive || isExternalLoading;
745
+ // Elapsed time is computed by SpinnerWithVerb from these refs on each
746
+ // animation frame, avoiding a useInterval that re-renders the entire REPL.
747
+ const [userInputOnProcessing, setUserInputOnProcessingRaw] = React.useState(undefined);
748
+ // messagesRef.current.length at the moment userInputOnProcessing was set.
749
+ // The placeholder hides once displayedMessages grows past this — i.e. the
750
+ // real user message has landed in the visible transcript.
751
+ const userInputBaselineRef = React.useRef(0);
752
+ // True while the submitted prompt is being processed but its user message
753
+ // hasn't reached setMessages yet. setMessages uses this to keep the
754
+ // baseline in sync when unrelated async messages (bridge status, hook
755
+ // results, scheduled tasks) land during that window.
756
+ const userMessagePendingRef = React.useRef(false);
757
+ // Wall-clock time tracking refs for accurate elapsed time calculation
758
+ const loadingStartTimeRef = React.useRef(0);
759
+ const totalPausedMsRef = React.useRef(0);
760
+ const pauseStartTimeRef = React.useRef(null);
761
+ const resetTimingRefs = React.useCallback(() => {
762
+ loadingStartTimeRef.current = Date.now();
763
+ totalPausedMsRef.current = 0;
764
+ pauseStartTimeRef.current = null;
765
+ }, []);
766
+ // Reset timing refs inline when isQueryActive transitions false→true.
767
+ // queryGuard.reserve() (in executeUserInput) fires BEFORE processUserInput's
768
+ // first await, but the ref reset in onQuery's try block runs AFTER. During
769
+ // that gap, React renders the spinner with loadingStartTimeRef=0, computing
770
+ // elapsedTimeMs = Date.now() - 0 ≈ 56 years. This inline reset runs on the
771
+ // first render where isQueryActive is observed true — the same render that
772
+ // first shows the spinner — so the ref is correct by the time the spinner
773
+ // reads it. See INC-4549.
774
+ const wasQueryActiveRef = React.useRef(false);
775
+ if (isQueryActive && !wasQueryActiveRef.current) {
776
+ resetTimingRefs();
777
+ }
778
+ wasQueryActiveRef.current = isQueryActive;
779
+ // TTS: speak assistant messages when query completes
780
+ const prevQueryActiveForTTS = React.useRef(false);
781
+ useEffect(() => {
782
+ if (prevQueryActiveForTTS.current && !isQueryActive) {
783
+ const msgs = messagesRef.current;
784
+ let lastAssistantText = '';
785
+ for (let i = msgs.length - 1; i >= 0; i--) {
786
+ const m = msgs[i];
787
+ if (m?.type !== 'assistant')
788
+ continue;
789
+ let text = getContentText(m.message?.content);
790
+ if (!text) {
791
+ const raw = m.content;
792
+ if (Array.isArray(raw)) {
793
+ text = raw.filter((b) => b.type === 'text').map((b) => b.text).join('\n');
794
+ }
795
+ }
796
+ if (text) {
797
+ lastAssistantText = text;
798
+ break;
799
+ }
800
+ }
801
+ // TTS: speak via ElevenLabs if enabled
802
+ try {
803
+ const tts = require('../services/elevenlabsTTS.js');
804
+ if (tts.isTTSEnabled() && lastAssistantText) {
805
+ void tts.speak(lastAssistantText);
806
+ }
807
+ }
808
+ catch { }
809
+ // Reactor voice channel: send response back to browser for TTS + particle brain
810
+ try {
811
+ const G = globalThis;
812
+ const rp = G.__reactorPending;
813
+ if (rp && rp.size > 0 && lastAssistantText) {
814
+ const [key, cb] = [...rp.entries()][0];
815
+ rp.delete(key);
816
+ console.log(`[Reactor] Response → browser (${lastAssistantText.length} chars, uuid=${key})`);
817
+ cb(lastAssistantText);
818
+ }
819
+ }
820
+ catch { }
821
+ }
822
+ prevQueryActiveForTTS.current = isQueryActive;
823
+ }, [isQueryActive]);
824
+ // Wrapper for setIsExternalLoading that resets timing refs on transition
825
+ // to true — SpinnerWithVerb reads these for elapsed time, so they must be
826
+ // reset for remote sessions / foregrounded tasks too (not just local
827
+ // queries, which reset them in onQuery). Without this, a remote-only
828
+ // session would show ~56 years elapsed (Date.now() - 0).
829
+ const setIsExternalLoading = React.useCallback((value) => {
830
+ setIsExternalLoadingRaw(value);
831
+ if (value)
832
+ resetTimingRefs();
833
+ }, [resetTimingRefs]);
834
+ // Start time of the first turn that had swarm teammates running
835
+ // Used to compute total elapsed time (including teammate execution) for the deferred message
836
+ const swarmStartTimeRef = React.useRef(null);
837
+ const swarmBudgetInfoRef = React.useRef(undefined);
838
+ // Ref to track current focusedInputDialog for use in callbacks
839
+ // This avoids stale closures when checking dialog state in timer callbacks
840
+ const focusedInputDialogRef = React.useRef(undefined);
841
+ // How long after the last keystroke before deferred dialogs are shown
842
+ const PROMPT_SUPPRESSION_MS = 1500;
843
+ // True when user is actively typing — defers interrupt dialogs so keystrokes
844
+ // don't accidentally dismiss or answer a permission prompt the user hasn't read yet.
845
+ const [isPromptInputActive, setIsPromptInputActive] = React.useState(false);
846
+ const [autoUpdaterResult, setAutoUpdaterResult] = useState(null);
847
+ useEffect(() => {
848
+ if (autoUpdaterResult?.notifications) {
849
+ autoUpdaterResult.notifications.forEach(notification => {
850
+ addNotification({
851
+ key: 'auto-updater-notification',
852
+ text: notification,
853
+ priority: 'low'
854
+ });
855
+ });
856
+ }
857
+ }, [autoUpdaterResult, addNotification]);
858
+ // tmux + fullscreen + `mouse off`: one-time hint that wheel won't scroll.
859
+ // We no longer mutate tmux's session-scoped mouse option (it poisoned
860
+ // sibling panes); tmux users already know this tradeoff from vim/less.
861
+ useEffect(() => {
862
+ if (isFullscreenEnvEnabled()) {
863
+ void maybeGetTmuxMouseHint().then(hint => {
864
+ if (hint) {
865
+ addNotification({
866
+ key: 'tmux-mouse-hint',
867
+ text: hint,
868
+ priority: 'low'
869
+ });
870
+ }
871
+ });
872
+ }
873
+ // eslint-disable-next-line react-hooks/exhaustive-deps
874
+ }, []);
875
+ const [showUndercoverCallout, setShowUndercoverCallout] = useState(false);
876
+ useEffect(() => {
877
+ if ("external" === 'ant') {
878
+ void (async () => {
879
+ // Wait for repo classification to settle (memoized, no-op if primed).
880
+ const { isInternalModelRepo } = await import('../utils/commitAttribution.js');
881
+ await isInternalModelRepo();
882
+ const { shouldShowUndercoverAutoNotice } = await import('../utils/undercover.js');
883
+ if (shouldShowUndercoverAutoNotice()) {
884
+ setShowUndercoverCallout(true);
885
+ }
886
+ })();
887
+ }
888
+ // eslint-disable-next-line react-hooks/exhaustive-deps
889
+ }, []);
890
+ const [toolJSX, setToolJSXInternal] = useState(null);
891
+ // Track local JSX commands separately so tools can't overwrite them.
892
+ // This enables "immediate" commands (like /btw) to persist while Thaddeus is processing.
893
+ const localJSXCommandRef = useRef(null);
894
+ // Wrapper for setToolJSX that preserves local JSX commands (like /btw).
895
+ // When a local JSX command is active, we ignore updates from tools
896
+ // unless they explicitly set clearLocalJSX: true (from onDone callbacks).
897
+ //
898
+ // TO ADD A NEW IMMEDIATE COMMAND:
899
+ // 1. Set `immediate: true` in the command definition
900
+ // 2. Set `isLocalJSXCommand: true` when calling setToolJSX in the command's JSX
901
+ // 3. In the onDone callback, use `setToolJSX({ jsx: null, shouldHidePromptInput: false, clearLocalJSX: true })`
902
+ // to explicitly clear the overlay when the user dismisses it
903
+ const setToolJSX = useCallback((args) => {
904
+ // If setting a local JSX command, store it in the ref
905
+ if (args?.isLocalJSXCommand) {
906
+ const { clearLocalJSX: _, ...rest } = args;
907
+ localJSXCommandRef.current = {
908
+ ...rest,
909
+ isLocalJSXCommand: true
910
+ };
911
+ setToolJSXInternal(rest);
912
+ return;
913
+ }
914
+ // If there's an active local JSX command in the ref
915
+ if (localJSXCommandRef.current) {
916
+ // Allow clearing only if explicitly requested (from onDone callbacks)
917
+ if (args?.clearLocalJSX) {
918
+ localJSXCommandRef.current = null;
919
+ setToolJSXInternal(null);
920
+ return;
921
+ }
922
+ // Otherwise, keep the local JSX command visible - ignore tool updates
923
+ return;
924
+ }
925
+ // No active local JSX command, allow any update
926
+ if (args?.clearLocalJSX) {
927
+ setToolJSXInternal(null);
928
+ return;
929
+ }
930
+ setToolJSXInternal(args);
931
+ }, []);
932
+ const [toolUseConfirmQueue, setToolUseConfirmQueue] = useState([]);
933
+ // Sticky footer JSX registered by permission request components (currently
934
+ // only ExitPlanModePermissionRequest). Renders in FullscreenLayout's `bottom`
935
+ // slot so response options stay visible while the user scrolls a long plan.
936
+ const [permissionStickyFooter, setPermissionStickyFooter] = useState(null);
937
+ const [sandboxPermissionRequestQueue, setSandboxPermissionRequestQueue] = useState([]);
938
+ const [promptQueue, setPromptQueue] = useState([]);
939
+ // Track bridge cleanup functions for sandbox permission requests so the
940
+ // local dialog handler can cancel the remote prompt when the local user
941
+ // responds first. Keyed by host to support concurrent same-host requests.
942
+ const sandboxBridgeCleanupRef = useRef(new Map());
943
+ // -- Terminal title management
944
+ // Session title (set via /rename or restored on resume) wins over
945
+ // the agent name, which wins over the Haiku-extracted topic;
946
+ // all fall back to the product name.
947
+ const terminalTitleFromRename = useAppState(s => s.settings.terminalTitleFromRename) !== false;
948
+ const sessionTitle = terminalTitleFromRename ? getCurrentSessionTitle(getSessionId()) : undefined;
949
+ const [haikuTitle, setHaikuTitle] = useState();
950
+ // Gates the one-shot Haiku call that generates the tab title. Seeded true
951
+ // on resume (initialMessages present) so we don't re-title a resumed
952
+ // session from mid-conversation context.
953
+ const haikuTitleAttemptedRef = useRef((initialMessages?.length ?? 0) > 0);
954
+ const agentTitle = mainThreadAgentDefinition?.agentType;
955
+ const terminalTitle = sessionTitle ?? agentTitle ?? haikuTitle ?? 'Thaddeus';
956
+ const isWaitingForApproval = toolUseConfirmQueue.length > 0 || promptQueue.length > 0 || pendingWorkerRequest || pendingSandboxRequest;
957
+ // Local-jsx commands (like /plugin, /config) show user-facing dialogs that
958
+ // wait for input. Require jsx != null — if the flag is stuck true but jsx
959
+ // is null, treat as not-showing so TextInput focus and queue processor
960
+ // aren't deadlocked by a phantom overlay.
961
+ const isShowingLocalJSXCommand = toolJSX?.isLocalJSXCommand === true && toolJSX?.jsx != null;
962
+ const titleIsAnimating = isLoading && !isWaitingForApproval && !isShowingLocalJSXCommand;
963
+ // Title animation state lives in <AnimatedTerminalTitle> so the 960ms tick
964
+ // doesn't re-render REPL. titleDisabled/terminalTitle are still computed
965
+ // here because onQueryImpl reads them (background session description,
966
+ // haiku title extraction gate).
967
+ // Prevent macOS from sleeping while Thaddeus is working
968
+ useEffect(() => {
969
+ if (isLoading && !isWaitingForApproval && !isShowingLocalJSXCommand) {
970
+ startPreventSleep();
971
+ return () => stopPreventSleep();
972
+ }
973
+ }, [isLoading, isWaitingForApproval, isShowingLocalJSXCommand]);
974
+ const sessionStatus = isWaitingForApproval || isShowingLocalJSXCommand ? 'waiting' : isLoading ? 'busy' : 'idle';
975
+ const waitingFor = sessionStatus !== 'waiting' ? undefined : toolUseConfirmQueue.length > 0 ? `approve ${toolUseConfirmQueue[0].tool.name}` : pendingWorkerRequest ? 'worker request' : pendingSandboxRequest ? 'sandbox request' : isShowingLocalJSXCommand ? 'dialog open' : 'input needed';
976
+ // Push status to the PID file for `thaddeus ps`. Fire-and-forget; ps falls
977
+ // back to transcript-tail derivation when this is missing/stale.
978
+ useEffect(() => {
979
+ if (feature('BG_SESSIONS')) {
980
+ void updateSessionActivity({
981
+ status: sessionStatus,
982
+ waitingFor
983
+ });
984
+ }
985
+ }, [sessionStatus, waitingFor]);
986
+ // 3P default: off — OSC 21337 is ant-only while the spec stabilizes.
987
+ // Gated so we can roll back if the sidebar indicator conflicts with
988
+ // the title spinner in terminals that render both. When the flag is
989
+ // on, the user-facing config setting controls whether it's active.
990
+ const tabStatusGateEnabled = getFeatureValue_CACHED_MAY_BE_STALE('thaddeus_terminal_sidebar', false);
991
+ const showStatusInTerminalTab = tabStatusGateEnabled && (getGlobalConfig().showStatusInTerminalTab ?? false);
992
+ useTabStatus(titleDisabled || !showStatusInTerminalTab ? null : sessionStatus);
993
+ // Register the leader's setToolUseConfirmQueue for in-process teammates
994
+ useEffect(() => {
995
+ registerLeaderToolUseConfirmQueue(setToolUseConfirmQueue);
996
+ return () => unregisterLeaderToolUseConfirmQueue();
997
+ }, [setToolUseConfirmQueue]);
998
+ const [messages, rawSetMessages] = useState(initialMessages ?? []);
999
+ const messagesRef = useRef(messages);
1000
+ // Stores the willowMode variant that was shown (or false if no hint shown).
1001
+ // Captured at hint_shown time so hint_converted telemetry reports the same
1002
+ // variant — the GrowthBook value shouldn't change mid-session, but reading
1003
+ // it once guarantees consistency between the paired events.
1004
+ const idleHintShownRef = useRef(false);
1005
+ // Wrap setMessages so messagesRef is always current the instant the
1006
+ // call returns — not when React later processes the batch. Apply the
1007
+ // updater eagerly against the ref, then hand React the computed value
1008
+ // (not the function). rawSetMessages batching becomes last-write-wins,
1009
+ // and the last write is correct because each call composes against the
1010
+ // already-updated ref. This is the Zustand pattern: ref is source of
1011
+ // truth, React state is the render projection. Without this, paths
1012
+ // that queue functional updaters then synchronously read the ref
1013
+ // (e.g. handleSpeculationAccept → onQuery) see stale data.
1014
+ const setMessages = useCallback((action) => {
1015
+ const prev = messagesRef.current;
1016
+ const next = typeof action === 'function' ? action(messagesRef.current) : action;
1017
+ messagesRef.current = next;
1018
+ if (next.length < userInputBaselineRef.current) {
1019
+ // Shrank (compact/rewind/clear) — clamp so placeholderText's length
1020
+ // check can't go stale.
1021
+ userInputBaselineRef.current = 0;
1022
+ }
1023
+ else if (next.length > prev.length && userMessagePendingRef.current) {
1024
+ // Grew while the submitted user message hasn't landed yet. If the
1025
+ // added messages don't include it (bridge status, hook results,
1026
+ // scheduled tasks landing async during processUserInputBase), bump
1027
+ // baseline so the placeholder stays visible. Once the user message
1028
+ // lands, stop tracking — later additions (assistant stream) should
1029
+ // not re-show the placeholder.
1030
+ const delta = next.length - prev.length;
1031
+ const added = prev.length === 0 || next[0] === prev[0] ? next.slice(-delta) : next.slice(0, delta);
1032
+ if (added.some(isHumanTurn)) {
1033
+ userMessagePendingRef.current = false;
1034
+ }
1035
+ else {
1036
+ userInputBaselineRef.current = next.length;
1037
+ }
1038
+ }
1039
+ rawSetMessages(next);
1040
+ }, []);
1041
+ // Capture the baseline message count alongside the placeholder text so
1042
+ // the render can hide it once displayedMessages grows past the baseline.
1043
+ const setUserInputOnProcessing = useCallback((input) => {
1044
+ if (input !== undefined) {
1045
+ userInputBaselineRef.current = messagesRef.current.length;
1046
+ userMessagePendingRef.current = true;
1047
+ }
1048
+ else {
1049
+ userMessagePendingRef.current = false;
1050
+ }
1051
+ setUserInputOnProcessingRaw(input);
1052
+ }, []);
1053
+ // Fullscreen: track the unseen-divider position. dividerIndex changes
1054
+ // only ~twice/scroll-session (first scroll-away + repin). pillVisible
1055
+ // and stickyPrompt now live in FullscreenLayout — they subscribe to
1056
+ // ScrollBox directly so per-frame scroll never re-renders REPL.
1057
+ const { dividerIndex, dividerYRef, onScrollAway, onRepin, jumpToNew, shiftDivider } = useUnseenDivider(messages.length);
1058
+ if (feature('AWAY_SUMMARY')) {
1059
+ // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
1060
+ useAwaySummary(messages, setMessages, isLoading);
1061
+ }
1062
+ const [cursor, setCursor] = useState(null);
1063
+ const cursorNavRef = useRef(null);
1064
+ // Memoized so Messages' React.memo holds.
1065
+ const unseenDivider = useMemo(() => computeUnseenDivider(messages, dividerIndex),
1066
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- length change covers appends; useUnseenDivider's count-drop guard clears dividerIndex on replace/rewind
1067
+ [dividerIndex, messages.length]);
1068
+ // Re-pin scroll to bottom and clear the unseen-messages baseline. Called
1069
+ // on any user-driven return-to-live action (submit, type-into-empty,
1070
+ // overlay appear/dismiss).
1071
+ const repinScroll = useCallback(() => {
1072
+ scrollRef.current?.scrollToBottom();
1073
+ onRepin();
1074
+ setCursor(null);
1075
+ }, [onRepin, setCursor]);
1076
+ // Backstop for the submit-handler repin at onSubmit. If a buffered stdin
1077
+ // event (wheel/drag) races between handler-fire and state-commit, the
1078
+ // handler's scrollToBottom can be undone. This effect fires on the render
1079
+ // where the user's message actually lands — tied to React's commit cycle,
1080
+ // so it can't race with stdin. Keyed on lastMsg identity (not messages.length)
1081
+ // so useAssistantHistory's prepends don't spuriously repin.
1082
+ const lastMsg = messages.at(-1);
1083
+ const lastMsgIsHuman = lastMsg != null && isHumanTurn(lastMsg);
1084
+ useEffect(() => {
1085
+ if (lastMsgIsHuman) {
1086
+ repinScroll();
1087
+ }
1088
+ }, [lastMsgIsHuman, lastMsg, repinScroll]);
1089
+ // Assistant-chat: lazy-load remote history on scroll-up. No-op unless
1090
+ // KAIROS build + config.viewerOnly. feature() is build-time constant so
1091
+ // the branch is dead-code-eliminated in non-KAIROS builds (same pattern
1092
+ // as useUnseenDivider above).
1093
+ const { maybeLoadOlder } = feature('KAIROS') ?
1094
+ // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
1095
+ useAssistantHistory({
1096
+ config: remoteSessionConfig,
1097
+ setMessages,
1098
+ scrollRef,
1099
+ onPrepend: shiftDivider
1100
+ }) : HISTORY_STUB;
1101
+ // Compose useUnseenDivider's callbacks with the lazy-load trigger.
1102
+ const composedOnScroll = useCallback((sticky, handle) => {
1103
+ lastUserScrollTsRef.current = Date.now();
1104
+ if (sticky) {
1105
+ onRepin();
1106
+ }
1107
+ else {
1108
+ onScrollAway(handle);
1109
+ if (feature('KAIROS'))
1110
+ maybeLoadOlder(handle);
1111
+ // Dismiss the companion bubble on scroll — it's absolute-positioned
1112
+ // at bottom-right and covers transcript content. Scrolling = user is
1113
+ // trying to read something under it.
1114
+ if (feature('BUDDY')) {
1115
+ setAppState(prev => prev.companionReaction === undefined ? prev : {
1116
+ ...prev,
1117
+ companionReaction: undefined
1118
+ });
1119
+ }
1120
+ }
1121
+ }, [onRepin, onScrollAway, maybeLoadOlder, setAppState]);
1122
+ // Deferred SessionStart hook messages — REPL renders immediately and
1123
+ // hook messages are injected when they resolve. awaitPendingHooks()
1124
+ // must be called before the first API call so the model sees hook context.
1125
+ const awaitPendingHooks = useDeferredHookMessages(pendingHookMessages, setMessages);
1126
+ // Deferred messages for the Messages component — renders at transition
1127
+ // priority so the reconciler yields every 5ms, keeping input responsive
1128
+ // while the expensive message processing pipeline runs.
1129
+ const deferredMessages = useDeferredValue(messages);
1130
+ const deferredBehind = messages.length - deferredMessages.length;
1131
+ if (deferredBehind > 0) {
1132
+ logForDebugging(`[useDeferredValue] Messages deferred by ${deferredBehind} (${deferredMessages.length}→${messages.length})`);
1133
+ }
1134
+ // Frozen state for transcript mode - stores lengths instead of cloning arrays for memory efficiency
1135
+ const [frozenTranscriptState, setFrozenTranscriptState] = useState(null);
1136
+ // Initialize input with any early input that was captured before REPL was ready.
1137
+ // Using lazy initialization ensures cursor offset is set correctly in PromptInput.
1138
+ const [inputValue, setInputValueRaw] = useState(() => consumeEarlyInput());
1139
+ const inputValueRef = useRef(inputValue);
1140
+ inputValueRef.current = inputValue;
1141
+ const insertTextRef = useRef(null);
1142
+ // Wrap setInputValue to co-locate suppression state updates.
1143
+ // Both setState calls happen in the same synchronous context so React
1144
+ // batches them into a single render, eliminating the extra render that
1145
+ // the previous useEffect → setState pattern caused.
1146
+ const setInputValue = useCallback((value) => {
1147
+ if (trySuggestBgPRIntercept(inputValueRef.current, value))
1148
+ return;
1149
+ // In fullscreen mode, typing into an empty prompt re-pins scroll to
1150
+ // bottom. Only fires on empty→non-empty so scrolling up to reference
1151
+ // something while composing a message doesn't yank the view back on
1152
+ // every keystroke. Restores the pre-fullscreen muscle memory of
1153
+ // typing to snap back to the end of the conversation.
1154
+ // Skipped if the user scrolled within the last 3s — they're actively
1155
+ // reading, not lost. lastUserScrollTsRef starts at 0 so the first-
1156
+ // ever keypress (no scroll yet) always repins.
1157
+ if (inputValueRef.current === '' && value !== '' && Date.now() - lastUserScrollTsRef.current >= RECENT_SCROLL_REPIN_WINDOW_MS) {
1158
+ repinScroll();
1159
+ }
1160
+ // Sync ref immediately (like setMessages) so callers that read
1161
+ // inputValueRef before React commits — e.g. the auto-restore finally
1162
+ // block's `=== ''` guard — see the fresh value, not the stale render.
1163
+ inputValueRef.current = value;
1164
+ setInputValueRaw(value);
1165
+ setIsPromptInputActive(value.trim().length > 0);
1166
+ }, [setIsPromptInputActive, repinScroll, trySuggestBgPRIntercept]);
1167
+ // Schedule a timeout to stop suppressing dialogs after the user stops typing.
1168
+ // Only manages the timeout — the immediate activation is handled by setInputValue above.
1169
+ useEffect(() => {
1170
+ if (inputValue.trim().length === 0)
1171
+ return;
1172
+ const timer = setTimeout(setIsPromptInputActive, PROMPT_SUPPRESSION_MS, false);
1173
+ return () => clearTimeout(timer);
1174
+ }, [inputValue]);
1175
+ const [inputMode, setInputMode] = useState('prompt');
1176
+ const [stashedPrompt, setStashedPrompt] = useState();
1177
+ // Callback to filter commands based on CCR's available slash commands
1178
+ const handleRemoteInit = useCallback((remoteSlashCommands) => {
1179
+ const remoteCommandSet = new Set(remoteSlashCommands);
1180
+ // Keep commands that CCR lists OR that are in the local-safe set
1181
+ setLocalCommands(prev => prev.filter(cmd => remoteCommandSet.has(cmd.name) || REMOTE_SAFE_COMMANDS.has(cmd)));
1182
+ }, [setLocalCommands]);
1183
+ const [inProgressToolUseIDs, setInProgressToolUseIDs] = useState(new Set());
1184
+ const hasInterruptibleToolInProgressRef = useRef(false);
1185
+ // Remote session hook - manages WebSocket connection and message handling for --remote mode
1186
+ const remoteSession = useRemoteSession({
1187
+ config: remoteSessionConfig,
1188
+ setMessages,
1189
+ setIsLoading: setIsExternalLoading,
1190
+ onInit: handleRemoteInit,
1191
+ setToolUseConfirmQueue,
1192
+ tools: combinedInitialTools,
1193
+ setStreamingToolUses,
1194
+ setStreamMode,
1195
+ setInProgressToolUseIDs
1196
+ });
1197
+ // Direct connect hook - manages WebSocket to a thaddeus server for `thaddeus connect` mode
1198
+ const directConnect = useDirectConnect({
1199
+ config: directConnectConfig,
1200
+ setMessages,
1201
+ setIsLoading: setIsExternalLoading,
1202
+ setToolUseConfirmQueue,
1203
+ tools: combinedInitialTools
1204
+ });
1205
+ // SSH session hook - manages ssh child process for `thaddeus ssh` mode.
1206
+ // Same callback shape as useDirectConnect; only the transport under the
1207
+ // hood differs (ChildProcess stdin/stdout vs WebSocket).
1208
+ const sshRemote = useSSHSession({
1209
+ session: sshSession,
1210
+ setMessages,
1211
+ setIsLoading: setIsExternalLoading,
1212
+ setToolUseConfirmQueue,
1213
+ tools: combinedInitialTools
1214
+ });
1215
+ // Use whichever remote mode is active
1216
+ const activeRemote = sshRemote.isRemoteMode ? sshRemote : directConnect.isRemoteMode ? directConnect : remoteSession;
1217
+ const [pastedContents, setPastedContents] = useState({});
1218
+ const [submitCount, setSubmitCount] = useState(0);
1219
+ // Ref instead of state to avoid triggering React re-renders on every
1220
+ // streaming text_delta. The spinner reads this via its animation timer.
1221
+ const responseLengthRef = useRef(0);
1222
+ // API performance metrics ref for ant-only spinner display (TTFT/OTPS).
1223
+ // Accumulates metrics from all API requests in a turn for P50 aggregation.
1224
+ const apiMetricsRef = useRef([]);
1225
+ const setResponseLength = useCallback((f) => {
1226
+ const prev = responseLengthRef.current;
1227
+ responseLengthRef.current = f(prev);
1228
+ // When content is added (not a compaction reset), update the latest
1229
+ // metrics entry so OTPS reflects all content generation activity.
1230
+ // Updating lastTokenTime here ensures the denominator includes both
1231
+ // streaming time AND subagent execution time, preventing inflation.
1232
+ if (responseLengthRef.current > prev) {
1233
+ const entries = apiMetricsRef.current;
1234
+ if (entries.length > 0) {
1235
+ const lastEntry = entries.at(-1);
1236
+ lastEntry.lastTokenTime = Date.now();
1237
+ lastEntry.endResponseLength = responseLengthRef.current;
1238
+ }
1239
+ }
1240
+ }, []);
1241
+ // Streaming text display: set state directly per delta (Ink's 16ms render
1242
+ // throttle batches rapid updates). Cleared on message arrival (messages.ts)
1243
+ // so displayedMessages switches from deferredMessages to messages atomically.
1244
+ const [streamingText, setStreamingText] = useState(null);
1245
+ const reducedMotion = useAppState(s => s.settings.prefersReducedMotion) ?? false;
1246
+ const showStreamingText = !reducedMotion && !hasCursorUpViewportYankBug();
1247
+ const onStreamingText = useCallback((f) => {
1248
+ if (!showStreamingText)
1249
+ return;
1250
+ setStreamingText(f);
1251
+ }, [showStreamingText]);
1252
+ // Hide the in-progress source line so text streams line-by-line, not
1253
+ // char-by-char. lastIndexOf returns -1 when no newline, giving '' → null.
1254
+ // Guard on showStreamingText so toggling reducedMotion mid-stream
1255
+ // immediately hides the streaming preview.
1256
+ const visibleStreamingText = streamingText && showStreamingText ? streamingText.substring(0, streamingText.lastIndexOf('\n') + 1) || null : null;
1257
+ const [lastQueryCompletionTime, setLastQueryCompletionTime] = useState(0);
1258
+ const [spinnerMessage, setSpinnerMessage] = useState(null);
1259
+ const [spinnerColor, setSpinnerColor] = useState(null);
1260
+ const [spinnerShimmerColor, setSpinnerShimmerColor] = useState(null);
1261
+ const [isMessageSelectorVisible, setIsMessageSelectorVisible] = useState(false);
1262
+ const [messageSelectorPreselect, setMessageSelectorPreselect] = useState(undefined);
1263
+ const [showCostDialog, setShowCostDialog] = useState(false);
1264
+ const [conversationId, setConversationId] = useState(randomUUID());
1265
+ // Idle-return dialog: shown when user submits after a long idle gap
1266
+ const [idleReturnPending, setIdleReturnPending] = useState(null);
1267
+ const skipIdleCheckRef = useRef(false);
1268
+ const lastQueryCompletionTimeRef = useRef(lastQueryCompletionTime);
1269
+ lastQueryCompletionTimeRef.current = lastQueryCompletionTime;
1270
+ // Aggregate tool result budget: per-conversation decision tracking.
1271
+ // When the GrowthBook flag is on, query.ts enforces the budget; when
1272
+ // off (undefined), enforcement is skipped entirely. Stale entries after
1273
+ // /clear, rewind, or compact are harmless (tool_use_ids are UUIDs, stale
1274
+ // keys are never looked up). Memory is bounded by total replacement count
1275
+ // × ~2KB preview over the REPL lifetime — negligible.
1276
+ //
1277
+ // Lazy init via useState initializer — useRef(expr) evaluates expr on every
1278
+ // render (React ignores it after first, but the computation still runs).
1279
+ // For large resumed sessions, reconstruction does O(messages × blocks)
1280
+ // work; we only want that once.
1281
+ const [contentReplacementStateRef] = useState(() => ({
1282
+ current: provisionContentReplacementState(initialMessages, initialContentReplacements)
1283
+ }));
1284
+ const [haveShownCostDialog, setHaveShownCostDialog] = useState(getGlobalConfig().hasAcknowledgedCostThreshold);
1285
+ const [vimMode, setVimMode] = useState('INSERT');
1286
+ const [showBashesDialog, setShowBashesDialog] = useState(false);
1287
+ const [isSearchingHistory, setIsSearchingHistory] = useState(false);
1288
+ const [isHelpOpen, setIsHelpOpen] = useState(false);
1289
+ // showBashesDialog is REPL-level so it survives PromptInput unmounting.
1290
+ // When ultraplan approval fires while the pill dialog is open, PromptInput
1291
+ // unmounts (focusedInputDialog → 'ultraplan-choice') but this stays true;
1292
+ // after accepting, PromptInput remounts into an empty "No tasks" dialog
1293
+ // (the completed ultraplan task has been filtered out). Close it here.
1294
+ useEffect(() => {
1295
+ if (ultraplanPendingChoice && showBashesDialog) {
1296
+ setShowBashesDialog(false);
1297
+ }
1298
+ }, [ultraplanPendingChoice, showBashesDialog]);
1299
+ // Defensive: reset stale isSearchingHistory when AI response completes.
1300
+ // If a rendering error or unmount/remount cycle during the response left
1301
+ // the history-search flag stuck, this auto-recovers input focus.
1302
+ const prevIsLoadingRef = useRef(false);
1303
+ useEffect(() => {
1304
+ if (prevIsLoadingRef.current && !isLoading && isSearchingHistory) {
1305
+ setIsSearchingHistory(false);
1306
+ }
1307
+ prevIsLoadingRef.current = isLoading;
1308
+ }, [isLoading, isSearchingHistory]);
1309
+ const isTerminalFocused = useTerminalFocus();
1310
+ const terminalFocusRef = useRef(isTerminalFocused);
1311
+ terminalFocusRef.current = isTerminalFocused;
1312
+ const [theme] = useTheme();
1313
+ // resetLoadingState runs twice per turn (onQueryImpl tail + onQuery finally).
1314
+ // Without this guard, both calls pick a tip → two recordShownTip → two
1315
+ // saveGlobalConfig writes back-to-back. Reset at submit in onSubmit.
1316
+ const tipPickedThisTurnRef = React.useRef(false);
1317
+ const pickNewSpinnerTip = useCallback(() => {
1318
+ if (tipPickedThisTurnRef.current)
1319
+ return;
1320
+ tipPickedThisTurnRef.current = true;
1321
+ const newMessages = messagesRef.current.slice(bashToolsProcessedIdx.current);
1322
+ for (const tool of extractBashToolsFromMessages(newMessages)) {
1323
+ bashTools.current.add(tool);
1324
+ }
1325
+ bashToolsProcessedIdx.current = messagesRef.current.length;
1326
+ void getTipToShowOnSpinner({
1327
+ theme,
1328
+ readFileState: readFileState.current,
1329
+ bashTools: bashTools.current
1330
+ }).then(async (tip) => {
1331
+ if (tip) {
1332
+ const content = await tip.content({
1333
+ theme
1334
+ });
1335
+ setAppState(prev => ({
1336
+ ...prev,
1337
+ spinnerTip: content
1338
+ }));
1339
+ recordShownTip(tip);
1340
+ }
1341
+ else {
1342
+ setAppState(prev => {
1343
+ if (prev.spinnerTip === undefined)
1344
+ return prev;
1345
+ return {
1346
+ ...prev,
1347
+ spinnerTip: undefined
1348
+ };
1349
+ });
1350
+ }
1351
+ });
1352
+ }, [setAppState, theme]);
1353
+ // Resets UI loading state. Does NOT call onTurnComplete - that should be
1354
+ // called explicitly only when a query turn actually completes.
1355
+ const resetLoadingState = useCallback(() => {
1356
+ // isLoading is now derived from queryGuard — no setter call needed.
1357
+ // queryGuard.end() (onQuery finally) or cancelReservation() (executeUserInput
1358
+ // finally) have already transitioned the guard to idle by the time this runs.
1359
+ // External loading (remote/backgrounding) is reset separately by those hooks.
1360
+ setIsExternalLoading(false);
1361
+ setUserInputOnProcessing(undefined);
1362
+ responseLengthRef.current = 0;
1363
+ apiMetricsRef.current = [];
1364
+ setStreamingText(null);
1365
+ setStreamingToolUses([]);
1366
+ setSpinnerMessage(null);
1367
+ setSpinnerColor(null);
1368
+ setSpinnerShimmerColor(null);
1369
+ pickNewSpinnerTip();
1370
+ endInteractionSpan();
1371
+ // Speculative bash classifier checks are only valid for the current
1372
+ // turn's commands — clear after each turn to avoid accumulating
1373
+ // Promise chains for unconsumed checks (denied/aborted paths).
1374
+ clearSpeculativeChecks();
1375
+ }, [pickNewSpinnerTip]);
1376
+ // Session backgrounding — hook is below, after getToolUseContext
1377
+ const hasRunningTeammates = useMemo(() => getAllInProcessTeammateTasks(tasks).some(t => t.status === 'running'), [tasks]);
1378
+ // Show deferred turn duration message once all swarm teammates finish
1379
+ useEffect(() => {
1380
+ if (!hasRunningTeammates && swarmStartTimeRef.current !== null) {
1381
+ const totalMs = Date.now() - swarmStartTimeRef.current;
1382
+ const deferredBudget = swarmBudgetInfoRef.current;
1383
+ swarmStartTimeRef.current = null;
1384
+ swarmBudgetInfoRef.current = undefined;
1385
+ setMessages(prev => [...prev, createTurnDurationMessage(totalMs, deferredBudget,
1386
+ // Count only what recordTranscript will persist — ephemeral
1387
+ // progress ticks and non-ant attachments are filtered by
1388
+ // isLoggableMessage and never reach disk. Using raw prev.length
1389
+ // would make checkResumeConsistency report false delta<0 for
1390
+ // every turn that ran a progress-emitting tool.
1391
+ count(prev, isLoggableMessage))]);
1392
+ }
1393
+ }, [hasRunningTeammates, setMessages]);
1394
+ // Show auto permissions warning when entering auto mode
1395
+ // (either via Shift+Tab toggle or on startup). Debounced to avoid
1396
+ // flashing when the user is cycling through modes quickly.
1397
+ // Only shown 3 times total across sessions.
1398
+ const safeYoloMessageShownRef = useRef(false);
1399
+ useEffect(() => {
1400
+ if (feature('TRANSCRIPT_CLASSIFIER')) {
1401
+ if (toolPermissionContext.mode !== 'auto') {
1402
+ safeYoloMessageShownRef.current = false;
1403
+ return;
1404
+ }
1405
+ if (safeYoloMessageShownRef.current)
1406
+ return;
1407
+ const config = getGlobalConfig();
1408
+ const count = config.autoPermissionsNotificationCount ?? 0;
1409
+ if (count >= 3)
1410
+ return;
1411
+ const timer = setTimeout((ref, setMessages) => {
1412
+ ref.current = true;
1413
+ saveGlobalConfig(prev => {
1414
+ const prevCount = prev.autoPermissionsNotificationCount ?? 0;
1415
+ if (prevCount >= 3)
1416
+ return prev;
1417
+ return {
1418
+ ...prev,
1419
+ autoPermissionsNotificationCount: prevCount + 1
1420
+ };
1421
+ });
1422
+ setMessages(prev => [...prev, createSystemMessage(AUTO_MODE_DESCRIPTION, 'warning')]);
1423
+ }, 800, safeYoloMessageShownRef, setMessages);
1424
+ return () => clearTimeout(timer);
1425
+ }
1426
+ }, [toolPermissionContext.mode, setMessages]);
1427
+ // If worktree creation was slow and sparse-checkout isn't configured,
1428
+ // nudge the user toward settings.worktree.sparsePaths.
1429
+ const worktreeTipShownRef = useRef(false);
1430
+ useEffect(() => {
1431
+ if (worktreeTipShownRef.current)
1432
+ return;
1433
+ const wt = getCurrentWorktreeSession();
1434
+ if (!wt?.creationDurationMs || wt.usedSparsePaths)
1435
+ return;
1436
+ if (wt.creationDurationMs < 15_000)
1437
+ return;
1438
+ worktreeTipShownRef.current = true;
1439
+ const secs = Math.round(wt.creationDurationMs / 1000);
1440
+ setMessages(prev => [...prev, createSystemMessage(`Worktree creation took ${secs}s. For large repos, set \`worktree.sparsePaths\` in .claude/settings.json to check out only the directories you need — e.g. \`{"worktree": {"sparsePaths": ["src", "packages/foo"]}}\`.`, 'info')]);
1441
+ }, [setMessages]);
1442
+ // Hide spinner when the only in-progress tool is Sleep
1443
+ const onlySleepToolActive = useMemo(() => {
1444
+ const lastAssistant = messages.findLast(m => m.type === 'assistant');
1445
+ if (lastAssistant?.type !== 'assistant')
1446
+ return false;
1447
+ const inProgressToolUses = lastAssistant.message.content.filter(b => b.type === 'tool_use' && inProgressToolUseIDs.has(b.id));
1448
+ return inProgressToolUses.length > 0 && inProgressToolUses.every(b => b.type === 'tool_use' && b.name === SLEEP_TOOL_NAME);
1449
+ }, [messages, inProgressToolUseIDs]);
1450
+ const { onBeforeQuery: mrOnBeforeQuery, onTurnComplete: mrOnTurnComplete, render: mrRender } = useMoreRight({
1451
+ enabled: moreRightEnabled,
1452
+ setMessages,
1453
+ inputValue,
1454
+ setInputValue,
1455
+ setToolJSX
1456
+ });
1457
+ const showSpinner = (!toolJSX || toolJSX.showSpinner === true) && toolUseConfirmQueue.length === 0 && promptQueue.length === 0 && (
1458
+ // Show spinner during input processing, API call, while teammates are running,
1459
+ // or while pending task notifications are queued (prevents spinner bounce between consecutive notifications)
1460
+ isLoading || userInputOnProcessing || hasRunningTeammates ||
1461
+ // Keep spinner visible while task notifications are queued for processing.
1462
+ // Without this, the spinner briefly disappears between consecutive notifications
1463
+ // (e.g., multiple background agents completing in rapid succession) because
1464
+ // isLoading goes false momentarily between processing each one.
1465
+ getCommandQueueLength() > 0) &&
1466
+ // Hide spinner when waiting for leader to approve permission request
1467
+ !pendingWorkerRequest && !onlySleepToolActive && (
1468
+ // Hide spinner when streaming text is visible (the text IS the feedback),
1469
+ // but keep it when isBriefOnly suppresses the streaming text display
1470
+ !visibleStreamingText || isBriefOnly);
1471
+ // Check if any permission or ask question prompt is currently visible
1472
+ // This is used to prevent the survey from opening while prompts are active
1473
+ const hasActivePrompt = toolUseConfirmQueue.length > 0 || promptQueue.length > 0 || sandboxPermissionRequestQueue.length > 0 || elicitation.queue.length > 0 || workerSandboxPermissions.queue.length > 0;
1474
+ const feedbackSurveyOriginal = useFeedbackSurvey(messages, isLoading, submitCount, 'session', hasActivePrompt);
1475
+ const skillImprovementSurvey = useSkillImprovementSurvey(setMessages);
1476
+ const showIssueFlagBanner = useIssueFlagBanner(messages, submitCount);
1477
+ // Wrap feedback survey handler to trigger auto-run /issue
1478
+ const feedbackSurvey = useMemo(() => ({
1479
+ ...feedbackSurveyOriginal,
1480
+ handleSelect: (selected) => {
1481
+ // Reset the ref when a new survey response comes in
1482
+ didAutoRunIssueRef.current = false;
1483
+ const showedTranscriptPrompt = feedbackSurveyOriginal.handleSelect(selected);
1484
+ // Auto-run /issue for "bad" if transcript prompt wasn't shown
1485
+ if (selected === 'bad' && !showedTranscriptPrompt && shouldAutoRunIssue('feedback_survey_bad')) {
1486
+ setAutoRunIssueReason('feedback_survey_bad');
1487
+ didAutoRunIssueRef.current = true;
1488
+ }
1489
+ }
1490
+ }), [feedbackSurveyOriginal]);
1491
+ // Post-compact survey: shown after compaction if feature gate is enabled
1492
+ const postCompactSurvey = usePostCompactSurvey(messages, isLoading, hasActivePrompt, {
1493
+ enabled: !isRemoteSession
1494
+ });
1495
+ // Memory survey: shown when the assistant mentions memory and a memory file
1496
+ // was read this conversation
1497
+ const memorySurvey = useMemorySurvey(messages, isLoading, hasActivePrompt, {
1498
+ enabled: !isRemoteSession
1499
+ });
1500
+ // Frustration detection: show transcript sharing prompt after detecting frustrated messages
1501
+ const frustrationDetection = useFrustrationDetection(messages, isLoading, hasActivePrompt, feedbackSurvey.state !== 'closed' || postCompactSurvey.state !== 'closed' || memorySurvey.state !== 'closed');
1502
+ // Initialize IDE integration
1503
+ useIDEIntegration({
1504
+ autoConnectIdeFlag,
1505
+ ideToInstallExtension,
1506
+ setDynamicMcpConfig,
1507
+ setShowIdeOnboarding,
1508
+ setIDEInstallationState: setIDEInstallationStatus
1509
+ });
1510
+ useFileHistorySnapshotInit(initialFileHistorySnapshots, fileHistory, fileHistoryState => setAppState(prev => ({
1511
+ ...prev,
1512
+ fileHistory: fileHistoryState
1513
+ })));
1514
+ const resume = useCallback(async (sessionId, log, entrypoint) => {
1515
+ const resumeStart = performance.now();
1516
+ try {
1517
+ // Deserialize messages to properly clean up the conversation
1518
+ // This filters unresolved tool uses and adds a synthetic assistant message if needed
1519
+ const messages = deserializeMessages(log.messages);
1520
+ // Match coordinator/normal mode to the resumed session
1521
+ if (feature('COORDINATOR_MODE')) {
1522
+ /* eslint-disable @typescript-eslint/no-require-imports */
1523
+ const coordinatorModule = require('../coordinator/coordinatorMode.js');
1524
+ /* eslint-enable @typescript-eslint/no-require-imports */
1525
+ const warning = coordinatorModule.matchSessionMode(log.mode);
1526
+ if (warning) {
1527
+ // Re-derive agent definitions after mode switch so built-in agents
1528
+ // reflect the new coordinator/normal mode
1529
+ /* eslint-disable @typescript-eslint/no-require-imports */
1530
+ const { getAgentDefinitionsWithOverrides, getActiveAgentsFromList } = require('../tools/AgentTool/loadAgentsDir.js');
1531
+ /* eslint-enable @typescript-eslint/no-require-imports */
1532
+ getAgentDefinitionsWithOverrides.cache.clear?.();
1533
+ const freshAgentDefs = await getAgentDefinitionsWithOverrides(getOriginalCwd());
1534
+ setAppState(prev => ({
1535
+ ...prev,
1536
+ agentDefinitions: {
1537
+ ...freshAgentDefs,
1538
+ allAgents: freshAgentDefs.allAgents,
1539
+ activeAgents: getActiveAgentsFromList(freshAgentDefs.allAgents)
1540
+ }
1541
+ }));
1542
+ messages.push(createSystemMessage(warning, 'warning'));
1543
+ }
1544
+ }
1545
+ // Fire SessionEnd hooks for the current session before starting the
1546
+ // resumed one, mirroring the /clear flow in conversation.ts.
1547
+ const sessionEndTimeoutMs = getSessionEndHookTimeoutMs();
1548
+ await executeSessionEndHooks('resume', {
1549
+ getAppState: () => store.getState(),
1550
+ setAppState,
1551
+ signal: AbortSignal.timeout(sessionEndTimeoutMs),
1552
+ timeoutMs: sessionEndTimeoutMs
1553
+ });
1554
+ // Process session start hooks for resume
1555
+ const hookMessages = await processSessionStartHooks('resume', {
1556
+ sessionId,
1557
+ agentType: mainThreadAgentDefinition?.agentType,
1558
+ model: mainLoopModel
1559
+ });
1560
+ // Append hook messages to the conversation
1561
+ messages.push(...hookMessages);
1562
+ // For forks, generate a new plan slug and copy the plan content so the
1563
+ // original and forked sessions don't clobber each other's plan files.
1564
+ // For regular resumes, reuse the original session's plan slug.
1565
+ if (entrypoint === 'fork') {
1566
+ void copyPlanForFork(log, asSessionId(sessionId));
1567
+ }
1568
+ else {
1569
+ void copyPlanForResume(log, asSessionId(sessionId));
1570
+ }
1571
+ // Restore file history and attribution state from the resumed conversation
1572
+ restoreSessionStateFromLog(log, setAppState);
1573
+ if (log.fileHistorySnapshots) {
1574
+ void copyFileHistoryForResume(log);
1575
+ }
1576
+ // Restore agent setting from the resumed conversation
1577
+ // Always reset to the new session's values (or clear if none),
1578
+ // matching the standaloneAgentContext pattern below
1579
+ const { agentDefinition: restoredAgent } = restoreAgentFromSession(log.agentSetting, initialMainThreadAgentDefinition, agentDefinitions);
1580
+ setMainThreadAgentDefinition(restoredAgent);
1581
+ setAppState(prev => ({
1582
+ ...prev,
1583
+ agent: restoredAgent?.agentType
1584
+ }));
1585
+ // Restore standalone agent context from the resumed conversation
1586
+ // Always reset to the new session's values (or clear if none)
1587
+ setAppState(prev => ({
1588
+ ...prev,
1589
+ standaloneAgentContext: computeStandaloneAgentContext(log.agentName, log.agentColor)
1590
+ }));
1591
+ void updateSessionName(log.agentName);
1592
+ // Restore read file state from the message history
1593
+ restoreReadFileState(messages, log.projectPath ?? getOriginalCwd());
1594
+ // Clear any active loading state (no queryId since we're not in a query)
1595
+ resetLoadingState();
1596
+ setAbortController(null);
1597
+ setConversationId(sessionId);
1598
+ // Get target session's costs BEFORE saving current session
1599
+ // (saveCurrentSessionCosts overwrites the config, so we need to read first)
1600
+ const targetSessionCosts = getStoredSessionCosts(sessionId);
1601
+ // Save current session's costs before switching to avoid losing accumulated costs
1602
+ saveCurrentSessionCosts();
1603
+ // Reset cost state for clean slate before restoring target session
1604
+ resetCostState();
1605
+ // Switch session (id + project dir atomically). fullPath may point to
1606
+ // a different project (cross-worktree, /branch); null derives from
1607
+ // current originalCwd.
1608
+ switchSession(asSessionId(sessionId), log.fullPath ? dirname(log.fullPath) : null);
1609
+ // Rename asciicast recording to match the resumed session ID
1610
+ const { renameRecordingForSession } = await import('../utils/asciicast.js');
1611
+ await renameRecordingForSession();
1612
+ await resetSessionFilePointer();
1613
+ // Clear then restore session metadata so it's re-appended on exit via
1614
+ // reAppendSessionMetadata. clearSessionMetadata must be called first:
1615
+ // restoreSessionMetadata only sets-if-truthy, so without the clear,
1616
+ // a session without an agent name would inherit the previous session's
1617
+ // cached name and write it to the wrong transcript on first message.
1618
+ clearSessionMetadata();
1619
+ restoreSessionMetadata(log);
1620
+ // Resumed sessions shouldn't re-title from mid-conversation context
1621
+ // (same reasoning as the useRef seed), and the previous session's
1622
+ // Haiku title shouldn't carry over.
1623
+ haikuTitleAttemptedRef.current = true;
1624
+ setHaikuTitle(undefined);
1625
+ // Exit any worktree a prior /resume entered, then cd into the one
1626
+ // this session was in. Without the exit, resuming from worktree B
1627
+ // to non-worktree C leaves cwd/currentWorktreeSession stale;
1628
+ // resuming B→C where C is also a worktree fails entirely
1629
+ // (getCurrentWorktreeSession guard blocks the switch).
1630
+ //
1631
+ // Skipped for /branch: forkLog doesn't carry worktreeSession, so
1632
+ // this would kick the user out of a worktree they're still working
1633
+ // in. Same fork skip as processResumedConversation for the adopt —
1634
+ // fork materializes its own file via recordTranscript on REPL mount.
1635
+ if (entrypoint !== 'fork') {
1636
+ exitRestoredWorktree();
1637
+ restoreWorktreeForResume(log.worktreeSession);
1638
+ adoptResumedSessionFile();
1639
+ void restoreRemoteAgentTasks({
1640
+ abortController: new AbortController(),
1641
+ getAppState: () => store.getState(),
1642
+ setAppState
1643
+ });
1644
+ }
1645
+ else {
1646
+ // Fork: same re-persist as /clear (conversation.ts). The clear
1647
+ // above wiped currentSessionWorktree, forkLog doesn't carry it,
1648
+ // and the process is still in the same worktree.
1649
+ const ws = getCurrentWorktreeSession();
1650
+ if (ws)
1651
+ saveWorktreeState(ws);
1652
+ }
1653
+ // Persist the current mode so future resumes know what mode this session was in
1654
+ if (feature('COORDINATOR_MODE')) {
1655
+ /* eslint-disable @typescript-eslint/no-require-imports */
1656
+ const { saveMode } = require('../utils/sessionStorage.js');
1657
+ const { isCoordinatorMode } = require('../coordinator/coordinatorMode.js');
1658
+ /* eslint-enable @typescript-eslint/no-require-imports */
1659
+ saveMode(isCoordinatorMode() ? 'coordinator' : 'normal');
1660
+ }
1661
+ // Restore target session's costs from the data we read earlier
1662
+ if (targetSessionCosts) {
1663
+ setCostStateForRestore(targetSessionCosts);
1664
+ }
1665
+ // Reconstruct replacement state for the resumed session. Runs after
1666
+ // setSessionId so any NEW replacements post-resume write to the
1667
+ // resumed session's tool-results dir. Gated on ref.current: the
1668
+ // initial mount already read the feature flag, so we don't re-read
1669
+ // it here (mid-session flag flips stay unobservable in both
1670
+ // directions).
1671
+ //
1672
+ // Skipped for in-session /branch: the existing ref is already correct
1673
+ // (branch preserves tool_use_ids), so there's no need to reconstruct.
1674
+ // createFork() does write content-replacement entries to the forked
1675
+ // JSONL with the fork's sessionId, so `claude -r {forkId}` also works.
1676
+ if (contentReplacementStateRef.current && entrypoint !== 'fork') {
1677
+ contentReplacementStateRef.current = reconstructContentReplacementState(messages, log.contentReplacements ?? []);
1678
+ }
1679
+ // Reset messages to the provided initial messages
1680
+ // Use a callback to ensure we're not dependent on stale state
1681
+ setMessages(() => messages);
1682
+ // Clear any active tool JSX
1683
+ setToolJSX(null);
1684
+ // Clear input to ensure no residual state
1685
+ setInputValue('');
1686
+ logEvent('thaddeus_session_resumed', {
1687
+ entrypoint: entrypoint,
1688
+ success: true,
1689
+ resume_duration_ms: Math.round(performance.now() - resumeStart)
1690
+ });
1691
+ }
1692
+ catch (error) {
1693
+ logEvent('thaddeus_session_resumed', {
1694
+ entrypoint: entrypoint,
1695
+ success: false
1696
+ });
1697
+ throw error;
1698
+ }
1699
+ }, [resetLoadingState, setAppState]);
1700
+ // Lazy init: useRef(createX()) would call createX on every render and
1701
+ // discard the result. LRUCache construction inside FileStateCache is
1702
+ // expensive (~170ms), so we use useState's lazy initializer to create
1703
+ // it exactly once, then feed that stable reference into useRef.
1704
+ const [initialReadFileState] = useState(() => createFileStateCacheWithSizeLimit(READ_FILE_STATE_CACHE_SIZE));
1705
+ const readFileState = useRef(initialReadFileState);
1706
+ const bashTools = useRef(new Set());
1707
+ const bashToolsProcessedIdx = useRef(0);
1708
+ // Session-scoped skill discovery tracking (feeds was_discovered on
1709
+ // thaddeus_skill_tool_invocation). Must persist across getToolUseContext
1710
+ // rebuilds within a session: turn-0 discovery writes via processUserInput
1711
+ // before onQuery builds its own context, and discovery on turn N must
1712
+ // still attribute a SkillTool call on turn N+k. Cleared in clearConversation.
1713
+ const discoveredSkillNamesRef = useRef(new Set());
1714
+ // Session-level dedup for nested_memory THADDEUS.md attachments.
1715
+ // readFileState is a 100-entry LRU; once it evicts a THADDEUS.md path,
1716
+ // the next discovery cycle re-injects it. Cleared in clearConversation.
1717
+ const loadedNestedMemoryPathsRef = useRef(new Set());
1718
+ // Helper to restore read file state from messages (used for resume flows)
1719
+ // This allows Thaddeus to edit files that were read in previous sessions
1720
+ const restoreReadFileState = useCallback((messages, cwd) => {
1721
+ const extracted = extractReadFilesFromMessages(messages, cwd, READ_FILE_STATE_CACHE_SIZE);
1722
+ readFileState.current = mergeFileStateCaches(readFileState.current, extracted);
1723
+ for (const tool of extractBashToolsFromMessages(messages)) {
1724
+ bashTools.current.add(tool);
1725
+ }
1726
+ }, []);
1727
+ // Extract read file state from initialMessages on mount
1728
+ // This handles CLI flag resume (--resume-session) and ResumeConversation screen
1729
+ // where messages are passed as props rather than through the resume callback
1730
+ useEffect(() => {
1731
+ if (initialMessages && initialMessages.length > 0) {
1732
+ restoreReadFileState(initialMessages, getOriginalCwd());
1733
+ void restoreRemoteAgentTasks({
1734
+ abortController: new AbortController(),
1735
+ getAppState: () => store.getState(),
1736
+ setAppState
1737
+ });
1738
+ }
1739
+ // Only run on mount - initialMessages shouldn't change during component lifetime
1740
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1741
+ }, []);
1742
+ const { status: apiKeyStatus, reverify } = useApiKeyVerification();
1743
+ // Auto-run /issue state
1744
+ const [autoRunIssueReason, setAutoRunIssueReason] = useState(null);
1745
+ // Ref to track if autoRunIssue was triggered this survey cycle,
1746
+ // so we can suppress the [1] follow-up prompt even after
1747
+ // autoRunIssueReason is cleared.
1748
+ const didAutoRunIssueRef = useRef(false);
1749
+ // State for exit feedback flow
1750
+ const [exitFlow, setExitFlow] = useState(null);
1751
+ const [isExiting, setIsExiting] = useState(false);
1752
+ // Calculate if cost dialog should be shown
1753
+ const showingCostDialog = !isLoading && showCostDialog;
1754
+ // Determine which dialog should have focus (if any)
1755
+ // Permission and interactive dialogs can show even when toolJSX is set,
1756
+ // as long as shouldContinueAnimation is true. This prevents deadlocks when
1757
+ // agents set background hints while waiting for user interaction.
1758
+ function getFocusedInputDialog() {
1759
+ // Exit states always take precedence
1760
+ if (isExiting || exitFlow)
1761
+ return undefined;
1762
+ // High priority dialogs (always show regardless of typing)
1763
+ if (isMessageSelectorVisible)
1764
+ return 'message-selector';
1765
+ // Suppress interrupt dialogs while user is actively typing
1766
+ if (isPromptInputActive)
1767
+ return undefined;
1768
+ if (sandboxPermissionRequestQueue[0])
1769
+ return 'sandbox-permission';
1770
+ // Permission/interactive dialogs (show unless blocked by toolJSX)
1771
+ const allowDialogsWithAnimation = !toolJSX || toolJSX.shouldContinueAnimation;
1772
+ if (allowDialogsWithAnimation && toolUseConfirmQueue[0])
1773
+ return 'tool-permission';
1774
+ if (allowDialogsWithAnimation && promptQueue[0])
1775
+ return 'prompt';
1776
+ // Worker sandbox permission prompts (network access) from swarm workers
1777
+ if (allowDialogsWithAnimation && workerSandboxPermissions.queue[0])
1778
+ return 'worker-sandbox-permission';
1779
+ if (allowDialogsWithAnimation && elicitation.queue[0])
1780
+ return 'elicitation';
1781
+ if (allowDialogsWithAnimation && showingCostDialog)
1782
+ return 'cost';
1783
+ if (allowDialogsWithAnimation && idleReturnPending)
1784
+ return 'idle-return';
1785
+ if (feature('ULTRAPLAN') && allowDialogsWithAnimation && !isLoading && ultraplanPendingChoice)
1786
+ return 'ultraplan-choice';
1787
+ if (feature('ULTRAPLAN') && allowDialogsWithAnimation && !isLoading && ultraplanLaunchPending)
1788
+ return 'ultraplan-launch';
1789
+ // Onboarding dialogs (special conditions)
1790
+ if (allowDialogsWithAnimation && showIdeOnboarding)
1791
+ return 'ide-onboarding';
1792
+ // Model switch callout (ant-only, eliminated from external builds)
1793
+ if ("external" === 'ant' && allowDialogsWithAnimation && showModelSwitchCallout)
1794
+ return 'model-switch';
1795
+ // Undercover auto-enable explainer (ant-only, eliminated from external builds)
1796
+ if ("external" === 'ant' && allowDialogsWithAnimation && showUndercoverCallout)
1797
+ return 'undercover-callout';
1798
+ // Effort callout (shown once for Opus 4.6 users when effort is enabled)
1799
+ if (allowDialogsWithAnimation && showEffortCallout)
1800
+ return 'effort-callout';
1801
+ // Remote callout (shown once before first bridge enable)
1802
+ if (allowDialogsWithAnimation && showRemoteCallout)
1803
+ return 'remote-callout';
1804
+ // LSP plugin recommendation (lowest priority - non-blocking suggestion)
1805
+ if (allowDialogsWithAnimation && lspRecommendation)
1806
+ return 'lsp-recommendation';
1807
+ // Plugin hint from CLI/SDK stderr (same priority band as LSP rec)
1808
+ if (allowDialogsWithAnimation && hintRecommendation)
1809
+ return 'plugin-hint';
1810
+ // Desktop app upsell (max 3 launches, lowest priority)
1811
+ if (allowDialogsWithAnimation && showDesktopUpsellStartup)
1812
+ return 'desktop-upsell';
1813
+ return undefined;
1814
+ }
1815
+ const focusedInputDialog = getFocusedInputDialog();
1816
+ // True when permission prompts exist but are hidden because the user is typing
1817
+ const hasSuppressedDialogs = isPromptInputActive && (sandboxPermissionRequestQueue[0] || toolUseConfirmQueue[0] || promptQueue[0] || workerSandboxPermissions.queue[0] || elicitation.queue[0] || showingCostDialog);
1818
+ // Keep ref in sync so timer callbacks can read the current value
1819
+ focusedInputDialogRef.current = focusedInputDialog;
1820
+ // Immediately capture pause/resume when focusedInputDialog changes
1821
+ // This ensures accurate timing even under high system load, rather than
1822
+ // relying on the 100ms polling interval to detect state changes
1823
+ useEffect(() => {
1824
+ if (!isLoading)
1825
+ return;
1826
+ const isPaused = focusedInputDialog === 'tool-permission';
1827
+ const now = Date.now();
1828
+ if (isPaused && pauseStartTimeRef.current === null) {
1829
+ // Just entered pause state - record the exact moment
1830
+ pauseStartTimeRef.current = now;
1831
+ }
1832
+ else if (!isPaused && pauseStartTimeRef.current !== null) {
1833
+ // Just exited pause state - accumulate paused time immediately
1834
+ totalPausedMsRef.current += now - pauseStartTimeRef.current;
1835
+ pauseStartTimeRef.current = null;
1836
+ }
1837
+ }, [focusedInputDialog, isLoading]);
1838
+ // Re-pin scroll to bottom whenever the permission overlay appears or
1839
+ // dismisses. Overlay now renders below messages inside the same
1840
+ // ScrollBox (no remount), so we need an explicit scrollToBottom for:
1841
+ // - appear: user may have been scrolled up (sticky broken) — the
1842
+ // dialog is blocking and must be visible
1843
+ // - dismiss: user may have scrolled up to read context during the
1844
+ // overlay, and onScroll was suppressed so the pill state is stale
1845
+ // useLayoutEffect so the re-pin commits before the Ink frame renders —
1846
+ // no 1-frame flash of the wrong scroll position.
1847
+ const prevDialogRef = useRef(focusedInputDialog);
1848
+ useLayoutEffect(() => {
1849
+ const was = prevDialogRef.current === 'tool-permission';
1850
+ const now = focusedInputDialog === 'tool-permission';
1851
+ if (was !== now)
1852
+ repinScroll();
1853
+ prevDialogRef.current = focusedInputDialog;
1854
+ }, [focusedInputDialog, repinScroll]);
1855
+ function onCancel() {
1856
+ if (focusedInputDialog === 'elicitation') {
1857
+ // Elicitation dialog handles its own Escape, and closing it shouldn't affect any loading state.
1858
+ return;
1859
+ }
1860
+ logForDebugging(`[onCancel] focusedInputDialog=${focusedInputDialog} streamMode=${streamMode}`);
1861
+ // Pause proactive mode so the user gets control back.
1862
+ // It will resume when they submit their next input (see onSubmit).
1863
+ if (feature('PROACTIVE') || feature('KAIROS')) {
1864
+ proactiveModule?.pauseProactive();
1865
+ }
1866
+ queryGuard.forceEnd();
1867
+ skipIdleCheckRef.current = false;
1868
+ // Preserve partially-streamed text so the user can read what was
1869
+ // generated before pressing Esc. Pushed before resetLoadingState clears
1870
+ // streamingText, and before query.ts yields the async interrupt marker,
1871
+ // giving final order [user, partial-assistant, [Request interrupted by user]].
1872
+ if (streamingText?.trim()) {
1873
+ setMessages(prev => [...prev, createAssistantMessage({
1874
+ content: streamingText
1875
+ })]);
1876
+ }
1877
+ resetLoadingState();
1878
+ // Clear any active token budget so the backstop doesn't fire on
1879
+ // a stale budget if the query generator hasn't exited yet.
1880
+ if (feature('TOKEN_BUDGET')) {
1881
+ snapshotOutputTokensForTurn(null);
1882
+ }
1883
+ if (focusedInputDialog === 'tool-permission') {
1884
+ // Tool use confirm handles the abort signal itself
1885
+ toolUseConfirmQueue[0]?.onAbort();
1886
+ setToolUseConfirmQueue([]);
1887
+ }
1888
+ else if (focusedInputDialog === 'prompt') {
1889
+ // Reject all pending prompts and clear the queue
1890
+ for (const item of promptQueue) {
1891
+ item.reject(new Error('Prompt cancelled by user'));
1892
+ }
1893
+ setPromptQueue([]);
1894
+ abortController?.abort('user-cancel');
1895
+ }
1896
+ else if (activeRemote.isRemoteMode) {
1897
+ // Remote mode: send interrupt signal to CCR
1898
+ activeRemote.cancelRequest();
1899
+ }
1900
+ else {
1901
+ abortController?.abort('user-cancel');
1902
+ }
1903
+ // Clear the controller so subsequent Escape presses don't see a stale
1904
+ // aborted signal. Without this, canCancelRunningTask is false (signal
1905
+ // defined but .aborted === true), so isActive becomes false if no other
1906
+ // activating conditions hold — leaving the Escape keybinding inactive.
1907
+ setAbortController(null);
1908
+ // forceEnd() skips the finally path — fire directly (aborted=true).
1909
+ void mrOnTurnComplete(messagesRef.current, true);
1910
+ }
1911
+ // Function to handle queued command when canceling a permission request
1912
+ const handleQueuedCommandOnCancel = useCallback(() => {
1913
+ const result = popAllEditable(inputValue, 0);
1914
+ if (!result)
1915
+ return;
1916
+ setInputValue(result.text);
1917
+ setInputMode('prompt');
1918
+ // Restore images from queued commands to pastedContents
1919
+ if (result.images.length > 0) {
1920
+ setPastedContents(prev => {
1921
+ const newContents = {
1922
+ ...prev
1923
+ };
1924
+ for (const image of result.images) {
1925
+ newContents[image.id] = image;
1926
+ }
1927
+ return newContents;
1928
+ });
1929
+ }
1930
+ }, [setInputValue, setInputMode, inputValue, setPastedContents]);
1931
+ // CancelRequestHandler props - rendered inside KeybindingSetup
1932
+ const cancelRequestProps = {
1933
+ setToolUseConfirmQueue,
1934
+ onCancel,
1935
+ onAgentsKilled: () => setMessages(prev => [...prev, createAgentsKilledMessage()]),
1936
+ isMessageSelectorVisible: isMessageSelectorVisible || !!showBashesDialog,
1937
+ screen,
1938
+ abortSignal: abortController?.signal,
1939
+ popCommandFromQueue: handleQueuedCommandOnCancel,
1940
+ vimMode,
1941
+ isLocalJSXCommand: toolJSX?.isLocalJSXCommand,
1942
+ isSearchingHistory,
1943
+ isHelpOpen,
1944
+ inputMode,
1945
+ inputValue,
1946
+ streamMode
1947
+ };
1948
+ useEffect(() => {
1949
+ const totalCost = getTotalCost();
1950
+ if (totalCost >= 5 /* $5 */ && !showCostDialog && !haveShownCostDialog) {
1951
+ logEvent('thaddeus_cost_threshold_reached', {});
1952
+ // Mark as shown even if the dialog won't render (no console billing
1953
+ // access). Otherwise this effect re-fires on every message change for
1954
+ // the rest of the session — 200k+ spurious events observed.
1955
+ setHaveShownCostDialog(true);
1956
+ if (hasConsoleBillingAccess()) {
1957
+ setShowCostDialog(true);
1958
+ }
1959
+ }
1960
+ }, [messages, showCostDialog, haveShownCostDialog]);
1961
+ const sandboxAskCallback = useCallback(async (hostPattern) => {
1962
+ // If running as a swarm worker, forward the request to the leader via mailbox
1963
+ if (isAgentSwarmsEnabled() && isSwarmWorker()) {
1964
+ const requestId = generateSandboxRequestId();
1965
+ // Send the request to the leader via mailbox
1966
+ const sent = await sendSandboxPermissionRequestViaMailbox(hostPattern.host, requestId);
1967
+ return new Promise(resolveShouldAllowHost => {
1968
+ if (!sent) {
1969
+ // If we couldn't send via mailbox, fall back to local handling
1970
+ setSandboxPermissionRequestQueue(prev => [...prev, {
1971
+ hostPattern,
1972
+ resolvePromise: resolveShouldAllowHost
1973
+ }]);
1974
+ return;
1975
+ }
1976
+ // Register the callback for when the leader responds
1977
+ registerSandboxPermissionCallback({
1978
+ requestId,
1979
+ host: hostPattern.host,
1980
+ resolve: resolveShouldAllowHost
1981
+ });
1982
+ // Update AppState to show pending indicator
1983
+ setAppState(prev => ({
1984
+ ...prev,
1985
+ pendingSandboxRequest: {
1986
+ requestId,
1987
+ host: hostPattern.host
1988
+ }
1989
+ }));
1990
+ });
1991
+ }
1992
+ // Normal flow for non-workers: show local UI and optionally race
1993
+ // against the REPL bridge (Remote Control) if connected.
1994
+ return new Promise(resolveShouldAllowHost => {
1995
+ let resolved = false;
1996
+ function resolveOnce(allow) {
1997
+ if (resolved)
1998
+ return;
1999
+ resolved = true;
2000
+ resolveShouldAllowHost(allow);
2001
+ }
2002
+ // Queue the local sandbox permission dialog
2003
+ setSandboxPermissionRequestQueue(prev => [...prev, {
2004
+ hostPattern,
2005
+ resolvePromise: resolveOnce
2006
+ }]);
2007
+ // When the REPL bridge is connected, also forward the sandbox
2008
+ // permission request as a can_use_tool control_request so the
2009
+ // remote user (e.g. on the-oracleai.com) can approve it too.
2010
+ if (feature('BRIDGE_MODE')) {
2011
+ const bridgeCallbacks = store.getState().replBridgePermissionCallbacks;
2012
+ if (bridgeCallbacks) {
2013
+ const bridgeRequestId = randomUUID();
2014
+ bridgeCallbacks.sendRequest(bridgeRequestId, SANDBOX_NETWORK_ACCESS_TOOL_NAME, {
2015
+ host: hostPattern.host
2016
+ }, randomUUID(), `Allow network connection to ${hostPattern.host}?`);
2017
+ const unsubscribe = bridgeCallbacks.onResponse(bridgeRequestId, response => {
2018
+ unsubscribe();
2019
+ const allow = response.behavior === 'allow';
2020
+ // Resolve ALL pending requests for the same host, not just
2021
+ // this one — mirrors the local dialog handler pattern.
2022
+ setSandboxPermissionRequestQueue(queue => {
2023
+ queue.filter(item => item.hostPattern.host === hostPattern.host).forEach(item => item.resolvePromise(allow));
2024
+ return queue.filter(item => item.hostPattern.host !== hostPattern.host);
2025
+ });
2026
+ // Clean up all sibling bridge subscriptions for this host
2027
+ // (other concurrent same-host requests) before deleting.
2028
+ const siblingCleanups = sandboxBridgeCleanupRef.current.get(hostPattern.host);
2029
+ if (siblingCleanups) {
2030
+ for (const fn of siblingCleanups) {
2031
+ fn();
2032
+ }
2033
+ sandboxBridgeCleanupRef.current.delete(hostPattern.host);
2034
+ }
2035
+ });
2036
+ // Register cleanup so the local dialog handler can cancel
2037
+ // the remote prompt and unsubscribe when the local user
2038
+ // responds first.
2039
+ const cleanup = () => {
2040
+ unsubscribe();
2041
+ bridgeCallbacks.cancelRequest(bridgeRequestId);
2042
+ };
2043
+ const existing = sandboxBridgeCleanupRef.current.get(hostPattern.host) ?? [];
2044
+ existing.push(cleanup);
2045
+ sandboxBridgeCleanupRef.current.set(hostPattern.host, existing);
2046
+ }
2047
+ }
2048
+ });
2049
+ }, [setAppState, store]);
2050
+ // #34044: if user explicitly set sandbox.enabled=true but deps are missing,
2051
+ // isSandboxingEnabled() returns false silently. Surface the reason once at
2052
+ // mount so users know their security config isn't being enforced. Full
2053
+ // reason goes to debug log; notification points to /sandbox for details.
2054
+ // addNotification is stable (useCallback) so the effect fires once.
2055
+ useEffect(() => {
2056
+ const reason = SandboxManager.getSandboxUnavailableReason();
2057
+ if (!reason)
2058
+ return;
2059
+ if (SandboxManager.isSandboxRequired()) {
2060
+ process.stderr.write(`\nError: sandbox required but unavailable: ${reason}\n` + ` sandbox.failIfUnavailable is set — refusing to start without a working sandbox.\n\n`);
2061
+ gracefulShutdownSync(1, 'other');
2062
+ return;
2063
+ }
2064
+ logForDebugging(`sandbox disabled: ${reason}`, {
2065
+ level: 'warn'
2066
+ });
2067
+ addNotification({
2068
+ key: 'sandbox-unavailable',
2069
+ jsx: _jsxs(_Fragment, { children: [_jsx(Text, { color: "warning", children: "sandbox disabled" }), _jsx(Text, { dimColor: true, children: " \u00B7 /sandbox" })] }),
2070
+ priority: 'medium'
2071
+ });
2072
+ }, [addNotification]);
2073
+ if (SandboxManager.isSandboxingEnabled()) {
2074
+ // If sandboxing is enabled (setting.sandbox is defined, initialise the manager)
2075
+ SandboxManager.initialize(sandboxAskCallback).catch(err => {
2076
+ // Initialization/validation failed - display error and exit
2077
+ process.stderr.write(`\n❌ Sandbox Error: ${errorMessage(err)}\n`);
2078
+ gracefulShutdownSync(1, 'other');
2079
+ });
2080
+ }
2081
+ const setToolPermissionContext = useCallback((context, options) => {
2082
+ setAppState(prev => ({
2083
+ ...prev,
2084
+ toolPermissionContext: {
2085
+ ...context,
2086
+ // Preserve the coordinator's mode only when explicitly requested.
2087
+ // Workers' getAppState() returns a transformed context with mode
2088
+ // 'acceptEdits' that must not leak into the coordinator's actual
2089
+ // state via permission-rule updates — those call sites pass
2090
+ // { preserveMode: true }. User-initiated mode changes (e.g.,
2091
+ // selecting "allow all edits") must NOT be overridden.
2092
+ mode: options?.preserveMode ? prev.toolPermissionContext.mode : context.mode
2093
+ }
2094
+ }));
2095
+ // When permission context changes, recheck all queued items
2096
+ // This handles the case where approving item1 with "don't ask again"
2097
+ // should auto-approve other queued items that now match the updated rules
2098
+ setImmediate(setToolUseConfirmQueue => {
2099
+ // Use setToolUseConfirmQueue callback to get current queue state
2100
+ // instead of capturing it in the closure, to avoid stale closure issues
2101
+ setToolUseConfirmQueue(currentQueue => {
2102
+ currentQueue.forEach(item => {
2103
+ void item.recheckPermission();
2104
+ });
2105
+ return currentQueue;
2106
+ });
2107
+ }, setToolUseConfirmQueue);
2108
+ }, [setAppState, setToolUseConfirmQueue]);
2109
+ // Register the leader's setToolPermissionContext for in-process teammates
2110
+ useEffect(() => {
2111
+ registerLeaderSetToolPermissionContext(setToolPermissionContext);
2112
+ return () => unregisterLeaderSetToolPermissionContext();
2113
+ }, [setToolPermissionContext]);
2114
+ const canUseTool = useCanUseTool(setToolUseConfirmQueue, setToolPermissionContext);
2115
+ const requestPrompt = useCallback((title, toolInputSummary) => (request) => new Promise((resolve, reject) => {
2116
+ setPromptQueue(prev => [...prev, {
2117
+ request,
2118
+ title,
2119
+ toolInputSummary,
2120
+ resolve,
2121
+ reject
2122
+ }]);
2123
+ }), []);
2124
+ const getToolUseContext = useCallback((messages, newMessages, abortController, mainLoopModel) => {
2125
+ // Read mutable values fresh from the store rather than closure-capturing
2126
+ // useAppState() snapshots. Same values today (closure is refreshed by the
2127
+ // render between turns); decouples freshness from React's render cycle for
2128
+ // a future headless conversation loop. Same pattern refreshTools() uses.
2129
+ const s = store.getState();
2130
+ // Compute tools fresh from store.getState() rather than the closure-
2131
+ // captured `tools`. useManageMCPConnections populates appState.mcp
2132
+ // async as servers connect — the store may have newer MCP state than
2133
+ // the closure captured at render time. Also doubles as refreshTools()
2134
+ // for mid-query tool list updates.
2135
+ const computeTools = () => {
2136
+ const state = store.getState();
2137
+ const assembled = assembleToolPool(state.toolPermissionContext, state.mcp.tools);
2138
+ const merged = mergeAndFilterTools(combinedInitialTools, assembled, state.toolPermissionContext.mode);
2139
+ if (!mainThreadAgentDefinition)
2140
+ return merged;
2141
+ return resolveAgentTools(mainThreadAgentDefinition, merged, false, true).resolvedTools;
2142
+ };
2143
+ return {
2144
+ abortController,
2145
+ options: {
2146
+ commands,
2147
+ tools: computeTools(),
2148
+ debug,
2149
+ verbose: s.verbose,
2150
+ mainLoopModel,
2151
+ thinkingConfig: s.thinkingEnabled !== false ? thinkingConfig : {
2152
+ type: 'disabled'
2153
+ },
2154
+ // Merge fresh from store rather than closing over useMergedClients'
2155
+ // memoized output. initialMcpClients is a prop (session-constant).
2156
+ mcpClients: mergeClients(initialMcpClients, s.mcp.clients),
2157
+ mcpResources: s.mcp.resources,
2158
+ ideInstallationStatus: ideInstallationStatus,
2159
+ isNonInteractiveSession: false,
2160
+ dynamicMcpConfig,
2161
+ theme,
2162
+ agentDefinitions: allowedAgentTypes ? {
2163
+ ...s.agentDefinitions,
2164
+ allowedAgentTypes
2165
+ } : s.agentDefinitions,
2166
+ customSystemPrompt,
2167
+ appendSystemPrompt,
2168
+ refreshTools: computeTools
2169
+ },
2170
+ getAppState: () => store.getState(),
2171
+ setAppState,
2172
+ messages,
2173
+ setMessages,
2174
+ updateFileHistoryState(updater) {
2175
+ // Perf: skip the setState when the updater returns the same reference
2176
+ // (e.g. fileHistoryTrackEdit returns `state` when the file is already
2177
+ // tracked). Otherwise every no-op call would notify all store listeners.
2178
+ setAppState(prev => {
2179
+ const updated = updater(prev.fileHistory);
2180
+ if (updated === prev.fileHistory)
2181
+ return prev;
2182
+ return {
2183
+ ...prev,
2184
+ fileHistory: updated
2185
+ };
2186
+ });
2187
+ },
2188
+ updateAttributionState(updater) {
2189
+ setAppState(prev => {
2190
+ const updated = updater(prev.attribution);
2191
+ if (updated === prev.attribution)
2192
+ return prev;
2193
+ return {
2194
+ ...prev,
2195
+ attribution: updated
2196
+ };
2197
+ });
2198
+ },
2199
+ openMessageSelector: () => {
2200
+ if (!disabled) {
2201
+ setIsMessageSelectorVisible(true);
2202
+ }
2203
+ },
2204
+ onChangeAPIKey: reverify,
2205
+ readFileState: readFileState.current,
2206
+ setToolJSX,
2207
+ addNotification,
2208
+ appendSystemMessage: msg => setMessages(prev => [...prev, msg]),
2209
+ sendOSNotification: opts => {
2210
+ void sendNotification(opts, terminal);
2211
+ },
2212
+ onChangeDynamicMcpConfig,
2213
+ onInstallIDEExtension: setIDEToInstallExtension,
2214
+ nestedMemoryAttachmentTriggers: new Set(),
2215
+ loadedNestedMemoryPaths: loadedNestedMemoryPathsRef.current,
2216
+ dynamicSkillDirTriggers: new Set(),
2217
+ discoveredSkillNames: discoveredSkillNamesRef.current,
2218
+ setResponseLength,
2219
+ pushApiMetricsEntry: "external" === 'ant' ? (ttftMs) => {
2220
+ const now = Date.now();
2221
+ const baseline = responseLengthRef.current;
2222
+ apiMetricsRef.current.push({
2223
+ ttftMs,
2224
+ firstTokenTime: now,
2225
+ lastTokenTime: now,
2226
+ responseLengthBaseline: baseline,
2227
+ endResponseLength: baseline
2228
+ });
2229
+ } : undefined,
2230
+ setStreamMode,
2231
+ onCompactProgress: event => {
2232
+ switch (event.type) {
2233
+ case 'hooks_start':
2234
+ setSpinnerColor('claudeBlue_FOR_SYSTEM_SPINNER');
2235
+ setSpinnerShimmerColor('claudeBlueShimmer_FOR_SYSTEM_SPINNER');
2236
+ setSpinnerMessage(event.hookType === 'pre_compact' ? 'Running PreCompact hooks\u2026' : event.hookType === 'post_compact' ? 'Running PostCompact hooks\u2026' : 'Running SessionStart hooks\u2026');
2237
+ break;
2238
+ case 'compact_start':
2239
+ setSpinnerMessage('Compacting conversation');
2240
+ break;
2241
+ case 'compact_end':
2242
+ setSpinnerMessage(null);
2243
+ setSpinnerColor(null);
2244
+ setSpinnerShimmerColor(null);
2245
+ break;
2246
+ }
2247
+ },
2248
+ setInProgressToolUseIDs,
2249
+ setHasInterruptibleToolInProgress: (v) => {
2250
+ hasInterruptibleToolInProgressRef.current = v;
2251
+ },
2252
+ resume,
2253
+ setConversationId,
2254
+ requestPrompt: feature('HOOK_PROMPTS') ? requestPrompt : undefined,
2255
+ contentReplacementState: contentReplacementStateRef.current
2256
+ };
2257
+ }, [commands, combinedInitialTools, mainThreadAgentDefinition, debug, initialMcpClients, ideInstallationStatus, dynamicMcpConfig, theme, allowedAgentTypes, store, setAppState, reverify, addNotification, setMessages, onChangeDynamicMcpConfig, resume, requestPrompt, disabled, customSystemPrompt, appendSystemPrompt, setConversationId]);
2258
+ // Session backgrounding (Ctrl+B to background/foreground)
2259
+ const handleBackgroundQuery = useCallback(() => {
2260
+ // Stop the foreground query so the background one takes over
2261
+ abortController?.abort('background');
2262
+ // Aborting subagents may produce task-completed notifications.
2263
+ // Clear task notifications so the queue processor doesn't immediately
2264
+ // start a new foreground query; forward them to the background session.
2265
+ const removedNotifications = removeByFilter(cmd => cmd.mode === 'task-notification');
2266
+ void (async () => {
2267
+ const toolUseContext = getToolUseContext(messagesRef.current, [], new AbortController(), mainLoopModel);
2268
+ const [defaultSystemPrompt, userContext, systemContext] = await Promise.all([getSystemPrompt(toolUseContext.options.tools, mainLoopModel, Array.from(toolPermissionContext.additionalWorkingDirectories.keys()), toolUseContext.options.mcpClients), getUserContext(), getSystemContext()]);
2269
+ const systemPrompt = buildEffectiveSystemPrompt({
2270
+ mainThreadAgentDefinition,
2271
+ toolUseContext,
2272
+ customSystemPrompt,
2273
+ defaultSystemPrompt,
2274
+ appendSystemPrompt
2275
+ });
2276
+ toolUseContext.renderedSystemPrompt = systemPrompt;
2277
+ const notificationAttachments = await getQueuedCommandAttachments(removedNotifications).catch(() => []);
2278
+ const notificationMessages = notificationAttachments.map(createAttachmentMessage);
2279
+ // Deduplicate: if the query loop already yielded a notification into
2280
+ // messagesRef before we removed it from the queue, skip duplicates.
2281
+ // We use prompt text for dedup because source_uuid is not set on
2282
+ // task-notification QueuedCommands (enqueuePendingNotification callers
2283
+ // don't pass uuid), so it would always be undefined.
2284
+ const existingPrompts = new Set();
2285
+ for (const m of messagesRef.current) {
2286
+ if (m.type === 'attachment' && m.attachment.type === 'queued_command' && m.attachment.commandMode === 'task-notification' && typeof m.attachment.prompt === 'string') {
2287
+ existingPrompts.add(m.attachment.prompt);
2288
+ }
2289
+ }
2290
+ const uniqueNotifications = notificationMessages.filter(m => m.attachment.type === 'queued_command' && (typeof m.attachment.prompt !== 'string' || !existingPrompts.has(m.attachment.prompt)));
2291
+ startBackgroundSession({
2292
+ messages: [...messagesRef.current, ...uniqueNotifications],
2293
+ queryParams: {
2294
+ systemPrompt,
2295
+ userContext,
2296
+ systemContext,
2297
+ canUseTool,
2298
+ toolUseContext,
2299
+ querySource: getQuerySourceForREPL()
2300
+ },
2301
+ description: terminalTitle,
2302
+ setAppState,
2303
+ agentDefinition: mainThreadAgentDefinition
2304
+ });
2305
+ })();
2306
+ }, [abortController, mainLoopModel, toolPermissionContext, mainThreadAgentDefinition, getToolUseContext, customSystemPrompt, appendSystemPrompt, canUseTool, setAppState]);
2307
+ const { handleBackgroundSession } = useSessionBackgrounding({
2308
+ setMessages,
2309
+ setIsLoading: setIsExternalLoading,
2310
+ resetLoadingState,
2311
+ setAbortController,
2312
+ onBackgroundQuery: handleBackgroundQuery
2313
+ });
2314
+ const onQueryEvent = useCallback((event) => {
2315
+ handleMessageFromStream(event, newMessage => {
2316
+ if (isCompactBoundaryMessage(newMessage)) {
2317
+ // Fullscreen: keep pre-compact messages for scrollback. query.ts
2318
+ // slices at the boundary for API calls, Messages.tsx skips the
2319
+ // boundary filter in fullscreen, and useLogMessages treats this
2320
+ // as an incremental append (first uuid unchanged). Cap at one
2321
+ // compact-interval of scrollback — normalizeMessages/applyGrouping
2322
+ // are O(n) per render, so drop everything before the previous
2323
+ // boundary to keep n bounded across multi-day sessions.
2324
+ if (isFullscreenEnvEnabled()) {
2325
+ setMessages(old => [...getMessagesAfterCompactBoundary(old, {
2326
+ includeSnipped: true
2327
+ }), newMessage]);
2328
+ }
2329
+ else {
2330
+ setMessages(() => [newMessage]);
2331
+ }
2332
+ // Bump conversationId so Messages.tsx row keys change and
2333
+ // stale memoized rows remount with post-compact content.
2334
+ setConversationId(randomUUID());
2335
+ // Compaction succeeded — clear the context-blocked flag so ticks resume
2336
+ if (feature('PROACTIVE') || feature('KAIROS')) {
2337
+ proactiveModule?.setContextBlocked(false);
2338
+ }
2339
+ }
2340
+ else if (newMessage.type === 'progress' && isEphemeralToolProgress(newMessage.data.type)) {
2341
+ // Replace the previous ephemeral progress tick for the same tool
2342
+ // call instead of appending. Sleep/Bash emit a tick per second and
2343
+ // only the last one is rendered; appending blows up the messages
2344
+ // array (13k+ observed) and the transcript (120MB of sleep_progress
2345
+ // lines). useLogMessages tracks length, so same-length replacement
2346
+ // also skips the transcript write.
2347
+ // agent_progress / hook_progress / skill_progress are NOT ephemeral
2348
+ // — each carries distinct state the UI needs (e.g. subagent tool
2349
+ // history). Replacing those leaves the AgentTool UI stuck at
2350
+ // "Initializing…" because it renders the full progress trail.
2351
+ setMessages(oldMessages => {
2352
+ const last = oldMessages.at(-1);
2353
+ if (last?.type === 'progress' && last.parentToolUseID === newMessage.parentToolUseID && last.data.type === newMessage.data.type) {
2354
+ const copy = oldMessages.slice();
2355
+ copy[copy.length - 1] = newMessage;
2356
+ return copy;
2357
+ }
2358
+ return [...oldMessages, newMessage];
2359
+ });
2360
+ }
2361
+ else {
2362
+ setMessages(oldMessages => [...oldMessages, newMessage]);
2363
+ }
2364
+ // Block ticks on API errors to prevent tick → error → tick
2365
+ // runaway loops (e.g., auth failure, rate limit, blocking limit).
2366
+ // Cleared on compact boundary (above) or successful response (below).
2367
+ if (feature('PROACTIVE') || feature('KAIROS')) {
2368
+ if (newMessage.type === 'assistant' && 'isApiErrorMessage' in newMessage && newMessage.isApiErrorMessage) {
2369
+ proactiveModule?.setContextBlocked(true);
2370
+ }
2371
+ else if (newMessage.type === 'assistant') {
2372
+ proactiveModule?.setContextBlocked(false);
2373
+ }
2374
+ }
2375
+ }, newContent => {
2376
+ // setResponseLength handles updating both responseLengthRef (for
2377
+ // spinner animation) and apiMetricsRef (endResponseLength/lastTokenTime
2378
+ // for OTPS). No separate metrics update needed here.
2379
+ setResponseLength(length => length + newContent.length);
2380
+ }, setStreamMode, setStreamingToolUses, tombstonedMessage => {
2381
+ setMessages(oldMessages => oldMessages.filter(m => m !== tombstonedMessage));
2382
+ void removeTranscriptMessage(tombstonedMessage.uuid);
2383
+ }, setStreamingThinking, metrics => {
2384
+ const now = Date.now();
2385
+ const baseline = responseLengthRef.current;
2386
+ apiMetricsRef.current.push({
2387
+ ...metrics,
2388
+ firstTokenTime: now,
2389
+ lastTokenTime: now,
2390
+ responseLengthBaseline: baseline,
2391
+ endResponseLength: baseline
2392
+ });
2393
+ }, onStreamingText);
2394
+ }, [setMessages, setResponseLength, setStreamMode, setStreamingToolUses, setStreamingThinking, onStreamingText]);
2395
+ const onQueryImpl = useCallback(async (messagesIncludingNewMessages, newMessages, abortController, shouldQuery, additionalAllowedTools, mainLoopModelParam, effort) => {
2396
+ // Prepare IDE integration for new prompt. Read mcpClients fresh from
2397
+ // store — useManageMCPConnections may have populated it since the
2398
+ // render that captured this closure (same pattern as computeTools).
2399
+ if (shouldQuery) {
2400
+ const freshClients = mergeClients(initialMcpClients, store.getState().mcp.clients);
2401
+ void diagnosticTracker.handleQueryStart(freshClients);
2402
+ const ideClient = getConnectedIdeClient(freshClients);
2403
+ if (ideClient) {
2404
+ void closeOpenDiffs(ideClient);
2405
+ }
2406
+ }
2407
+ // Mark onboarding as complete when any user message is sent to Thaddeus
2408
+ void maybeMarkProjectOnboardingComplete();
2409
+ // Extract a session title from the first real user message. One-shot
2410
+ // via ref (was thaddeus_birch_mist experiment: first-message-only to save
2411
+ // Haiku calls). The ref replaces the old `messages.length <= 1` check,
2412
+ // which was broken by SessionStart hook messages (prepended via
2413
+ // useDeferredHookMessages) and attachment messages (appended by
2414
+ // processTextPrompt) — both pushed length past 1 on turn one, so the
2415
+ // title silently fell through to the "Thaddeus" default.
2416
+ if (!titleDisabled && !sessionTitle && !agentTitle && !haikuTitleAttemptedRef.current) {
2417
+ const firstUserMessage = newMessages.find(m => m.type === 'user' && !m.isMeta);
2418
+ const text = firstUserMessage?.type === 'user' ? getContentText(firstUserMessage.message.content) : null;
2419
+ // Skip synthetic breadcrumbs — slash-command output, prompt-skill
2420
+ // expansions (/commit → <command-message>), local-command headers
2421
+ // (/help → <command-name>), and bash-mode (!cmd → <bash-input>).
2422
+ // None of these are the user's topic; wait for real prose.
2423
+ if (text && !text.startsWith(`<${LOCAL_COMMAND_STDOUT_TAG}>`) && !text.startsWith(`<${COMMAND_MESSAGE_TAG}>`) && !text.startsWith(`<${COMMAND_NAME_TAG}>`) && !text.startsWith(`<${BASH_INPUT_TAG}>`)) {
2424
+ haikuTitleAttemptedRef.current = true;
2425
+ void generateSessionTitle(text, new AbortController().signal).then(title => {
2426
+ if (title)
2427
+ setHaikuTitle(title);
2428
+ else
2429
+ haikuTitleAttemptedRef.current = false;
2430
+ }, () => {
2431
+ haikuTitleAttemptedRef.current = false;
2432
+ });
2433
+ }
2434
+ }
2435
+ // Apply slash-command-scoped allowedTools (from skill frontmatter) to the
2436
+ // store once per turn. This also covers the reset: the next non-skill turn
2437
+ // passes [] and clears it. Must run before the !shouldQuery gate: forked
2438
+ // commands (executeForkedSlashCommand) return shouldQuery=false, and
2439
+ // createGetAppStateWithAllowedTools in forkedAgent.ts reads this field, so
2440
+ // stale skill tools would otherwise leak into forked agent permissions.
2441
+ // Previously this write was hidden inside getToolUseContext's getAppState
2442
+ // (~85 calls/turn); hoisting it here makes getAppState a pure read and stops
2443
+ // ephemeral contexts (permission dialog, BackgroundTasksDialog) from
2444
+ // accidentally clearing it mid-turn.
2445
+ store.setState(prev => {
2446
+ const cur = prev.toolPermissionContext.alwaysAllowRules.command;
2447
+ if (cur === additionalAllowedTools || cur?.length === additionalAllowedTools.length && cur.every((v, i) => v === additionalAllowedTools[i])) {
2448
+ return prev;
2449
+ }
2450
+ return {
2451
+ ...prev,
2452
+ toolPermissionContext: {
2453
+ ...prev.toolPermissionContext,
2454
+ alwaysAllowRules: {
2455
+ ...prev.toolPermissionContext.alwaysAllowRules,
2456
+ command: additionalAllowedTools
2457
+ }
2458
+ }
2459
+ };
2460
+ });
2461
+ // The last message is an assistant message if the user input was a bash command,
2462
+ // or if the user input was an invalid slash command.
2463
+ if (!shouldQuery) {
2464
+ // Manual /compact sets messages directly (shouldQuery=false) bypassing
2465
+ // handleMessageFromStream. Clear context-blocked if a compact boundary
2466
+ // is present so proactive ticks resume after compaction.
2467
+ if (newMessages.some(isCompactBoundaryMessage)) {
2468
+ // Bump conversationId so Messages.tsx row keys change and
2469
+ // stale memoized rows remount with post-compact content.
2470
+ setConversationId(randomUUID());
2471
+ if (feature('PROACTIVE') || feature('KAIROS')) {
2472
+ proactiveModule?.setContextBlocked(false);
2473
+ }
2474
+ }
2475
+ resetLoadingState();
2476
+ setAbortController(null);
2477
+ return;
2478
+ }
2479
+ const toolUseContext = getToolUseContext(messagesIncludingNewMessages, newMessages, abortController, mainLoopModelParam);
2480
+ // getToolUseContext reads tools/mcpClients fresh from store.getState()
2481
+ // (via computeTools/mergeClients). Use those rather than the closure-
2482
+ // captured `tools`/`mcpClients` — useManageMCPConnections may have
2483
+ // flushed new MCP state between the render that captured this closure
2484
+ // and now. Turn 1 via processInitialMessage is the main beneficiary.
2485
+ const { tools: freshTools, mcpClients: freshMcpClients } = toolUseContext.options;
2486
+ // Scope the skill's effort override to this turn's context only —
2487
+ // wrapping getAppState keeps the override out of the global store so
2488
+ // background agents and UI subscribers (Spinner, LogoV2) never see it.
2489
+ if (effort !== undefined) {
2490
+ const previousGetAppState = toolUseContext.getAppState;
2491
+ toolUseContext.getAppState = () => ({
2492
+ ...previousGetAppState(),
2493
+ effortValue: effort
2494
+ });
2495
+ }
2496
+ queryCheckpoint('query_context_loading_start');
2497
+ const [, , defaultSystemPrompt, baseUserContext, systemContext] = await Promise.all([
2498
+ // IMPORTANT: do this after setMessages() above, to avoid UI jank
2499
+ checkAndDisableBypassPermissionsIfNeeded(toolPermissionContext, setAppState),
2500
+ // Gated on TRANSCRIPT_CLASSIFIER so GrowthBook kill switch runs wherever auto mode is built in
2501
+ feature('TRANSCRIPT_CLASSIFIER') ? checkAndDisableAutoModeIfNeeded(toolPermissionContext, setAppState, store.getState().fastMode) : undefined, getSystemPrompt(freshTools, mainLoopModelParam, Array.from(toolPermissionContext.additionalWorkingDirectories.keys()), freshMcpClients), getUserContext(), getSystemContext()
2502
+ ]);
2503
+ const userContext = {
2504
+ ...baseUserContext,
2505
+ ...getCoordinatorUserContext(freshMcpClients, isScratchpadEnabled() ? getScratchpadDir() : undefined),
2506
+ ...((feature('PROACTIVE') || feature('KAIROS')) && proactiveModule?.isProactiveActive() && !terminalFocusRef.current ? {
2507
+ terminalFocus: 'The terminal is unfocused \u2014 the user is not actively watching.'
2508
+ } : {})
2509
+ };
2510
+ queryCheckpoint('query_context_loading_end');
2511
+ const systemPrompt = buildEffectiveSystemPrompt({
2512
+ mainThreadAgentDefinition,
2513
+ toolUseContext,
2514
+ customSystemPrompt,
2515
+ defaultSystemPrompt,
2516
+ appendSystemPrompt
2517
+ });
2518
+ toolUseContext.renderedSystemPrompt = systemPrompt;
2519
+ queryCheckpoint('query_query_start');
2520
+ resetTurnHookDuration();
2521
+ resetTurnToolDuration();
2522
+ resetTurnClassifierDuration();
2523
+ for await (const event of query({
2524
+ messages: messagesIncludingNewMessages,
2525
+ systemPrompt,
2526
+ userContext,
2527
+ systemContext,
2528
+ canUseTool,
2529
+ toolUseContext,
2530
+ querySource: getQuerySourceForREPL()
2531
+ })) {
2532
+ onQueryEvent(event);
2533
+ }
2534
+ if (feature('BUDDY')) {
2535
+ void fireCompanionObserver(messagesRef.current, reaction => setAppState(prev => prev.companionReaction === reaction ? prev : {
2536
+ ...prev,
2537
+ companionReaction: reaction
2538
+ }));
2539
+ }
2540
+ queryCheckpoint('query_end');
2541
+ // Capture ant-only API metrics before resetLoadingState clears the ref.
2542
+ // For multi-request turns (tool use loops), compute P50 across all requests.
2543
+ if ("external" === 'ant' && apiMetricsRef.current.length > 0) {
2544
+ const entries = apiMetricsRef.current;
2545
+ const ttfts = entries.map(e => e.ttftMs);
2546
+ // Compute per-request OTPS using only active streaming time and
2547
+ // streaming-only content. endResponseLength tracks content added by
2548
+ // streaming deltas only, excluding subagent/compaction inflation.
2549
+ const otpsValues = entries.map(e => {
2550
+ const delta = Math.round((e.endResponseLength - e.responseLengthBaseline) / 4);
2551
+ const samplingMs = e.lastTokenTime - e.firstTokenTime;
2552
+ return samplingMs > 0 ? Math.round(delta / (samplingMs / 1000)) : 0;
2553
+ });
2554
+ const isMultiRequest = entries.length > 1;
2555
+ const hookMs = getTurnHookDurationMs();
2556
+ const hookCount = getTurnHookCount();
2557
+ const toolMs = getTurnToolDurationMs();
2558
+ const toolCount = getTurnToolCount();
2559
+ const classifierMs = getTurnClassifierDurationMs();
2560
+ const classifierCount = getTurnClassifierCount();
2561
+ const turnMs = Date.now() - loadingStartTimeRef.current;
2562
+ setMessages(prev => [...prev, createApiMetricsMessage({
2563
+ ttftMs: isMultiRequest ? median(ttfts) : ttfts[0],
2564
+ otps: isMultiRequest ? median(otpsValues) : otpsValues[0],
2565
+ isP50: isMultiRequest,
2566
+ hookDurationMs: hookMs > 0 ? hookMs : undefined,
2567
+ hookCount: hookCount > 0 ? hookCount : undefined,
2568
+ turnDurationMs: turnMs > 0 ? turnMs : undefined,
2569
+ toolDurationMs: toolMs > 0 ? toolMs : undefined,
2570
+ toolCount: toolCount > 0 ? toolCount : undefined,
2571
+ classifierDurationMs: classifierMs > 0 ? classifierMs : undefined,
2572
+ classifierCount: classifierCount > 0 ? classifierCount : undefined,
2573
+ configWriteCount: getGlobalConfigWriteCount()
2574
+ })]);
2575
+ }
2576
+ resetLoadingState();
2577
+ // Log query profiling report if enabled
2578
+ logQueryProfileReport();
2579
+ // Signal that a query turn has completed successfully
2580
+ await onTurnComplete?.(messagesRef.current);
2581
+ // TTS is handled by the useEffect on isQueryActive (see "TTS: speak" above)
2582
+ }, [initialMcpClients, resetLoadingState, getToolUseContext, toolPermissionContext, setAppState, customSystemPrompt, onTurnComplete, appendSystemPrompt, canUseTool, mainThreadAgentDefinition, onQueryEvent, sessionTitle, titleDisabled]);
2583
+ const onQuery = useCallback(async (newMessages, abortController, shouldQuery, additionalAllowedTools, mainLoopModelParam, onBeforeQueryCallback, input, effort) => {
2584
+ // If this is a teammate, mark them as active when starting a turn
2585
+ if (isAgentSwarmsEnabled()) {
2586
+ const teamName = getTeamName();
2587
+ const agentName = getAgentName();
2588
+ if (teamName && agentName) {
2589
+ // Fire and forget - turn starts immediately, write happens in background
2590
+ void setMemberActive(teamName, agentName, true);
2591
+ }
2592
+ }
2593
+ // Concurrent guard via state machine. tryStart() atomically checks
2594
+ // and transitions idle→running, returning the generation number.
2595
+ // Returns null if already running — no separate check-then-set.
2596
+ const thisGeneration = queryGuard.tryStart();
2597
+ if (thisGeneration === null) {
2598
+ logEvent('thaddeus_concurrent_onquery_detected', {});
2599
+ // Extract and enqueue user message text, skipping meta messages
2600
+ // (e.g. expanded skill content, tick prompts) that should not be
2601
+ // replayed as user-visible text.
2602
+ newMessages.filter((m) => m.type === 'user' && !m.isMeta).map(_ => getContentText(_.message.content)).filter(_ => _ !== null).forEach((msg, i) => {
2603
+ enqueue({
2604
+ value: msg,
2605
+ mode: 'prompt'
2606
+ });
2607
+ if (i === 0) {
2608
+ logEvent('thaddeus_concurrent_onquery_enqueued', {});
2609
+ }
2610
+ });
2611
+ return;
2612
+ }
2613
+ try {
2614
+ // isLoading is derived from queryGuard — tryStart() above already
2615
+ // transitioned dispatching→running, so no setter call needed here.
2616
+ resetTimingRefs();
2617
+ setMessages(oldMessages => [...oldMessages, ...newMessages]);
2618
+ responseLengthRef.current = 0;
2619
+ if (feature('TOKEN_BUDGET')) {
2620
+ const parsedBudget = input ? parseTokenBudget(input) : null;
2621
+ snapshotOutputTokensForTurn(parsedBudget ?? getCurrentTurnTokenBudget());
2622
+ }
2623
+ apiMetricsRef.current = [];
2624
+ setStreamingToolUses([]);
2625
+ setStreamingText(null);
2626
+ // messagesRef is updated synchronously by the setMessages wrapper
2627
+ // above, so it already includes newMessages from the append at the
2628
+ // top of this try block. No reconstruction needed, no waiting for
2629
+ // React's scheduler (previously cost 20-56ms per prompt; the 56ms
2630
+ // case was a GC pause caught during the await).
2631
+ const latestMessages = messagesRef.current;
2632
+ if (input) {
2633
+ await mrOnBeforeQuery(input, latestMessages, newMessages.length);
2634
+ }
2635
+ // Pass full conversation history to callback
2636
+ if (onBeforeQueryCallback && input) {
2637
+ const shouldProceed = await onBeforeQueryCallback(input, latestMessages);
2638
+ if (!shouldProceed) {
2639
+ return;
2640
+ }
2641
+ }
2642
+ await onQueryImpl(latestMessages, newMessages, abortController, shouldQuery, additionalAllowedTools, mainLoopModelParam, effort);
2643
+ }
2644
+ finally {
2645
+ // queryGuard.end() atomically checks generation and transitions
2646
+ // running→idle. Returns false if a newer query owns the guard
2647
+ // (cancel+resubmit race where the stale finally fires as a microtask).
2648
+ if (queryGuard.end(thisGeneration)) {
2649
+ setLastQueryCompletionTime(Date.now());
2650
+ skipIdleCheckRef.current = false;
2651
+ // Always reset loading state in finally - this ensures cleanup even
2652
+ // if onQueryImpl throws. onTurnComplete is called separately in
2653
+ // onQueryImpl only on successful completion.
2654
+ resetLoadingState();
2655
+ await mrOnTurnComplete(messagesRef.current, abortController.signal.aborted);
2656
+ // Notify bridge clients that the turn is complete so mobile apps
2657
+ // can stop the spark animation and show post-turn UI.
2658
+ sendBridgeResultRef.current();
2659
+ // Auto-hide tungsten panel content at turn end (ant-only), but keep
2660
+ // tungstenActiveSession set so the pill stays in the footer and the user
2661
+ // can reopen the panel. Background tmux tasks (e.g. /hunter) run for
2662
+ // minutes — wiping the session made the pill disappear entirely, forcing
2663
+ // the user to re-invoke Tmux just to peek. Skip on abort so the panel
2664
+ // stays open for inspection (matches the turn-duration guard below).
2665
+ if ("external" === 'ant' && !abortController.signal.aborted) {
2666
+ setAppState(prev => {
2667
+ if (prev.tungstenActiveSession === undefined)
2668
+ return prev;
2669
+ if (prev.tungstenPanelAutoHidden === true)
2670
+ return prev;
2671
+ return {
2672
+ ...prev,
2673
+ tungstenPanelAutoHidden: true
2674
+ };
2675
+ });
2676
+ }
2677
+ // Capture budget info before clearing (ant-only)
2678
+ let budgetInfo;
2679
+ if (feature('TOKEN_BUDGET')) {
2680
+ if (getCurrentTurnTokenBudget() !== null && getCurrentTurnTokenBudget() > 0 && !abortController.signal.aborted) {
2681
+ budgetInfo = {
2682
+ tokens: getTurnOutputTokens(),
2683
+ limit: getCurrentTurnTokenBudget(),
2684
+ nudges: getBudgetContinuationCount()
2685
+ };
2686
+ }
2687
+ snapshotOutputTokensForTurn(null);
2688
+ }
2689
+ // Add turn duration message for turns longer than 30s or with a budget
2690
+ // Skip if user aborted or if in loop mode (too noisy between ticks)
2691
+ // Defer if swarm teammates are still running (show when they finish)
2692
+ const turnDurationMs = Date.now() - loadingStartTimeRef.current - totalPausedMsRef.current;
2693
+ if ((turnDurationMs > 30000 || budgetInfo !== undefined) && !abortController.signal.aborted && !proactiveActive) {
2694
+ const hasRunningSwarmAgents = getAllInProcessTeammateTasks(store.getState().tasks).some(t => t.status === 'running');
2695
+ if (hasRunningSwarmAgents) {
2696
+ // Only record start time on the first deferred turn
2697
+ if (swarmStartTimeRef.current === null) {
2698
+ swarmStartTimeRef.current = loadingStartTimeRef.current;
2699
+ }
2700
+ // Always update budget — later turns may carry the actual budget
2701
+ if (budgetInfo) {
2702
+ swarmBudgetInfoRef.current = budgetInfo;
2703
+ }
2704
+ }
2705
+ else {
2706
+ setMessages(prev => [...prev, createTurnDurationMessage(turnDurationMs, budgetInfo, count(prev, isLoggableMessage))]);
2707
+ }
2708
+ }
2709
+ // Clear the controller so CancelRequestHandler's canCancelRunningTask
2710
+ // reads false at the idle prompt. Without this, the stale non-aborted
2711
+ // controller makes ctrl+c fire onCancel() (aborting nothing) instead of
2712
+ // propagating to the double-press exit flow.
2713
+ setAbortController(null);
2714
+ }
2715
+ // Auto-restore: if the user interrupted before any meaningful response
2716
+ // arrived, rewind the conversation and restore their prompt — same as
2717
+ // opening the message selector and picking the last message.
2718
+ // This runs OUTSIDE the queryGuard.end() check because onCancel calls
2719
+ // forceEnd(), which bumps the generation so end() returns false above.
2720
+ // Guards: reason === 'user-cancel' (onCancel/Esc; programmatic aborts
2721
+ // use 'background'/'interrupt' and must not rewind — note abort() with
2722
+ // no args sets reason to a DOMException, not undefined), !isActive (no
2723
+ // newer query started — cancel+resubmit race), empty input (don't
2724
+ // clobber text typed during loading), no queued commands (user queued
2725
+ // B while A was loading → they've moved on, don't restore A; also
2726
+ // avoids removeLastFromHistory removing B's entry instead of A's),
2727
+ // not viewing a teammate (messagesRef is the main conversation — the
2728
+ // old Up-arrow quick-restore had this guard, preserve it).
2729
+ if (abortController.signal.reason === 'user-cancel' && !queryGuard.isActive && inputValueRef.current === '' && getCommandQueueLength() === 0 && !store.getState().viewingAgentTaskId) {
2730
+ const msgs = messagesRef.current;
2731
+ const lastUserMsg = msgs.findLast(selectableUserMessagesFilter);
2732
+ if (lastUserMsg) {
2733
+ const idx = msgs.lastIndexOf(lastUserMsg);
2734
+ if (messagesAfterAreOnlySynthetic(msgs, idx)) {
2735
+ // The submit is being undone — undo its history entry too,
2736
+ // otherwise Up-arrow shows the restored text twice.
2737
+ removeLastFromHistory();
2738
+ restoreMessageSyncRef.current(lastUserMsg);
2739
+ }
2740
+ }
2741
+ }
2742
+ }
2743
+ }, [onQueryImpl, setAppState, resetLoadingState, queryGuard, mrOnBeforeQuery, mrOnTurnComplete]);
2744
+ // Handle initial message (from CLI args or plan mode exit with context clear)
2745
+ // This effect runs when isLoading becomes false and there's a pending message
2746
+ const initialMessageRef = useRef(false);
2747
+ useEffect(() => {
2748
+ const pending = initialMessage;
2749
+ if (!pending || isLoading || initialMessageRef.current)
2750
+ return;
2751
+ // Mark as processing to prevent re-entry
2752
+ initialMessageRef.current = true;
2753
+ async function processInitialMessage(initialMsg) {
2754
+ // Clear context if requested (plan mode exit)
2755
+ if (initialMsg.clearContext) {
2756
+ // Preserve the plan slug before clearing context, so the new session
2757
+ // can access the same plan file after regenerateSessionId()
2758
+ const oldPlanSlug = initialMsg.message.planContent ? getPlanSlug() : undefined;
2759
+ const { clearConversation } = await import('../commands/clear/conversation.js');
2760
+ await clearConversation({
2761
+ setMessages,
2762
+ readFileState: readFileState.current,
2763
+ discoveredSkillNames: discoveredSkillNamesRef.current,
2764
+ loadedNestedMemoryPaths: loadedNestedMemoryPathsRef.current,
2765
+ getAppState: () => store.getState(),
2766
+ setAppState,
2767
+ setConversationId
2768
+ });
2769
+ haikuTitleAttemptedRef.current = false;
2770
+ setHaikuTitle(undefined);
2771
+ bashTools.current.clear();
2772
+ bashToolsProcessedIdx.current = 0;
2773
+ // Restore the plan slug for the new session so getPlan() finds the file
2774
+ if (oldPlanSlug) {
2775
+ setPlanSlug(getSessionId(), oldPlanSlug);
2776
+ }
2777
+ }
2778
+ // Atomically: clear initial message, set permission mode and rules, and store plan for verification
2779
+ const shouldStorePlanForVerification = initialMsg.message.planContent && "external" === 'ant' && isEnvTruthy(undefined);
2780
+ setAppState(prev => {
2781
+ // Build and apply permission updates (mode + allowedPrompts rules)
2782
+ let updatedToolPermissionContext = initialMsg.mode ? applyPermissionUpdates(prev.toolPermissionContext, buildPermissionUpdates(initialMsg.mode, initialMsg.allowedPrompts)) : prev.toolPermissionContext;
2783
+ // For auto, override the mode (buildPermissionUpdates maps
2784
+ // it to 'default' via toExternalPermissionMode) and strip dangerous rules
2785
+ if (feature('TRANSCRIPT_CLASSIFIER') && initialMsg.mode === 'auto') {
2786
+ updatedToolPermissionContext = stripDangerousPermissionsForAutoMode({
2787
+ ...updatedToolPermissionContext,
2788
+ mode: 'auto',
2789
+ prePlanMode: undefined
2790
+ });
2791
+ }
2792
+ return {
2793
+ ...prev,
2794
+ initialMessage: null,
2795
+ toolPermissionContext: updatedToolPermissionContext,
2796
+ ...(shouldStorePlanForVerification && {
2797
+ pendingPlanVerification: {
2798
+ plan: initialMsg.message.planContent,
2799
+ verificationStarted: false,
2800
+ verificationCompleted: false
2801
+ }
2802
+ })
2803
+ };
2804
+ });
2805
+ // Create file history snapshot for code rewind
2806
+ if (fileHistoryEnabled()) {
2807
+ void fileHistoryMakeSnapshot((updater) => {
2808
+ setAppState(prev => ({
2809
+ ...prev,
2810
+ fileHistory: updater(prev.fileHistory)
2811
+ }));
2812
+ }, initialMsg.message.uuid);
2813
+ }
2814
+ // Ensure SessionStart hook context is available before the first API
2815
+ // call. onSubmit calls this internally but the onQuery path below
2816
+ // bypasses onSubmit — hoist here so both paths see hook messages.
2817
+ await awaitPendingHooks();
2818
+ // Route all initial prompts through onSubmit to ensure UserPromptSubmit hooks fire
2819
+ // TODO: Simplify by always routing through onSubmit once it supports
2820
+ // ContentBlockParam arrays (images) as input
2821
+ const content = initialMsg.message.message.content;
2822
+ // Route all string content through onSubmit to ensure hooks fire
2823
+ // For complex content (images, etc.), fall back to direct onQuery
2824
+ // Plan messages bypass onSubmit to preserve planContent metadata for rendering
2825
+ if (typeof content === 'string' && !initialMsg.message.planContent) {
2826
+ // Route through onSubmit for proper processing including UserPromptSubmit hooks
2827
+ void onSubmit(content, {
2828
+ setCursorOffset: () => { },
2829
+ clearBuffer: () => { },
2830
+ resetHistory: () => { }
2831
+ });
2832
+ }
2833
+ else {
2834
+ // Plan messages or complex content (images, etc.) - send directly to model
2835
+ // Plan messages use onQuery to preserve planContent metadata for rendering
2836
+ // TODO: Once onSubmit supports ContentBlockParam arrays, remove this branch
2837
+ const newAbortController = createAbortController();
2838
+ setAbortController(newAbortController);
2839
+ void onQuery([initialMsg.message], newAbortController, true,
2840
+ // shouldQuery
2841
+ [],
2842
+ // additionalAllowedTools
2843
+ mainLoopModel);
2844
+ }
2845
+ // Reset ref after a delay to allow new initial messages
2846
+ setTimeout(ref => {
2847
+ ref.current = false;
2848
+ }, 100, initialMessageRef);
2849
+ }
2850
+ void processInitialMessage(pending);
2851
+ }, [initialMessage, isLoading, setMessages, setAppState, onQuery, mainLoopModel, tools]);
2852
+ const onSubmit = useCallback(async (input, helpers, speculationAccept, options) => {
2853
+ // Re-pin scroll to bottom on submit so the user always sees the new
2854
+ // exchange (matches OpenCode's auto-scroll behavior).
2855
+ repinScroll();
2856
+ // Resume loop mode if paused
2857
+ if (feature('PROACTIVE') || feature('KAIROS')) {
2858
+ proactiveModule?.resumeProactive();
2859
+ }
2860
+ // Handle immediate commands - these bypass the queue and execute right away
2861
+ // even while Thaddeus is processing. Commands opt-in via `immediate: true`.
2862
+ // Commands triggered via keybindings are always treated as immediate.
2863
+ if (!speculationAccept && input.trim().startsWith('/')) {
2864
+ // Expand [Pasted text #N] refs so immediate commands (e.g. /btw) receive
2865
+ // the pasted content, not the placeholder. The non-immediate path gets
2866
+ // this expansion later in handlePromptSubmit.
2867
+ const trimmedInput = expandPastedTextRefs(input, pastedContents).trim();
2868
+ const spaceIndex = trimmedInput.indexOf(' ');
2869
+ const commandName = spaceIndex === -1 ? trimmedInput.slice(1) : trimmedInput.slice(1, spaceIndex);
2870
+ const commandArgs = spaceIndex === -1 ? '' : trimmedInput.slice(spaceIndex + 1).trim();
2871
+ // Find matching command - treat as immediate if:
2872
+ // 1. Command has `immediate: true`, OR
2873
+ // 2. Command was triggered via keybinding (fromKeybinding option)
2874
+ const matchingCommand = commands.find(cmd => isCommandEnabled(cmd) && (cmd.name === commandName || cmd.aliases?.includes(commandName) || getCommandName(cmd) === commandName));
2875
+ if (matchingCommand?.name === 'clear' && idleHintShownRef.current) {
2876
+ logEvent('thaddeus_idle_return_action', {
2877
+ action: 'hint_converted',
2878
+ variant: idleHintShownRef.current,
2879
+ idleMinutes: Math.round((Date.now() - lastQueryCompletionTimeRef.current) / 60_000),
2880
+ messageCount: messagesRef.current.length,
2881
+ totalInputTokens: getTotalInputTokens()
2882
+ });
2883
+ idleHintShownRef.current = false;
2884
+ }
2885
+ const shouldTreatAsImmediate = queryGuard.isActive && (matchingCommand?.immediate || options?.fromKeybinding);
2886
+ if (matchingCommand && shouldTreatAsImmediate && matchingCommand.type === 'local-jsx') {
2887
+ // Only clear input if the submitted text matches what's in the prompt.
2888
+ // When a command keybinding fires, input is "/<command>" but the actual
2889
+ // input value is the user's existing text - don't clear it in that case.
2890
+ if (input.trim() === inputValueRef.current.trim()) {
2891
+ setInputValue('');
2892
+ helpers.setCursorOffset(0);
2893
+ helpers.clearBuffer();
2894
+ setPastedContents({});
2895
+ }
2896
+ const pastedTextRefs = parseReferences(input).filter(r => pastedContents[r.id]?.type === 'text');
2897
+ const pastedTextCount = pastedTextRefs.length;
2898
+ const pastedTextBytes = pastedTextRefs.reduce((sum, r) => sum + (pastedContents[r.id]?.content.length ?? 0), 0);
2899
+ logEvent('thaddeus_paste_text', {
2900
+ pastedTextCount,
2901
+ pastedTextBytes
2902
+ });
2903
+ logEvent('thaddeus_immediate_command_executed', {
2904
+ commandName: matchingCommand.name,
2905
+ fromKeybinding: options?.fromKeybinding ?? false
2906
+ });
2907
+ // Execute the command directly
2908
+ const executeImmediateCommand = async () => {
2909
+ let doneWasCalled = false;
2910
+ const onDone = (result, doneOptions) => {
2911
+ doneWasCalled = true;
2912
+ setToolJSX({
2913
+ jsx: null,
2914
+ shouldHidePromptInput: false,
2915
+ clearLocalJSX: true
2916
+ });
2917
+ const newMessages = [];
2918
+ if (result && doneOptions?.display !== 'skip') {
2919
+ addNotification({
2920
+ key: `immediate-${matchingCommand.name}`,
2921
+ text: result,
2922
+ priority: 'immediate'
2923
+ });
2924
+ // In fullscreen the command just showed as a centered modal
2925
+ // pane — the notification above is enough feedback. Adding
2926
+ // "❯ /config" + "⎿ dismissed" to the transcript is clutter
2927
+ // (those messages are type:system subtype:local_command —
2928
+ // user-visible but NOT sent to the model, so skipping them
2929
+ // doesn't change model context). Outside fullscreen the
2930
+ // transcript entry stays so scrollback shows what ran.
2931
+ if (!isFullscreenEnvEnabled()) {
2932
+ newMessages.push(createCommandInputMessage(formatCommandInputTags(getCommandName(matchingCommand), commandArgs)), createCommandInputMessage(`<${LOCAL_COMMAND_STDOUT_TAG}>${escapeXml(result)}</${LOCAL_COMMAND_STDOUT_TAG}>`));
2933
+ }
2934
+ }
2935
+ // Inject meta messages (model-visible, user-hidden) into the transcript
2936
+ if (doneOptions?.metaMessages?.length) {
2937
+ newMessages.push(...doneOptions.metaMessages.map(content => createUserMessage({
2938
+ content,
2939
+ isMeta: true
2940
+ })));
2941
+ }
2942
+ if (newMessages.length) {
2943
+ setMessages(prev => [...prev, ...newMessages]);
2944
+ }
2945
+ // Restore stashed prompt after local-jsx command completes.
2946
+ // The normal stash restoration path (below) is skipped because
2947
+ // local-jsx commands return early from onSubmit.
2948
+ if (stashedPrompt !== undefined) {
2949
+ setInputValue(stashedPrompt.text);
2950
+ helpers.setCursorOffset(stashedPrompt.cursorOffset);
2951
+ setPastedContents(stashedPrompt.pastedContents);
2952
+ setStashedPrompt(undefined);
2953
+ }
2954
+ };
2955
+ // Build context for the command (reuses existing getToolUseContext).
2956
+ // Read messages via ref to keep onSubmit stable across message
2957
+ // updates — matches the pattern at L2384/L2400/L2662 and avoids
2958
+ // pinning stale REPL render scopes in downstream closures.
2959
+ const context = getToolUseContext(messagesRef.current, [], createAbortController(), mainLoopModel);
2960
+ const mod = await matchingCommand.load();
2961
+ const jsx = await mod.call(onDone, context, commandArgs);
2962
+ // Skip if onDone already fired — prevents stuck isLocalJSXCommand
2963
+ // (see processSlashCommand.tsx local-jsx case for full mechanism).
2964
+ if (jsx && !doneWasCalled) {
2965
+ // shouldHidePromptInput: false keeps Notifications mounted
2966
+ // so the onDone result isn't lost
2967
+ setToolJSX({
2968
+ jsx,
2969
+ shouldHidePromptInput: false,
2970
+ isLocalJSXCommand: true
2971
+ });
2972
+ }
2973
+ };
2974
+ void executeImmediateCommand();
2975
+ return; // Always return early - don't add to history or queue
2976
+ }
2977
+ }
2978
+ // Remote mode: skip empty input early before any state mutations
2979
+ if (activeRemote.isRemoteMode && !input.trim()) {
2980
+ return;
2981
+ }
2982
+ // Idle-return: prompt returning users to start fresh when the
2983
+ // conversation is large and the cache is cold. thaddeus_willow_mode
2984
+ // controls treatment: "dialog" (blocking), "hint" (notification), "off".
2985
+ {
2986
+ const willowMode = getFeatureValue_CACHED_MAY_BE_STALE('thaddeus_willow_mode', 'off');
2987
+ const idleThresholdMin = Number(process.env.THADDEUS_IDLE_THRESHOLD_MINUTES ?? 75);
2988
+ const tokenThreshold = Number(process.env.THADDEUS_IDLE_TOKEN_THRESHOLD ?? 100_000);
2989
+ if (willowMode !== 'off' && !getGlobalConfig().idleReturnDismissed && !skipIdleCheckRef.current && !speculationAccept && !input.trim().startsWith('/') && lastQueryCompletionTimeRef.current > 0 && getTotalInputTokens() >= tokenThreshold) {
2990
+ const idleMs = Date.now() - lastQueryCompletionTimeRef.current;
2991
+ const idleMinutes = idleMs / 60_000;
2992
+ if (idleMinutes >= idleThresholdMin && willowMode === 'dialog') {
2993
+ setIdleReturnPending({
2994
+ input,
2995
+ idleMinutes
2996
+ });
2997
+ setInputValue('');
2998
+ helpers.setCursorOffset(0);
2999
+ helpers.clearBuffer();
3000
+ return;
3001
+ }
3002
+ }
3003
+ }
3004
+ // Add to history for direct user submissions.
3005
+ // Queued command processing (executeQueuedInput) doesn't call onSubmit,
3006
+ // so notifications and already-queued user input won't be added to history here.
3007
+ // Skip history for keybinding-triggered commands (user didn't type the command).
3008
+ if (!options?.fromKeybinding) {
3009
+ addToHistory({
3010
+ display: speculationAccept ? input : prependModeCharacterToInput(input, inputMode),
3011
+ pastedContents: speculationAccept ? {} : pastedContents
3012
+ });
3013
+ // Add the just-submitted command to the front of the ghost-text
3014
+ // cache so it's suggested immediately (not after the 60s TTL).
3015
+ if (inputMode === 'bash') {
3016
+ prependToShellHistoryCache(input.trim());
3017
+ }
3018
+ }
3019
+ // Restore stash if present, but NOT for slash commands or when loading.
3020
+ // - Slash commands (especially interactive ones like /model, /context) hide
3021
+ // the prompt and show a picker UI. Restoring the stash during a command would
3022
+ // place the text in a hidden input, and the user would lose it by typing the
3023
+ // next command. Instead, preserve the stash so it survives across command runs.
3024
+ // - When loading, the submitted input will be queued and handlePromptSubmit
3025
+ // will clear the input field (onInputChange('')), which would clobber the
3026
+ // restored stash. Defer restoration to after handlePromptSubmit (below).
3027
+ // Remote mode is exempt: it sends via WebSocket and returns early without
3028
+ // calling handlePromptSubmit, so there's no clobbering risk — restore eagerly.
3029
+ // In both deferred cases, the stash is restored after await handlePromptSubmit.
3030
+ const isSlashCommand = !speculationAccept && input.trim().startsWith('/');
3031
+ // Submit runs "now" (not queued) when not already loading, or when
3032
+ // accepting speculation, or in remote mode (which sends via WS and
3033
+ // returns early without calling handlePromptSubmit).
3034
+ const submitsNow = !isLoading || speculationAccept || activeRemote.isRemoteMode;
3035
+ if (stashedPrompt !== undefined && !isSlashCommand && submitsNow) {
3036
+ setInputValue(stashedPrompt.text);
3037
+ helpers.setCursorOffset(stashedPrompt.cursorOffset);
3038
+ setPastedContents(stashedPrompt.pastedContents);
3039
+ setStashedPrompt(undefined);
3040
+ }
3041
+ else if (submitsNow) {
3042
+ if (!options?.fromKeybinding) {
3043
+ // Clear input when not loading or accepting speculation.
3044
+ // Preserve input for keybinding-triggered commands.
3045
+ setInputValue('');
3046
+ helpers.setCursorOffset(0);
3047
+ }
3048
+ setPastedContents({});
3049
+ }
3050
+ if (submitsNow) {
3051
+ setInputMode('prompt');
3052
+ setIDESelection(undefined);
3053
+ setSubmitCount(_ => _ + 1);
3054
+ helpers.clearBuffer();
3055
+ tipPickedThisTurnRef.current = false;
3056
+ // Show the placeholder in the same React batch as setInputValue('').
3057
+ // Skip for slash/bash (they have their own echo), speculation and remote
3058
+ // mode (both setMessages directly with no gap to bridge).
3059
+ if (!isSlashCommand && inputMode === 'prompt' && !speculationAccept && !activeRemote.isRemoteMode) {
3060
+ setUserInputOnProcessing(input);
3061
+ // showSpinner includes userInputOnProcessing, so the spinner appears
3062
+ // on this render. Reset timing refs now (before queryGuard.reserve()
3063
+ // would) so elapsed time doesn't read as Date.now() - 0. The
3064
+ // isQueryActive transition above does the same reset — idempotent.
3065
+ resetTimingRefs();
3066
+ }
3067
+ // Increment prompt count for attribution tracking and save snapshot
3068
+ // The snapshot persists promptCount so it survives compaction
3069
+ if (feature('COMMIT_ATTRIBUTION')) {
3070
+ setAppState(prev => ({
3071
+ ...prev,
3072
+ attribution: incrementPromptCount(prev.attribution, snapshot => {
3073
+ void recordAttributionSnapshot(snapshot).catch(error => {
3074
+ logForDebugging(`Attribution: Failed to save snapshot: ${error}`);
3075
+ });
3076
+ })
3077
+ }));
3078
+ }
3079
+ }
3080
+ // Handle speculation acceptance
3081
+ if (speculationAccept) {
3082
+ const { queryRequired } = await handleSpeculationAccept(speculationAccept.state, speculationAccept.speculationSessionTimeSavedMs, speculationAccept.setAppState, input, {
3083
+ setMessages,
3084
+ readFileState,
3085
+ cwd: getOriginalCwd()
3086
+ });
3087
+ if (queryRequired) {
3088
+ const newAbortController = createAbortController();
3089
+ setAbortController(newAbortController);
3090
+ void onQuery([], newAbortController, true, [], mainLoopModel);
3091
+ }
3092
+ return;
3093
+ }
3094
+ // Remote mode: send input via stream-json instead of local query.
3095
+ // Permission requests from the remote are bridged into toolUseConfirmQueue
3096
+ // and rendered using the standard PermissionRequest component.
3097
+ //
3098
+ // local-jsx slash commands (e.g. /agents, /config) render UI in THIS
3099
+ // process — they have no remote equivalent. Let those fall through to
3100
+ // handlePromptSubmit so they execute locally. Prompt commands and
3101
+ // plain text go to the remote.
3102
+ if (activeRemote.isRemoteMode && !(isSlashCommand && commands.find(c => {
3103
+ const name = input.trim().slice(1).split(/\s/)[0];
3104
+ return isCommandEnabled(c) && (c.name === name || c.aliases?.includes(name) || getCommandName(c) === name);
3105
+ })?.type === 'local-jsx')) {
3106
+ // Build content blocks when there are pasted attachments (images)
3107
+ const pastedValues = Object.values(pastedContents);
3108
+ const imageContents = pastedValues.filter(c => c.type === 'image');
3109
+ const imagePasteIds = imageContents.length > 0 ? imageContents.map(c => c.id) : undefined;
3110
+ let messageContent = input.trim();
3111
+ let remoteContent = input.trim();
3112
+ if (pastedValues.length > 0) {
3113
+ const contentBlocks = [];
3114
+ const remoteBlocks = [];
3115
+ const trimmedInput = input.trim();
3116
+ if (trimmedInput) {
3117
+ contentBlocks.push({
3118
+ type: 'text',
3119
+ text: trimmedInput
3120
+ });
3121
+ remoteBlocks.push({
3122
+ type: 'text',
3123
+ text: trimmedInput
3124
+ });
3125
+ }
3126
+ for (const pasted of pastedValues) {
3127
+ if (pasted.type === 'image') {
3128
+ const source = {
3129
+ type: 'base64',
3130
+ media_type: (pasted.mediaType ?? 'image/png'),
3131
+ data: pasted.content
3132
+ };
3133
+ contentBlocks.push({
3134
+ type: 'image',
3135
+ source
3136
+ });
3137
+ remoteBlocks.push({
3138
+ type: 'image',
3139
+ source
3140
+ });
3141
+ }
3142
+ else {
3143
+ contentBlocks.push({
3144
+ type: 'text',
3145
+ text: pasted.content
3146
+ });
3147
+ remoteBlocks.push({
3148
+ type: 'text',
3149
+ text: pasted.content
3150
+ });
3151
+ }
3152
+ }
3153
+ messageContent = contentBlocks;
3154
+ remoteContent = remoteBlocks;
3155
+ }
3156
+ // Create and add user message to UI
3157
+ // Note: empty input already handled by early return above
3158
+ const userMessage = createUserMessage({
3159
+ content: messageContent,
3160
+ imagePasteIds
3161
+ });
3162
+ setMessages(prev => [...prev, userMessage]);
3163
+ // Send to remote session
3164
+ await activeRemote.sendMessage(remoteContent, {
3165
+ uuid: userMessage.uuid
3166
+ });
3167
+ return;
3168
+ }
3169
+ // Ensure SessionStart hook context is available before the first API call.
3170
+ await awaitPendingHooks();
3171
+ await handlePromptSubmit({
3172
+ input,
3173
+ helpers,
3174
+ queryGuard,
3175
+ isExternalLoading,
3176
+ mode: inputMode,
3177
+ commands,
3178
+ onInputChange: setInputValue,
3179
+ setPastedContents,
3180
+ setToolJSX,
3181
+ getToolUseContext,
3182
+ messages: messagesRef.current,
3183
+ mainLoopModel,
3184
+ pastedContents,
3185
+ ideSelection,
3186
+ setUserInputOnProcessing,
3187
+ setAbortController,
3188
+ abortController,
3189
+ onQuery,
3190
+ setAppState,
3191
+ querySource: getQuerySourceForREPL(),
3192
+ onBeforeQuery,
3193
+ canUseTool,
3194
+ addNotification,
3195
+ setMessages,
3196
+ // Read via ref so streamMode can be dropped from onSubmit deps —
3197
+ // handlePromptSubmit only uses it for debug log + telemetry event.
3198
+ streamMode: streamModeRef.current,
3199
+ hasInterruptibleToolInProgress: hasInterruptibleToolInProgressRef.current
3200
+ });
3201
+ // Restore stash that was deferred above. Two cases:
3202
+ // - Slash command: handlePromptSubmit awaited the full command execution
3203
+ // (including interactive pickers). Restoring now places the stash back in
3204
+ // the visible input.
3205
+ // - Loading (queued): handlePromptSubmit enqueued + cleared input, then
3206
+ // returned quickly. Restoring now places the stash back after the clear.
3207
+ if ((isSlashCommand || isLoading) && stashedPrompt !== undefined) {
3208
+ setInputValue(stashedPrompt.text);
3209
+ helpers.setCursorOffset(stashedPrompt.cursorOffset);
3210
+ setPastedContents(stashedPrompt.pastedContents);
3211
+ setStashedPrompt(undefined);
3212
+ }
3213
+ }, [queryGuard,
3214
+ // isLoading is read at the !isLoading checks above for input-clearing
3215
+ // and submitCount gating. It's derived from isQueryActive || isExternalLoading,
3216
+ // so including it here ensures the closure captures the fresh value.
3217
+ isLoading, isExternalLoading, inputMode, commands, setInputValue, setInputMode, setPastedContents, setSubmitCount, setIDESelection, setToolJSX, getToolUseContext,
3218
+ // messages is read via messagesRef.current inside the callback to
3219
+ // keep onSubmit stable across message updates (see L2384/L2400/L2662).
3220
+ // Without this, each setMessages call (~30× per turn) recreates
3221
+ // onSubmit, pinning the REPL render scope (1776B) + that render's
3222
+ // messages array in downstream closures (PromptInput, handleAutoRunIssue).
3223
+ // Heap analysis showed ~9 REPL scopes and ~15 messages array versions
3224
+ // accumulating after #20174/#20175, all traced to this dep.
3225
+ mainLoopModel, pastedContents, ideSelection, setUserInputOnProcessing, setAbortController, addNotification, onQuery, stashedPrompt, setStashedPrompt, setAppState, onBeforeQuery, canUseTool, remoteSession, setMessages, awaitPendingHooks, repinScroll]);
3226
+ // Callback for when user submits input while viewing a teammate's transcript
3227
+ const onAgentSubmit = useCallback(async (input, task, helpers) => {
3228
+ if (isLocalAgentTask(task)) {
3229
+ appendMessageToLocalAgent(task.id, createUserMessage({
3230
+ content: input
3231
+ }), setAppState);
3232
+ if (task.status === 'running') {
3233
+ queuePendingMessage(task.id, input, setAppState);
3234
+ }
3235
+ else {
3236
+ void resumeAgentBackground({
3237
+ agentId: task.id,
3238
+ prompt: input,
3239
+ toolUseContext: getToolUseContext(messagesRef.current, [], new AbortController(), mainLoopModel),
3240
+ canUseTool
3241
+ }).catch(err => {
3242
+ logForDebugging(`resumeAgentBackground failed: ${errorMessage(err)}`);
3243
+ addNotification({
3244
+ key: `resume-agent-failed-${task.id}`,
3245
+ jsx: _jsxs(Text, { color: "error", children: ["Failed to resume agent: ", errorMessage(err)] }),
3246
+ priority: 'low'
3247
+ });
3248
+ });
3249
+ }
3250
+ }
3251
+ else {
3252
+ injectUserMessageToTeammate(task.id, input, setAppState);
3253
+ }
3254
+ setInputValue('');
3255
+ helpers.setCursorOffset(0);
3256
+ helpers.clearBuffer();
3257
+ }, [setAppState, setInputValue, getToolUseContext, canUseTool, mainLoopModel, addNotification]);
3258
+ // Handlers for auto-run /issue or /good-claude (defined after onSubmit)
3259
+ const handleAutoRunIssue = useCallback(() => {
3260
+ const command = autoRunIssueReason ? getAutoRunCommand(autoRunIssueReason) : '/issue';
3261
+ setAutoRunIssueReason(null); // Clear the state
3262
+ onSubmit(command, {
3263
+ setCursorOffset: () => { },
3264
+ clearBuffer: () => { },
3265
+ resetHistory: () => { }
3266
+ }).catch(err => {
3267
+ logForDebugging(`Auto-run ${command} failed: ${errorMessage(err)}`);
3268
+ });
3269
+ }, [onSubmit, autoRunIssueReason]);
3270
+ const handleCancelAutoRunIssue = useCallback(() => {
3271
+ setAutoRunIssueReason(null);
3272
+ }, []);
3273
+ // Handler for when user presses 1 on survey thanks screen to share details
3274
+ const handleSurveyRequestFeedback = useCallback(() => {
3275
+ const command = "external" === 'ant' ? '/issue' : '/feedback';
3276
+ onSubmit(command, {
3277
+ setCursorOffset: () => { },
3278
+ clearBuffer: () => { },
3279
+ resetHistory: () => { }
3280
+ }).catch(err => {
3281
+ logForDebugging(`Survey feedback request failed: ${err instanceof Error ? err.message : String(err)}`);
3282
+ });
3283
+ }, [onSubmit]);
3284
+ // onSubmit is unstable (deps include `messages` which changes every turn).
3285
+ // `handleOpenRateLimitOptions` is prop-drilled to every MessageRow, and each
3286
+ // MessageRow fiber pins the closure (and transitively the entire REPL render
3287
+ // scope, ~1.8KB) at mount time. Using a ref keeps this callback stable so
3288
+ // old REPL scopes can be GC'd — saves ~35MB over a 1000-turn session.
3289
+ const onSubmitRef = useRef(onSubmit);
3290
+ onSubmitRef.current = onSubmit;
3291
+ const handleOpenRateLimitOptions = useCallback(() => {
3292
+ void onSubmitRef.current('/rate-limit-options', {
3293
+ setCursorOffset: () => { },
3294
+ clearBuffer: () => { },
3295
+ resetHistory: () => { }
3296
+ });
3297
+ }, []);
3298
+ const handleExit = useCallback(async () => {
3299
+ setIsExiting(true);
3300
+ // In bg sessions, always detach instead of kill — even when a worktree is
3301
+ // active. Without this guard, the worktree branch below short-circuits into
3302
+ // ExitFlow (which calls gracefulShutdown) before exit.tsx is ever loaded.
3303
+ if (feature('BG_SESSIONS') && isBgSession()) {
3304
+ spawnSync('tmux', ['detach-client'], {
3305
+ stdio: 'ignore'
3306
+ });
3307
+ setIsExiting(false);
3308
+ return;
3309
+ }
3310
+ const showWorktree = getCurrentWorktreeSession() !== null;
3311
+ if (showWorktree) {
3312
+ setExitFlow(_jsx(ExitFlow, { showWorktree: true, onDone: () => { }, onCancel: () => {
3313
+ setExitFlow(null);
3314
+ setIsExiting(false);
3315
+ } }));
3316
+ return;
3317
+ }
3318
+ const exitMod = await exit.load();
3319
+ const exitFlowResult = await exitMod.call(() => { });
3320
+ setExitFlow(exitFlowResult);
3321
+ // If call() returned without killing the process (bg session detach),
3322
+ // clear isExiting so the UI is usable on reattach. No-op on the normal
3323
+ // path — gracefulShutdown's process.exit() means we never get here.
3324
+ if (exitFlowResult === null) {
3325
+ setIsExiting(false);
3326
+ }
3327
+ }, []);
3328
+ const handleShowMessageSelector = useCallback(() => {
3329
+ setIsMessageSelectorVisible(prev => !prev);
3330
+ }, []);
3331
+ // Rewind conversation state to just before `message`: slice messages,
3332
+ // reset conversation ID, microcompact state, permission mode, prompt suggestion.
3333
+ // Does NOT touch the prompt input. Index is computed from messagesRef (always
3334
+ // fresh via the setMessages wrapper) so callers don't need to worry about
3335
+ // stale closures.
3336
+ const rewindConversationTo = useCallback((message) => {
3337
+ const prev = messagesRef.current;
3338
+ const messageIndex = prev.lastIndexOf(message);
3339
+ if (messageIndex === -1)
3340
+ return;
3341
+ logEvent('thaddeus_conversation_rewind', {
3342
+ preRewindMessageCount: prev.length,
3343
+ postRewindMessageCount: messageIndex,
3344
+ messagesRemoved: prev.length - messageIndex,
3345
+ rewindToMessageIndex: messageIndex
3346
+ });
3347
+ setMessages(prev.slice(0, messageIndex));
3348
+ // Careful, this has to happen after setMessages
3349
+ setConversationId(randomUUID());
3350
+ // Reset cached microcompact state so stale pinned cache edits
3351
+ // don't reference tool_use_ids from truncated messages
3352
+ resetMicrocompactState();
3353
+ if (feature('CONTEXT_COLLAPSE')) {
3354
+ // Rewind truncates the REPL array. Commits whose archived span
3355
+ // was past the rewind point can't be projected anymore
3356
+ // (projectView silently skips them) but the staged queue and ID
3357
+ // maps reference stale uuids. Simplest safe reset: drop
3358
+ // everything. The ctx-agent will re-stage on the next
3359
+ // threshold crossing.
3360
+ /* eslint-disable @typescript-eslint/no-require-imports */
3361
+ ;
3362
+ require('../services/contextCollapse/index.js').resetContextCollapse();
3363
+ /* eslint-enable @typescript-eslint/no-require-imports */
3364
+ }
3365
+ // Restore state from the message we're rewinding to
3366
+ setAppState(prev => ({
3367
+ ...prev,
3368
+ // Restore permission mode from the message
3369
+ toolPermissionContext: message.permissionMode && prev.toolPermissionContext.mode !== message.permissionMode ? {
3370
+ ...prev.toolPermissionContext,
3371
+ mode: message.permissionMode
3372
+ } : prev.toolPermissionContext,
3373
+ // Clear stale prompt suggestion from previous conversation state
3374
+ promptSuggestion: {
3375
+ text: null,
3376
+ promptId: null,
3377
+ shownAt: 0,
3378
+ acceptedAt: 0,
3379
+ generationRequestId: null
3380
+ }
3381
+ }));
3382
+ }, [setMessages, setAppState]);
3383
+ // Synchronous rewind + input population. Used directly by auto-restore on
3384
+ // interrupt (so React batches with the abort's setMessages → single render,
3385
+ // no flicker). MessageSelector wraps this in setImmediate via handleRestoreMessage.
3386
+ const restoreMessageSync = useCallback((message) => {
3387
+ rewindConversationTo(message);
3388
+ const r = textForResubmit(message);
3389
+ if (r) {
3390
+ setInputValue(r.text);
3391
+ setInputMode(r.mode);
3392
+ }
3393
+ // Restore pasted images
3394
+ if (Array.isArray(message.message.content) && message.message.content.some(block => block.type === 'image')) {
3395
+ const imageBlocks = message.message.content.filter(block => block.type === 'image');
3396
+ if (imageBlocks.length > 0) {
3397
+ const newPastedContents = {};
3398
+ imageBlocks.forEach((block, index) => {
3399
+ if (block.source.type === 'base64') {
3400
+ const id = message.imagePasteIds?.[index] ?? index + 1;
3401
+ newPastedContents[id] = {
3402
+ id,
3403
+ type: 'image',
3404
+ content: block.source.data,
3405
+ mediaType: block.source.media_type
3406
+ };
3407
+ }
3408
+ });
3409
+ setPastedContents(newPastedContents);
3410
+ }
3411
+ }
3412
+ }, [rewindConversationTo, setInputValue]);
3413
+ restoreMessageSyncRef.current = restoreMessageSync;
3414
+ // MessageSelector path: defer via setImmediate so the "Interrupted" message
3415
+ // renders to static output before rewind — otherwise it remains vestigial
3416
+ // at the top of the screen.
3417
+ const handleRestoreMessage = useCallback(async (message) => {
3418
+ setImmediate((restore, message) => restore(message), restoreMessageSync, message);
3419
+ }, [restoreMessageSync]);
3420
+ // Not memoized — hook stores caps via ref, reads latest closure at dispatch.
3421
+ // 24-char prefix: deriveUUID preserves first 24, renderable uuid prefix-matches raw source.
3422
+ const findRawIndex = (uuid) => {
3423
+ const prefix = uuid.slice(0, 24);
3424
+ return messages.findIndex(m => m.uuid.slice(0, 24) === prefix);
3425
+ };
3426
+ const messageActionCaps = {
3427
+ copy: text =>
3428
+ // setClipboard RETURNS OSC 52 — caller must stdout.write (tmux side-effects load-buffer, but that's tmux-only).
3429
+ void setClipboard(text).then(raw => {
3430
+ if (raw)
3431
+ process.stdout.write(raw);
3432
+ addNotification({
3433
+ // Same key as text-selection copy — repeated copies replace toast, don't queue.
3434
+ key: 'selection-copied',
3435
+ text: 'copied',
3436
+ color: 'success',
3437
+ priority: 'immediate',
3438
+ timeoutMs: 2000
3439
+ });
3440
+ }),
3441
+ edit: async (msg) => {
3442
+ // Same skip-confirm check as /rewind: lossless → direct, else confirm dialog.
3443
+ const rawIdx = findRawIndex(msg.uuid);
3444
+ const raw = rawIdx >= 0 ? messages[rawIdx] : undefined;
3445
+ if (!raw || !selectableUserMessagesFilter(raw))
3446
+ return;
3447
+ const noFileChanges = !(await fileHistoryHasAnyChanges(fileHistory, raw.uuid));
3448
+ const onlySynthetic = messagesAfterAreOnlySynthetic(messages, rawIdx);
3449
+ if (noFileChanges && onlySynthetic) {
3450
+ // rewindConversationTo's setMessages races stream appends — cancel first (idempotent).
3451
+ onCancel();
3452
+ // handleRestoreMessage also restores pasted images.
3453
+ void handleRestoreMessage(raw);
3454
+ }
3455
+ else {
3456
+ // Dialog path: onPreRestore (= onCancel) fires when user CONFIRMS, not on nevermind.
3457
+ setMessageSelectorPreselect(raw);
3458
+ setIsMessageSelectorVisible(true);
3459
+ }
3460
+ }
3461
+ };
3462
+ const { enter: enterMessageActions, handlers: messageActionHandlers } = useMessageActions(cursor, setCursor, cursorNavRef, messageActionCaps);
3463
+ async function onInit() {
3464
+ // Always verify API key on startup, so we can show the user an error in the
3465
+ // bottom right corner of the screen if the API key is invalid.
3466
+ void reverify();
3467
+ // Populate readFileState with THADDEUS.md files at startup
3468
+ const memoryFiles = await getMemoryFiles();
3469
+ if (memoryFiles.length > 0) {
3470
+ const fileList = memoryFiles.map(f => ` [${f.type}] ${f.path} (${f.content.length} chars)${f.parent ? ` (included by ${f.parent})` : ''}`).join('\n');
3471
+ logForDebugging(`Loaded ${memoryFiles.length} THADDEUS.md/rules files:\n${fileList}`);
3472
+ }
3473
+ else {
3474
+ logForDebugging('No THADDEUS.md/rules files found');
3475
+ }
3476
+ for (const file of memoryFiles) {
3477
+ // When the injected content doesn't match disk (stripped HTML comments,
3478
+ // stripped frontmatter, MEMORY.md truncation), cache the RAW disk bytes
3479
+ // with isPartialView so Edit/Write require a real Read first while
3480
+ // getChangedFiles + nested_memory dedup still work.
3481
+ readFileState.current.set(file.path, {
3482
+ content: file.contentDiffersFromDisk ? file.rawContent ?? file.content : file.content,
3483
+ timestamp: Date.now(),
3484
+ offset: undefined,
3485
+ limit: undefined,
3486
+ isPartialView: file.contentDiffersFromDisk
3487
+ });
3488
+ }
3489
+ // Initial message handling is done via the initialMessage effect
3490
+ }
3491
+ // Register cost summary tracker
3492
+ useCostSummary(useFpsMetrics());
3493
+ // Record transcripts locally, for debugging and conversation recovery
3494
+ // Don't record conversation if we only have initial messages; optimizes
3495
+ // the case where user resumes a conversation then quites before doing
3496
+ // anything else
3497
+ useLogMessages(messages, messages.length === initialMessages?.length);
3498
+ // REPL Bridge: replicate user/assistant messages to the bridge session
3499
+ // for remote access via the-oracleai.com. No-op in external builds or when not enabled.
3500
+ const { sendBridgeResult } = useReplBridge(messages, setMessages, abortControllerRef, commands, mainLoopModel);
3501
+ sendBridgeResultRef.current = sendBridgeResult;
3502
+ useAfterFirstRender();
3503
+ // Track prompt queue usage for analytics. Fire once per transition from
3504
+ // empty to non-empty, not on every length change -- otherwise a render loop
3505
+ // (concurrent onQuery thrashing, etc.) spams saveGlobalConfig, which hits
3506
+ // ELOCKED under concurrent sessions and falls back to unlocked writes.
3507
+ // That write storm is the primary trigger for ~/.claude.json corruption
3508
+ // (GH #3117).
3509
+ const hasCountedQueueUseRef = useRef(false);
3510
+ useEffect(() => {
3511
+ if (queuedCommands.length < 1) {
3512
+ hasCountedQueueUseRef.current = false;
3513
+ return;
3514
+ }
3515
+ if (hasCountedQueueUseRef.current)
3516
+ return;
3517
+ hasCountedQueueUseRef.current = true;
3518
+ saveGlobalConfig(current => ({
3519
+ ...current,
3520
+ promptQueueUseCount: (current.promptQueueUseCount ?? 0) + 1
3521
+ }));
3522
+ }, [queuedCommands.length]);
3523
+ // Process queued commands when query completes and queue has items
3524
+ const executeQueuedInput = useCallback(async (queuedCommands) => {
3525
+ await handlePromptSubmit({
3526
+ helpers: {
3527
+ setCursorOffset: () => { },
3528
+ clearBuffer: () => { },
3529
+ resetHistory: () => { }
3530
+ },
3531
+ queryGuard,
3532
+ commands,
3533
+ onInputChange: () => { },
3534
+ setPastedContents: () => { },
3535
+ setToolJSX,
3536
+ getToolUseContext,
3537
+ messages,
3538
+ mainLoopModel,
3539
+ ideSelection,
3540
+ setUserInputOnProcessing,
3541
+ setAbortController,
3542
+ onQuery,
3543
+ setAppState,
3544
+ querySource: getQuerySourceForREPL(),
3545
+ onBeforeQuery,
3546
+ canUseTool,
3547
+ addNotification,
3548
+ setMessages,
3549
+ queuedCommands
3550
+ });
3551
+ }, [queryGuard, commands, setToolJSX, getToolUseContext, messages, mainLoopModel, ideSelection, setUserInputOnProcessing, canUseTool, setAbortController, onQuery, addNotification, setAppState, onBeforeQuery]);
3552
+ useQueueProcessor({
3553
+ executeQueuedInput,
3554
+ hasActiveLocalJsxUI: isShowingLocalJSXCommand,
3555
+ queryGuard
3556
+ });
3557
+ // We'll use the global lastInteractionTime from state.ts
3558
+ // Update last interaction time when input changes.
3559
+ // Must be immediate because useEffect runs after the Ink render cycle flush.
3560
+ useEffect(() => {
3561
+ activityManager.recordUserActivity();
3562
+ updateLastInteractionTime(true);
3563
+ }, [inputValue, submitCount]);
3564
+ useEffect(() => {
3565
+ if (submitCount === 1) {
3566
+ startBackgroundHousekeeping();
3567
+ }
3568
+ }, [submitCount]);
3569
+ // Show notification when Thaddeus is done responding and user is idle
3570
+ useEffect(() => {
3571
+ // Don't set up notification if Thaddeus is busy
3572
+ if (isLoading)
3573
+ return;
3574
+ // Only enable notifications after the first new interaction in this session
3575
+ if (submitCount === 0)
3576
+ return;
3577
+ // No query has completed yet
3578
+ if (lastQueryCompletionTime === 0)
3579
+ return;
3580
+ // Set timeout to check idle state
3581
+ const timer = setTimeout((lastQueryCompletionTime, isLoading, toolJSX, focusedInputDialogRef, terminal) => {
3582
+ // Check if user has interacted since the response ended
3583
+ const lastUserInteraction = getLastInteractionTime();
3584
+ if (lastUserInteraction > lastQueryCompletionTime) {
3585
+ // User has interacted since Thaddeus finished - they're not idle, don't notify
3586
+ return;
3587
+ }
3588
+ // User hasn't interacted since response ended, check other conditions
3589
+ const idleTimeSinceResponse = Date.now() - lastQueryCompletionTime;
3590
+ if (!isLoading && !toolJSX &&
3591
+ // Use ref to get current dialog state, avoiding stale closure
3592
+ focusedInputDialogRef.current === undefined && idleTimeSinceResponse >= getGlobalConfig().messageIdleNotifThresholdMs) {
3593
+ void sendNotification({
3594
+ message: 'Thaddeus is waiting for your input',
3595
+ notificationType: 'idle_prompt'
3596
+ }, terminal);
3597
+ }
3598
+ }, getGlobalConfig().messageIdleNotifThresholdMs, lastQueryCompletionTime, isLoading, toolJSX, focusedInputDialogRef, terminal);
3599
+ return () => clearTimeout(timer);
3600
+ }, [isLoading, toolJSX, submitCount, lastQueryCompletionTime, terminal]);
3601
+ // Idle-return hint: show notification when idle threshold is exceeded.
3602
+ // Timer fires after the configured idle period; notification persists until
3603
+ // dismissed or the user submits.
3604
+ useEffect(() => {
3605
+ if (lastQueryCompletionTime === 0)
3606
+ return;
3607
+ if (isLoading)
3608
+ return;
3609
+ const willowMode = getFeatureValue_CACHED_MAY_BE_STALE('thaddeus_willow_mode', 'off');
3610
+ if (willowMode !== 'hint' && willowMode !== 'hint_v2')
3611
+ return;
3612
+ if (getGlobalConfig().idleReturnDismissed)
3613
+ return;
3614
+ const tokenThreshold = Number(process.env.THADDEUS_IDLE_TOKEN_THRESHOLD ?? 100_000);
3615
+ if (getTotalInputTokens() < tokenThreshold)
3616
+ return;
3617
+ const idleThresholdMs = Number(process.env.THADDEUS_IDLE_THRESHOLD_MINUTES ?? 75) * 60_000;
3618
+ const elapsed = Date.now() - lastQueryCompletionTime;
3619
+ const remaining = idleThresholdMs - elapsed;
3620
+ const timer = setTimeout((lqct, addNotif, msgsRef, mode, hintRef) => {
3621
+ if (msgsRef.current.length === 0)
3622
+ return;
3623
+ const totalTokens = getTotalInputTokens();
3624
+ const formattedTokens = formatTokens(totalTokens);
3625
+ const idleMinutes = (Date.now() - lqct) / 60_000;
3626
+ addNotif({
3627
+ key: 'idle-return-hint',
3628
+ jsx: mode === 'hint_v2' ? _jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "new task? " }), _jsx(Text, { color: "suggestion", children: "/clear" }), _jsx(Text, { dimColor: true, children: " to save " }), _jsxs(Text, { color: "suggestion", children: [formattedTokens, " tokens"] })] }) : _jsxs(Text, { color: "warning", children: ["new task? /clear to save ", formattedTokens, " tokens"] }),
3629
+ priority: 'medium',
3630
+ // Persist until submit — the hint fires at T+75min idle, user may
3631
+ // not return for hours. removeNotification in useEffect cleanup
3632
+ // handles dismissal. 0x7FFFFFFF = setTimeout max (~24.8 days).
3633
+ timeoutMs: 0x7fffffff
3634
+ });
3635
+ hintRef.current = mode;
3636
+ logEvent('thaddeus_idle_return_action', {
3637
+ action: 'hint_shown',
3638
+ variant: mode,
3639
+ idleMinutes: Math.round(idleMinutes),
3640
+ messageCount: msgsRef.current.length,
3641
+ totalInputTokens: totalTokens
3642
+ });
3643
+ }, Math.max(0, remaining), lastQueryCompletionTime, addNotification, messagesRef, willowMode, idleHintShownRef);
3644
+ return () => {
3645
+ clearTimeout(timer);
3646
+ removeNotification('idle-return-hint');
3647
+ idleHintShownRef.current = false;
3648
+ };
3649
+ }, [lastQueryCompletionTime, isLoading, addNotification, removeNotification]);
3650
+ // Submits incoming prompts from teammate messages or tasks mode as new turns
3651
+ // Returns true if submission succeeded, false if a query is already running
3652
+ const handleIncomingPrompt = useCallback((content, options) => {
3653
+ if (queryGuard.isActive)
3654
+ return false;
3655
+ // Defer to user-queued commands — user input always takes priority
3656
+ // over system messages (teammate messages, task list items, etc.)
3657
+ // Read from the module-level store at call time (not the render-time
3658
+ // snapshot) to avoid a stale closure — this callback's deps don't
3659
+ // include the queue.
3660
+ if (getCommandQueue().some(cmd => cmd.mode === 'prompt' || cmd.mode === 'bash')) {
3661
+ return false;
3662
+ }
3663
+ const newAbortController = createAbortController();
3664
+ setAbortController(newAbortController);
3665
+ // Create a user message with the formatted content (includes XML wrapper)
3666
+ const userMessage = createUserMessage({
3667
+ content,
3668
+ isMeta: options?.isMeta ? true : undefined
3669
+ });
3670
+ void onQuery([userMessage], newAbortController, true, [], mainLoopModel);
3671
+ return true;
3672
+ }, [onQuery, mainLoopModel, store]);
3673
+ // Voice input integration (VOICE_MODE builds only)
3674
+ const voice = feature('VOICE_MODE') ?
3675
+ // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
3676
+ useVoiceIntegration({
3677
+ setInputValueRaw,
3678
+ inputValueRef,
3679
+ insertTextRef
3680
+ }) : {
3681
+ stripTrailing: () => 0,
3682
+ handleKeyEvent: () => { },
3683
+ resetAnchor: () => { },
3684
+ interimRange: null
3685
+ };
3686
+ useInboxPoller({
3687
+ enabled: isAgentSwarmsEnabled(),
3688
+ isLoading,
3689
+ focusedInputDialog,
3690
+ onSubmitMessage: handleIncomingPrompt
3691
+ });
3692
+ useMailboxBridge({
3693
+ isLoading,
3694
+ onSubmitMessage: handleIncomingPrompt
3695
+ });
3696
+ // Scheduled tasks from .claude/scheduled_tasks.json (CronCreate/Delete/List)
3697
+ if (feature('AGENT_TRIGGERS')) {
3698
+ // Assistant mode bypasses the isLoading gate (the proactive tick →
3699
+ // Sleep → tick loop would otherwise starve the scheduler).
3700
+ // kairosEnabled is set once in initialState (main.tsx) and never mutated — no
3701
+ // subscription needed. The thaddeus_kairos_cron runtime gate is checked inside
3702
+ // useScheduledTasks's effect (not here) since wrapping a hook call in a dynamic
3703
+ // condition would break rules-of-hooks.
3704
+ const assistantMode = store.getState().kairosEnabled;
3705
+ // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
3706
+ useScheduledTasks({
3707
+ isLoading,
3708
+ assistantMode,
3709
+ setMessages
3710
+ });
3711
+ }
3712
+ // Note: Permission polling is now handled by useInboxPoller
3713
+ // - Workers receive permission responses via mailbox messages
3714
+ // - Leaders receive permission requests via mailbox messages
3715
+ if ("external" === 'ant') {
3716
+ // Tasks mode: watch for tasks and auto-process them
3717
+ // eslint-disable-next-line react-hooks/rules-of-hooks
3718
+ // biome-ignore lint/correctness/useHookAtTopLevel: conditional for dead code elimination in external builds
3719
+ useTaskListWatcher({
3720
+ taskListId,
3721
+ isLoading,
3722
+ onSubmitTask: handleIncomingPrompt
3723
+ });
3724
+ // Loop mode: auto-tick when enabled (via /job command)
3725
+ // eslint-disable-next-line react-hooks/rules-of-hooks
3726
+ // biome-ignore lint/correctness/useHookAtTopLevel: conditional for dead code elimination in external builds
3727
+ useProactive?.({
3728
+ // Suppress ticks while an initial message is pending — the initial
3729
+ // message will be processed asynchronously and a premature tick would
3730
+ // race with it, causing concurrent-query enqueue of expanded skill text.
3731
+ isLoading: isLoading || initialMessage !== null,
3732
+ queuedCommandsLength: queuedCommands.length,
3733
+ hasActiveLocalJsxUI: isShowingLocalJSXCommand,
3734
+ isInPlanMode: toolPermissionContext.mode === 'plan',
3735
+ onSubmitTick: (prompt) => handleIncomingPrompt(prompt, {
3736
+ isMeta: true
3737
+ }),
3738
+ onQueueTick: (prompt) => enqueue({
3739
+ mode: 'prompt',
3740
+ value: prompt,
3741
+ isMeta: true
3742
+ })
3743
+ });
3744
+ }
3745
+ // Abort the current operation when a 'now' priority message arrives
3746
+ // (e.g. from a chat UI client via UDS).
3747
+ useEffect(() => {
3748
+ if (queuedCommands.some(cmd => cmd.priority === 'now')) {
3749
+ abortControllerRef.current?.abort('interrupt');
3750
+ }
3751
+ }, [queuedCommands]);
3752
+ // Initial load
3753
+ useEffect(() => {
3754
+ void onInit();
3755
+ // Cleanup on unmount
3756
+ return () => {
3757
+ void diagnosticTracker.shutdown();
3758
+ };
3759
+ // TODO: fix this
3760
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3761
+ }, []);
3762
+ // Listen for suspend/resume events
3763
+ const { internal_eventEmitter } = useStdin();
3764
+ const [remountKey, setRemountKey] = useState(0);
3765
+ useEffect(() => {
3766
+ const handleSuspend = () => {
3767
+ // Print suspension instructions
3768
+ process.stdout.write(`\nThaddeus has been suspended. Run \`fg\` to bring Thaddeus back.\nNote: ctrl + z now suspends Thaddeus, ctrl + _ undoes input.\n`);
3769
+ };
3770
+ const handleResume = () => {
3771
+ // Force complete component tree replacement instead of terminal clear
3772
+ // Ink now handles line count reset internally on SIGCONT
3773
+ setRemountKey(prev => prev + 1);
3774
+ };
3775
+ internal_eventEmitter?.on('suspend', handleSuspend);
3776
+ internal_eventEmitter?.on('resume', handleResume);
3777
+ return () => {
3778
+ internal_eventEmitter?.off('suspend', handleSuspend);
3779
+ internal_eventEmitter?.off('resume', handleResume);
3780
+ };
3781
+ }, [internal_eventEmitter]);
3782
+ // Derive stop hook spinner suffix from messages state
3783
+ const stopHookSpinnerSuffix = useMemo(() => {
3784
+ if (!isLoading)
3785
+ return null;
3786
+ // Find stop hook progress messages
3787
+ const progressMsgs = messages.filter((m) => m.type === 'progress' && m.data.type === 'hook_progress' && (m.data.hookEvent === 'Stop' || m.data.hookEvent === 'SubagentStop'));
3788
+ if (progressMsgs.length === 0)
3789
+ return null;
3790
+ // Get the most recent stop hook execution
3791
+ const currentToolUseID = progressMsgs.at(-1)?.toolUseID;
3792
+ if (!currentToolUseID)
3793
+ return null;
3794
+ // Check if there's already a summary message for this execution (hooks completed)
3795
+ const hasSummaryForCurrentExecution = messages.some(m => m.type === 'system' && m.subtype === 'stop_hook_summary' && m.toolUseID === currentToolUseID);
3796
+ if (hasSummaryForCurrentExecution)
3797
+ return null;
3798
+ const currentHooks = progressMsgs.filter(p => p.toolUseID === currentToolUseID);
3799
+ const total = currentHooks.length;
3800
+ // Count completed hooks
3801
+ const completedCount = count(messages, m => {
3802
+ if (m.type !== 'attachment')
3803
+ return false;
3804
+ const attachment = m.attachment;
3805
+ return 'hookEvent' in attachment && (attachment.hookEvent === 'Stop' || attachment.hookEvent === 'SubagentStop') && 'toolUseID' in attachment && attachment.toolUseID === currentToolUseID;
3806
+ });
3807
+ // Check if any hook has a custom status message
3808
+ const customMessage = currentHooks.find(p => p.data.statusMessage)?.data.statusMessage;
3809
+ if (customMessage) {
3810
+ // Use custom message with progress counter if multiple hooks
3811
+ return total === 1 ? `${customMessage}…` : `${customMessage}… ${completedCount}/${total}`;
3812
+ }
3813
+ // Fall back to default behavior
3814
+ const hookType = currentHooks[0]?.data.hookEvent === 'SubagentStop' ? 'subagent stop' : 'stop';
3815
+ if ("external" === 'ant') {
3816
+ const cmd = currentHooks[completedCount]?.data.command;
3817
+ const label = cmd ? ` '${truncateToWidth(cmd, 40)}'` : '';
3818
+ return total === 1 ? `running ${hookType} hook${label}` : `running ${hookType} hook${label}\u2026 ${completedCount}/${total}`;
3819
+ }
3820
+ return total === 1 ? `running ${hookType} hook` : `running stop hooks… ${completedCount}/${total}`;
3821
+ }, [messages, isLoading]);
3822
+ // Callback to capture frozen state when entering transcript mode
3823
+ const handleEnterTranscript = useCallback(() => {
3824
+ setFrozenTranscriptState({
3825
+ messagesLength: messages.length,
3826
+ streamingToolUsesLength: streamingToolUses.length
3827
+ });
3828
+ }, [messages.length, streamingToolUses.length]);
3829
+ // Callback to clear frozen state when exiting transcript mode
3830
+ const handleExitTranscript = useCallback(() => {
3831
+ setFrozenTranscriptState(null);
3832
+ }, []);
3833
+ // Props for GlobalKeybindingHandlers component (rendered inside KeybindingSetup)
3834
+ const virtualScrollActive = isFullscreenEnvEnabled() && !disableVirtualScroll;
3835
+ // Transcript search state. Hooks must be unconditional so they live here
3836
+ // (not inside the `if (screen === 'transcript')` branch below); isActive
3837
+ // gates the useInput. Query persists across bar open/close so n/N keep
3838
+ // working after Enter dismisses the bar (less semantics).
3839
+ const jumpRef = useRef(null);
3840
+ const [searchOpen, setSearchOpen] = useState(false);
3841
+ const [searchQuery, setSearchQuery] = useState('');
3842
+ const [searchCount, setSearchCount] = useState(0);
3843
+ const [searchCurrent, setSearchCurrent] = useState(0);
3844
+ const onSearchMatchesChange = useCallback((count, current) => {
3845
+ setSearchCount(count);
3846
+ setSearchCurrent(current);
3847
+ }, []);
3848
+ useInput((input, key, event) => {
3849
+ if (key.ctrl || key.meta)
3850
+ return;
3851
+ // No Esc handling here — less has no navigating mode. Search state
3852
+ // (highlights, n/N) is just state. Esc/q/ctrl+c → transcript:exit
3853
+ // (ungated). Highlights clear on exit via the screen-change effect.
3854
+ if (input === '/') {
3855
+ // Capture scrollTop NOW — typing is a preview, 0-matches snaps
3856
+ // back here. Synchronous ref write, fires before the bar's
3857
+ // mount-effect calls setSearchQuery.
3858
+ jumpRef.current?.setAnchor();
3859
+ setSearchOpen(true);
3860
+ event.stopImmediatePropagation();
3861
+ return;
3862
+ }
3863
+ // Held-key batching: tokenizer coalesces to 'nnn'. Same uniform-batch
3864
+ // pattern as modalPagerAction in ScrollKeybindingHandler.tsx. Each
3865
+ // repeat is a step (n isn't idempotent like g).
3866
+ const c = input[0];
3867
+ if ((c === 'n' || c === 'N') && input === c.repeat(input.length) && searchCount > 0) {
3868
+ const fn = c === 'n' ? jumpRef.current?.nextMatch : jumpRef.current?.prevMatch;
3869
+ if (fn)
3870
+ for (let i = 0; i < input.length; i++)
3871
+ fn();
3872
+ event.stopImmediatePropagation();
3873
+ }
3874
+ },
3875
+ // Search needs virtual scroll (jumpRef drives VirtualMessageList). [
3876
+ // kills it, so !dumpMode — after [ there's nothing to jump in.
3877
+ {
3878
+ isActive: screen === 'transcript' && virtualScrollActive && !searchOpen && !dumpMode
3879
+ });
3880
+ const { setQuery: setHighlight, scanElement, setPositions } = useSearchHighlight();
3881
+ // Resize → abort search. Positions are (msg, query, WIDTH)-keyed —
3882
+ // cached positions are stale after a width change (new layout, new
3883
+ // wrapping). Clearing searchQuery triggers VML's setSearchQuery('')
3884
+ // which clears positionsCache + setPositions(null). Bar closes.
3885
+ // User hits / again → fresh everything.
3886
+ const transcriptCols = useTerminalSize().columns;
3887
+ const prevColsRef = React.useRef(transcriptCols);
3888
+ React.useEffect(() => {
3889
+ if (prevColsRef.current !== transcriptCols) {
3890
+ prevColsRef.current = transcriptCols;
3891
+ if (searchQuery || searchOpen) {
3892
+ setSearchOpen(false);
3893
+ setSearchQuery('');
3894
+ setSearchCount(0);
3895
+ setSearchCurrent(0);
3896
+ jumpRef.current?.disarmSearch();
3897
+ setHighlight('');
3898
+ }
3899
+ }
3900
+ }, [transcriptCols, searchQuery, searchOpen, setHighlight]);
3901
+ // Transcript escape hatches. Bare letters in modal context (no prompt
3902
+ // competing for input) — same class as g/G/j/k in ScrollKeybindingHandler.
3903
+ useInput((input, key, event) => {
3904
+ if (key.ctrl || key.meta)
3905
+ return;
3906
+ if (input === 'q') {
3907
+ // less: q quits the pager. ctrl+o toggles; q is the lineage exit.
3908
+ handleExitTranscript();
3909
+ event.stopImmediatePropagation();
3910
+ return;
3911
+ }
3912
+ if (input === '[' && !dumpMode) {
3913
+ // Force dump-to-scrollback. Also expand + uncap — no point dumping
3914
+ // a subset. Terminal/tmux cmd-F can now find anything. Guard here
3915
+ // (not in isActive) so v still works post-[ — dump-mode footer at
3916
+ // ~4898 wires editorStatus, confirming v is meant to stay live.
3917
+ setDumpMode(true);
3918
+ setShowAllInTranscript(true);
3919
+ event.stopImmediatePropagation();
3920
+ }
3921
+ else if (input === 'v') {
3922
+ // less-style: v opens the file in $VISUAL/$EDITOR. Render the full
3923
+ // transcript (same path /export uses), write to tmp, hand off.
3924
+ // openFileInExternalEditor handles alt-screen suspend/resume for
3925
+ // terminal editors; GUI editors spawn detached.
3926
+ event.stopImmediatePropagation();
3927
+ // Drop double-taps: the render is async and a second press before it
3928
+ // completes would run a second parallel render (double memory, two
3929
+ // tempfiles, two editor spawns). editorGenRef only guards
3930
+ // transcript-exit staleness, not same-session concurrency.
3931
+ if (editorRenderingRef.current)
3932
+ return;
3933
+ editorRenderingRef.current = true;
3934
+ // Capture generation + make a staleness-aware setter. Each write
3935
+ // checks gen (transcript exit bumps it → late writes from the
3936
+ // async render go silent).
3937
+ const gen = editorGenRef.current;
3938
+ const setStatus = (s) => {
3939
+ if (gen !== editorGenRef.current)
3940
+ return;
3941
+ clearTimeout(editorTimerRef.current);
3942
+ setEditorStatus(s);
3943
+ };
3944
+ setStatus(`rendering ${deferredMessages.length} messages…`);
3945
+ void (async () => {
3946
+ try {
3947
+ // Width = terminal minus vim's line-number gutter (4 digits +
3948
+ // space + slack). Floor at 80. PassThrough has no .columns so
3949
+ // without this Ink defaults to 80. Trailing-space strip: right-
3950
+ // aligned timestamps still leave a flexbox spacer run at EOL.
3951
+ // eslint-disable-next-line custom-rules/prefer-use-terminal-size -- one-shot at keypress time, not a reactive render dep
3952
+ const w = Math.max(80, (process.stdout.columns ?? 80) - 6);
3953
+ const raw = await renderMessagesToPlainText(deferredMessages, tools, w);
3954
+ const text = raw.replace(/[ \t]+$/gm, '');
3955
+ const path = join(tmpdir(), `cc-transcript-${Date.now()}.txt`);
3956
+ await writeFile(path, text);
3957
+ const opened = openFileInExternalEditor(path);
3958
+ setStatus(opened ? `opening ${path}` : `wrote ${path} · no $VISUAL/$EDITOR set`);
3959
+ }
3960
+ catch (e) {
3961
+ setStatus(`render failed: ${e instanceof Error ? e.message : String(e)}`);
3962
+ }
3963
+ editorRenderingRef.current = false;
3964
+ if (gen !== editorGenRef.current)
3965
+ return;
3966
+ editorTimerRef.current = setTimeout(s => s(''), 4000, setEditorStatus);
3967
+ })();
3968
+ }
3969
+ },
3970
+ // !searchOpen: typing 'v' or '[' in the search bar is search input, not
3971
+ // a command. No !dumpMode here — v should work after [ (the [ handler
3972
+ // guards itself inline).
3973
+ {
3974
+ isActive: screen === 'transcript' && virtualScrollActive && !searchOpen
3975
+ });
3976
+ // Fresh `less` per transcript entry. Prevents stale highlights matching
3977
+ // unrelated normal-mode text (overlay is alt-screen-global) and avoids
3978
+ // surprise n/N on re-entry. Same exit resets [ dump mode — each ctrl+o
3979
+ // entry is a fresh instance.
3980
+ const inTranscript = screen === 'transcript' && virtualScrollActive;
3981
+ useEffect(() => {
3982
+ if (!inTranscript) {
3983
+ setSearchQuery('');
3984
+ setSearchCount(0);
3985
+ setSearchCurrent(0);
3986
+ setSearchOpen(false);
3987
+ editorGenRef.current++;
3988
+ clearTimeout(editorTimerRef.current);
3989
+ setDumpMode(false);
3990
+ setEditorStatus('');
3991
+ }
3992
+ }, [inTranscript]);
3993
+ useEffect(() => {
3994
+ setHighlight(inTranscript ? searchQuery : '');
3995
+ // Clear the position-based CURRENT (yellow) overlay too. setHighlight
3996
+ // only clears the scan-based inverse. Without this, the yellow box
3997
+ // persists at its last screen coords after ctrl-c exits transcript.
3998
+ if (!inTranscript)
3999
+ setPositions(null);
4000
+ }, [inTranscript, searchQuery, setHighlight, setPositions]);
4001
+ const globalKeybindingProps = {
4002
+ screen,
4003
+ setScreen,
4004
+ showAllInTranscript,
4005
+ setShowAllInTranscript,
4006
+ messageCount: messages.length,
4007
+ onEnterTranscript: handleEnterTranscript,
4008
+ onExitTranscript: handleExitTranscript,
4009
+ virtualScrollActive,
4010
+ // Bar-open is a mode (owns keystrokes — j/k type, Esc cancels).
4011
+ // Navigating (query set, bar closed) is NOT — Esc exits transcript,
4012
+ // same as less q with highlights still visible. useSearchInput
4013
+ // doesn't stopPropagation, so without this gate transcript:exit
4014
+ // would fire on the same Esc that cancels the bar (child registers
4015
+ // first, fires first, bubbles).
4016
+ searchBarOpen: searchOpen
4017
+ };
4018
+ // Use frozen lengths to slice arrays, avoiding memory overhead of cloning
4019
+ const transcriptMessages = frozenTranscriptState ? deferredMessages.slice(0, frozenTranscriptState.messagesLength) : deferredMessages;
4020
+ const transcriptStreamingToolUses = frozenTranscriptState ? streamingToolUses.slice(0, frozenTranscriptState.streamingToolUsesLength) : streamingToolUses;
4021
+ // Handle shift+down for teammate navigation and background task management.
4022
+ // Guard onOpenBackgroundTasks when a local-jsx dialog (e.g. /mcp) is open —
4023
+ // otherwise Shift+Down stacks BackgroundTasksDialog on top and deadlocks input.
4024
+ useBackgroundTaskNavigation({
4025
+ onOpenBackgroundTasks: isShowingLocalJSXCommand ? undefined : () => setShowBashesDialog(true)
4026
+ });
4027
+ // Auto-exit viewing mode when teammate completes or errors
4028
+ useTeammateViewAutoExit();
4029
+ if (screen === 'transcript') {
4030
+ // Virtual scroll replaces the 30-message cap: everything is scrollable
4031
+ // and memory is bounded by the viewport. Without it, wrapping transcript
4032
+ // in a ScrollBox would mount all messages (~250 MB on long sessions —
4033
+ // the exact problem), so the kill switch and non-fullscreen paths must
4034
+ // fall through to the legacy render: no alt screen, dump to terminal
4035
+ // scrollback, 30-cap + Ctrl+E. Reusing scrollRef is safe — normal-mode
4036
+ // and transcript-mode are mutually exclusive (this early return), so
4037
+ // only one ScrollBox is ever mounted at a time.
4038
+ const transcriptScrollRef = isFullscreenEnvEnabled() && !disableVirtualScroll && !dumpMode ? scrollRef : undefined;
4039
+ const transcriptMessagesElement = _jsx(Messages, { messages: transcriptMessages, tools: tools, commands: commands, verbose: true, toolJSX: null, toolUseConfirmQueue: [], inProgressToolUseIDs: inProgressToolUseIDs, isMessageSelectorVisible: false, conversationId: conversationId, screen: screen, agentDefinitions: agentDefinitions, streamingToolUses: transcriptStreamingToolUses, showAllInTranscript: showAllInTranscript, onOpenRateLimitOptions: handleOpenRateLimitOptions, isLoading: isLoading, hidePastThinking: true, streamingThinking: streamingThinking, scrollRef: transcriptScrollRef, jumpRef: jumpRef, onSearchMatchesChange: onSearchMatchesChange, scanElement: scanElement, setPositions: setPositions, disableRenderCap: dumpMode });
4040
+ const transcriptToolJSX = toolJSX && _jsx(Box, { flexDirection: "column", width: "100%", children: toolJSX.jsx });
4041
+ const transcriptReturn = _jsxs(KeybindingSetup, { children: [_jsx(AnimatedTerminalTitle, { isAnimating: titleIsAnimating, title: terminalTitle, disabled: titleDisabled, noPrefix: showStatusInTerminalTab }), _jsx(GlobalKeybindingHandlers, { ...globalKeybindingProps }), feature('VOICE_MODE') ? _jsx(VoiceKeybindingHandler, { voiceHandleKeyEvent: voice.handleKeyEvent, stripTrailing: voice.stripTrailing, resetAnchor: voice.resetAnchor, isActive: !toolJSX?.isLocalJSXCommand }) : null, _jsx(CommandKeybindingHandlers, { onSubmit: onSubmit, isActive: !toolJSX?.isLocalJSXCommand }), transcriptScrollRef ?
4042
+ // ScrollKeybindingHandler must mount before CancelRequestHandler so
4043
+ // ctrl+c-with-selection copies instead of cancelling the active task.
4044
+ // Its raw useInput handler only stops propagation when a selection
4045
+ // exists — without one, ctrl+c falls through to CancelRequestHandler.
4046
+ _jsx(ScrollKeybindingHandler, { scrollRef: scrollRef,
4047
+ // Yield wheel/ctrl+u/d to UltraplanChoiceDialog's own scroll
4048
+ // handler while the modal is showing.
4049
+ isActive: focusedInputDialog !== 'ultraplan-choice',
4050
+ // g/G/j/k/ctrl+u/ctrl+d would eat keystrokes the search bar
4051
+ // wants. Off while searching.
4052
+ isModal: !searchOpen,
4053
+ // Manual scroll exits the search context — clear the yellow
4054
+ // current-match marker. Positions are (msg, rowOffset)-keyed;
4055
+ // j/k changes scrollTop so rowOffset is stale → wrong row
4056
+ // gets yellow. Next n/N re-establishes via step()→jump().
4057
+ onScroll: () => jumpRef.current?.disarmSearch() }) : null, _jsx(CancelRequestHandler, { ...cancelRequestProps }), transcriptScrollRef ? _jsx(FullscreenLayout, { scrollRef: scrollRef, scrollable: _jsxs(_Fragment, { children: [transcriptMessagesElement, transcriptToolJSX, _jsx(SandboxViolationExpandedView, {})] }), bottom: searchOpen ? _jsx(TranscriptSearchBar, { jumpRef: jumpRef,
4058
+ // Seed was tried (c01578c8) — broke /hello muscle
4059
+ // memory (cursor lands after 'foo', /hello → foohello).
4060
+ // Cancel-restore handles the 'don't lose prior search'
4061
+ // concern differently (onCancel re-applies searchQuery).
4062
+ initialQuery: "", count: searchCount, current: searchCurrent, onClose: q => {
4063
+ // Enter — commit. 0-match guard: junk query shouldn't
4064
+ // persist (badge hidden, n/N dead anyway).
4065
+ setSearchQuery(searchCount > 0 ? q : '');
4066
+ setSearchOpen(false);
4067
+ // onCancel path: bar unmounts before its useEffect([query])
4068
+ // can fire with ''. Without this, searchCount stays stale
4069
+ // (n guard at :4956 passes) and VML's matches[] too
4070
+ // (nextMatch walks the old array). Phantom nav, no
4071
+ // highlight. onExit (Enter, q non-empty) still commits.
4072
+ if (!q) {
4073
+ setSearchCount(0);
4074
+ setSearchCurrent(0);
4075
+ jumpRef.current?.setSearchQuery('');
4076
+ }
4077
+ }, onCancel: () => {
4078
+ // Esc/ctrl+c/ctrl+g — undo. Bar's effect last fired
4079
+ // with whatever was typed. searchQuery (REPL state)
4080
+ // is unchanged since / (onClose = commit, didn't run).
4081
+ // Two VML calls: '' restores anchor (0-match else-
4082
+ // branch), then searchQuery re-scans from anchor's
4083
+ // nearest. Both synchronous — one React batch.
4084
+ // setHighlight explicit: REPL's sync-effect dep is
4085
+ // searchQuery (unchanged), wouldn't re-fire.
4086
+ setSearchOpen(false);
4087
+ jumpRef.current?.setSearchQuery('');
4088
+ jumpRef.current?.setSearchQuery(searchQuery);
4089
+ setHighlight(searchQuery);
4090
+ }, setHighlight: setHighlight }) : _jsx(TranscriptModeFooter, { showAllInTranscript: showAllInTranscript, virtualScroll: true, status: editorStatus || undefined, searchBadge: searchQuery && searchCount > 0 ? {
4091
+ current: searchCurrent,
4092
+ count: searchCount
4093
+ } : undefined }) }) : _jsxs(_Fragment, { children: [transcriptMessagesElement, transcriptToolJSX, _jsx(SandboxViolationExpandedView, {}), _jsx(TranscriptModeFooter, { showAllInTranscript: showAllInTranscript, virtualScroll: false, suppressShowAll: dumpMode, status: editorStatus || undefined })] })] });
4094
+ // The virtual-scroll branch (FullscreenLayout above) needs
4095
+ // <AlternateScreen>'s <Box height={rows}> constraint — without it,
4096
+ // ScrollBox's flexGrow has no ceiling, viewport = content height,
4097
+ // scrollTop pins at 0, and Ink's screen buffer sizes to the full
4098
+ // spacer (200×5k+ rows on long sessions). Same root type + props as
4099
+ // normal mode's wrap below so React reconciles and the alt buffer
4100
+ // stays entered across toggle. The 30-cap dump branch stays
4101
+ // unwrapped — it wants native terminal scrollback.
4102
+ if (transcriptScrollRef) {
4103
+ return _jsx(AlternateScreen, { mouseTracking: isMouseTrackingEnabled(), children: transcriptReturn });
4104
+ }
4105
+ return transcriptReturn;
4106
+ }
4107
+ // Get viewed agent task (inlined from selectors for explicit data flow).
4108
+ // viewedAgentTask: teammate OR local_agent — drives the boolean checks
4109
+ // below. viewedTeammateTask: teammate-only narrowed, for teammate-specific
4110
+ // field access (inProgressToolUseIDs).
4111
+ const viewedTask = viewingAgentTaskId ? tasks[viewingAgentTaskId] : undefined;
4112
+ const viewedTeammateTask = viewedTask && isInProcessTeammateTask(viewedTask) ? viewedTask : undefined;
4113
+ const viewedAgentTask = viewedTeammateTask ?? (viewedTask && isLocalAgentTask(viewedTask) ? viewedTask : undefined);
4114
+ // Bypass useDeferredValue when streaming text is showing so Messages renders
4115
+ // the final message in the same frame streaming text clears. Also bypass when
4116
+ // not loading — deferredMessages only matters during streaming (keeps input
4117
+ // responsive); after the turn ends, showing messages immediately prevents a
4118
+ // jitter gap where the spinner is gone but the answer hasn't appeared yet.
4119
+ // Only reducedMotion users keep the deferred path during loading.
4120
+ const usesSyncMessages = showStreamingText || !isLoading;
4121
+ // When viewing an agent, never fall through to leader — empty until
4122
+ // bootstrap/stream fills. Closes the see-leader-type-agent footgun.
4123
+ const displayedMessages = viewedAgentTask ? viewedAgentTask.messages ?? [] : usesSyncMessages ? messages : deferredMessages;
4124
+ // Show the placeholder until the real user message appears in
4125
+ // displayedMessages. userInputOnProcessing stays set for the whole turn
4126
+ // (cleared in resetLoadingState); this length check hides it once
4127
+ // displayedMessages grows past the baseline captured at submit time.
4128
+ // Covers both gaps: before setMessages is called (processUserInput), and
4129
+ // while deferredMessages lags behind messages. Suppressed when viewing an
4130
+ // agent — displayedMessages is a different array there, and onAgentSubmit
4131
+ // doesn't use the placeholder anyway.
4132
+ const placeholderText = userInputOnProcessing && !viewedAgentTask && displayedMessages.length <= userInputBaselineRef.current ? userInputOnProcessing : undefined;
4133
+ const toolPermissionOverlay = focusedInputDialog === 'tool-permission' ? _jsx(PermissionRequest, { onDone: () => setToolUseConfirmQueue(([_, ...tail]) => tail), onReject: handleQueuedCommandOnCancel, toolUseConfirm: toolUseConfirmQueue[0], toolUseContext: getToolUseContext(messages, messages, abortController ?? createAbortController(), mainLoopModel), verbose: verbose, workerBadge: toolUseConfirmQueue[0]?.workerBadge, setStickyFooter: isFullscreenEnvEnabled() ? setPermissionStickyFooter : undefined }, toolUseConfirmQueue[0]?.toolUseID) : null;
4134
+ // Narrow terminals: companion collapses to a one-liner that REPL stacks
4135
+ // on its own row (above input in fullscreen, below in scrollback) instead
4136
+ // of row-beside. Wide terminals keep the row layout with sprite on the right.
4137
+ const companionNarrow = transcriptCols < MIN_COLS_FOR_FULL_SPRITE;
4138
+ // Hide the sprite when PromptInput early-returns BackgroundTasksDialog.
4139
+ // The sprite sits as a row sibling of PromptInput, so the dialog's Pane
4140
+ // divider draws at useTerminalSize() width but only gets terminalWidth -
4141
+ // spriteWidth — divider stops short and dialog text wraps early. Don't
4142
+ // check footerSelection: pill FOCUS (arrow-down to tasks pill) must keep
4143
+ // the sprite visible so arrow-right can navigate to it.
4144
+ const companionVisible = !toolJSX?.shouldHidePromptInput && !focusedInputDialog && !showBashesDialog;
4145
+ // In fullscreen, ALL local-jsx slash commands float in the modal slot —
4146
+ // FullscreenLayout wraps them in an absolute-positioned bottom-anchored
4147
+ // pane (▔ divider, ModalContext). Pane/Dialog inside detect the context
4148
+ // and skip their own top-level frame. Non-fullscreen keeps the inline
4149
+ // render paths below. Commands that used to route through bottom
4150
+ // (immediate: /model, /mcp, /btw, ...) and scrollable (non-immediate:
4151
+ // /config, /theme, /diff, ...) both go here now.
4152
+ const toolJsxCentered = isFullscreenEnvEnabled() && toolJSX?.isLocalJSXCommand === true;
4153
+ const centeredModal = toolJsxCentered ? toolJSX.jsx : null;
4154
+ // <AlternateScreen> at the root: everything below is inside its
4155
+ // <Box height={rows}>. Handlers/contexts are zero-height so ScrollBox's
4156
+ // flexGrow in FullscreenLayout resolves against this Box. The transcript
4157
+ // early return above wraps its virtual-scroll branch the same way; only
4158
+ // the 30-cap dump branch stays unwrapped for native terminal scrollback.
4159
+ const mainReturn = _jsxs(KeybindingSetup, { children: [_jsx(AnimatedTerminalTitle, { isAnimating: titleIsAnimating, title: terminalTitle, disabled: titleDisabled, noPrefix: showStatusInTerminalTab }), _jsx(GlobalKeybindingHandlers, { ...globalKeybindingProps }), feature('VOICE_MODE') ? _jsx(VoiceKeybindingHandler, { voiceHandleKeyEvent: voice.handleKeyEvent, stripTrailing: voice.stripTrailing, resetAnchor: voice.resetAnchor, isActive: !toolJSX?.isLocalJSXCommand }) : null, _jsx(CommandKeybindingHandlers, { onSubmit: onSubmit, isActive: !toolJSX?.isLocalJSXCommand }), _jsx(ScrollKeybindingHandler, { scrollRef: scrollRef, isActive: isFullscreenEnvEnabled() && (centeredModal != null || !focusedInputDialog || focusedInputDialog === 'tool-permission'), onScroll: centeredModal || toolPermissionOverlay || viewedAgentTask ? undefined : composedOnScroll }), feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? _jsx(MessageActionsKeybindings, { handlers: messageActionHandlers, isActive: cursor !== null }) : null, _jsx(CancelRequestHandler, { ...cancelRequestProps }), _jsx(MCPConnectionManager, { dynamicMcpConfig: dynamicMcpConfig, isStrictMcpConfig: strictMcpConfig, children: _jsx(FullscreenLayout, { scrollRef: scrollRef, overlay: toolPermissionOverlay, bottomFloat: feature('BUDDY') && companionVisible && !companionNarrow ? _jsx(CompanionFloatingBubble, {}) : undefined, modal: centeredModal, modalScrollRef: modalScrollRef, dividerYRef: dividerYRef, hidePill: !!viewedAgentTask, hideSticky: !!viewedTeammateTask, newMessageCount: unseenDivider?.count ?? 0, onPillClick: () => {
4160
+ setCursor(null);
4161
+ jumpToNew(scrollRef.current);
4162
+ }, scrollable: _jsxs(_Fragment, { children: [_jsx(TeammateViewHeader, {}), _jsx(Messages, { messages: displayedMessages, tools: tools, commands: commands, verbose: verbose, toolJSX: toolJSX, toolUseConfirmQueue: toolUseConfirmQueue, inProgressToolUseIDs: viewedTeammateTask ? viewedTeammateTask.inProgressToolUseIDs ?? new Set() : inProgressToolUseIDs, isMessageSelectorVisible: isMessageSelectorVisible, conversationId: conversationId, screen: screen, streamingToolUses: streamingToolUses, showAllInTranscript: showAllInTranscript, agentDefinitions: agentDefinitions, onOpenRateLimitOptions: handleOpenRateLimitOptions, isLoading: isLoading, streamingText: isLoading && !viewedAgentTask ? visibleStreamingText : null, isBriefOnly: viewedAgentTask ? false : isBriefOnly, unseenDivider: viewedAgentTask ? undefined : unseenDivider, scrollRef: isFullscreenEnvEnabled() ? scrollRef : undefined, trackStickyPrompt: isFullscreenEnvEnabled() ? true : undefined, cursor: cursor, setCursor: setCursor, cursorNavRef: cursorNavRef }), _jsx(AwsAuthStatusBox, {}), !disabled && placeholderText && !centeredModal && _jsx(UserTextMessage, { param: {
4163
+ text: placeholderText,
4164
+ type: 'text'
4165
+ }, addMargin: true, verbose: verbose }), toolJSX && !(toolJSX.isLocalJSXCommand && toolJSX.isImmediate) && !toolJsxCentered && _jsx(Box, { flexDirection: "column", width: "100%", children: toolJSX.jsx }), "external" === 'ant' && _jsx(TungstenLiveMonitor, {}), feature('WEB_BROWSER_TOOL') ? WebBrowserPanelModule && _jsx(WebBrowserPanelModule.WebBrowserPanel, {}) : null, _jsx(Box, { flexGrow: 1 }), showSpinner && _jsx(SpinnerWithVerb, { mode: streamMode, spinnerTip: spinnerTip, responseLengthRef: responseLengthRef, apiMetricsRef: apiMetricsRef, overrideMessage: spinnerMessage, spinnerSuffix: stopHookSpinnerSuffix, verbose: verbose, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, overrideColor: spinnerColor, overrideShimmerColor: spinnerShimmerColor, hasActiveTools: inProgressToolUseIDs.size > 0, leaderIsIdle: !isLoading }), !showSpinner && !isLoading && !userInputOnProcessing && !hasRunningTeammates && isBriefOnly && !viewedAgentTask && _jsx(BriefIdleStatus, {}), isFullscreenEnvEnabled() && _jsx(PromptInputQueuedCommands, {})] }), bottom: _jsxs(Box, { flexDirection: feature('BUDDY') && companionNarrow ? 'column' : 'row', width: "100%", alignItems: feature('BUDDY') && companionNarrow ? undefined : 'flex-end', children: [feature('BUDDY') && companionNarrow && isFullscreenEnvEnabled() && companionVisible ? _jsx(CompanionSprite, {}) : null, _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [permissionStickyFooter, toolJSX?.isLocalJSXCommand && toolJSX.isImmediate && !toolJsxCentered && _jsx(Box, { flexDirection: "column", width: "100%", children: toolJSX.jsx }), !showSpinner && !toolJSX?.isLocalJSXCommand && showExpandedTodos && tasksV2 && tasksV2.length > 0 && _jsx(Box, { width: "100%", flexDirection: "column", children: _jsx(TaskListV2, { tasks: tasksV2, isStandalone: true }) }), focusedInputDialog === 'sandbox-permission' && _jsx(SandboxPermissionRequest, { hostPattern: sandboxPermissionRequestQueue[0].hostPattern, onUserResponse: (response) => {
4166
+ const { allow, persistToSettings } = response;
4167
+ const currentRequest = sandboxPermissionRequestQueue[0];
4168
+ if (!currentRequest)
4169
+ return;
4170
+ const approvedHost = currentRequest.hostPattern.host;
4171
+ if (persistToSettings) {
4172
+ const update = {
4173
+ type: 'addRules',
4174
+ rules: [{
4175
+ toolName: WEB_FETCH_TOOL_NAME,
4176
+ ruleContent: `domain:${approvedHost}`
4177
+ }],
4178
+ behavior: (allow ? 'allow' : 'deny'),
4179
+ destination: 'localSettings'
4180
+ };
4181
+ setAppState(prev => ({
4182
+ ...prev,
4183
+ toolPermissionContext: applyPermissionUpdate(prev.toolPermissionContext, update)
4184
+ }));
4185
+ persistPermissionUpdate(update);
4186
+ // Immediately update sandbox in-memory config to prevent race conditions
4187
+ // where pending requests slip through before settings change is detected
4188
+ SandboxManager.refreshConfig();
4189
+ }
4190
+ // Resolve ALL pending requests for the same host (not just the first one)
4191
+ // This handles the case where multiple parallel requests came in for the same domain
4192
+ setSandboxPermissionRequestQueue(queue => {
4193
+ queue.filter(item => item.hostPattern.host === approvedHost).forEach(item => item.resolvePromise(allow));
4194
+ return queue.filter(item => item.hostPattern.host !== approvedHost);
4195
+ });
4196
+ // Clean up bridge subscriptions and cancel remote prompts
4197
+ // for this host since the local user already responded.
4198
+ const cleanups = sandboxBridgeCleanupRef.current.get(approvedHost);
4199
+ if (cleanups) {
4200
+ for (const fn of cleanups) {
4201
+ fn();
4202
+ }
4203
+ sandboxBridgeCleanupRef.current.delete(approvedHost);
4204
+ }
4205
+ } }, sandboxPermissionRequestQueue[0].hostPattern.host), focusedInputDialog === 'prompt' && _jsx(PromptDialog, { title: promptQueue[0].title, toolInputSummary: promptQueue[0].toolInputSummary, request: promptQueue[0].request, onRespond: selectedKey => {
4206
+ const item = promptQueue[0];
4207
+ if (!item)
4208
+ return;
4209
+ item.resolve({
4210
+ prompt_response: item.request.prompt,
4211
+ selected: selectedKey
4212
+ });
4213
+ setPromptQueue(([, ...tail]) => tail);
4214
+ }, onAbort: () => {
4215
+ const item = promptQueue[0];
4216
+ if (!item)
4217
+ return;
4218
+ item.reject(new Error('Prompt cancelled by user'));
4219
+ setPromptQueue(([, ...tail]) => tail);
4220
+ } }, promptQueue[0].request.prompt), pendingWorkerRequest && _jsx(WorkerPendingPermission, { toolName: pendingWorkerRequest.toolName, description: pendingWorkerRequest.description }), pendingSandboxRequest && _jsx(WorkerPendingPermission, { toolName: "Network Access", description: `Waiting for leader to approve network access to ${pendingSandboxRequest.host}` }), focusedInputDialog === 'worker-sandbox-permission' && _jsx(SandboxPermissionRequest, { hostPattern: {
4221
+ host: workerSandboxPermissions.queue[0].host,
4222
+ port: undefined
4223
+ }, onUserResponse: (response) => {
4224
+ const { allow, persistToSettings } = response;
4225
+ const currentRequest = workerSandboxPermissions.queue[0];
4226
+ if (!currentRequest)
4227
+ return;
4228
+ const approvedHost = currentRequest.host;
4229
+ // Send response via mailbox to the worker
4230
+ void sendSandboxPermissionResponseViaMailbox(currentRequest.workerName, currentRequest.requestId, approvedHost, allow, teamContext?.teamName);
4231
+ if (persistToSettings && allow) {
4232
+ const update = {
4233
+ type: 'addRules',
4234
+ rules: [{
4235
+ toolName: WEB_FETCH_TOOL_NAME,
4236
+ ruleContent: `domain:${approvedHost}`
4237
+ }],
4238
+ behavior: 'allow',
4239
+ destination: 'localSettings'
4240
+ };
4241
+ setAppState(prev => ({
4242
+ ...prev,
4243
+ toolPermissionContext: applyPermissionUpdate(prev.toolPermissionContext, update)
4244
+ }));
4245
+ persistPermissionUpdate(update);
4246
+ SandboxManager.refreshConfig();
4247
+ }
4248
+ // Remove from queue
4249
+ setAppState(prev => ({
4250
+ ...prev,
4251
+ workerSandboxPermissions: {
4252
+ ...prev.workerSandboxPermissions,
4253
+ queue: prev.workerSandboxPermissions.queue.slice(1)
4254
+ }
4255
+ }));
4256
+ } }, workerSandboxPermissions.queue[0].requestId), focusedInputDialog === 'elicitation' && _jsx(ElicitationDialog, { event: elicitation.queue[0], onResponse: (action, content) => {
4257
+ const currentRequest = elicitation.queue[0];
4258
+ if (!currentRequest)
4259
+ return;
4260
+ // Call respond callback to resolve Promise
4261
+ currentRequest.respond({
4262
+ action,
4263
+ content
4264
+ });
4265
+ // For URL accept, keep in queue for phase 2
4266
+ const isUrlAccept = currentRequest.params.mode === 'url' && action === 'accept';
4267
+ if (!isUrlAccept) {
4268
+ setAppState(prev => ({
4269
+ ...prev,
4270
+ elicitation: {
4271
+ queue: prev.elicitation.queue.slice(1)
4272
+ }
4273
+ }));
4274
+ }
4275
+ }, onWaitingDismiss: action => {
4276
+ const currentRequest = elicitation.queue[0];
4277
+ // Remove from queue
4278
+ setAppState(prev => ({
4279
+ ...prev,
4280
+ elicitation: {
4281
+ queue: prev.elicitation.queue.slice(1)
4282
+ }
4283
+ }));
4284
+ currentRequest?.onWaitingDismiss?.(action);
4285
+ } }, elicitation.queue[0].serverName + ':' + String(elicitation.queue[0].requestId)), focusedInputDialog === 'cost' && _jsx(CostThresholdDialog, { onDone: () => {
4286
+ setShowCostDialog(false);
4287
+ setHaveShownCostDialog(true);
4288
+ saveGlobalConfig(current => ({
4289
+ ...current,
4290
+ hasAcknowledgedCostThreshold: true
4291
+ }));
4292
+ logEvent('thaddeus_cost_threshold_acknowledged', {});
4293
+ } }), focusedInputDialog === 'idle-return' && idleReturnPending && _jsx(IdleReturnDialog, { idleMinutes: idleReturnPending.idleMinutes, totalInputTokens: getTotalInputTokens(), onDone: async (action) => {
4294
+ const pending = idleReturnPending;
4295
+ setIdleReturnPending(null);
4296
+ logEvent('thaddeus_idle_return_action', {
4297
+ action: action,
4298
+ idleMinutes: Math.round(pending.idleMinutes),
4299
+ messageCount: messagesRef.current.length,
4300
+ totalInputTokens: getTotalInputTokens()
4301
+ });
4302
+ if (action === 'dismiss') {
4303
+ setInputValue(pending.input);
4304
+ return;
4305
+ }
4306
+ if (action === 'never') {
4307
+ saveGlobalConfig(current => {
4308
+ if (current.idleReturnDismissed)
4309
+ return current;
4310
+ return {
4311
+ ...current,
4312
+ idleReturnDismissed: true
4313
+ };
4314
+ });
4315
+ }
4316
+ if (action === 'clear') {
4317
+ const { clearConversation } = await import('../commands/clear/conversation.js');
4318
+ await clearConversation({
4319
+ setMessages,
4320
+ readFileState: readFileState.current,
4321
+ discoveredSkillNames: discoveredSkillNamesRef.current,
4322
+ loadedNestedMemoryPaths: loadedNestedMemoryPathsRef.current,
4323
+ getAppState: () => store.getState(),
4324
+ setAppState,
4325
+ setConversationId
4326
+ });
4327
+ haikuTitleAttemptedRef.current = false;
4328
+ setHaikuTitle(undefined);
4329
+ bashTools.current.clear();
4330
+ bashToolsProcessedIdx.current = 0;
4331
+ }
4332
+ skipIdleCheckRef.current = true;
4333
+ void onSubmitRef.current(pending.input, {
4334
+ setCursorOffset: () => { },
4335
+ clearBuffer: () => { },
4336
+ resetHistory: () => { }
4337
+ });
4338
+ } }), focusedInputDialog === 'ide-onboarding' && _jsx(IdeOnboardingDialog, { onDone: () => setShowIdeOnboarding(false), installationStatus: ideInstallationStatus }), "external" === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && _jsx(AntModelSwitchCallout, { onDone: (selection, modelAlias) => {
4339
+ setShowModelSwitchCallout(false);
4340
+ if (selection === 'switch' && modelAlias) {
4341
+ setAppState(prev => ({
4342
+ ...prev,
4343
+ mainLoopModel: modelAlias,
4344
+ mainLoopModelForSession: null
4345
+ }));
4346
+ }
4347
+ } }), "external" === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && _jsx(UndercoverAutoCallout, { onDone: () => setShowUndercoverCallout(false) }), focusedInputDialog === 'effort-callout' && _jsx(EffortCallout, { model: mainLoopModel, onDone: selection => {
4348
+ setShowEffortCallout(false);
4349
+ if (selection !== 'dismiss') {
4350
+ setAppState(prev => ({
4351
+ ...prev,
4352
+ effortValue: selection
4353
+ }));
4354
+ }
4355
+ } }), focusedInputDialog === 'remote-callout' && _jsx(RemoteCallout, { onDone: selection => {
4356
+ setAppState(prev => {
4357
+ if (!prev.showRemoteCallout)
4358
+ return prev;
4359
+ return {
4360
+ ...prev,
4361
+ showRemoteCallout: false,
4362
+ ...(selection === 'enable' && {
4363
+ replBridgeEnabled: true,
4364
+ replBridgeExplicit: true,
4365
+ replBridgeOutboundOnly: false
4366
+ })
4367
+ };
4368
+ });
4369
+ } }), exitFlow, focusedInputDialog === 'plugin-hint' && hintRecommendation && _jsx(PluginHintMenu, { pluginName: hintRecommendation.pluginName, pluginDescription: hintRecommendation.pluginDescription, marketplaceName: hintRecommendation.marketplaceName, sourceCommand: hintRecommendation.sourceCommand, onResponse: handleHintResponse }), focusedInputDialog === 'lsp-recommendation' && lspRecommendation && _jsx(LspRecommendationMenu, { pluginName: lspRecommendation.pluginName, pluginDescription: lspRecommendation.pluginDescription, fileExtension: lspRecommendation.fileExtension, onResponse: handleLspResponse }), focusedInputDialog === 'desktop-upsell' && _jsx(DesktopUpsellStartup, { onDone: () => setShowDesktopUpsellStartup(false) }), feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-choice' && ultraplanPendingChoice && _jsx(UltraplanChoiceDialog, { plan: ultraplanPendingChoice.plan, sessionId: ultraplanPendingChoice.sessionId, taskId: ultraplanPendingChoice.taskId, setMessages: setMessages, readFileState: readFileState.current, getAppState: () => store.getState(), setConversationId: setConversationId }) : null, feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-launch' && ultraplanLaunchPending && _jsx(UltraplanLaunchDialog, { onChoice: (choice, opts) => {
4370
+ const blurb = ultraplanLaunchPending.blurb;
4371
+ setAppState(prev => prev.ultraplanLaunchPending ? {
4372
+ ...prev,
4373
+ ultraplanLaunchPending: undefined
4374
+ } : prev);
4375
+ if (choice === 'cancel')
4376
+ return;
4377
+ // Command's onDone used display:'skip', so add the
4378
+ // echo here — gives immediate feedback before the
4379
+ // ~5s teleportToRemote resolves.
4380
+ setMessages(prev => [...prev, createCommandInputMessage(formatCommandInputTags('ultraplan', blurb))]);
4381
+ const appendStdout = (msg) => setMessages(prev => [...prev, createCommandInputMessage(`<${LOCAL_COMMAND_STDOUT_TAG}>${escapeXml(msg)}</${LOCAL_COMMAND_STDOUT_TAG}>`)]);
4382
+ // Defer the second message if a query is mid-turn
4383
+ // so it lands after the assistant reply, not
4384
+ // between the user's prompt and the reply.
4385
+ const appendWhenIdle = (msg) => {
4386
+ if (!queryGuard.isActive) {
4387
+ appendStdout(msg);
4388
+ return;
4389
+ }
4390
+ const unsub = queryGuard.subscribe(() => {
4391
+ if (queryGuard.isActive)
4392
+ return;
4393
+ unsub();
4394
+ // Skip if the user stopped ultraplan while we
4395
+ // were waiting — avoids a stale "Monitoring
4396
+ // <url>" message for a session that's gone.
4397
+ if (!store.getState().ultraplanSessionUrl)
4398
+ return;
4399
+ appendStdout(msg);
4400
+ });
4401
+ };
4402
+ void launchUltraplan({
4403
+ blurb,
4404
+ getAppState: () => store.getState(),
4405
+ setAppState,
4406
+ signal: createAbortController().signal,
4407
+ disconnectedBridge: opts?.disconnectedBridge,
4408
+ onSessionReady: appendWhenIdle
4409
+ }).then(appendStdout).catch(logError);
4410
+ } }) : null, mrRender(), !toolJSX?.shouldHidePromptInput && !focusedInputDialog && !isExiting && !disabled && !cursor && _jsxs(_Fragment, { children: [autoRunIssueReason && _jsx(AutoRunIssueNotification, { onRun: handleAutoRunIssue, onCancel: handleCancelAutoRunIssue, reason: getAutoRunIssueReasonText(autoRunIssueReason) }), postCompactSurvey.state !== 'closed' ? _jsx(FeedbackSurvey, { state: postCompactSurvey.state, lastResponse: postCompactSurvey.lastResponse, handleSelect: postCompactSurvey.handleSelect, inputValue: inputValue, setInputValue: setInputValue, onRequestFeedback: handleSurveyRequestFeedback }) : memorySurvey.state !== 'closed' ? _jsx(FeedbackSurvey, { state: memorySurvey.state, lastResponse: memorySurvey.lastResponse, handleSelect: memorySurvey.handleSelect, handleTranscriptSelect: memorySurvey.handleTranscriptSelect, inputValue: inputValue, setInputValue: setInputValue, onRequestFeedback: handleSurveyRequestFeedback, message: "How well did Thaddeus use its memory? (optional)" }) : _jsx(FeedbackSurvey, { state: feedbackSurvey.state, lastResponse: feedbackSurvey.lastResponse, handleSelect: feedbackSurvey.handleSelect, handleTranscriptSelect: feedbackSurvey.handleTranscriptSelect, inputValue: inputValue, setInputValue: setInputValue, onRequestFeedback: didAutoRunIssueRef.current ? undefined : handleSurveyRequestFeedback }), frustrationDetection.state !== 'closed' && _jsx(FeedbackSurvey, { state: frustrationDetection.state, lastResponse: null, handleSelect: () => { }, handleTranscriptSelect: frustrationDetection.handleTranscriptSelect, inputValue: inputValue, setInputValue: setInputValue }), "external" === 'ant' && skillImprovementSurvey.suggestion && _jsx(SkillImprovementSurvey, { isOpen: skillImprovementSurvey.isOpen, skillName: skillImprovementSurvey.suggestion.skillName, updates: skillImprovementSurvey.suggestion.updates, handleSelect: skillImprovementSurvey.handleSelect, inputValue: inputValue, setInputValue: setInputValue }), showIssueFlagBanner && _jsx(IssueFlagBanner, {}), _jsx(PromptInput, { debug: debug, ideSelection: ideSelection, hasSuppressedDialogs: !!hasSuppressedDialogs, isLocalJSXCommandActive: isShowingLocalJSXCommand, getToolUseContext: getToolUseContext, toolPermissionContext: toolPermissionContext, setToolPermissionContext: setToolPermissionContext, apiKeyStatus: apiKeyStatus, commands: commands, agents: agentDefinitions.activeAgents, isLoading: isLoading, onExit: handleExit, verbose: verbose, messages: messages, onAutoUpdaterResult: setAutoUpdaterResult, autoUpdaterResult: autoUpdaterResult, input: inputValue, onInputChange: setInputValue, mode: inputMode, onModeChange: setInputMode, stashedPrompt: stashedPrompt, setStashedPrompt: setStashedPrompt, submitCount: submitCount, onShowMessageSelector: handleShowMessageSelector, onMessageActionsEnter:
4411
+ // Works during isLoading — edit cancels first; uuid selection survives appends.
4412
+ feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? enterMessageActions : undefined, mcpClients: mcpClients, pastedContents: pastedContents, setPastedContents: setPastedContents, vimMode: vimMode, setVimMode: setVimMode, showBashesDialog: showBashesDialog, setShowBashesDialog: setShowBashesDialog, onSubmit: onSubmit, onAgentSubmit: onAgentSubmit, isSearchingHistory: isSearchingHistory, setIsSearchingHistory: setIsSearchingHistory, helpOpen: isHelpOpen, setHelpOpen: setIsHelpOpen, insertTextRef: feature('VOICE_MODE') ? insertTextRef : undefined, voiceInterimRange: voice.interimRange }), _jsx(SessionBackgroundHint, { onBackgroundSession: handleBackgroundSession, isLoading: isLoading })] }), cursor &&
4413
+ // inputValue is REPL state; typed text survives the round-trip.
4414
+ _jsx(MessageActionsBar, { cursor: cursor }), focusedInputDialog === 'message-selector' && _jsx(MessageSelector, { messages: messages, preselectedMessage: messageSelectorPreselect, onPreRestore: onCancel, onRestoreCode: async (message) => {
4415
+ await fileHistoryRewind((updater) => {
4416
+ setAppState(prev => ({
4417
+ ...prev,
4418
+ fileHistory: updater(prev.fileHistory)
4419
+ }));
4420
+ }, message.uuid);
4421
+ }, onSummarize: async (message, feedback, direction = 'from') => {
4422
+ // Project snipped messages so the compact model
4423
+ // doesn't summarize content that was intentionally removed.
4424
+ const compactMessages = getMessagesAfterCompactBoundary(messages);
4425
+ const messageIndex = compactMessages.indexOf(message);
4426
+ if (messageIndex === -1) {
4427
+ // Selected a snipped or pre-compact message that the
4428
+ // selector still shows (REPL keeps full history for
4429
+ // scrollback). Surface why nothing happened instead
4430
+ // of silently no-oping.
4431
+ setMessages(prev => [...prev, createSystemMessage('That message is no longer in the active context (snipped or pre-compact). Choose a more recent message.', 'warning')]);
4432
+ return;
4433
+ }
4434
+ const newAbortController = createAbortController();
4435
+ const context = getToolUseContext(compactMessages, [], newAbortController, mainLoopModel);
4436
+ const appState = context.getAppState();
4437
+ const defaultSysPrompt = await getSystemPrompt(context.options.tools, context.options.mainLoopModel, Array.from(appState.toolPermissionContext.additionalWorkingDirectories.keys()), context.options.mcpClients);
4438
+ const systemPrompt = buildEffectiveSystemPrompt({
4439
+ mainThreadAgentDefinition: undefined,
4440
+ toolUseContext: context,
4441
+ customSystemPrompt: context.options.customSystemPrompt,
4442
+ defaultSystemPrompt: defaultSysPrompt,
4443
+ appendSystemPrompt: context.options.appendSystemPrompt
4444
+ });
4445
+ const [userContext, systemContext] = await Promise.all([getUserContext(), getSystemContext()]);
4446
+ const result = await partialCompactConversation(compactMessages, messageIndex, context, {
4447
+ systemPrompt,
4448
+ userContext,
4449
+ systemContext,
4450
+ toolUseContext: context,
4451
+ forkContextMessages: compactMessages
4452
+ }, feedback, direction);
4453
+ const kept = result.messagesToKeep ?? [];
4454
+ const ordered = direction === 'up_to' ? [...result.summaryMessages, ...kept] : [...kept, ...result.summaryMessages];
4455
+ const postCompact = [result.boundaryMarker, ...ordered, ...result.attachments, ...result.hookResults];
4456
+ // Fullscreen 'from' keeps scrollback; 'up_to' must not
4457
+ // (old[0] unchanged + grown array means incremental
4458
+ // useLogMessages path, so boundary never persisted).
4459
+ // Find by uuid since old is raw REPL history and snipped
4460
+ // entries can shift the projected messageIndex.
4461
+ if (isFullscreenEnvEnabled() && direction === 'from') {
4462
+ setMessages(old => {
4463
+ const rawIdx = old.findIndex(m => m.uuid === message.uuid);
4464
+ return [...old.slice(0, rawIdx === -1 ? 0 : rawIdx), ...postCompact];
4465
+ });
4466
+ }
4467
+ else {
4468
+ setMessages(postCompact);
4469
+ }
4470
+ // Partial compact bypasses handleMessageFromStream — clear
4471
+ // the context-blocked flag so proactive ticks resume.
4472
+ if (feature('PROACTIVE') || feature('KAIROS')) {
4473
+ proactiveModule?.setContextBlocked(false);
4474
+ }
4475
+ setConversationId(randomUUID());
4476
+ runPostCompactCleanup(context.options.querySource);
4477
+ if (direction === 'from') {
4478
+ const r = textForResubmit(message);
4479
+ if (r) {
4480
+ setInputValue(r.text);
4481
+ setInputMode(r.mode);
4482
+ }
4483
+ }
4484
+ // Show notification with ctrl+o hint
4485
+ const historyShortcut = getShortcutDisplay('app:toggleTranscript', 'Global', 'ctrl+o');
4486
+ addNotification({
4487
+ key: 'summarize-ctrl-o-hint',
4488
+ text: `Conversation summarized (${historyShortcut} for history)`,
4489
+ priority: 'medium',
4490
+ timeoutMs: 8000
4491
+ });
4492
+ }, onRestoreMessage: handleRestoreMessage, onClose: () => {
4493
+ setIsMessageSelectorVisible(false);
4494
+ setMessageSelectorPreselect(undefined);
4495
+ } }), "external" === 'ant' && _jsx(DevBar, {})] }), feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? _jsx(CompanionSprite, {}) : null] }) }) }, remountKey)] });
4496
+ if (isFullscreenEnvEnabled()) {
4497
+ return _jsx(AlternateScreen, { mouseTracking: isMouseTrackingEnabled(), children: mainReturn });
4498
+ }
4499
+ return mainReturn;
4500
+ }