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,4162 @@
1
+ import { feature } from 'bun:bundle';
2
+ // Sync fs primitives for readFileTailSync — separate from fs/promises
3
+ // imports above. Named (not wildcard) per THADDEUS.md style; no collisions
4
+ // with the async-suffixed names.
5
+ import { closeSync, fstatSync, openSync, readSync } from 'fs';
6
+ import { appendFile as fsAppendFile, open as fsOpen, mkdir, readdir, readFile, stat, unlink, writeFile, } from 'fs/promises';
7
+ import memoize from 'lodash-es/memoize.js';
8
+ import { basename, dirname, join } from 'path';
9
+ import { logEvent, } from 'src/services/analytics/index.js';
10
+ import { getOriginalCwd, getPlanSlugCache, getPromptId, getSessionId, getSessionProjectDir, isSessionPersistenceDisabled, switchSession, } from '../bootstrap/state.js';
11
+ import { builtInCommandNames } from '../commands.js';
12
+ import { COMMAND_NAME_TAG, TICK_TAG } from '../constants/xml.js';
13
+ import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js';
14
+ import * as sessionIngress from '../services/api/sessionIngress.js';
15
+ import { REPL_TOOL_NAME } from '../tools/REPLTool/constants.js';
16
+ import { asAgentId, asSessionId, } from '../types/ids.js';
17
+ import { sortLogs, } from '../types/logs.js';
18
+ import { uniq } from './array.js';
19
+ import { registerCleanup } from './cleanupRegistry.js';
20
+ import { updateSessionName } from './concurrentSessions.js';
21
+ import { getCwd } from './cwd.js';
22
+ import { logForDebugging } from './debug.js';
23
+ import { logForDiagnosticsNoPII } from './diagLogs.js';
24
+ import { getClaudeConfigHomeDir, isEnvTruthy } from './envUtils.js';
25
+ import { isFsInaccessible } from './errors.js';
26
+ import { formatFileSize } from './format.js';
27
+ import { getFsImplementation } from './fsOperations.js';
28
+ import { getWorktreePaths } from './getWorktreePaths.js';
29
+ import { getBranch } from './git.js';
30
+ import { gracefulShutdownSync, isShuttingDown } from './gracefulShutdown.js';
31
+ import { parseJSONL } from './json.js';
32
+ import { logError } from './log.js';
33
+ import { extractTag, isCompactBoundaryMessage } from './messages.js';
34
+ import { sanitizePath } from './path.js';
35
+ import { extractJsonStringField, extractLastJsonStringField, LITE_READ_BUF_SIZE, readHeadAndTail, readTranscriptForLoad, SKIP_PRECOMPACT_THRESHOLD, } from './sessionStoragePortable.js';
36
+ import { getSettings_DEPRECATED } from './settings/settings.js';
37
+ import { jsonParse, jsonStringify } from './slowOperations.js';
38
+ import { validateUuid } from './uuid.js';
39
+ // Cache MACRO.VERSION at module level to work around bun --define bug in async contexts
40
+ // See: https://github.com/oven-sh/bun/issues/26168
41
+ const VERSION = typeof MACRO !== 'undefined' ? MACRO.VERSION : 'unknown';
42
+ // Use getOriginalCwd() at each call site instead of capturing at module load
43
+ // time. getCwd() at import time may run before bootstrap resolves symlinks via
44
+ // realpathSync, causing a different sanitized project directory than what
45
+ // getOriginalCwd() returns after bootstrap. This split-brain made sessions
46
+ // saved under one path invisible when loaded via the other.
47
+ /**
48
+ * Pre-compiled regex to skip non-meaningful messages when extracting first prompt.
49
+ * Matches anything starting with a lowercase XML-like tag (IDE context, hook
50
+ * output, task notifications, channel messages, etc.) or a synthetic interrupt
51
+ * marker. Kept in sync with sessionStoragePortable.ts — generic pattern avoids
52
+ * an ever-growing allowlist that falls behind as new notification types ship.
53
+ */
54
+ // 50MB — prevents OOM in the tombstone slow path which reads + rewrites the
55
+ // entire session file. Session files can grow to multiple GB (inc-3930).
56
+ const MAX_TOMBSTONE_REWRITE_BYTES = 50 * 1024 * 1024;
57
+ const SKIP_FIRST_PROMPT_PATTERN = /^(?:\s*<[a-z][\w-]*[\s>]|\[Request interrupted by user[^\]]*\])/;
58
+ /**
59
+ * Type guard to check if an entry is a transcript message.
60
+ * Transcript messages include user, assistant, attachment, and system messages.
61
+ * IMPORTANT: This is the single source of truth for what constitutes a transcript message.
62
+ * loadTranscriptFile() uses this to determine which messages to load into the chain.
63
+ *
64
+ * Progress messages are NOT transcript messages. They are ephemeral UI state
65
+ * and must not be persisted to the JSONL or participate in the parentUuid
66
+ * chain. Including them caused chain forks that orphaned real conversation
67
+ * messages on resume (see #14373, #23537).
68
+ */
69
+ export function isTranscriptMessage(entry) {
70
+ return (entry.type === 'user' ||
71
+ entry.type === 'assistant' ||
72
+ entry.type === 'attachment' ||
73
+ entry.type === 'system');
74
+ }
75
+ /**
76
+ * Entries that participate in the parentUuid chain. Used on the write path
77
+ * (insertMessageChain, useLogMessages) to skip progress when assigning
78
+ * parentUuid. Old transcripts with progress already in the chain are handled
79
+ * by the progressBridge rewrite in loadTranscriptFile.
80
+ */
81
+ export function isChainParticipant(m) {
82
+ return m.type !== 'progress';
83
+ }
84
+ /**
85
+ * Progress entries in transcripts written before PR #24099. They are not
86
+ * in the Entry type union anymore but still exist on disk with uuid and
87
+ * parentUuid fields. loadTranscriptFile bridges the chain across them.
88
+ */
89
+ function isLegacyProgressEntry(entry) {
90
+ return (typeof entry === 'object' &&
91
+ entry !== null &&
92
+ 'type' in entry &&
93
+ entry.type === 'progress' &&
94
+ 'uuid' in entry &&
95
+ typeof entry.uuid === 'string');
96
+ }
97
+ /**
98
+ * High-frequency tool progress ticks (1/sec for Sleep, per-chunk for Bash).
99
+ * These are UI-only: not sent to the API, not rendered after the tool
100
+ * completes. Used by REPL.tsx to replace-in-place instead of appending, and
101
+ * by loadTranscriptFile to skip legacy entries from old transcripts.
102
+ */
103
+ const EPHEMERAL_PROGRESS_TYPES = new Set([
104
+ 'bash_progress',
105
+ 'powershell_progress',
106
+ 'mcp_progress',
107
+ ...(feature('PROACTIVE') || feature('KAIROS')
108
+ ? ['sleep_progress']
109
+ : []),
110
+ ]);
111
+ export function isEphemeralToolProgress(dataType) {
112
+ return typeof dataType === 'string' && EPHEMERAL_PROGRESS_TYPES.has(dataType);
113
+ }
114
+ export function getProjectsDir() {
115
+ return join(getClaudeConfigHomeDir(), 'projects');
116
+ }
117
+ export function getTranscriptPath() {
118
+ const projectDir = getSessionProjectDir() ?? getProjectDir(getOriginalCwd());
119
+ return join(projectDir, `${getSessionId()}.jsonl`);
120
+ }
121
+ export function getTranscriptPathForSession(sessionId) {
122
+ // When asking for the CURRENT session's transcript, honor sessionProjectDir
123
+ // the same way getTranscriptPath() does. Without this, hooks get a
124
+ // transcript_path computed from originalCwd while the actual file was
125
+ // written to sessionProjectDir (set by switchActiveSession on resume/branch)
126
+ // — different directories, so the hook sees MISSING (gh-30217). CC-34
127
+ // made sessionId + sessionProjectDir atomic precisely to prevent this
128
+ // kind of drift; this function just wasn't updated to read both.
129
+ //
130
+ // For OTHER session IDs we can only guess via originalCwd — we don't
131
+ // track a sessionId→projectDir map. Callers wanting a specific other
132
+ // session's path should pass fullPath explicitly (most save* functions
133
+ // already accept this).
134
+ if (sessionId === getSessionId()) {
135
+ return getTranscriptPath();
136
+ }
137
+ const projectDir = getProjectDir(getOriginalCwd());
138
+ return join(projectDir, `${sessionId}.jsonl`);
139
+ }
140
+ // 50 MB — session JSONL can grow to multiple GB (inc-3930). Callers that
141
+ // read the raw transcript must bail out above this threshold to avoid OOM.
142
+ export const MAX_TRANSCRIPT_READ_BYTES = 50 * 1024 * 1024;
143
+ // In-memory map of agentId → subdirectory for grouping related subagent
144
+ // transcripts (e.g. workflow runs write to subagents/workflows/<runId>/).
145
+ // Populated before the agent runs; consulted by getAgentTranscriptPath.
146
+ const agentTranscriptSubdirs = new Map();
147
+ export function setAgentTranscriptSubdir(agentId, subdir) {
148
+ agentTranscriptSubdirs.set(agentId, subdir);
149
+ }
150
+ export function clearAgentTranscriptSubdir(agentId) {
151
+ agentTranscriptSubdirs.delete(agentId);
152
+ }
153
+ export function getAgentTranscriptPath(agentId) {
154
+ // Same sessionProjectDir consistency as getTranscriptPathForSession —
155
+ // subagent transcripts live under the session dir, so if the session
156
+ // transcript is at sessionProjectDir, subagent transcripts are too.
157
+ const projectDir = getSessionProjectDir() ?? getProjectDir(getOriginalCwd());
158
+ const sessionId = getSessionId();
159
+ const subdir = agentTranscriptSubdirs.get(agentId);
160
+ const base = subdir
161
+ ? join(projectDir, sessionId, 'subagents', subdir)
162
+ : join(projectDir, sessionId, 'subagents');
163
+ return join(base, `agent-${agentId}.jsonl`);
164
+ }
165
+ function getAgentMetadataPath(agentId) {
166
+ return getAgentTranscriptPath(agentId).replace(/\.jsonl$/, '.meta.json');
167
+ }
168
+ /**
169
+ * Persist the agentType used to launch a subagent. Read by resume to
170
+ * route correctly when subagent_type is omitted — without this, resuming
171
+ * a fork silently degrades to general-purpose (4KB system prompt, no
172
+ * inherited history). Sidecar file avoids JSONL schema changes.
173
+ *
174
+ * Also stores the worktreePath when the agent was spawned with worktree
175
+ * isolation, enabling resume to restore the correct cwd.
176
+ */
177
+ export async function writeAgentMetadata(agentId, metadata) {
178
+ const path = getAgentMetadataPath(agentId);
179
+ await mkdir(dirname(path), { recursive: true });
180
+ await writeFile(path, JSON.stringify(metadata));
181
+ }
182
+ export async function readAgentMetadata(agentId) {
183
+ const path = getAgentMetadataPath(agentId);
184
+ try {
185
+ const raw = await readFile(path, 'utf-8');
186
+ return JSON.parse(raw);
187
+ }
188
+ catch (e) {
189
+ if (isFsInaccessible(e))
190
+ return null;
191
+ throw e;
192
+ }
193
+ }
194
+ function getRemoteAgentsDir() {
195
+ // Same sessionProjectDir fallback as getAgentTranscriptPath — the project
196
+ // dir (containing the .jsonl), not the session dir, so sessionId is joined.
197
+ const projectDir = getSessionProjectDir() ?? getProjectDir(getOriginalCwd());
198
+ return join(projectDir, getSessionId(), 'remote-agents');
199
+ }
200
+ function getRemoteAgentMetadataPath(taskId) {
201
+ return join(getRemoteAgentsDir(), `remote-agent-${taskId}.meta.json`);
202
+ }
203
+ /**
204
+ * Persist metadata for a remote-agent task so it can be restored on session
205
+ * resume. Per-task sidecar file (sibling dir to subagents/) survives
206
+ * hydrateSessionFromRemote's .jsonl wipe; status is always fetched fresh
207
+ * from CCR on restore — only identity is persisted locally.
208
+ */
209
+ export async function writeRemoteAgentMetadata(taskId, metadata) {
210
+ const path = getRemoteAgentMetadataPath(taskId);
211
+ await mkdir(dirname(path), { recursive: true });
212
+ await writeFile(path, JSON.stringify(metadata));
213
+ }
214
+ export async function readRemoteAgentMetadata(taskId) {
215
+ const path = getRemoteAgentMetadataPath(taskId);
216
+ try {
217
+ const raw = await readFile(path, 'utf-8');
218
+ return JSON.parse(raw);
219
+ }
220
+ catch (e) {
221
+ if (isFsInaccessible(e))
222
+ return null;
223
+ throw e;
224
+ }
225
+ }
226
+ export async function deleteRemoteAgentMetadata(taskId) {
227
+ const path = getRemoteAgentMetadataPath(taskId);
228
+ try {
229
+ await unlink(path);
230
+ }
231
+ catch (e) {
232
+ if (isFsInaccessible(e))
233
+ return;
234
+ throw e;
235
+ }
236
+ }
237
+ /**
238
+ * Scan the remote-agents/ directory for all persisted metadata files.
239
+ * Used by restoreRemoteAgentTasks to reconnect to still-running CCR sessions.
240
+ */
241
+ export async function listRemoteAgentMetadata() {
242
+ const dir = getRemoteAgentsDir();
243
+ let entries;
244
+ try {
245
+ entries = await readdir(dir, { withFileTypes: true });
246
+ }
247
+ catch (e) {
248
+ if (isFsInaccessible(e))
249
+ return [];
250
+ throw e;
251
+ }
252
+ const results = [];
253
+ for (const entry of entries) {
254
+ if (!entry.isFile() || !entry.name.endsWith('.meta.json'))
255
+ continue;
256
+ try {
257
+ const raw = await readFile(join(dir, entry.name), 'utf-8');
258
+ results.push(JSON.parse(raw));
259
+ }
260
+ catch (e) {
261
+ // Skip unreadable or corrupt files — a partial write from a crashed
262
+ // fire-and-forget persist shouldn't take down the whole restore.
263
+ logForDebugging(`listRemoteAgentMetadata: skipping ${entry.name}: ${String(e)}`);
264
+ }
265
+ }
266
+ return results;
267
+ }
268
+ export function sessionIdExists(sessionId) {
269
+ const projectDir = getProjectDir(getOriginalCwd());
270
+ const sessionFile = join(projectDir, `${sessionId}.jsonl`);
271
+ const fs = getFsImplementation();
272
+ try {
273
+ fs.statSync(sessionFile);
274
+ return true;
275
+ }
276
+ catch {
277
+ return false;
278
+ }
279
+ }
280
+ // exported for testing
281
+ export function getNodeEnv() {
282
+ return process.env.NODE_ENV || 'development';
283
+ }
284
+ // exported for testing
285
+ export function getUserType() {
286
+ return process.env.USER_TYPE || 'external';
287
+ }
288
+ function getEntrypoint() {
289
+ return process.env.THADDEUS_ENTRYPOINT;
290
+ }
291
+ export function isCustomTitleEnabled() {
292
+ return true;
293
+ }
294
+ // Memoized: called 12+ times per turn via hooks.ts createBaseHookInput
295
+ // (PostToolUse path, 5×/turn) + various save* functions. Input is a cwd
296
+ // string; homedir/env/regex are all session-invariant so the result is
297
+ // stable for a given input. Worktree switches just change the key — no
298
+ // cache clear needed.
299
+ export const getProjectDir = memoize((projectDir) => {
300
+ return join(getProjectsDir(), sanitizePath(projectDir));
301
+ });
302
+ let project = null;
303
+ let cleanupRegistered = false;
304
+ function getProject() {
305
+ if (!project) {
306
+ project = new Project();
307
+ // Register flush as a cleanup handler (only once)
308
+ if (!cleanupRegistered) {
309
+ registerCleanup(async () => {
310
+ // Flush queued writes first, then re-append session metadata
311
+ // (customTitle, tag) so they always appear in the last 64KB tail
312
+ // window. readLiteMetadata only reads the tail to extract these
313
+ // fields — if enough messages are appended after a /rename, the
314
+ // custom-title entry gets pushed outside the window and --resume
315
+ // shows the auto-generated firstPrompt instead.
316
+ await project?.flush();
317
+ try {
318
+ project?.reAppendSessionMetadata();
319
+ }
320
+ catch {
321
+ // Best-effort — don't let metadata re-append crash the cleanup
322
+ }
323
+ });
324
+ cleanupRegistered = true;
325
+ }
326
+ }
327
+ return project;
328
+ }
329
+ /**
330
+ * Reset the Project singleton's flush state for testing.
331
+ * This ensures tests don't interfere with each other via shared counter state.
332
+ */
333
+ export function resetProjectFlushStateForTesting() {
334
+ project?._resetFlushState();
335
+ }
336
+ /**
337
+ * Reset the entire Project singleton for testing.
338
+ * This ensures tests with different CLAUDE_CONFIG_DIR values
339
+ * don't share stale sessionFile paths.
340
+ */
341
+ export function resetProjectForTesting() {
342
+ project = null;
343
+ }
344
+ export function setSessionFileForTesting(path) {
345
+ getProject().sessionFile = path;
346
+ }
347
+ /**
348
+ * Register a CCR v2 internal event writer for transcript persistence.
349
+ * When set, transcript messages are written as internal worker events
350
+ * instead of going through v1 Session Ingress.
351
+ */
352
+ export function setInternalEventWriter(writer) {
353
+ getProject().setInternalEventWriter(writer);
354
+ }
355
+ /**
356
+ * Register a CCR v2 internal event reader for session resume.
357
+ * When set, hydrateFromCCRv2InternalEvents() can fetch foreground and
358
+ * subagent internal events to reconstruct conversation state on reconnection.
359
+ */
360
+ export function setInternalEventReader(reader, subagentReader) {
361
+ getProject().setInternalEventReader(reader);
362
+ getProject().setInternalSubagentEventReader(subagentReader);
363
+ }
364
+ /**
365
+ * Set the remote ingress URL on the current Project for testing.
366
+ * This simulates what hydrateRemoteSession does in production.
367
+ */
368
+ export function setRemoteIngressUrlForTesting(url) {
369
+ getProject().setRemoteIngressUrl(url);
370
+ }
371
+ const REMOTE_FLUSH_INTERVAL_MS = 10;
372
+ class Project {
373
+ // Minimal cache for current session only (not all sessions)
374
+ currentSessionTag;
375
+ currentSessionTitle;
376
+ currentSessionAgentName;
377
+ currentSessionAgentColor;
378
+ currentSessionLastPrompt;
379
+ currentSessionAgentSetting;
380
+ currentSessionMode;
381
+ // Tri-state: undefined = never touched (don't write), null = exited worktree,
382
+ // object = currently in worktree. reAppendSessionMetadata writes null so
383
+ // --resume knows the session exited (vs. crashed while inside).
384
+ currentSessionWorktree;
385
+ currentSessionPrNumber;
386
+ currentSessionPrUrl;
387
+ currentSessionPrRepository;
388
+ sessionFile = null;
389
+ // Entries buffered while sessionFile is null. Flushed by materializeSessionFile
390
+ // on the first user/assistant message — prevents metadata-only session files.
391
+ pendingEntries = [];
392
+ remoteIngressUrl = null;
393
+ internalEventWriter = null;
394
+ internalEventReader = null;
395
+ internalSubagentEventReader = null;
396
+ pendingWriteCount = 0;
397
+ flushResolvers = [];
398
+ // Per-file write queues. Each entry carries a resolve callback so
399
+ // callers of enqueueWrite can optionally await their specific write.
400
+ writeQueues = new Map();
401
+ flushTimer = null;
402
+ activeDrain = null;
403
+ FLUSH_INTERVAL_MS = 100;
404
+ MAX_CHUNK_BYTES = 100 * 1024 * 1024;
405
+ constructor() { }
406
+ /** @internal Reset flush/queue state for testing. */
407
+ _resetFlushState() {
408
+ this.pendingWriteCount = 0;
409
+ this.flushResolvers = [];
410
+ if (this.flushTimer)
411
+ clearTimeout(this.flushTimer);
412
+ this.flushTimer = null;
413
+ this.activeDrain = null;
414
+ this.writeQueues = new Map();
415
+ }
416
+ incrementPendingWrites() {
417
+ this.pendingWriteCount++;
418
+ }
419
+ decrementPendingWrites() {
420
+ this.pendingWriteCount--;
421
+ if (this.pendingWriteCount === 0) {
422
+ // Resolve all waiting flush promises
423
+ for (const resolve of this.flushResolvers) {
424
+ resolve();
425
+ }
426
+ this.flushResolvers = [];
427
+ }
428
+ }
429
+ async trackWrite(fn) {
430
+ this.incrementPendingWrites();
431
+ try {
432
+ return await fn();
433
+ }
434
+ finally {
435
+ this.decrementPendingWrites();
436
+ }
437
+ }
438
+ enqueueWrite(filePath, entry) {
439
+ return new Promise(resolve => {
440
+ let queue = this.writeQueues.get(filePath);
441
+ if (!queue) {
442
+ queue = [];
443
+ this.writeQueues.set(filePath, queue);
444
+ }
445
+ queue.push({ entry, resolve });
446
+ this.scheduleDrain();
447
+ });
448
+ }
449
+ scheduleDrain() {
450
+ if (this.flushTimer) {
451
+ return;
452
+ }
453
+ this.flushTimer = setTimeout(async () => {
454
+ this.flushTimer = null;
455
+ this.activeDrain = this.drainWriteQueue();
456
+ await this.activeDrain;
457
+ this.activeDrain = null;
458
+ // If more items arrived during drain, schedule again
459
+ if (this.writeQueues.size > 0) {
460
+ this.scheduleDrain();
461
+ }
462
+ }, this.FLUSH_INTERVAL_MS);
463
+ }
464
+ async appendToFile(filePath, data) {
465
+ try {
466
+ await fsAppendFile(filePath, data, { mode: 0o600 });
467
+ }
468
+ catch {
469
+ // Directory may not exist — some NFS-like filesystems return
470
+ // unexpected error codes, so don't discriminate on code.
471
+ await mkdir(dirname(filePath), { recursive: true, mode: 0o700 });
472
+ await fsAppendFile(filePath, data, { mode: 0o600 });
473
+ }
474
+ }
475
+ async drainWriteQueue() {
476
+ for (const [filePath, queue] of this.writeQueues) {
477
+ if (queue.length === 0) {
478
+ continue;
479
+ }
480
+ const batch = queue.splice(0);
481
+ let content = '';
482
+ const resolvers = [];
483
+ for (const { entry, resolve } of batch) {
484
+ const line = jsonStringify(entry) + '\n';
485
+ if (content.length + line.length >= this.MAX_CHUNK_BYTES) {
486
+ // Flush chunk and resolve its entries before starting a new one
487
+ await this.appendToFile(filePath, content);
488
+ for (const r of resolvers) {
489
+ r();
490
+ }
491
+ resolvers.length = 0;
492
+ content = '';
493
+ }
494
+ content += line;
495
+ resolvers.push(resolve);
496
+ }
497
+ if (content.length > 0) {
498
+ await this.appendToFile(filePath, content);
499
+ for (const r of resolvers) {
500
+ r();
501
+ }
502
+ }
503
+ }
504
+ // Clean up empty queues
505
+ for (const [filePath, queue] of this.writeQueues) {
506
+ if (queue.length === 0) {
507
+ this.writeQueues.delete(filePath);
508
+ }
509
+ }
510
+ }
511
+ resetSessionFile() {
512
+ this.sessionFile = null;
513
+ this.pendingEntries = [];
514
+ }
515
+ /**
516
+ * Re-append cached session metadata to the end of the transcript file.
517
+ * This ensures metadata stays within the tail window that readLiteMetadata
518
+ * reads during progressive loading.
519
+ *
520
+ * Called from two contexts with different file-ordering implications:
521
+ * - During compaction (compact.ts, reactiveCompact.ts): writes metadata
522
+ * just before the boundary marker is emitted - these entries end up
523
+ * before the boundary and are recovered by scanPreBoundaryMetadata.
524
+ * - On session exit (cleanup handler): writes metadata at EOF after all
525
+ * boundaries - this is what enables loadTranscriptFile's pre-compact
526
+ * skip to find metadata without a forward scan.
527
+ *
528
+ * External-writer safety for SDK-mutable fields (custom-title, tag):
529
+ * before re-appending, refresh the cache from the tail scan window. If an
530
+ * external process (SDK renameSession/tagSession) wrote a fresher value,
531
+ * our stale cache absorbs it and the re-append below persists it — not
532
+ * the stale CLI value. If no entry is in the tail (evicted, or never
533
+ * written by the SDK), the cache is the only source of truth and is
534
+ * re-appended as-is.
535
+ *
536
+ * Re-append is unconditional (even when the value is already in the
537
+ * tail): during compaction, a title 40KB from EOF is inside the current
538
+ * tail window but will fall out once the post-compaction session grows.
539
+ * Skipping the re-append would defeat the purpose of this call. Fields
540
+ * the SDK cannot touch (last-prompt, agent-*, mode, pr-link) have no
541
+ * external-writer concern — their caches are authoritative.
542
+ */
543
+ reAppendSessionMetadata(skipTitleRefresh = false) {
544
+ if (!this.sessionFile)
545
+ return;
546
+ const sessionId = getSessionId();
547
+ if (!sessionId)
548
+ return;
549
+ // One sync tail read to refresh SDK-mutable fields. Same
550
+ // LITE_READ_BUF_SIZE window readLiteMetadata uses. Empty string on
551
+ // failure → extract returns null → cache is the only source of truth.
552
+ const tail = readFileTailSync(this.sessionFile);
553
+ // Absorb any fresher SDK-written title/tag into our cache. If the SDK
554
+ // wrote while we had the session open, our cache is stale — the tail
555
+ // value is authoritative. If the tail has nothing (evicted or never
556
+ // written externally), the cache stands.
557
+ //
558
+ // Filter with startsWith to match only top-level JSONL entries (col 0)
559
+ // and not "type":"tag" appearing inside a nested tool_use input that
560
+ // happens to be JSON-serialized into a message.
561
+ const tailLines = tail.split('\n');
562
+ if (!skipTitleRefresh) {
563
+ const titleLine = tailLines.findLast(l => l.startsWith('{"type":"custom-title"'));
564
+ if (titleLine) {
565
+ const tailTitle = extractLastJsonStringField(titleLine, 'customTitle');
566
+ // `!== undefined` distinguishes no-match from empty-string match.
567
+ // renameSession rejects empty titles, but the CLI is defensive: an
568
+ // external writer with customTitle:"" should clear the cache so the
569
+ // re-append below skips it (instead of resurrecting a stale title).
570
+ if (tailTitle !== undefined) {
571
+ this.currentSessionTitle = tailTitle || undefined;
572
+ }
573
+ }
574
+ }
575
+ const tagLine = tailLines.findLast(l => l.startsWith('{"type":"tag"'));
576
+ if (tagLine) {
577
+ const tailTag = extractLastJsonStringField(tagLine, 'tag');
578
+ // Same: tagSession(id, null) writes `tag:""` to clear.
579
+ if (tailTag !== undefined) {
580
+ this.currentSessionTag = tailTag || undefined;
581
+ }
582
+ }
583
+ // lastPrompt is re-appended so readLiteMetadata can show what the
584
+ // user was most recently doing. Written first so customTitle/tag/etc
585
+ // land closer to EOF (they're the more critical fields for tail reads).
586
+ if (this.currentSessionLastPrompt) {
587
+ appendEntryToFile(this.sessionFile, {
588
+ type: 'last-prompt',
589
+ lastPrompt: this.currentSessionLastPrompt,
590
+ sessionId,
591
+ });
592
+ }
593
+ // Unconditional: cache was refreshed from tail above; re-append keeps
594
+ // the entry at EOF so compaction-pushed content doesn't evict it.
595
+ if (this.currentSessionTitle) {
596
+ appendEntryToFile(this.sessionFile, {
597
+ type: 'custom-title',
598
+ customTitle: this.currentSessionTitle,
599
+ sessionId,
600
+ });
601
+ }
602
+ if (this.currentSessionTag) {
603
+ appendEntryToFile(this.sessionFile, {
604
+ type: 'tag',
605
+ tag: this.currentSessionTag,
606
+ sessionId,
607
+ });
608
+ }
609
+ if (this.currentSessionAgentName) {
610
+ appendEntryToFile(this.sessionFile, {
611
+ type: 'agent-name',
612
+ agentName: this.currentSessionAgentName,
613
+ sessionId,
614
+ });
615
+ }
616
+ if (this.currentSessionAgentColor) {
617
+ appendEntryToFile(this.sessionFile, {
618
+ type: 'agent-color',
619
+ agentColor: this.currentSessionAgentColor,
620
+ sessionId,
621
+ });
622
+ }
623
+ if (this.currentSessionAgentSetting) {
624
+ appendEntryToFile(this.sessionFile, {
625
+ type: 'agent-setting',
626
+ agentSetting: this.currentSessionAgentSetting,
627
+ sessionId,
628
+ });
629
+ }
630
+ if (this.currentSessionMode) {
631
+ appendEntryToFile(this.sessionFile, {
632
+ type: 'mode',
633
+ mode: this.currentSessionMode,
634
+ sessionId,
635
+ });
636
+ }
637
+ if (this.currentSessionWorktree !== undefined) {
638
+ appendEntryToFile(this.sessionFile, {
639
+ type: 'worktree-state',
640
+ worktreeSession: this.currentSessionWorktree,
641
+ sessionId,
642
+ });
643
+ }
644
+ if (this.currentSessionPrNumber !== undefined &&
645
+ this.currentSessionPrUrl &&
646
+ this.currentSessionPrRepository) {
647
+ appendEntryToFile(this.sessionFile, {
648
+ type: 'pr-link',
649
+ sessionId,
650
+ prNumber: this.currentSessionPrNumber,
651
+ prUrl: this.currentSessionPrUrl,
652
+ prRepository: this.currentSessionPrRepository,
653
+ timestamp: new Date().toISOString(),
654
+ });
655
+ }
656
+ }
657
+ async flush() {
658
+ // Cancel pending timer
659
+ if (this.flushTimer) {
660
+ clearTimeout(this.flushTimer);
661
+ this.flushTimer = null;
662
+ }
663
+ // Wait for any in-flight drain to finish
664
+ if (this.activeDrain) {
665
+ await this.activeDrain;
666
+ }
667
+ // Drain anything remaining in the queues
668
+ await this.drainWriteQueue();
669
+ // Wait for non-queue tracked operations (e.g. removeMessageByUuid)
670
+ if (this.pendingWriteCount === 0) {
671
+ return;
672
+ }
673
+ return new Promise(resolve => {
674
+ this.flushResolvers.push(resolve);
675
+ });
676
+ }
677
+ /**
678
+ * Remove a message from the transcript by UUID.
679
+ * Used for tombstoning orphaned messages from failed streaming attempts.
680
+ *
681
+ * The target is almost always the most recently appended entry, so we
682
+ * read only the tail, locate the line, and splice it out with a
683
+ * positional write + truncate instead of rewriting the whole file.
684
+ */
685
+ async removeMessageByUuid(targetUuid) {
686
+ return this.trackWrite(async () => {
687
+ if (this.sessionFile === null)
688
+ return;
689
+ try {
690
+ let fileSize = 0;
691
+ const fh = await fsOpen(this.sessionFile, 'r+');
692
+ try {
693
+ const { size } = await fh.stat();
694
+ fileSize = size;
695
+ if (size === 0)
696
+ return;
697
+ const chunkLen = Math.min(size, LITE_READ_BUF_SIZE);
698
+ const tailStart = size - chunkLen;
699
+ const buf = Buffer.allocUnsafe(chunkLen);
700
+ const { bytesRead } = await fh.read(buf, 0, chunkLen, tailStart);
701
+ const tail = buf.subarray(0, bytesRead);
702
+ // Entries are serialized via JSON.stringify (no key-value
703
+ // whitespace). Search for the full `"uuid":"..."` pattern, not
704
+ // just the bare UUID, so we do not match the same value sitting
705
+ // in `parentUuid` of a child entry. UUIDs are pure ASCII so a
706
+ // byte-level search is correct.
707
+ const needle = `"uuid":"${targetUuid}"`;
708
+ const matchIdx = tail.lastIndexOf(needle);
709
+ if (matchIdx >= 0) {
710
+ // 0x0a never appears inside a UTF-8 multi-byte sequence, so
711
+ // byte-scanning for line boundaries is safe even if the chunk
712
+ // starts mid-character.
713
+ const prevNl = tail.lastIndexOf(0x0a, matchIdx);
714
+ // If the preceding newline is outside our chunk and we did not
715
+ // read from the start of the file, the line is longer than the
716
+ // window - fall through to the slow path.
717
+ if (prevNl >= 0 || tailStart === 0) {
718
+ const lineStart = prevNl + 1; // 0 when prevNl === -1
719
+ const nextNl = tail.indexOf(0x0a, matchIdx + needle.length);
720
+ const lineEnd = nextNl >= 0 ? nextNl + 1 : bytesRead;
721
+ const absLineStart = tailStart + lineStart;
722
+ const afterLen = bytesRead - lineEnd;
723
+ // Truncate first, then re-append the trailing lines. In the
724
+ // common case (target is the last entry) afterLen is 0 and
725
+ // this is a single ftruncate.
726
+ await fh.truncate(absLineStart);
727
+ if (afterLen > 0) {
728
+ await fh.write(tail, lineEnd, afterLen, absLineStart);
729
+ }
730
+ return;
731
+ }
732
+ }
733
+ }
734
+ finally {
735
+ await fh.close();
736
+ }
737
+ // Slow path: target was not in the last 64KB. Rare - requires many
738
+ // large entries to have landed between the write and the tombstone.
739
+ if (fileSize > MAX_TOMBSTONE_REWRITE_BYTES) {
740
+ logForDebugging(`Skipping tombstone removal: session file too large (${formatFileSize(fileSize)})`, { level: 'warn' });
741
+ return;
742
+ }
743
+ const content = await readFile(this.sessionFile, { encoding: 'utf-8' });
744
+ const lines = content.split('\n').filter((line) => {
745
+ if (!line.trim())
746
+ return true;
747
+ try {
748
+ const entry = jsonParse(line);
749
+ return entry.uuid !== targetUuid;
750
+ }
751
+ catch {
752
+ return true; // Keep malformed lines
753
+ }
754
+ });
755
+ await writeFile(this.sessionFile, lines.join('\n'), {
756
+ encoding: 'utf8',
757
+ });
758
+ }
759
+ catch {
760
+ // Silently ignore errors - the file might not exist yet
761
+ }
762
+ });
763
+ }
764
+ /**
765
+ * True when test env / cleanupPeriodDays=0 / --no-session-persistence /
766
+ * THADDEUS_SKIP_PROMPT_HISTORY should suppress all transcript writes.
767
+ * Shared guard for appendEntry and materializeSessionFile so both skip
768
+ * consistently. The env var is set by tmuxSocket.ts so Tungsten-spawned
769
+ * test sessions don't pollute the user's --resume list.
770
+ */
771
+ shouldSkipPersistence() {
772
+ const allowTestPersistence = isEnvTruthy(process.env.TEST_ENABLE_SESSION_PERSISTENCE);
773
+ return ((getNodeEnv() === 'test' && !allowTestPersistence) ||
774
+ getSettings_DEPRECATED()?.cleanupPeriodDays === 0 ||
775
+ isSessionPersistenceDisabled() ||
776
+ isEnvTruthy(process.env.THADDEUS_SKIP_PROMPT_HISTORY));
777
+ }
778
+ /**
779
+ * Create the session file, write cached startup metadata, and flush
780
+ * buffered entries. Called on the first user/assistant message.
781
+ */
782
+ async materializeSessionFile() {
783
+ // Guard here too — reAppendSessionMetadata writes via appendEntryToFile
784
+ // (not appendEntry) so it would bypass the per-entry persistence check
785
+ // and create a metadata-only file despite --no-session-persistence.
786
+ if (this.shouldSkipPersistence())
787
+ return;
788
+ this.ensureCurrentSessionFile();
789
+ // mode/agentSetting are cache-only pre-materialization; write them now.
790
+ this.reAppendSessionMetadata();
791
+ if (this.pendingEntries.length > 0) {
792
+ const buffered = this.pendingEntries;
793
+ this.pendingEntries = [];
794
+ for (const entry of buffered) {
795
+ await this.appendEntry(entry);
796
+ }
797
+ }
798
+ }
799
+ async insertMessageChain(messages, isSidechain = false, agentId, startingParentUuid, teamInfo) {
800
+ return this.trackWrite(async () => {
801
+ let parentUuid = startingParentUuid ?? null;
802
+ // First user/assistant message materializes the session file.
803
+ // Hook progress/attachment messages alone stay buffered.
804
+ if (this.sessionFile === null &&
805
+ messages.some(m => m.type === 'user' || m.type === 'assistant')) {
806
+ await this.materializeSessionFile();
807
+ }
808
+ // Get current git branch once for this message chain
809
+ let gitBranch;
810
+ try {
811
+ gitBranch = await getBranch();
812
+ }
813
+ catch {
814
+ // Not in a git repo or git command failed
815
+ gitBranch = undefined;
816
+ }
817
+ // Get slug if one exists for this session (used for plan files, etc.)
818
+ const sessionId = getSessionId();
819
+ const slug = getPlanSlugCache().get(sessionId);
820
+ for (const message of messages) {
821
+ const isCompactBoundary = isCompactBoundaryMessage(message);
822
+ // For tool_result messages, use the assistant message UUID from the message
823
+ // if available (set at creation time), otherwise fall back to sequential parent
824
+ let effectiveParentUuid = parentUuid;
825
+ if (message.type === 'user' &&
826
+ 'sourceToolAssistantUUID' in message &&
827
+ message.sourceToolAssistantUUID) {
828
+ effectiveParentUuid = message.sourceToolAssistantUUID;
829
+ }
830
+ const transcriptMessage = {
831
+ parentUuid: isCompactBoundary ? null : effectiveParentUuid,
832
+ logicalParentUuid: isCompactBoundary ? parentUuid : undefined,
833
+ isSidechain,
834
+ teamName: teamInfo?.teamName,
835
+ agentName: teamInfo?.agentName,
836
+ promptId: message.type === 'user' ? (getPromptId() ?? undefined) : undefined,
837
+ agentId,
838
+ ...message,
839
+ // Session-stamp fields MUST come after the spread. On --fork-session
840
+ // and --resume, messages arrive as SerializedMessage (carries source
841
+ // sessionId/cwd/etc. because removeExtraFields only strips parentUuid
842
+ // and isSidechain). If sessionId isn't re-stamped, FRESH.jsonl ends up
843
+ // with messages stamped sessionId=A but content-replacement entries
844
+ // stamped sessionId=FRESH (from insertContentReplacement), and
845
+ // loadFullLog's sessionId-keyed contentReplacements lookup misses →
846
+ // replacement records lost → FROZEN misclassification.
847
+ userType: getUserType(),
848
+ entrypoint: getEntrypoint(),
849
+ cwd: getCwd(),
850
+ sessionId,
851
+ version: VERSION,
852
+ gitBranch,
853
+ slug,
854
+ };
855
+ await this.appendEntry(transcriptMessage);
856
+ if (isChainParticipant(message)) {
857
+ parentUuid = message.uuid;
858
+ }
859
+ }
860
+ // Cache this turn's user prompt for reAppendSessionMetadata —
861
+ // the --resume picker shows what the user was last doing.
862
+ // Overwritten every turn by design.
863
+ if (!isSidechain) {
864
+ const text = getFirstMeaningfulUserMessageTextContent(messages);
865
+ if (text) {
866
+ const flat = text.replace(/\n/g, ' ').trim();
867
+ this.currentSessionLastPrompt =
868
+ flat.length > 200 ? flat.slice(0, 200).trim() + '…' : flat;
869
+ }
870
+ }
871
+ });
872
+ }
873
+ async insertFileHistorySnapshot(messageId, snapshot, isSnapshotUpdate) {
874
+ return this.trackWrite(async () => {
875
+ const fileHistoryMessage = {
876
+ type: 'file-history-snapshot',
877
+ messageId,
878
+ snapshot,
879
+ isSnapshotUpdate,
880
+ };
881
+ await this.appendEntry(fileHistoryMessage);
882
+ });
883
+ }
884
+ async insertQueueOperation(queueOp) {
885
+ return this.trackWrite(async () => {
886
+ await this.appendEntry(queueOp);
887
+ });
888
+ }
889
+ async insertAttributionSnapshot(snapshot) {
890
+ return this.trackWrite(async () => {
891
+ await this.appendEntry(snapshot);
892
+ });
893
+ }
894
+ async insertContentReplacement(replacements, agentId) {
895
+ return this.trackWrite(async () => {
896
+ const entry = {
897
+ type: 'content-replacement',
898
+ sessionId: getSessionId(),
899
+ agentId,
900
+ replacements,
901
+ };
902
+ await this.appendEntry(entry);
903
+ });
904
+ }
905
+ async appendEntry(entry, sessionId = getSessionId()) {
906
+ if (this.shouldSkipPersistence()) {
907
+ return;
908
+ }
909
+ const currentSessionId = getSessionId();
910
+ const isCurrentSession = sessionId === currentSessionId;
911
+ let sessionFile;
912
+ if (isCurrentSession) {
913
+ // Buffer until materializeSessionFile runs (first user/assistant message).
914
+ if (this.sessionFile === null) {
915
+ this.pendingEntries.push(entry);
916
+ return;
917
+ }
918
+ sessionFile = this.sessionFile;
919
+ }
920
+ else {
921
+ const existing = await this.getExistingSessionFile(sessionId);
922
+ if (!existing) {
923
+ logError(new Error(`appendEntry: session file not found for other session ${sessionId}`));
924
+ return;
925
+ }
926
+ sessionFile = existing;
927
+ }
928
+ // Only load current session messages if needed
929
+ if (entry.type === 'summary') {
930
+ // Summaries can always be appended
931
+ void this.enqueueWrite(sessionFile, entry);
932
+ }
933
+ else if (entry.type === 'custom-title') {
934
+ // Custom titles can always be appended
935
+ void this.enqueueWrite(sessionFile, entry);
936
+ }
937
+ else if (entry.type === 'ai-title') {
938
+ // AI titles can always be appended
939
+ void this.enqueueWrite(sessionFile, entry);
940
+ }
941
+ else if (entry.type === 'last-prompt') {
942
+ void this.enqueueWrite(sessionFile, entry);
943
+ }
944
+ else if (entry.type === 'task-summary') {
945
+ void this.enqueueWrite(sessionFile, entry);
946
+ }
947
+ else if (entry.type === 'tag') {
948
+ // Tags can always be appended
949
+ void this.enqueueWrite(sessionFile, entry);
950
+ }
951
+ else if (entry.type === 'agent-name') {
952
+ // Agent names can always be appended
953
+ void this.enqueueWrite(sessionFile, entry);
954
+ }
955
+ else if (entry.type === 'agent-color') {
956
+ // Agent colors can always be appended
957
+ void this.enqueueWrite(sessionFile, entry);
958
+ }
959
+ else if (entry.type === 'agent-setting') {
960
+ // Agent settings can always be appended
961
+ void this.enqueueWrite(sessionFile, entry);
962
+ }
963
+ else if (entry.type === 'pr-link') {
964
+ // PR links can always be appended
965
+ void this.enqueueWrite(sessionFile, entry);
966
+ }
967
+ else if (entry.type === 'file-history-snapshot') {
968
+ // File history snapshots can always be appended
969
+ void this.enqueueWrite(sessionFile, entry);
970
+ }
971
+ else if (entry.type === 'attribution-snapshot') {
972
+ // Attribution snapshots can always be appended
973
+ void this.enqueueWrite(sessionFile, entry);
974
+ }
975
+ else if (entry.type === 'speculation-accept') {
976
+ // Speculation accept entries can always be appended
977
+ void this.enqueueWrite(sessionFile, entry);
978
+ }
979
+ else if (entry.type === 'mode') {
980
+ // Mode entries can always be appended
981
+ void this.enqueueWrite(sessionFile, entry);
982
+ }
983
+ else if (entry.type === 'worktree-state') {
984
+ void this.enqueueWrite(sessionFile, entry);
985
+ }
986
+ else if (entry.type === 'content-replacement') {
987
+ // Content replacement records can always be appended. Subagent records
988
+ // go to the sidechain file (for AgentTool resume); main-thread
989
+ // records go to the session file (for /resume).
990
+ const targetFile = entry.agentId
991
+ ? getAgentTranscriptPath(entry.agentId)
992
+ : sessionFile;
993
+ void this.enqueueWrite(targetFile, entry);
994
+ }
995
+ else if (entry.type === 'marble-origami-commit') {
996
+ // Always append. Commit order matters for restore (later commits may
997
+ // reference earlier commits' summary messages), so these must be
998
+ // written in the order received and read back sequentially.
999
+ void this.enqueueWrite(sessionFile, entry);
1000
+ }
1001
+ else if (entry.type === 'marble-origami-snapshot') {
1002
+ // Always append. Last-wins on restore — later entries supersede.
1003
+ void this.enqueueWrite(sessionFile, entry);
1004
+ }
1005
+ else {
1006
+ const messageSet = await getSessionMessages(sessionId);
1007
+ if (entry.type === 'queue-operation') {
1008
+ // Queue operations are always appended to the session file
1009
+ void this.enqueueWrite(sessionFile, entry);
1010
+ }
1011
+ else {
1012
+ // At this point, entry must be a TranscriptMessage (user/assistant/attachment/system)
1013
+ // All other entry types have been handled above
1014
+ const isAgentSidechain = entry.isSidechain && entry.agentId !== undefined;
1015
+ const targetFile = isAgentSidechain
1016
+ ? getAgentTranscriptPath(asAgentId(entry.agentId))
1017
+ : sessionFile;
1018
+ // For message entries, check if UUID already exists in current session.
1019
+ // Skip dedup for agent sidechain LOCAL writes — they go to a separate
1020
+ // file, and fork-inherited parent messages share UUIDs with the main
1021
+ // session transcript. Deduping against the main session's set would
1022
+ // drop them, leaving the persisted sidechain transcript incomplete
1023
+ // (resume-of-fork loads a 10KB file instead of the full 85KB inherited
1024
+ // context).
1025
+ //
1026
+ // The sidechain bypass applies ONLY to the local file write — remote
1027
+ // persistence (session-ingress) uses a single Last-Uuid chain per
1028
+ // sessionId, so re-POSTing a UUID it already has 409s and eventually
1029
+ // exhausts retries → gracefulShutdownSync(1). See inc-4718.
1030
+ const isNewUuid = !messageSet.has(entry.uuid);
1031
+ if (isAgentSidechain || isNewUuid) {
1032
+ // Enqueue write — appendToFile handles ENOENT by creating directories
1033
+ void this.enqueueWrite(targetFile, entry);
1034
+ if (!isAgentSidechain) {
1035
+ // messageSet is main-file-authoritative. Sidechain entries go to a
1036
+ // separate agent file — adding their UUIDs here causes recordTranscript
1037
+ // to skip them on the main thread (line ~1270), so the message is never
1038
+ // written to the main session file. The next main-thread message then
1039
+ // chains its parentUuid to a UUID that only exists in the agent file,
1040
+ // and --resume's buildConversationChain terminates at the dangling ref.
1041
+ // Same constraint for remote (inc-4718 above): sidechain persisting a
1042
+ // UUID the main thread hasn't written yet → 409 when main writes it.
1043
+ messageSet.add(entry.uuid);
1044
+ if (isTranscriptMessage(entry)) {
1045
+ await this.persistToRemote(sessionId, entry);
1046
+ }
1047
+ }
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+ /**
1053
+ * Loads the sessionFile variable.
1054
+ * Do not need to create session files until they are written to.
1055
+ */
1056
+ ensureCurrentSessionFile() {
1057
+ if (this.sessionFile === null) {
1058
+ this.sessionFile = getTranscriptPath();
1059
+ }
1060
+ return this.sessionFile;
1061
+ }
1062
+ /**
1063
+ * Returns the session file path if it exists, null otherwise.
1064
+ * Used for writing to sessions other than the current one.
1065
+ * Caches positive results so we only stat once per session.
1066
+ */
1067
+ existingSessionFiles = new Map();
1068
+ async getExistingSessionFile(sessionId) {
1069
+ const cached = this.existingSessionFiles.get(sessionId);
1070
+ if (cached)
1071
+ return cached;
1072
+ const targetFile = getTranscriptPathForSession(sessionId);
1073
+ try {
1074
+ await stat(targetFile);
1075
+ this.existingSessionFiles.set(sessionId, targetFile);
1076
+ return targetFile;
1077
+ }
1078
+ catch (e) {
1079
+ if (isFsInaccessible(e))
1080
+ return null;
1081
+ throw e;
1082
+ }
1083
+ }
1084
+ async persistToRemote(sessionId, entry) {
1085
+ if (isShuttingDown()) {
1086
+ return;
1087
+ }
1088
+ // CCR v2 path: write as internal worker event
1089
+ if (this.internalEventWriter) {
1090
+ try {
1091
+ await this.internalEventWriter('transcript', entry, {
1092
+ ...(isCompactBoundaryMessage(entry) && { isCompaction: true }),
1093
+ ...(entry.agentId && { agentId: entry.agentId }),
1094
+ });
1095
+ }
1096
+ catch {
1097
+ logEvent('thaddeus_session_persistence_failed', {});
1098
+ logForDebugging('Failed to write transcript as internal event');
1099
+ }
1100
+ return;
1101
+ }
1102
+ // v1 Session Ingress path
1103
+ if (!isEnvTruthy(process.env.ENABLE_SESSION_PERSISTENCE) ||
1104
+ !this.remoteIngressUrl) {
1105
+ return;
1106
+ }
1107
+ const success = await sessionIngress.appendSessionLog(sessionId, entry, this.remoteIngressUrl);
1108
+ if (!success) {
1109
+ logEvent('thaddeus_session_persistence_failed', {});
1110
+ gracefulShutdownSync(1, 'other');
1111
+ }
1112
+ }
1113
+ setRemoteIngressUrl(url) {
1114
+ this.remoteIngressUrl = url;
1115
+ logForDebugging(`Remote persistence enabled with URL: ${url}`);
1116
+ if (url) {
1117
+ // If using CCR, don't delay messages by any more than 10ms.
1118
+ this.FLUSH_INTERVAL_MS = REMOTE_FLUSH_INTERVAL_MS;
1119
+ }
1120
+ }
1121
+ setInternalEventWriter(writer) {
1122
+ this.internalEventWriter = writer;
1123
+ logForDebugging('CCR v2 internal event writer registered for transcript persistence');
1124
+ // Use fast flush interval for CCR v2
1125
+ this.FLUSH_INTERVAL_MS = REMOTE_FLUSH_INTERVAL_MS;
1126
+ }
1127
+ setInternalEventReader(reader) {
1128
+ this.internalEventReader = reader;
1129
+ logForDebugging('CCR v2 internal event reader registered for session resume');
1130
+ }
1131
+ setInternalSubagentEventReader(reader) {
1132
+ this.internalSubagentEventReader = reader;
1133
+ logForDebugging('CCR v2 subagent event reader registered for session resume');
1134
+ }
1135
+ getInternalEventReader() {
1136
+ return this.internalEventReader;
1137
+ }
1138
+ getInternalSubagentEventReader() {
1139
+ return this.internalSubagentEventReader;
1140
+ }
1141
+ }
1142
+ // Filter out already-recorded messages before passing to insertMessageChain.
1143
+ // Without this, after compaction messagesToKeep (same UUIDs as pre-compact
1144
+ // messages) are dedup-skipped by appendEntry but still advance the parentUuid
1145
+ // cursor in insertMessageChain, causing new messages to chain from pre-compact
1146
+ // UUIDs instead of the post-compact summary — orphaning the compact boundary.
1147
+ //
1148
+ // `startingParentUuidHint`: used by useLogMessages to pass the parent from
1149
+ // the previous incremental slice, avoiding an O(n) scan to rediscover it.
1150
+ //
1151
+ // Skip-tracking: already-recorded messages are tracked as the parent ONLY if
1152
+ // they form a PREFIX (appear before any new message). This handles both cases:
1153
+ // - Growing-array callers (QueryEngine, queryHelpers, LocalMainSessionTask,
1154
+ // trajectory): recorded messages are always a prefix → tracked → correct
1155
+ // parent chain for new messages.
1156
+ // - Compaction (useLogMessages): new CB/summary appear FIRST, then recorded
1157
+ // messagesToKeep → not a prefix → not tracked → CB gets parentUuid=null
1158
+ // (correct: truncates --continue chain at compact boundary).
1159
+ export async function recordTranscript(messages, teamInfo, startingParentUuidHint, allMessages) {
1160
+ const cleanedMessages = cleanMessagesForLogging(messages, allMessages);
1161
+ const sessionId = getSessionId();
1162
+ const messageSet = await getSessionMessages(sessionId);
1163
+ const newMessages = [];
1164
+ let startingParentUuid = startingParentUuidHint;
1165
+ let seenNewMessage = false;
1166
+ for (const m of cleanedMessages) {
1167
+ if (messageSet.has(m.uuid)) {
1168
+ // Only track skipped messages that form a prefix. After compaction,
1169
+ // messagesToKeep appear AFTER new CB/summary, so this skips them.
1170
+ if (!seenNewMessage && isChainParticipant(m)) {
1171
+ startingParentUuid = m.uuid;
1172
+ }
1173
+ }
1174
+ else {
1175
+ newMessages.push(m);
1176
+ seenNewMessage = true;
1177
+ }
1178
+ }
1179
+ if (newMessages.length > 0) {
1180
+ await getProject().insertMessageChain(newMessages, false, undefined, startingParentUuid, teamInfo);
1181
+ }
1182
+ // Return the last ACTUALLY recorded chain-participant's UUID, OR the
1183
+ // prefix-tracked UUID if no new chain participants were recorded. This lets
1184
+ // callers (useLogMessages) maintain the correct parent chain even when the
1185
+ // slice is all-recorded (rewind, /resume scenarios where every message is
1186
+ // already in messageSet). Progress is skipped — it's written to the JSONL
1187
+ // but nothing chains TO it (see isChainParticipant).
1188
+ const lastRecorded = newMessages.findLast(isChainParticipant);
1189
+ return lastRecorded?.uuid ?? startingParentUuid ?? null;
1190
+ }
1191
+ export async function recordSidechainTranscript(messages, agentId, startingParentUuid) {
1192
+ await getProject().insertMessageChain(cleanMessagesForLogging(messages), true, agentId, startingParentUuid);
1193
+ }
1194
+ export async function recordQueueOperation(queueOp) {
1195
+ await getProject().insertQueueOperation(queueOp);
1196
+ }
1197
+ /**
1198
+ * Remove a message from the transcript by UUID.
1199
+ * Used when a tombstone is received for an orphaned message.
1200
+ */
1201
+ export async function removeTranscriptMessage(targetUuid) {
1202
+ await getProject().removeMessageByUuid(targetUuid);
1203
+ }
1204
+ export async function recordFileHistorySnapshot(messageId, snapshot, isSnapshotUpdate) {
1205
+ await getProject().insertFileHistorySnapshot(messageId, snapshot, isSnapshotUpdate);
1206
+ }
1207
+ export async function recordAttributionSnapshot(snapshot) {
1208
+ await getProject().insertAttributionSnapshot(snapshot);
1209
+ }
1210
+ export async function recordContentReplacement(replacements, agentId) {
1211
+ await getProject().insertContentReplacement(replacements, agentId);
1212
+ }
1213
+ /**
1214
+ * Reset the session file pointer after switchSession/regenerateSessionId.
1215
+ * The new file is created lazily on the first user/assistant message.
1216
+ */
1217
+ export async function resetSessionFilePointer() {
1218
+ getProject().resetSessionFile();
1219
+ }
1220
+ /**
1221
+ * Adopt the existing session file after --continue/--resume (non-fork).
1222
+ * Call after switchSession + resetSessionFilePointer + restoreSessionMetadata:
1223
+ * getTranscriptPath() now derives the resumed file's path from the switched
1224
+ * sessionId, and the cache holds the final metadata (--name title, resumed
1225
+ * mode/tag/agent).
1226
+ *
1227
+ * Setting sessionFile here — instead of waiting for materializeSessionFile
1228
+ * on the first user message — lets the exit cleanup handler's
1229
+ * reAppendSessionMetadata run (it bails when sessionFile is null). Without
1230
+ * this, `-c -n foo` + quit-before-message drops the title on the floor:
1231
+ * the in-memory cache is correct but never written. The resumed file
1232
+ * already exists on disk (we loaded from it), so this can't create an
1233
+ * orphan the way a fresh --name session would.
1234
+ *
1235
+ * skipTitleRefresh: restoreSessionMetadata populated the cache from the
1236
+ * same disk read microseconds ago, so refreshing from the tail here is a
1237
+ * no-op — unless --name was used, in which case it would clobber the fresh
1238
+ * CLI title with the stale disk value. After this write, disk == cache and
1239
+ * later calls (compaction, exit cleanup) absorb SDK writes normally.
1240
+ */
1241
+ export function adoptResumedSessionFile() {
1242
+ const project = getProject();
1243
+ project.sessionFile = getTranscriptPath();
1244
+ project.reAppendSessionMetadata(true);
1245
+ }
1246
+ /**
1247
+ * Append a context-collapse commit entry to the transcript. One entry per
1248
+ * commit, in commit order. On resume these are collected into an ordered
1249
+ * array and handed to restoreFromEntries() which rebuilds the commit log.
1250
+ */
1251
+ export async function recordContextCollapseCommit(commit) {
1252
+ const sessionId = getSessionId();
1253
+ if (!sessionId)
1254
+ return;
1255
+ await getProject().appendEntry({
1256
+ type: 'marble-origami-commit',
1257
+ sessionId,
1258
+ ...commit,
1259
+ });
1260
+ }
1261
+ /**
1262
+ * Snapshot the staged queue + spawn state. Written after each ctx-agent
1263
+ * spawn resolves (when staged contents may have changed). Last-wins on
1264
+ * restore — the loader keeps only the most recent snapshot entry.
1265
+ */
1266
+ export async function recordContextCollapseSnapshot(snapshot) {
1267
+ const sessionId = getSessionId();
1268
+ if (!sessionId)
1269
+ return;
1270
+ await getProject().appendEntry({
1271
+ type: 'marble-origami-snapshot',
1272
+ sessionId,
1273
+ ...snapshot,
1274
+ });
1275
+ }
1276
+ export async function flushSessionStorage() {
1277
+ await getProject().flush();
1278
+ }
1279
+ export async function hydrateRemoteSession(sessionId, ingressUrl) {
1280
+ switchSession(asSessionId(sessionId));
1281
+ const project = getProject();
1282
+ try {
1283
+ const remoteLogs = (await sessionIngress.getSessionLogs(sessionId, ingressUrl)) || [];
1284
+ // Ensure the project directory and session file exist
1285
+ const projectDir = getProjectDir(getOriginalCwd());
1286
+ await mkdir(projectDir, { recursive: true, mode: 0o700 });
1287
+ const sessionFile = getTranscriptPathForSession(sessionId);
1288
+ // Replace local logs with remote logs. writeFile truncates, so no
1289
+ // unlink is needed; an empty remoteLogs array produces an empty file.
1290
+ const content = remoteLogs.map(e => jsonStringify(e) + '\n').join('');
1291
+ await writeFile(sessionFile, content, { encoding: 'utf8', mode: 0o600 });
1292
+ logForDebugging(`Hydrated ${remoteLogs.length} entries from remote`);
1293
+ return remoteLogs.length > 0;
1294
+ }
1295
+ catch (error) {
1296
+ logForDebugging(`Error hydrating session from remote: ${error}`);
1297
+ logForDiagnosticsNoPII('error', 'hydrate_remote_session_fail');
1298
+ return false;
1299
+ }
1300
+ finally {
1301
+ // Set remote ingress URL after hydrating the remote session
1302
+ // to ensure we've always synced with the remote session
1303
+ // prior to enabling persistence
1304
+ project.setRemoteIngressUrl(ingressUrl);
1305
+ }
1306
+ }
1307
+ /**
1308
+ * Hydrate session state from CCR v2 internal events.
1309
+ * Fetches foreground and subagent events via the registered readers,
1310
+ * extracts transcript entries from payloads, and writes them to the
1311
+ * local transcript files (main + per-agent).
1312
+ * The server handles compaction filtering — it returns events starting
1313
+ * from the latest compaction boundary.
1314
+ */
1315
+ export async function hydrateFromCCRv2InternalEvents(sessionId) {
1316
+ const startMs = Date.now();
1317
+ switchSession(asSessionId(sessionId));
1318
+ const project = getProject();
1319
+ const reader = project.getInternalEventReader();
1320
+ if (!reader) {
1321
+ logForDebugging('No internal event reader registered for CCR v2 resume');
1322
+ return false;
1323
+ }
1324
+ try {
1325
+ // Fetch foreground events
1326
+ const events = await reader();
1327
+ if (!events) {
1328
+ logForDebugging('Failed to read internal events for resume');
1329
+ logForDiagnosticsNoPII('error', 'hydrate_ccr_v2_read_fail');
1330
+ return false;
1331
+ }
1332
+ const projectDir = getProjectDir(getOriginalCwd());
1333
+ await mkdir(projectDir, { recursive: true, mode: 0o700 });
1334
+ // Write foreground transcript
1335
+ const sessionFile = getTranscriptPathForSession(sessionId);
1336
+ const fgContent = events.map(e => jsonStringify(e.payload) + '\n').join('');
1337
+ await writeFile(sessionFile, fgContent, { encoding: 'utf8', mode: 0o600 });
1338
+ logForDebugging(`Hydrated ${events.length} foreground entries from CCR v2 internal events`);
1339
+ // Fetch and write subagent events
1340
+ let subagentEventCount = 0;
1341
+ const subagentReader = project.getInternalSubagentEventReader();
1342
+ if (subagentReader) {
1343
+ const subagentEvents = await subagentReader();
1344
+ if (subagentEvents && subagentEvents.length > 0) {
1345
+ subagentEventCount = subagentEvents.length;
1346
+ // Group by agent_id
1347
+ const byAgent = new Map();
1348
+ for (const e of subagentEvents) {
1349
+ const agentId = e.agent_id || '';
1350
+ if (!agentId)
1351
+ continue;
1352
+ let list = byAgent.get(agentId);
1353
+ if (!list) {
1354
+ list = [];
1355
+ byAgent.set(agentId, list);
1356
+ }
1357
+ list.push(e.payload);
1358
+ }
1359
+ // Write each agent's transcript to its own file
1360
+ for (const [agentId, entries] of byAgent) {
1361
+ const agentFile = getAgentTranscriptPath(asAgentId(agentId));
1362
+ await mkdir(dirname(agentFile), { recursive: true, mode: 0o700 });
1363
+ const agentContent = entries
1364
+ .map(p => jsonStringify(p) + '\n')
1365
+ .join('');
1366
+ await writeFile(agentFile, agentContent, {
1367
+ encoding: 'utf8',
1368
+ mode: 0o600,
1369
+ });
1370
+ }
1371
+ logForDebugging(`Hydrated ${subagentEvents.length} subagent entries across ${byAgent.size} agents`);
1372
+ }
1373
+ }
1374
+ logForDiagnosticsNoPII('info', 'hydrate_ccr_v2_completed', {
1375
+ duration_ms: Date.now() - startMs,
1376
+ event_count: events.length,
1377
+ subagent_event_count: subagentEventCount,
1378
+ });
1379
+ return events.length > 0;
1380
+ }
1381
+ catch (error) {
1382
+ // Re-throw epoch mismatch so the worker doesn't race against gracefulShutdown
1383
+ if (error instanceof Error &&
1384
+ error.message === 'CCRClient: Epoch mismatch (409)') {
1385
+ throw error;
1386
+ }
1387
+ logForDebugging(`Error hydrating session from CCR v2: ${error}`);
1388
+ logForDiagnosticsNoPII('error', 'hydrate_ccr_v2_fail');
1389
+ return false;
1390
+ }
1391
+ }
1392
+ function extractFirstPrompt(transcript) {
1393
+ const textContent = getFirstMeaningfulUserMessageTextContent(transcript);
1394
+ if (textContent) {
1395
+ let result = textContent.replace(/\n/g, ' ').trim();
1396
+ // Store a reasonably long version for display-time truncation
1397
+ // The actual truncation will be applied at display time based on terminal width
1398
+ if (result.length > 200) {
1399
+ result = result.slice(0, 200).trim() + '…';
1400
+ }
1401
+ return result;
1402
+ }
1403
+ return 'No prompt';
1404
+ }
1405
+ /**
1406
+ * Gets the last user message that was processed (i.e., before any non-user message appears).
1407
+ * Used to determine if a session has valid user interaction.
1408
+ */
1409
+ export function getFirstMeaningfulUserMessageTextContent(transcript) {
1410
+ for (const msg of transcript) {
1411
+ if (msg.type !== 'user' || msg.isMeta)
1412
+ continue;
1413
+ // Skip compact summary messages - they should not be treated as the first prompt
1414
+ if ('isCompactSummary' in msg && msg.isCompactSummary)
1415
+ continue;
1416
+ const content = msg.message?.content;
1417
+ if (!content)
1418
+ continue;
1419
+ // Collect all text values. For array content (common in VS Code where
1420
+ // IDE metadata tags come before the user's actual prompt), iterate all
1421
+ // text blocks so we don't miss the real prompt hidden behind
1422
+ // <ide_selection>/<ide_opened_file> blocks.
1423
+ const texts = [];
1424
+ if (typeof content === 'string') {
1425
+ texts.push(content);
1426
+ }
1427
+ else if (Array.isArray(content)) {
1428
+ for (const block of content) {
1429
+ if (block.type === 'text' && block.text) {
1430
+ texts.push(block.text);
1431
+ }
1432
+ }
1433
+ }
1434
+ for (const textContent of texts) {
1435
+ if (!textContent)
1436
+ continue;
1437
+ const commandNameTag = extractTag(textContent, COMMAND_NAME_TAG);
1438
+ if (commandNameTag) {
1439
+ const commandName = commandNameTag.replace(/^\//, '');
1440
+ // If it's a built-in command, then it's unlikely to provide
1441
+ // meaningful context (e.g. `/model sonnet`)
1442
+ if (builtInCommandNames().has(commandName)) {
1443
+ continue;
1444
+ }
1445
+ else {
1446
+ // Otherwise, for custom commands, then keep it only if it has
1447
+ // arguments (e.g. `/review reticulate splines`)
1448
+ const commandArgs = extractTag(textContent, 'command-args')?.trim();
1449
+ if (!commandArgs) {
1450
+ continue;
1451
+ }
1452
+ // Return clean formatted command instead of raw XML
1453
+ return `${commandNameTag} ${commandArgs}`;
1454
+ }
1455
+ }
1456
+ // Format bash input with ! prefix (as user typed it). Checked before
1457
+ // the generic XML skip so bash-mode sessions get a meaningful title.
1458
+ const bashInput = extractTag(textContent, 'bash-input');
1459
+ if (bashInput) {
1460
+ return `! ${bashInput}`;
1461
+ }
1462
+ // Skip non-meaningful messages (local command output, hook output,
1463
+ // autonomous tick prompts, task notifications, pure IDE metadata tags)
1464
+ if (SKIP_FIRST_PROMPT_PATTERN.test(textContent)) {
1465
+ continue;
1466
+ }
1467
+ return textContent;
1468
+ }
1469
+ }
1470
+ return undefined;
1471
+ }
1472
+ export function removeExtraFields(transcript) {
1473
+ return transcript.map(m => {
1474
+ const { isSidechain, parentUuid, ...serializedMessage } = m;
1475
+ return serializedMessage;
1476
+ });
1477
+ }
1478
+ /**
1479
+ * Splice the preserved segment back into the chain after compaction.
1480
+ *
1481
+ * Preserved messages exist in the JSONL with their ORIGINAL pre-compact
1482
+ * parentUuids (recordTranscript dedup-skipped them — can't rewrite).
1483
+ * The internal chain (keep[i+1]→keep[i]) is intact; only endpoints need
1484
+ * patching: head→anchor, and anchor's other children→tail. Anchor is the
1485
+ * last summary for suffix-preserving, boundary itself for prefix-preserving.
1486
+ *
1487
+ * Only the LAST seg-boundary is relinked — earlier segs were summarized
1488
+ * into it. Everything physically before the absolute-last boundary (except
1489
+ * preservedUuids) is deleted, which handles all multi-boundary shapes
1490
+ * without special-casing.
1491
+ *
1492
+ * Mutates the Map in place.
1493
+ */
1494
+ function applyPreservedSegmentRelinks(messages) {
1495
+ // Find the absolute-last boundary and the last seg-boundary (can differ:
1496
+ // manual /compact after reactive compact → seg is stale).
1497
+ let lastSeg;
1498
+ let lastSegBoundaryIdx = -1;
1499
+ let absoluteLastBoundaryIdx = -1;
1500
+ const entryIndex = new Map();
1501
+ let i = 0;
1502
+ for (const entry of messages.values()) {
1503
+ entryIndex.set(entry.uuid, i);
1504
+ if (isCompactBoundaryMessage(entry)) {
1505
+ absoluteLastBoundaryIdx = i;
1506
+ const seg = entry.compactMetadata?.preservedSegment;
1507
+ if (seg) {
1508
+ lastSeg = seg;
1509
+ lastSegBoundaryIdx = i;
1510
+ }
1511
+ }
1512
+ i++;
1513
+ }
1514
+ // No seg anywhere → no-op. findUnresolvedToolUse etc. read the full map.
1515
+ if (!lastSeg)
1516
+ return;
1517
+ // Seg stale (no-seg boundary came after): skip relink, still prune at
1518
+ // absolute — otherwise the stale preserved chain becomes a phantom leaf.
1519
+ const segIsLive = lastSegBoundaryIdx === absoluteLastBoundaryIdx;
1520
+ // Validate tail→head BEFORE mutating so malformed metadata is a true
1521
+ // no-op (walk stops at headUuid, doesn't need the relink to run first).
1522
+ const preservedUuids = new Set();
1523
+ if (segIsLive) {
1524
+ const walkSeen = new Set();
1525
+ let cur = messages.get(lastSeg.tailUuid);
1526
+ let reachedHead = false;
1527
+ while (cur && !walkSeen.has(cur.uuid)) {
1528
+ walkSeen.add(cur.uuid);
1529
+ preservedUuids.add(cur.uuid);
1530
+ if (cur.uuid === lastSeg.headUuid) {
1531
+ reachedHead = true;
1532
+ break;
1533
+ }
1534
+ cur = cur.parentUuid ? messages.get(cur.parentUuid) : undefined;
1535
+ }
1536
+ if (!reachedHead) {
1537
+ // tail→head walk broke — a UUID in the preserved segment isn't in the
1538
+ // transcript. Returning here skips the prune below, so resume loads
1539
+ // the full pre-compact history. Known cause: mid-turn-yielded
1540
+ // attachment pushed to mutableMessages but never recordTranscript'd
1541
+ // (SDK subprocess restarted before next turn's qe:420 flush).
1542
+ logEvent('thaddeus_relink_walk_broken', {
1543
+ tailInTranscript: messages.has(lastSeg.tailUuid),
1544
+ headInTranscript: messages.has(lastSeg.headUuid),
1545
+ anchorInTranscript: messages.has(lastSeg.anchorUuid),
1546
+ walkSteps: walkSeen.size,
1547
+ transcriptSize: messages.size,
1548
+ });
1549
+ return;
1550
+ }
1551
+ }
1552
+ if (segIsLive) {
1553
+ const head = messages.get(lastSeg.headUuid);
1554
+ if (head) {
1555
+ messages.set(lastSeg.headUuid, {
1556
+ ...head,
1557
+ parentUuid: lastSeg.anchorUuid,
1558
+ });
1559
+ }
1560
+ // Tail-splice: anchor's other children → tail. No-op if already pointing
1561
+ // at tail (the useLogMessages race case).
1562
+ for (const [uuid, msg] of messages) {
1563
+ if (msg.parentUuid === lastSeg.anchorUuid && uuid !== lastSeg.headUuid) {
1564
+ messages.set(uuid, { ...msg, parentUuid: lastSeg.tailUuid });
1565
+ }
1566
+ }
1567
+ // Zero stale usage: on-disk input_tokens reflect pre-compact context
1568
+ // (~190K) — stripStaleUsage only patched in-memory copies that were
1569
+ // dedup-skipped. Without this, resume → immediate autocompact spiral.
1570
+ for (const uuid of preservedUuids) {
1571
+ const msg = messages.get(uuid);
1572
+ if (msg?.type !== 'assistant')
1573
+ continue;
1574
+ messages.set(uuid, {
1575
+ ...msg,
1576
+ message: {
1577
+ ...msg.message,
1578
+ usage: {
1579
+ ...msg.message.usage,
1580
+ input_tokens: 0,
1581
+ output_tokens: 0,
1582
+ cache_creation_input_tokens: 0,
1583
+ cache_read_input_tokens: 0,
1584
+ },
1585
+ },
1586
+ });
1587
+ }
1588
+ }
1589
+ // Prune everything physically before the absolute-last boundary that
1590
+ // isn't preserved. preservedUuids empty when !segIsLive → full prune.
1591
+ const toDelete = [];
1592
+ for (const [uuid] of messages) {
1593
+ const idx = entryIndex.get(uuid);
1594
+ if (idx !== undefined &&
1595
+ idx < absoluteLastBoundaryIdx &&
1596
+ !preservedUuids.has(uuid)) {
1597
+ toDelete.push(uuid);
1598
+ }
1599
+ }
1600
+ for (const uuid of toDelete)
1601
+ messages.delete(uuid);
1602
+ }
1603
+ /**
1604
+ * Delete messages that Snip executions removed from the in-memory array,
1605
+ * and relink parentUuid across the gaps.
1606
+ *
1607
+ * Unlike compact_boundary which truncates a prefix, snip removes
1608
+ * middle ranges. The JSONL is append-only, so removed messages stay on disk
1609
+ * and the surviving messages' parentUuid chains walk through them. Without
1610
+ * this filter, buildConversationChain reconstructs the full unsnipped history
1611
+ * and resume immediately PTLs (adamr-20260320-165831: 397K displayed → 1.65M
1612
+ * actual).
1613
+ *
1614
+ * Deleting alone is not enough: the surviving message AFTER a removed range
1615
+ * has parentUuid pointing INTO the gap. buildConversationChain would hit
1616
+ * messages.get(undefined) and stop, orphaning everything before the gap. So
1617
+ * after delete we relink: for each survivor with a dangling parentUuid, walk
1618
+ * backward through the removed region's own parent links to the first
1619
+ * non-removed ancestor.
1620
+ *
1621
+ * The boundary records removedUuids at execution time so we can replay the
1622
+ * exact removal on load. Older boundaries without removedUuids are skipped —
1623
+ * resume loads their pre-snip history (the pre-fix behavior).
1624
+ *
1625
+ * Mutates the Map in place.
1626
+ */
1627
+ function applySnipRemovals(messages) {
1628
+ const toDelete = new Set();
1629
+ for (const entry of messages.values()) {
1630
+ const removedUuids = entry.snipMetadata?.removedUuids;
1631
+ if (!removedUuids)
1632
+ continue;
1633
+ for (const uuid of removedUuids)
1634
+ toDelete.add(uuid);
1635
+ }
1636
+ if (toDelete.size === 0)
1637
+ return;
1638
+ // Capture each to-delete entry's own parentUuid BEFORE deleting so we can
1639
+ // walk backward through contiguous removed ranges. Entries not in the Map
1640
+ // (already absent, e.g. from a prior compact_boundary prune) contribute no
1641
+ // link; the relink walk will stop at the gap and pick up null (chain-root
1642
+ // behavior — same as if compact truncated there, which it did).
1643
+ const deletedParent = new Map();
1644
+ let removedCount = 0;
1645
+ for (const uuid of toDelete) {
1646
+ const entry = messages.get(uuid);
1647
+ if (!entry)
1648
+ continue;
1649
+ deletedParent.set(uuid, entry.parentUuid);
1650
+ messages.delete(uuid);
1651
+ removedCount++;
1652
+ }
1653
+ // Relink survivors with dangling parentUuid. Walk backward through
1654
+ // deletedParent until we hit a UUID not in toDelete (or null). Path
1655
+ // compression: after resolving, seed the map with the resolved link so
1656
+ // subsequent survivors sharing the same chain segment don't re-walk.
1657
+ const resolve = (start) => {
1658
+ const path = [];
1659
+ let cur = start;
1660
+ while (cur && toDelete.has(cur)) {
1661
+ path.push(cur);
1662
+ cur = deletedParent.get(cur);
1663
+ if (cur === undefined) {
1664
+ cur = null;
1665
+ break;
1666
+ }
1667
+ }
1668
+ for (const p of path)
1669
+ deletedParent.set(p, cur);
1670
+ return cur;
1671
+ };
1672
+ let relinkedCount = 0;
1673
+ for (const [uuid, msg] of messages) {
1674
+ if (!msg.parentUuid || !toDelete.has(msg.parentUuid))
1675
+ continue;
1676
+ messages.set(uuid, { ...msg, parentUuid: resolve(msg.parentUuid) });
1677
+ relinkedCount++;
1678
+ }
1679
+ logEvent('thaddeus_snip_resume_filtered', {
1680
+ removed_count: removedCount,
1681
+ relinked_count: relinkedCount,
1682
+ });
1683
+ }
1684
+ /**
1685
+ * O(n) single-pass: find the message with the latest timestamp matching a predicate.
1686
+ * Replaces the `[...values].filter(pred).sort((a,b) => Date(b)-Date(a))[0]` pattern
1687
+ * which is O(n log n) + 2n Date allocations.
1688
+ */
1689
+ function findLatestMessage(messages, predicate) {
1690
+ let latest;
1691
+ let maxTime = -Infinity;
1692
+ for (const m of messages) {
1693
+ if (!predicate(m))
1694
+ continue;
1695
+ const t = Date.parse(m.timestamp);
1696
+ if (t > maxTime) {
1697
+ maxTime = t;
1698
+ latest = m;
1699
+ }
1700
+ }
1701
+ return latest;
1702
+ }
1703
+ /**
1704
+ * Builds a conversation chain from a leaf message to root
1705
+ * @param messages Map of all messages
1706
+ * @param leafMessage The leaf message to start from
1707
+ * @returns Array of messages from root to leaf
1708
+ */
1709
+ export function buildConversationChain(messages, leafMessage) {
1710
+ const transcript = [];
1711
+ const seen = new Set();
1712
+ let currentMsg = leafMessage;
1713
+ while (currentMsg) {
1714
+ if (seen.has(currentMsg.uuid)) {
1715
+ logError(new Error(`Cycle detected in parentUuid chain at message ${currentMsg.uuid}. Returning partial transcript.`));
1716
+ logEvent('thaddeus_chain_parent_cycle', {});
1717
+ break;
1718
+ }
1719
+ seen.add(currentMsg.uuid);
1720
+ transcript.push(currentMsg);
1721
+ currentMsg = currentMsg.parentUuid
1722
+ ? messages.get(currentMsg.parentUuid)
1723
+ : undefined;
1724
+ }
1725
+ transcript.reverse();
1726
+ return recoverOrphanedParallelToolResults(messages, transcript, seen);
1727
+ }
1728
+ /**
1729
+ * Post-pass for buildConversationChain: recover sibling assistant blocks and
1730
+ * tool_results that the single-parent walk orphaned.
1731
+ *
1732
+ * Streaming (claude.ts:~2024) emits one AssistantMessage per content_block_stop
1733
+ * — N parallel tool_uses → N messages, distinct uuid, same message.id. Each
1734
+ * tool_result's sourceToolAssistantUUID points to its own one-block assistant,
1735
+ * so insertMessageChain's override (line ~894) writes each TR's parentUuid to a
1736
+ * DIFFERENT assistant. The topology is a DAG; the walk above is a linked-list
1737
+ * traversal and keeps only one branch.
1738
+ *
1739
+ * Two loss modes observed in production (both fixed here):
1740
+ * 1. Sibling assistant orphaned: walk goes prev→asstA→TR_A→next, drops asstB
1741
+ * (same message.id, chained off asstA) and TR_B.
1742
+ * 2. Progress-fork (legacy, pre-#23537): each tool_use asst had a progress
1743
+ * child (continued the write chain) AND a TR child. Walk followed
1744
+ * progress; TRs were dropped. No longer written (progress removed from
1745
+ * transcript persistence), but old transcripts still have this shape.
1746
+ *
1747
+ * Read-side fix: the write topology is already on disk for old transcripts;
1748
+ * this recovery pass handles them.
1749
+ */
1750
+ function recoverOrphanedParallelToolResults(messages, chain, seen) {
1751
+ const chainAssistants = chain.filter((m) => m.type === 'assistant');
1752
+ if (chainAssistants.length === 0)
1753
+ return chain;
1754
+ // Anchor = last on-chain member of each sibling group. chainAssistants is
1755
+ // already in chain order, so later iterations overwrite → last wins.
1756
+ const anchorByMsgId = new Map();
1757
+ for (const a of chainAssistants) {
1758
+ if (a.message.id)
1759
+ anchorByMsgId.set(a.message.id, a);
1760
+ }
1761
+ // O(n) precompute: sibling groups and TR index.
1762
+ // TRs indexed by parentUuid — insertMessageChain:~894 already wrote that
1763
+ // as the srcUUID, and --fork-session strips srcUUID but keeps parentUuid.
1764
+ const siblingsByMsgId = new Map();
1765
+ const toolResultsByAsst = new Map();
1766
+ for (const m of messages.values()) {
1767
+ if (m.type === 'assistant' && m.message.id) {
1768
+ const group = siblingsByMsgId.get(m.message.id);
1769
+ if (group)
1770
+ group.push(m);
1771
+ else
1772
+ siblingsByMsgId.set(m.message.id, [m]);
1773
+ }
1774
+ else if (m.type === 'user' &&
1775
+ m.parentUuid &&
1776
+ Array.isArray(m.message.content) &&
1777
+ m.message.content.some(b => b.type === 'tool_result')) {
1778
+ const group = toolResultsByAsst.get(m.parentUuid);
1779
+ if (group)
1780
+ group.push(m);
1781
+ else
1782
+ toolResultsByAsst.set(m.parentUuid, [m]);
1783
+ }
1784
+ }
1785
+ // For each message.id group touching the chain: collect off-chain siblings,
1786
+ // then off-chain TRs for ALL members. Splice right after the last on-chain
1787
+ // member so the group stays contiguous for normalizeMessagesForAPI's merge
1788
+ // and every TR lands after its tool_use.
1789
+ const processedGroups = new Set();
1790
+ const inserts = new Map();
1791
+ let recoveredCount = 0;
1792
+ for (const asst of chainAssistants) {
1793
+ const msgId = asst.message.id;
1794
+ if (!msgId || processedGroups.has(msgId))
1795
+ continue;
1796
+ processedGroups.add(msgId);
1797
+ const group = siblingsByMsgId.get(msgId) ?? [asst];
1798
+ const orphanedSiblings = group.filter(s => !seen.has(s.uuid));
1799
+ const orphanedTRs = [];
1800
+ for (const member of group) {
1801
+ const trs = toolResultsByAsst.get(member.uuid);
1802
+ if (!trs)
1803
+ continue;
1804
+ for (const tr of trs) {
1805
+ if (!seen.has(tr.uuid))
1806
+ orphanedTRs.push(tr);
1807
+ }
1808
+ }
1809
+ if (orphanedSiblings.length === 0 && orphanedTRs.length === 0)
1810
+ continue;
1811
+ // Timestamp sort keeps content-block / completion order; stable-sort
1812
+ // preserves JSONL write order on ties.
1813
+ orphanedSiblings.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1814
+ orphanedTRs.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1815
+ const anchor = anchorByMsgId.get(msgId);
1816
+ const recovered = [...orphanedSiblings, ...orphanedTRs];
1817
+ for (const r of recovered)
1818
+ seen.add(r.uuid);
1819
+ recoveredCount += recovered.length;
1820
+ inserts.set(anchor.uuid, recovered);
1821
+ }
1822
+ if (recoveredCount === 0)
1823
+ return chain;
1824
+ logEvent('thaddeus_chain_parallel_tr_recovered', {
1825
+ recovered_count: recoveredCount,
1826
+ });
1827
+ const result = [];
1828
+ for (const m of chain) {
1829
+ result.push(m);
1830
+ const toInsert = inserts.get(m.uuid);
1831
+ if (toInsert)
1832
+ result.push(...toInsert);
1833
+ }
1834
+ return result;
1835
+ }
1836
+ /**
1837
+ * Find the latest turn_duration checkpoint in the reconstructed chain and
1838
+ * compare its recorded messageCount against the chain's position at that
1839
+ * point. Emits thaddeus_resume_consistency_delta for BigQuery monitoring of
1840
+ * write→load round-trip drift — the class of bugs where snip/compact/
1841
+ * parallel-TR operations mutate in-memory but the parentUuid walk on disk
1842
+ * reconstructs a different set (adamr-20260320-165831: 397K displayed →
1843
+ * 1.65M actual on resume).
1844
+ *
1845
+ * delta > 0: resume loaded MORE than in-session (the usual failure mode)
1846
+ * delta < 0: resume loaded FEWER (chain truncation — #22453 class)
1847
+ * delta = 0: round-trip consistent
1848
+ *
1849
+ * Called from loadConversationForResume — fires once per resume, not on
1850
+ * /share or log-listing chain rebuilds.
1851
+ */
1852
+ export function checkResumeConsistency(chain) {
1853
+ for (let i = chain.length - 1; i >= 0; i--) {
1854
+ const m = chain[i];
1855
+ if (m.type !== 'system' || m.subtype !== 'turn_duration')
1856
+ continue;
1857
+ const expected = m.messageCount;
1858
+ if (expected === undefined)
1859
+ return;
1860
+ // `i` is the 0-based index of the checkpoint in the reconstructed chain.
1861
+ // The checkpoint was appended AFTER messageCount messages, so its own
1862
+ // position should be messageCount (i.e., i === expected).
1863
+ const actual = i;
1864
+ logEvent('thaddeus_resume_consistency_delta', {
1865
+ expected,
1866
+ actual,
1867
+ delta: actual - expected,
1868
+ chain_length: chain.length,
1869
+ checkpoint_age_entries: chain.length - 1 - i,
1870
+ });
1871
+ return;
1872
+ }
1873
+ }
1874
+ /**
1875
+ * Builds a filie history snapshot chain from the conversation
1876
+ */
1877
+ function buildFileHistorySnapshotChain(fileHistorySnapshots, conversation) {
1878
+ const snapshots = [];
1879
+ // messageId → last index in snapshots[] for O(1) update lookup
1880
+ const indexByMessageId = new Map();
1881
+ for (const message of conversation) {
1882
+ const snapshotMessage = fileHistorySnapshots.get(message.uuid);
1883
+ if (!snapshotMessage) {
1884
+ continue;
1885
+ }
1886
+ const { snapshot, isSnapshotUpdate } = snapshotMessage;
1887
+ const existingIndex = isSnapshotUpdate
1888
+ ? indexByMessageId.get(snapshot.messageId)
1889
+ : undefined;
1890
+ if (existingIndex === undefined) {
1891
+ indexByMessageId.set(snapshot.messageId, snapshots.length);
1892
+ snapshots.push(snapshot);
1893
+ }
1894
+ else {
1895
+ snapshots[existingIndex] = snapshot;
1896
+ }
1897
+ }
1898
+ return snapshots;
1899
+ }
1900
+ /**
1901
+ * Builds an attribution snapshot chain from the conversation.
1902
+ * Unlike file history snapshots, attribution snapshots are returned in full
1903
+ * because they use generated UUIDs (not message UUIDs) and represent
1904
+ * cumulative state that should be restored on session resume.
1905
+ */
1906
+ function buildAttributionSnapshotChain(attributionSnapshots, _conversation) {
1907
+ // Return all attribution snapshots - they will be merged during restore
1908
+ return Array.from(attributionSnapshots.values());
1909
+ }
1910
+ /**
1911
+ * Loads a transcript from a JSON or JSONL file and converts it to LogOption format
1912
+ * @param filePath Path to the transcript file (.json or .jsonl)
1913
+ * @returns LogOption containing the transcript messages
1914
+ * @throws Error if file doesn't exist or contains invalid data
1915
+ */
1916
+ export async function loadTranscriptFromFile(filePath) {
1917
+ if (filePath.endsWith('.jsonl')) {
1918
+ const { messages, summaries, customTitles, tags, fileHistorySnapshots, attributionSnapshots, contextCollapseCommits, contextCollapseSnapshot, leafUuids, contentReplacements, worktreeStates, } = await loadTranscriptFile(filePath);
1919
+ if (messages.size === 0) {
1920
+ throw new Error('No messages found in JSONL file');
1921
+ }
1922
+ // Find the most recent leaf message using pre-computed leaf UUIDs
1923
+ const leafMessage = findLatestMessage(messages.values(), msg => leafUuids.has(msg.uuid));
1924
+ if (!leafMessage) {
1925
+ throw new Error('No valid conversation chain found in JSONL file');
1926
+ }
1927
+ // Build the conversation chain backwards from leaf to root
1928
+ const transcript = buildConversationChain(messages, leafMessage);
1929
+ const summary = summaries.get(leafMessage.uuid);
1930
+ const customTitle = customTitles.get(leafMessage.sessionId);
1931
+ const tag = tags.get(leafMessage.sessionId);
1932
+ const sessionId = leafMessage.sessionId;
1933
+ return {
1934
+ ...convertToLogOption(transcript, 0, summary, customTitle, buildFileHistorySnapshotChain(fileHistorySnapshots, transcript), tag, filePath, buildAttributionSnapshotChain(attributionSnapshots, transcript), undefined, contentReplacements.get(sessionId) ?? []),
1935
+ contextCollapseCommits: contextCollapseCommits.filter(e => e.sessionId === sessionId),
1936
+ contextCollapseSnapshot: contextCollapseSnapshot?.sessionId === sessionId
1937
+ ? contextCollapseSnapshot
1938
+ : undefined,
1939
+ worktreeSession: worktreeStates.has(sessionId)
1940
+ ? worktreeStates.get(sessionId)
1941
+ : undefined,
1942
+ };
1943
+ }
1944
+ // json log files
1945
+ const content = await readFile(filePath, { encoding: 'utf-8' });
1946
+ let parsed;
1947
+ try {
1948
+ parsed = jsonParse(content);
1949
+ }
1950
+ catch (error) {
1951
+ throw new Error(`Invalid JSON in transcript file: ${error}`);
1952
+ }
1953
+ let messages;
1954
+ if (Array.isArray(parsed)) {
1955
+ messages = parsed;
1956
+ }
1957
+ else if (parsed && typeof parsed === 'object' && 'messages' in parsed) {
1958
+ if (!Array.isArray(parsed.messages)) {
1959
+ throw new Error('Transcript messages must be an array');
1960
+ }
1961
+ messages = parsed.messages;
1962
+ }
1963
+ else {
1964
+ throw new Error('Transcript must be an array of messages or an object with a messages array');
1965
+ }
1966
+ return convertToLogOption(messages, 0, undefined, undefined, undefined, undefined, filePath);
1967
+ }
1968
+ /**
1969
+ * Checks if a user message has visible content (text or image, not just tool_result).
1970
+ * Tool results are displayed as part of collapsed groups, not as standalone messages.
1971
+ * Also excludes meta messages which are not shown to the user.
1972
+ */
1973
+ function hasVisibleUserContent(message) {
1974
+ if (message.type !== 'user')
1975
+ return false;
1976
+ // Meta messages are not shown to the user
1977
+ if (message.isMeta)
1978
+ return false;
1979
+ const content = message.message?.content;
1980
+ if (!content)
1981
+ return false;
1982
+ // String content is always visible
1983
+ if (typeof content === 'string') {
1984
+ return content.trim().length > 0;
1985
+ }
1986
+ // Array content: check for text or image blocks (not tool_result)
1987
+ if (Array.isArray(content)) {
1988
+ return content.some(block => block.type === 'text' ||
1989
+ block.type === 'image' ||
1990
+ block.type === 'document');
1991
+ }
1992
+ return false;
1993
+ }
1994
+ /**
1995
+ * Checks if an assistant message has visible text content (not just tool_use blocks).
1996
+ * Tool uses are displayed as grouped/collapsed UI elements, not as standalone messages.
1997
+ */
1998
+ function hasVisibleAssistantContent(message) {
1999
+ if (message.type !== 'assistant')
2000
+ return false;
2001
+ const content = message.message?.content;
2002
+ if (!content || !Array.isArray(content))
2003
+ return false;
2004
+ // Check for text block (not just tool_use/thinking blocks)
2005
+ return content.some(block => block.type === 'text' &&
2006
+ typeof block.text === 'string' &&
2007
+ block.text.trim().length > 0);
2008
+ }
2009
+ /**
2010
+ * Counts visible messages that would appear as conversation turns in the UI.
2011
+ * Excludes:
2012
+ * - System, attachment, and progress messages
2013
+ * - User messages with isMeta flag (hidden from user)
2014
+ * - User messages that only contain tool_result blocks (displayed as collapsed groups)
2015
+ * - Assistant messages that only contain tool_use blocks (displayed as collapsed groups)
2016
+ */
2017
+ function countVisibleMessages(transcript) {
2018
+ let count = 0;
2019
+ for (const message of transcript) {
2020
+ switch (message.type) {
2021
+ case 'user':
2022
+ // Count user messages with visible content (text, image, not just tool_result or meta)
2023
+ if (hasVisibleUserContent(message)) {
2024
+ count++;
2025
+ }
2026
+ break;
2027
+ case 'assistant':
2028
+ // Count assistant messages with text content (not just tool_use)
2029
+ if (hasVisibleAssistantContent(message)) {
2030
+ count++;
2031
+ }
2032
+ break;
2033
+ case 'attachment':
2034
+ case 'system':
2035
+ case 'progress':
2036
+ // These message types are not counted as visible conversation turns
2037
+ break;
2038
+ }
2039
+ }
2040
+ return count;
2041
+ }
2042
+ function convertToLogOption(transcript, value = 0, summary, customTitle, fileHistorySnapshots, tag, fullPath, attributionSnapshots, agentSetting, contentReplacements) {
2043
+ const lastMessage = transcript.at(-1);
2044
+ const firstMessage = transcript[0];
2045
+ // Get the first user message for the prompt
2046
+ const firstPrompt = extractFirstPrompt(transcript);
2047
+ // Create timestamps from message timestamps
2048
+ const created = new Date(firstMessage.timestamp);
2049
+ const modified = new Date(lastMessage.timestamp);
2050
+ return {
2051
+ date: lastMessage.timestamp,
2052
+ messages: removeExtraFields(transcript),
2053
+ fullPath,
2054
+ value,
2055
+ created,
2056
+ modified,
2057
+ firstPrompt,
2058
+ messageCount: countVisibleMessages(transcript),
2059
+ isSidechain: firstMessage.isSidechain,
2060
+ teamName: firstMessage.teamName,
2061
+ agentName: firstMessage.agentName,
2062
+ agentSetting,
2063
+ leafUuid: lastMessage.uuid,
2064
+ summary,
2065
+ customTitle,
2066
+ tag,
2067
+ fileHistorySnapshots: fileHistorySnapshots,
2068
+ attributionSnapshots: attributionSnapshots,
2069
+ contentReplacements,
2070
+ gitBranch: lastMessage.gitBranch,
2071
+ projectPath: firstMessage.cwd,
2072
+ };
2073
+ }
2074
+ async function trackSessionBranchingAnalytics(logs) {
2075
+ const sessionIdCounts = new Map();
2076
+ let maxCount = 0;
2077
+ for (const log of logs) {
2078
+ const sessionId = getSessionIdFromLog(log);
2079
+ if (sessionId) {
2080
+ const newCount = (sessionIdCounts.get(sessionId) || 0) + 1;
2081
+ sessionIdCounts.set(sessionId, newCount);
2082
+ maxCount = Math.max(newCount, maxCount);
2083
+ }
2084
+ }
2085
+ // Early exit if no duplicates detected
2086
+ if (maxCount <= 1) {
2087
+ return;
2088
+ }
2089
+ // Count sessions with branches and calculate stats using functional approach
2090
+ const branchCounts = Array.from(sessionIdCounts.values()).filter(c => c > 1);
2091
+ const sessionsWithBranches = branchCounts.length;
2092
+ const totalBranches = branchCounts.reduce((sum, count) => sum + count, 0);
2093
+ logEvent('thaddeus_session_forked_branches_fetched', {
2094
+ total_sessions: sessionIdCounts.size,
2095
+ sessions_with_branches: sessionsWithBranches,
2096
+ max_branches_per_session: Math.max(...branchCounts),
2097
+ avg_branches_per_session: Math.round(totalBranches / sessionsWithBranches),
2098
+ total_transcript_count: logs.length,
2099
+ });
2100
+ }
2101
+ export async function fetchLogs(limit) {
2102
+ const projectDir = getProjectDir(getOriginalCwd());
2103
+ const logs = await getSessionFilesLite(projectDir, limit, getOriginalCwd());
2104
+ await trackSessionBranchingAnalytics(logs);
2105
+ return logs;
2106
+ }
2107
+ /**
2108
+ * Append an entry to a session file. Creates the parent dir if missing.
2109
+ */
2110
+ /* eslint-disable custom-rules/no-sync-fs -- sync callers (exit cleanup, materialize) */
2111
+ function appendEntryToFile(fullPath, entry) {
2112
+ const fs = getFsImplementation();
2113
+ const line = jsonStringify(entry) + '\n';
2114
+ try {
2115
+ fs.appendFileSync(fullPath, line, { mode: 0o600 });
2116
+ }
2117
+ catch {
2118
+ fs.mkdirSync(dirname(fullPath), { mode: 0o700 });
2119
+ fs.appendFileSync(fullPath, line, { mode: 0o600 });
2120
+ }
2121
+ }
2122
+ /**
2123
+ * Sync tail read for reAppendSessionMetadata's external-writer check.
2124
+ * fstat on the already-open fd (no extra path lookup); reads the same
2125
+ * LITE_READ_BUF_SIZE window that readLiteMetadata scans. Returns empty
2126
+ * string on any error so callers fall through to unconditional behavior.
2127
+ */
2128
+ function readFileTailSync(fullPath) {
2129
+ let fd;
2130
+ try {
2131
+ fd = openSync(fullPath, 'r');
2132
+ const st = fstatSync(fd);
2133
+ const tailOffset = Math.max(0, st.size - LITE_READ_BUF_SIZE);
2134
+ const buf = Buffer.allocUnsafe(Math.min(LITE_READ_BUF_SIZE, st.size - tailOffset));
2135
+ const bytesRead = readSync(fd, buf, 0, buf.length, tailOffset);
2136
+ return buf.toString('utf8', 0, bytesRead);
2137
+ }
2138
+ catch {
2139
+ return '';
2140
+ }
2141
+ finally {
2142
+ if (fd !== undefined) {
2143
+ try {
2144
+ closeSync(fd);
2145
+ }
2146
+ catch {
2147
+ // closeSync can throw; swallow to preserve return '' contract
2148
+ }
2149
+ }
2150
+ }
2151
+ }
2152
+ /* eslint-enable custom-rules/no-sync-fs */
2153
+ export async function saveCustomTitle(sessionId, customTitle, fullPath, source = 'user') {
2154
+ // Fall back to computed path if fullPath is not provided
2155
+ const resolvedPath = fullPath ?? getTranscriptPathForSession(sessionId);
2156
+ appendEntryToFile(resolvedPath, {
2157
+ type: 'custom-title',
2158
+ customTitle,
2159
+ sessionId,
2160
+ });
2161
+ // Cache for current session only (for immediate visibility)
2162
+ if (sessionId === getSessionId()) {
2163
+ getProject().currentSessionTitle = customTitle;
2164
+ }
2165
+ logEvent('thaddeus_session_renamed', {
2166
+ source: source,
2167
+ });
2168
+ }
2169
+ /**
2170
+ * Persist an AI-generated title to the JSONL as a distinct `ai-title` entry.
2171
+ *
2172
+ * Writing a separate entry type (vs. reusing `custom-title`) is load-bearing:
2173
+ * - Read preference: readers prefer `customTitle` field over `aiTitle`, so
2174
+ * a user rename always wins regardless of append order.
2175
+ * - Resume safety: `loadTranscriptFile` only populates the `customTitles`
2176
+ * Map from `custom-title` entries, so `restoreSessionMetadata` never
2177
+ * caches an AI title and `reAppendSessionMetadata` never re-appends one
2178
+ * at EOF — avoiding the clobber-on-resume bug where a stale AI title
2179
+ * overwrites a mid-session user rename.
2180
+ * - CAS semantics: VS Code's `onlyIfNoCustomTitle` check scans for the
2181
+ * `customTitle` field only, so AI can overwrite its own previous AI
2182
+ * title but never a user title.
2183
+ * - Metrics: `thaddeus_session_renamed` is not fired for AI titles.
2184
+ *
2185
+ * Because the entry is never re-appended, it scrolls out of the 64KB tail
2186
+ * window once enough messages accumulate. Readers (`readLiteMetadata`,
2187
+ * `listSessionsImpl`, VS Code `fetchSessions`) fall back to scanning the
2188
+ * head buffer for `aiTitle` in that case. Both head and tail reads are
2189
+ * bounded (64KB each via `extractLastJsonStringField`), never a full scan.
2190
+ *
2191
+ * Callers with a stale-write guard (e.g., VS Code client) should prefer
2192
+ * passing `persist: false` to the SDK control request and persisting
2193
+ * through their own rename path after the guard passes, to avoid a race
2194
+ * where the AI title lands after a mid-flight user rename.
2195
+ */
2196
+ export function saveAiGeneratedTitle(sessionId, aiTitle) {
2197
+ appendEntryToFile(getTranscriptPathForSession(sessionId), {
2198
+ type: 'ai-title',
2199
+ aiTitle,
2200
+ sessionId,
2201
+ });
2202
+ }
2203
+ /**
2204
+ * Append a periodic task summary for `claude ps`. Unlike ai-title this is
2205
+ * not re-appended by reAppendSessionMetadata — it's a rolling snapshot of
2206
+ * what the agent is doing *now*, so staleness is fine; ps reads the most
2207
+ * recent one from the tail.
2208
+ */
2209
+ export function saveTaskSummary(sessionId, summary) {
2210
+ appendEntryToFile(getTranscriptPathForSession(sessionId), {
2211
+ type: 'task-summary',
2212
+ summary,
2213
+ sessionId,
2214
+ timestamp: new Date().toISOString(),
2215
+ });
2216
+ }
2217
+ export async function saveTag(sessionId, tag, fullPath) {
2218
+ // Fall back to computed path if fullPath is not provided
2219
+ const resolvedPath = fullPath ?? getTranscriptPathForSession(sessionId);
2220
+ appendEntryToFile(resolvedPath, { type: 'tag', tag, sessionId });
2221
+ // Cache for current session only (for immediate visibility)
2222
+ if (sessionId === getSessionId()) {
2223
+ getProject().currentSessionTag = tag;
2224
+ }
2225
+ logEvent('thaddeus_session_tagged', {});
2226
+ }
2227
+ /**
2228
+ * Link a session to a GitHub pull request.
2229
+ * This stores the PR number, URL, and repository for tracking and navigation.
2230
+ */
2231
+ export async function linkSessionToPR(sessionId, prNumber, prUrl, prRepository, fullPath) {
2232
+ const resolvedPath = fullPath ?? getTranscriptPathForSession(sessionId);
2233
+ appendEntryToFile(resolvedPath, {
2234
+ type: 'pr-link',
2235
+ sessionId,
2236
+ prNumber,
2237
+ prUrl,
2238
+ prRepository,
2239
+ timestamp: new Date().toISOString(),
2240
+ });
2241
+ // Cache for current session so reAppendSessionMetadata can re-write after compaction
2242
+ if (sessionId === getSessionId()) {
2243
+ const project = getProject();
2244
+ project.currentSessionPrNumber = prNumber;
2245
+ project.currentSessionPrUrl = prUrl;
2246
+ project.currentSessionPrRepository = prRepository;
2247
+ }
2248
+ logEvent('thaddeus_session_linked_to_pr', { prNumber });
2249
+ }
2250
+ export function getCurrentSessionTag(sessionId) {
2251
+ // Only returns tag for current session (the only one we cache)
2252
+ if (sessionId === getSessionId()) {
2253
+ return getProject().currentSessionTag;
2254
+ }
2255
+ return undefined;
2256
+ }
2257
+ export function getCurrentSessionTitle(sessionId) {
2258
+ // Only returns title for current session (the only one we cache)
2259
+ if (sessionId === getSessionId()) {
2260
+ return getProject().currentSessionTitle;
2261
+ }
2262
+ return undefined;
2263
+ }
2264
+ export function getCurrentSessionAgentColor() {
2265
+ return getProject().currentSessionAgentColor;
2266
+ }
2267
+ /**
2268
+ * Restore session metadata into in-memory cache on resume.
2269
+ * Populates the cache so metadata is available for display (e.g. the
2270
+ * agent banner) and re-appended on session exit via reAppendSessionMetadata.
2271
+ */
2272
+ export function restoreSessionMetadata(meta) {
2273
+ const project = getProject();
2274
+ // ??= so --name (cacheSessionTitle) wins over the resumed
2275
+ // session's title. REPL.tsx clears before calling, so /resume is unaffected.
2276
+ if (meta.customTitle)
2277
+ project.currentSessionTitle ??= meta.customTitle;
2278
+ if (meta.tag !== undefined)
2279
+ project.currentSessionTag = meta.tag || undefined;
2280
+ if (meta.agentName)
2281
+ project.currentSessionAgentName = meta.agentName;
2282
+ if (meta.agentColor)
2283
+ project.currentSessionAgentColor = meta.agentColor;
2284
+ if (meta.agentSetting)
2285
+ project.currentSessionAgentSetting = meta.agentSetting;
2286
+ if (meta.mode)
2287
+ project.currentSessionMode = meta.mode;
2288
+ if (meta.worktreeSession !== undefined)
2289
+ project.currentSessionWorktree = meta.worktreeSession;
2290
+ if (meta.prNumber !== undefined)
2291
+ project.currentSessionPrNumber = meta.prNumber;
2292
+ if (meta.prUrl)
2293
+ project.currentSessionPrUrl = meta.prUrl;
2294
+ if (meta.prRepository)
2295
+ project.currentSessionPrRepository = meta.prRepository;
2296
+ }
2297
+ /**
2298
+ * Clear all cached session metadata (title, tag, agent name/color).
2299
+ * Called when /clear creates a new session so stale metadata
2300
+ * from the previous session does not leak into the new one.
2301
+ */
2302
+ export function clearSessionMetadata() {
2303
+ const project = getProject();
2304
+ project.currentSessionTitle = undefined;
2305
+ project.currentSessionTag = undefined;
2306
+ project.currentSessionAgentName = undefined;
2307
+ project.currentSessionAgentColor = undefined;
2308
+ project.currentSessionLastPrompt = undefined;
2309
+ project.currentSessionAgentSetting = undefined;
2310
+ project.currentSessionMode = undefined;
2311
+ project.currentSessionWorktree = undefined;
2312
+ project.currentSessionPrNumber = undefined;
2313
+ project.currentSessionPrUrl = undefined;
2314
+ project.currentSessionPrRepository = undefined;
2315
+ }
2316
+ /**
2317
+ * Re-append cached session metadata (custom title, tag) to the end of the
2318
+ * transcript file. Call this after compaction so the metadata stays within
2319
+ * the 16KB tail window that readLiteMetadata reads during progressive loading.
2320
+ * Without this, enough post-compaction messages can push the metadata entry
2321
+ * out of the window, causing `--resume` to show the auto-generated firstPrompt
2322
+ * instead of the user-set session name.
2323
+ */
2324
+ export function reAppendSessionMetadata() {
2325
+ getProject().reAppendSessionMetadata();
2326
+ }
2327
+ export async function saveAgentName(sessionId, agentName, fullPath, source = 'user') {
2328
+ const resolvedPath = fullPath ?? getTranscriptPathForSession(sessionId);
2329
+ appendEntryToFile(resolvedPath, { type: 'agent-name', agentName, sessionId });
2330
+ // Cache for current session only (for immediate visibility)
2331
+ if (sessionId === getSessionId()) {
2332
+ getProject().currentSessionAgentName = agentName;
2333
+ void updateSessionName(agentName);
2334
+ }
2335
+ logEvent('thaddeus_agent_name_set', {
2336
+ source: source,
2337
+ });
2338
+ }
2339
+ export async function saveAgentColor(sessionId, agentColor, fullPath) {
2340
+ const resolvedPath = fullPath ?? getTranscriptPathForSession(sessionId);
2341
+ appendEntryToFile(resolvedPath, {
2342
+ type: 'agent-color',
2343
+ agentColor,
2344
+ sessionId,
2345
+ });
2346
+ // Cache for current session only (for immediate visibility)
2347
+ if (sessionId === getSessionId()) {
2348
+ getProject().currentSessionAgentColor = agentColor;
2349
+ }
2350
+ logEvent('thaddeus_agent_color_set', {});
2351
+ }
2352
+ /**
2353
+ * Cache the session agent setting. Written to disk by materializeSessionFile
2354
+ * on the first user message, and re-stamped by reAppendSessionMetadata on exit.
2355
+ * Cache-only here to avoid creating metadata-only session files at startup.
2356
+ */
2357
+ export function saveAgentSetting(agentSetting) {
2358
+ getProject().currentSessionAgentSetting = agentSetting;
2359
+ }
2360
+ /**
2361
+ * Cache a session title set at startup (--name). Written to disk by
2362
+ * materializeSessionFile on the first user message. Cache-only here so no
2363
+ * orphan metadata-only file is created before the session ID is finalized.
2364
+ */
2365
+ export function cacheSessionTitle(customTitle) {
2366
+ getProject().currentSessionTitle = customTitle;
2367
+ }
2368
+ /**
2369
+ * Cache the session mode. Written to disk by materializeSessionFile on the
2370
+ * first user message, and re-stamped by reAppendSessionMetadata on exit.
2371
+ * Cache-only here to avoid creating metadata-only session files at startup.
2372
+ */
2373
+ export function saveMode(mode) {
2374
+ getProject().currentSessionMode = mode;
2375
+ }
2376
+ /**
2377
+ * Record the session's worktree state for --resume. Written to disk by
2378
+ * materializeSessionFile on the first user message and re-stamped by
2379
+ * reAppendSessionMetadata on exit. Pass null when exiting a worktree
2380
+ * so --resume knows not to cd back into it.
2381
+ */
2382
+ export function saveWorktreeState(worktreeSession) {
2383
+ // Strip ephemeral fields (creationDurationMs, usedSparsePaths) that callers
2384
+ // may pass via full WorktreeSession objects — TypeScript structural typing
2385
+ // allows this, but we don't want them serialized to the transcript.
2386
+ const stripped = worktreeSession
2387
+ ? {
2388
+ originalCwd: worktreeSession.originalCwd,
2389
+ worktreePath: worktreeSession.worktreePath,
2390
+ worktreeName: worktreeSession.worktreeName,
2391
+ worktreeBranch: worktreeSession.worktreeBranch,
2392
+ originalBranch: worktreeSession.originalBranch,
2393
+ originalHeadCommit: worktreeSession.originalHeadCommit,
2394
+ sessionId: worktreeSession.sessionId,
2395
+ tmuxSessionName: worktreeSession.tmuxSessionName,
2396
+ hookBased: worktreeSession.hookBased,
2397
+ }
2398
+ : null;
2399
+ const project = getProject();
2400
+ project.currentSessionWorktree = stripped;
2401
+ // Write eagerly when the file already exists (mid-session enter/exit).
2402
+ // For --worktree startup, sessionFile is null — materializeSessionFile
2403
+ // will write it on the first message via reAppendSessionMetadata.
2404
+ if (project.sessionFile) {
2405
+ appendEntryToFile(project.sessionFile, {
2406
+ type: 'worktree-state',
2407
+ worktreeSession: stripped,
2408
+ sessionId: getSessionId(),
2409
+ });
2410
+ }
2411
+ }
2412
+ /**
2413
+ * Extracts the session ID from a log.
2414
+ * For lite logs, uses the sessionId field directly.
2415
+ * For full logs, extracts from the first message.
2416
+ */
2417
+ export function getSessionIdFromLog(log) {
2418
+ // For lite logs, use the direct sessionId field
2419
+ if (log.sessionId) {
2420
+ return log.sessionId;
2421
+ }
2422
+ // Fall back to extracting from first message (full logs)
2423
+ return log.messages[0]?.sessionId;
2424
+ }
2425
+ /**
2426
+ * Checks if a log is a lite log that needs full loading.
2427
+ * Lite logs have messages: [] and sessionId set.
2428
+ */
2429
+ export function isLiteLog(log) {
2430
+ return log.messages.length === 0 && log.sessionId !== undefined;
2431
+ }
2432
+ /**
2433
+ * Loads full messages for a lite log by reading its JSONL file.
2434
+ * Returns a new LogOption with populated messages array.
2435
+ * If the log is already full or loading fails, returns the original log.
2436
+ */
2437
+ export async function loadFullLog(log) {
2438
+ // If already full, return as-is
2439
+ if (!isLiteLog(log)) {
2440
+ return log;
2441
+ }
2442
+ // Use the fullPath from the index entry directly
2443
+ const sessionFile = log.fullPath;
2444
+ if (!sessionFile) {
2445
+ return log;
2446
+ }
2447
+ try {
2448
+ const { messages, summaries, customTitles, tags, agentNames, agentColors, agentSettings, prNumbers, prUrls, prRepositories, modes, worktreeStates, fileHistorySnapshots, attributionSnapshots, contentReplacements, contextCollapseCommits, contextCollapseSnapshot, leafUuids, } = await loadTranscriptFile(sessionFile);
2449
+ if (messages.size === 0) {
2450
+ return log;
2451
+ }
2452
+ // Find the most recent user/assistant leaf message from the transcript
2453
+ const mostRecentLeaf = findLatestMessage(messages.values(), msg => leafUuids.has(msg.uuid) &&
2454
+ (msg.type === 'user' || msg.type === 'assistant'));
2455
+ if (!mostRecentLeaf) {
2456
+ return log;
2457
+ }
2458
+ // Build the conversation chain from this leaf
2459
+ const transcript = buildConversationChain(messages, mostRecentLeaf);
2460
+ // Leaf's sessionId — forked sessions copy chain[0] from the source, but
2461
+ // metadata entries (custom-title etc.) are keyed by the current session.
2462
+ const sessionId = mostRecentLeaf.sessionId;
2463
+ return {
2464
+ ...log,
2465
+ messages: removeExtraFields(transcript),
2466
+ firstPrompt: extractFirstPrompt(transcript),
2467
+ messageCount: countVisibleMessages(transcript),
2468
+ summary: mostRecentLeaf
2469
+ ? summaries.get(mostRecentLeaf.uuid)
2470
+ : log.summary,
2471
+ customTitle: sessionId ? customTitles.get(sessionId) : log.customTitle,
2472
+ tag: sessionId ? tags.get(sessionId) : log.tag,
2473
+ agentName: sessionId ? agentNames.get(sessionId) : log.agentName,
2474
+ agentColor: sessionId ? agentColors.get(sessionId) : log.agentColor,
2475
+ agentSetting: sessionId ? agentSettings.get(sessionId) : log.agentSetting,
2476
+ mode: sessionId ? modes.get(sessionId) : log.mode,
2477
+ worktreeSession: sessionId && worktreeStates.has(sessionId)
2478
+ ? worktreeStates.get(sessionId)
2479
+ : log.worktreeSession,
2480
+ prNumber: sessionId ? prNumbers.get(sessionId) : log.prNumber,
2481
+ prUrl: sessionId ? prUrls.get(sessionId) : log.prUrl,
2482
+ prRepository: sessionId
2483
+ ? prRepositories.get(sessionId)
2484
+ : log.prRepository,
2485
+ gitBranch: mostRecentLeaf?.gitBranch ?? log.gitBranch,
2486
+ isSidechain: transcript[0]?.isSidechain ?? log.isSidechain,
2487
+ teamName: transcript[0]?.teamName ?? log.teamName,
2488
+ leafUuid: mostRecentLeaf?.uuid ?? log.leafUuid,
2489
+ fileHistorySnapshots: buildFileHistorySnapshotChain(fileHistorySnapshots, transcript),
2490
+ attributionSnapshots: buildAttributionSnapshotChain(attributionSnapshots, transcript),
2491
+ contentReplacements: sessionId
2492
+ ? (contentReplacements.get(sessionId) ?? [])
2493
+ : log.contentReplacements,
2494
+ // Filter to the resumed session's entries. loadTranscriptFile reads
2495
+ // the file sequentially so the array is already in commit order;
2496
+ // filter preserves that.
2497
+ contextCollapseCommits: sessionId
2498
+ ? contextCollapseCommits.filter(e => e.sessionId === sessionId)
2499
+ : undefined,
2500
+ contextCollapseSnapshot: sessionId && contextCollapseSnapshot?.sessionId === sessionId
2501
+ ? contextCollapseSnapshot
2502
+ : undefined,
2503
+ };
2504
+ }
2505
+ catch {
2506
+ // If loading fails, return the original log
2507
+ return log;
2508
+ }
2509
+ }
2510
+ /**
2511
+ * Searches for sessions by custom title match.
2512
+ * Returns matches sorted by recency (newest first).
2513
+ * Uses case-insensitive matching for better UX.
2514
+ * Deduplicates by sessionId (keeps most recent per session).
2515
+ * Searches across same-repo worktrees by default.
2516
+ */
2517
+ export async function searchSessionsByCustomTitle(query, options) {
2518
+ const { limit, exact } = options || {};
2519
+ // Use worktree-aware loading to search across same-repo sessions
2520
+ const worktreePaths = await getWorktreePaths(getOriginalCwd());
2521
+ const allStatLogs = await getStatOnlyLogsForWorktrees(worktreePaths);
2522
+ // Enrich all logs to access customTitle metadata
2523
+ const { logs } = await enrichLogs(allStatLogs, 0, allStatLogs.length);
2524
+ const normalizedQuery = query.toLowerCase().trim();
2525
+ const matchingLogs = logs.filter(log => {
2526
+ const title = log.customTitle?.toLowerCase().trim();
2527
+ if (!title)
2528
+ return false;
2529
+ return exact ? title === normalizedQuery : title.includes(normalizedQuery);
2530
+ });
2531
+ // Deduplicate by sessionId - multiple logs can have the same sessionId
2532
+ // if they're different branches of the same conversation. Keep most recent.
2533
+ const sessionIdToLog = new Map();
2534
+ for (const log of matchingLogs) {
2535
+ const sessionId = getSessionIdFromLog(log);
2536
+ if (sessionId) {
2537
+ const existing = sessionIdToLog.get(sessionId);
2538
+ if (!existing || log.modified > existing.modified) {
2539
+ sessionIdToLog.set(sessionId, log);
2540
+ }
2541
+ }
2542
+ }
2543
+ const deduplicated = Array.from(sessionIdToLog.values());
2544
+ // Sort by recency
2545
+ deduplicated.sort((a, b) => b.modified.getTime() - a.modified.getTime());
2546
+ // Apply limit if specified
2547
+ if (limit) {
2548
+ return deduplicated.slice(0, limit);
2549
+ }
2550
+ return deduplicated;
2551
+ }
2552
+ /**
2553
+ * Metadata entry types that can appear before a compact boundary but must
2554
+ * still be loaded (they're session-scoped, not message-scoped).
2555
+ * Kept as raw JSON string markers for cheap line filtering during streaming.
2556
+ */
2557
+ const METADATA_TYPE_MARKERS = [
2558
+ '"type":"summary"',
2559
+ '"type":"custom-title"',
2560
+ '"type":"tag"',
2561
+ '"type":"agent-name"',
2562
+ '"type":"agent-color"',
2563
+ '"type":"agent-setting"',
2564
+ '"type":"mode"',
2565
+ '"type":"worktree-state"',
2566
+ '"type":"pr-link"',
2567
+ ];
2568
+ const METADATA_MARKER_BUFS = METADATA_TYPE_MARKERS.map(m => Buffer.from(m));
2569
+ // Longest marker is 22 bytes; +1 for leading `{` = 23.
2570
+ const METADATA_PREFIX_BOUND = 25;
2571
+ // null = carry spans whole chunk. Skips concat when carry provably isn't
2572
+ // a metadata line (markers sit at byte 1 after `{`).
2573
+ function resolveMetadataBuf(carry, chunkBuf) {
2574
+ if (carry === null || carry.length === 0)
2575
+ return chunkBuf;
2576
+ if (carry.length < METADATA_PREFIX_BOUND) {
2577
+ return Buffer.concat([carry, chunkBuf]);
2578
+ }
2579
+ if (carry[0] === 0x7b /* { */) {
2580
+ for (const m of METADATA_MARKER_BUFS) {
2581
+ if (carry.compare(m, 0, m.length, 1, 1 + m.length) === 0) {
2582
+ return Buffer.concat([carry, chunkBuf]);
2583
+ }
2584
+ }
2585
+ }
2586
+ const firstNl = chunkBuf.indexOf(0x0a);
2587
+ return firstNl === -1 ? null : chunkBuf.subarray(firstNl + 1);
2588
+ }
2589
+ /**
2590
+ * Lightweight forward scan of [0, endOffset) collecting only metadata-entry lines.
2591
+ * Uses raw Buffer chunks and byte-level marker matching — no readline, no per-line
2592
+ * string conversion for the ~99% of lines that are message content.
2593
+ *
2594
+ * Fast path: if a chunk contains zero markers (the common case — metadata entries
2595
+ * are <50 per session), the entire chunk is skipped without line splitting.
2596
+ */
2597
+ async function scanPreBoundaryMetadata(filePath, endOffset) {
2598
+ const { createReadStream } = await import('fs');
2599
+ const NEWLINE = 0x0a;
2600
+ const stream = createReadStream(filePath, { end: endOffset - 1 });
2601
+ const metadataLines = [];
2602
+ let carry = null;
2603
+ for await (const chunk of stream) {
2604
+ const chunkBuf = chunk;
2605
+ const buf = resolveMetadataBuf(carry, chunkBuf);
2606
+ if (buf === null) {
2607
+ carry = null;
2608
+ continue;
2609
+ }
2610
+ // Fast path: most chunks contain zero metadata markers. Skip line splitting.
2611
+ let hasAnyMarker = false;
2612
+ for (const m of METADATA_MARKER_BUFS) {
2613
+ if (buf.includes(m)) {
2614
+ hasAnyMarker = true;
2615
+ break;
2616
+ }
2617
+ }
2618
+ if (hasAnyMarker) {
2619
+ let lineStart = 0;
2620
+ let nl = buf.indexOf(NEWLINE);
2621
+ while (nl !== -1) {
2622
+ // Bounded marker check: only look within this line's byte range
2623
+ for (const m of METADATA_MARKER_BUFS) {
2624
+ const mIdx = buf.indexOf(m, lineStart);
2625
+ if (mIdx !== -1 && mIdx < nl) {
2626
+ metadataLines.push(buf.toString('utf-8', lineStart, nl));
2627
+ break;
2628
+ }
2629
+ }
2630
+ lineStart = nl + 1;
2631
+ nl = buf.indexOf(NEWLINE, lineStart);
2632
+ }
2633
+ carry = buf.subarray(lineStart);
2634
+ }
2635
+ else {
2636
+ // No markers in this chunk — just preserve the incomplete trailing line
2637
+ const lastNl = buf.lastIndexOf(NEWLINE);
2638
+ carry = lastNl >= 0 ? buf.subarray(lastNl + 1) : buf;
2639
+ }
2640
+ // Guard against quadratic carry growth for pathological huge lines
2641
+ // (e.g., a 10 MB tool-output line with no newline). Real metadata entries
2642
+ // are <1 KB, so if carry exceeds this we're mid-message-content — drop it.
2643
+ if (carry.length > 64 * 1024)
2644
+ carry = null;
2645
+ }
2646
+ // Final incomplete line (no trailing newline at endOffset)
2647
+ if (carry !== null && carry.length > 0) {
2648
+ for (const m of METADATA_MARKER_BUFS) {
2649
+ if (carry.includes(m)) {
2650
+ metadataLines.push(carry.toString('utf-8'));
2651
+ break;
2652
+ }
2653
+ }
2654
+ }
2655
+ return metadataLines;
2656
+ }
2657
+ /**
2658
+ * Byte-level pre-filter that excises dead fork branches before parseJSONL.
2659
+ *
2660
+ * Every rewind/ctrl-z leaves an orphaned chain branch in the append-only
2661
+ * JSONL forever. buildConversationChain walks parentUuid from the latest leaf
2662
+ * and discards everything else, but by then parseJSONL has already paid to
2663
+ * JSON.parse all of it. Measured on fork-heavy sessions:
2664
+ *
2665
+ * 41 MB, 99% dead: parseJSONL 56.0 ms -> 3.9 ms (-93%)
2666
+ * 151 MB, 92% dead: 47.3 ms -> 9.4 ms (-80%)
2667
+ *
2668
+ * Sessions with few dead branches (5-7%) see a small win from the overhead of
2669
+ * the index pass roughly canceling the parse savings, so this is gated on
2670
+ * buffer size (same threshold as SKIP_PRECOMPACT_THRESHOLD).
2671
+ *
2672
+ * Relies on two invariants verified across 25k+ message lines in local
2673
+ * sessions (0 violations):
2674
+ *
2675
+ * 1. Transcript messages always serialize with parentUuid as the first key.
2676
+ * JSON.stringify emits keys in insertion order and recordTranscript's
2677
+ * object literal puts parentUuid first. So `{"parentUuid":` is a stable
2678
+ * line prefix that distinguishes transcript messages from metadata.
2679
+ *
2680
+ * 2. Top-level uuid detection is handled by a suffix check + depth check
2681
+ * (see inline comment in the scan loop). toolUseResult/mcpMeta serialize
2682
+ * AFTER uuid with arbitrary server-controlled objects, and agent_progress
2683
+ * entries serialize a nested Message in data BEFORE uuid — both can
2684
+ * produce nested `"uuid":"<36>","timestamp":"` bytes, so suffix alone
2685
+ * is insufficient. When multiple suffix matches exist, a brace-depth
2686
+ * scan disambiguates.
2687
+ *
2688
+ * The append-only write discipline guarantees parents appear at earlier file
2689
+ * offsets than children, so walking backward from EOF always finds them.
2690
+ */
2691
+ /**
2692
+ * Disambiguate multiple `"uuid":"<36>","timestamp":"` matches in one line by
2693
+ * finding the one at JSON nesting depth 1. String-aware brace counter:
2694
+ * `{`/`}` inside string values don't count; `\"` and `\\` inside strings are
2695
+ * handled. Candidates is sorted ascending (the scan loop produces them in
2696
+ * byte order). Returns the first depth-1 candidate, or the last candidate if
2697
+ * none are at depth 1 (shouldn't happen for well-formed JSONL — depth-1 is
2698
+ * where the top-level object's fields live).
2699
+ *
2700
+ * Only called when ≥2 suffix matches exist (agent_progress with a nested
2701
+ * Message, or mcpMeta with a coincidentally-suffixed object). Cost is
2702
+ * O(max(candidates) - lineStart) — one forward byte pass, stopping at the
2703
+ * first depth-1 hit.
2704
+ */
2705
+ function pickDepthOneUuidCandidate(buf, lineStart, candidates) {
2706
+ const QUOTE = 0x22;
2707
+ const BACKSLASH = 0x5c;
2708
+ const OPEN_BRACE = 0x7b;
2709
+ const CLOSE_BRACE = 0x7d;
2710
+ let depth = 0;
2711
+ let inString = false;
2712
+ let escapeNext = false;
2713
+ let ci = 0;
2714
+ for (let i = lineStart; ci < candidates.length; i++) {
2715
+ if (i === candidates[ci]) {
2716
+ if (depth === 1 && !inString)
2717
+ return candidates[ci];
2718
+ ci++;
2719
+ }
2720
+ const b = buf[i];
2721
+ if (escapeNext) {
2722
+ escapeNext = false;
2723
+ }
2724
+ else if (inString) {
2725
+ if (b === BACKSLASH)
2726
+ escapeNext = true;
2727
+ else if (b === QUOTE)
2728
+ inString = false;
2729
+ }
2730
+ else if (b === QUOTE)
2731
+ inString = true;
2732
+ else if (b === OPEN_BRACE)
2733
+ depth++;
2734
+ else if (b === CLOSE_BRACE)
2735
+ depth--;
2736
+ }
2737
+ return candidates.at(-1);
2738
+ }
2739
+ function walkChainBeforeParse(buf) {
2740
+ const NEWLINE = 0x0a;
2741
+ const OPEN_BRACE = 0x7b;
2742
+ const QUOTE = 0x22;
2743
+ const PARENT_PREFIX = Buffer.from('{"parentUuid":');
2744
+ const UUID_KEY = Buffer.from('"uuid":"');
2745
+ const SIDECHAIN_TRUE = Buffer.from('"isSidechain":true');
2746
+ const UUID_LEN = 36;
2747
+ const TS_SUFFIX = Buffer.from('","timestamp":"');
2748
+ const TS_SUFFIX_LEN = TS_SUFFIX.length;
2749
+ const PREFIX_LEN = PARENT_PREFIX.length;
2750
+ const KEY_LEN = UUID_KEY.length;
2751
+ // Stride-3 flat index of transcript messages: [lineStart, lineEnd, parentStart].
2752
+ // parentStart is the byte offset of the parent uuid's first char, or -1 for null.
2753
+ // Metadata lines (summary, mode, file-history-snapshot, etc.) go in metaRanges
2754
+ // unfiltered - they lack the parentUuid prefix and downstream needs all of them.
2755
+ const msgIdx = [];
2756
+ const metaRanges = [];
2757
+ const uuidToSlot = new Map();
2758
+ let pos = 0;
2759
+ const len = buf.length;
2760
+ while (pos < len) {
2761
+ const nl = buf.indexOf(NEWLINE, pos);
2762
+ const lineEnd = nl === -1 ? len : nl + 1;
2763
+ if (lineEnd - pos > PREFIX_LEN &&
2764
+ buf[pos] === OPEN_BRACE &&
2765
+ buf.compare(PARENT_PREFIX, 0, PREFIX_LEN, pos, pos + PREFIX_LEN) === 0) {
2766
+ // `{"parentUuid":null,` or `{"parentUuid":"<36 chars>",`
2767
+ const parentStart = buf[pos + PREFIX_LEN] === QUOTE ? pos + PREFIX_LEN + 1 : -1;
2768
+ // The top-level uuid is immediately followed by `","timestamp":"` in
2769
+ // user/assistant/attachment entries (the create* helpers put them
2770
+ // adjacent; both always defined). But the suffix is NOT unique:
2771
+ // - agent_progress entries carry a nested Message in data.message,
2772
+ // serialized BEFORE top-level uuid — that inner Message has its
2773
+ // own uuid,timestamp adjacent, so its bytes also satisfy the
2774
+ // suffix check.
2775
+ // - mcpMeta/toolUseResult come AFTER top-level uuid and hold
2776
+ // server-controlled Record<string,unknown> — a server returning
2777
+ // {uuid:"<36>",timestamp:"..."} would also match.
2778
+ // Collect all suffix matches; a single one is unambiguous (common
2779
+ // case), multiple need a brace-depth check to pick the one at
2780
+ // JSON nesting depth 1. Entries with NO suffix match (some progress
2781
+ // variants put timestamp BEFORE uuid → `"uuid":"<36>"}` at EOL)
2782
+ // have only one `"uuid":"` and the first-match fallback is sound.
2783
+ let firstAny = -1;
2784
+ let suffix0 = -1;
2785
+ let suffixN;
2786
+ let from = pos;
2787
+ for (;;) {
2788
+ const next = buf.indexOf(UUID_KEY, from);
2789
+ if (next < 0 || next >= lineEnd)
2790
+ break;
2791
+ if (firstAny < 0)
2792
+ firstAny = next;
2793
+ const after = next + KEY_LEN + UUID_LEN;
2794
+ if (after + TS_SUFFIX_LEN <= lineEnd &&
2795
+ buf.compare(TS_SUFFIX, 0, TS_SUFFIX_LEN, after, after + TS_SUFFIX_LEN) === 0) {
2796
+ if (suffix0 < 0)
2797
+ suffix0 = next;
2798
+ else
2799
+ (suffixN ??= [suffix0]).push(next);
2800
+ }
2801
+ from = next + KEY_LEN;
2802
+ }
2803
+ const uk = suffixN
2804
+ ? pickDepthOneUuidCandidate(buf, pos, suffixN)
2805
+ : suffix0 >= 0
2806
+ ? suffix0
2807
+ : firstAny;
2808
+ if (uk >= 0) {
2809
+ const uuidStart = uk + KEY_LEN;
2810
+ // UUIDs are pure ASCII so latin1 avoids UTF-8 decode overhead.
2811
+ const uuid = buf.toString('latin1', uuidStart, uuidStart + UUID_LEN);
2812
+ uuidToSlot.set(uuid, msgIdx.length);
2813
+ msgIdx.push(pos, lineEnd, parentStart);
2814
+ }
2815
+ else {
2816
+ metaRanges.push(pos, lineEnd);
2817
+ }
2818
+ }
2819
+ else {
2820
+ metaRanges.push(pos, lineEnd);
2821
+ }
2822
+ pos = lineEnd;
2823
+ }
2824
+ // Leaf = last non-sidechain entry. isSidechain is the 2nd or 3rd key
2825
+ // (after parentUuid, maybe logicalParentUuid) so indexOf from lineStart
2826
+ // finds it within a few dozen bytes when present; when absent it spills
2827
+ // into the next line, caught by the bounds check.
2828
+ let leafSlot = -1;
2829
+ for (let i = msgIdx.length - 3; i >= 0; i -= 3) {
2830
+ const sc = buf.indexOf(SIDECHAIN_TRUE, msgIdx[i]);
2831
+ if (sc === -1 || sc >= msgIdx[i + 1]) {
2832
+ leafSlot = i;
2833
+ break;
2834
+ }
2835
+ }
2836
+ if (leafSlot < 0)
2837
+ return buf;
2838
+ // Walk parentUuid to root. Collect kept-message line starts and sum their
2839
+ // byte lengths so we can decide whether the concat is worth it. A dangling
2840
+ // parent (uuid not in file) is the normal termination for forked sessions
2841
+ // and post-boundary chains -- same semantics as buildConversationChain.
2842
+ // Correctness against index poisoning rests on the timestamp suffix check
2843
+ // above: a nested `"uuid":"` match without the suffix never becomes uk.
2844
+ const seen = new Set();
2845
+ const chain = new Set();
2846
+ let chainBytes = 0;
2847
+ let slot = leafSlot;
2848
+ while (slot !== undefined) {
2849
+ if (seen.has(slot))
2850
+ break;
2851
+ seen.add(slot);
2852
+ chain.add(msgIdx[slot]);
2853
+ chainBytes += msgIdx[slot + 1] - msgIdx[slot];
2854
+ const parentStart = msgIdx[slot + 2];
2855
+ if (parentStart < 0)
2856
+ break;
2857
+ const parent = buf.toString('latin1', parentStart, parentStart + UUID_LEN);
2858
+ slot = uuidToSlot.get(parent);
2859
+ }
2860
+ // parseJSONL cost scales with bytes, not entry count. A session can have
2861
+ // thousands of dead entries by count but only single-digit-% of bytes if
2862
+ // the dead branches are short turns and the live chain holds the fat
2863
+ // assistant responses (measured: 107 MB session, 69% dead entries, 30%
2864
+ // dead bytes - index+concat overhead exceeded parse savings). Gate on
2865
+ // bytes: only stitch if we would drop at least half the buffer. Metadata
2866
+ // is tiny so len - chainBytes approximates dead bytes closely enough.
2867
+ // Near break-even the concat memcpy (copying chainBytes into a fresh
2868
+ // allocation) dominates, so a conservative 50% gate stays safely on the
2869
+ // winning side.
2870
+ if (len - chainBytes < len >> 1)
2871
+ return buf;
2872
+ // Merge chain entries with metadata in original file order. Both msgIdx and
2873
+ // metaRanges are already sorted by offset; interleave them into subarray
2874
+ // views and concat once.
2875
+ const parts = [];
2876
+ let m = 0;
2877
+ for (let i = 0; i < msgIdx.length; i += 3) {
2878
+ const start = msgIdx[i];
2879
+ while (m < metaRanges.length && metaRanges[m] < start) {
2880
+ parts.push(buf.subarray(metaRanges[m], metaRanges[m + 1]));
2881
+ m += 2;
2882
+ }
2883
+ if (chain.has(start)) {
2884
+ parts.push(buf.subarray(start, msgIdx[i + 1]));
2885
+ }
2886
+ }
2887
+ while (m < metaRanges.length) {
2888
+ parts.push(buf.subarray(metaRanges[m], metaRanges[m + 1]));
2889
+ m += 2;
2890
+ }
2891
+ return Buffer.concat(parts);
2892
+ }
2893
+ /**
2894
+ * Loads all messages, summaries, and file history snapshots from a transcript file.
2895
+ * Returns the messages, summaries, custom titles, tags, file history snapshots, and attribution snapshots.
2896
+ */
2897
+ export async function loadTranscriptFile(filePath, opts) {
2898
+ const messages = new Map();
2899
+ const summaries = new Map();
2900
+ const customTitles = new Map();
2901
+ const tags = new Map();
2902
+ const agentNames = new Map();
2903
+ const agentColors = new Map();
2904
+ const agentSettings = new Map();
2905
+ const prNumbers = new Map();
2906
+ const prUrls = new Map();
2907
+ const prRepositories = new Map();
2908
+ const modes = new Map();
2909
+ const worktreeStates = new Map();
2910
+ const fileHistorySnapshots = new Map();
2911
+ const attributionSnapshots = new Map();
2912
+ const contentReplacements = new Map();
2913
+ const agentContentReplacements = new Map();
2914
+ // Array, not Map — commit order matters (nested collapses).
2915
+ const contextCollapseCommits = [];
2916
+ // Last-wins — later entries supersede.
2917
+ let contextCollapseSnapshot;
2918
+ try {
2919
+ // For large transcripts, avoid materializing megabytes of stale content.
2920
+ // Single forward chunked read: attribution-snapshot lines are skipped at
2921
+ // the fd level (never buffered), compact boundaries truncate the
2922
+ // accumulator in-stream. Peak allocation is the OUTPUT size, not the
2923
+ // file size — a 151 MB session that is 84% stale attr-snaps allocates
2924
+ // ~32 MB instead of 159+64 MB. This matters because mimalloc does not
2925
+ // return those pages to the OS even after JS-level GC frees the backing
2926
+ // buffers (measured: arrayBuffers=0 after Bun.gc(true) but RSS stuck at
2927
+ // ~316 MB on the old scan+strip path vs ~155 MB here).
2928
+ //
2929
+ // Pre-boundary metadata (agent-setting, mode, pr-link, etc.) is recovered
2930
+ // via a cheap byte-level forward scan of [0, boundary).
2931
+ let buf = null;
2932
+ let metadataLines = null;
2933
+ let hasPreservedSegment = false;
2934
+ if (!isEnvTruthy(process.env.THADDEUS_DISABLE_PRECOMPACT_SKIP)) {
2935
+ const { size } = await stat(filePath);
2936
+ if (size > SKIP_PRECOMPACT_THRESHOLD) {
2937
+ const scan = await readTranscriptForLoad(filePath, size);
2938
+ buf = scan.postBoundaryBuf;
2939
+ hasPreservedSegment = scan.hasPreservedSegment;
2940
+ // >0 means we truncated pre-boundary bytes and must recover
2941
+ // session-scoped metadata from that range. A preservedSegment
2942
+ // boundary does not truncate (preserved messages are physically
2943
+ // pre-boundary), so offset stays 0 unless an EARLIER non-preserved
2944
+ // boundary already truncated — in which case the preserved messages
2945
+ // for the later boundary are post-that-earlier-boundary and were
2946
+ // kept, and we still want the metadata scan.
2947
+ if (scan.boundaryStartOffset > 0) {
2948
+ metadataLines = await scanPreBoundaryMetadata(filePath, scan.boundaryStartOffset);
2949
+ }
2950
+ }
2951
+ }
2952
+ buf ??= await readFile(filePath);
2953
+ // For large buffers (which here means readTranscriptForLoad output with
2954
+ // attr-snaps already stripped at the fd level — the <5MB readFile path
2955
+ // falls through the size gate below), the dominant cost is parsing dead
2956
+ // fork branches that buildConversationChain would discard anyway. Skip
2957
+ // when the caller needs all
2958
+ // leaves (loadAllLogsFromSessionFile for /insights picks the branch with
2959
+ // most user messages, not the latest), when the boundary has a
2960
+ // preservedSegment (those messages keep their pre-compact parentUuid on
2961
+ // disk -- applyPreservedSegmentRelinks splices them in-memory AFTER
2962
+ // parse, so a pre-parse chain walk would drop them as orphans), and when
2963
+ // THADDEUS_DISABLE_PRECOMPACT_SKIP is set (that kill switch means
2964
+ // "load everything, skip nothing"; this is another skip-before-parse
2965
+ // optimization and the scan it depends on for hasPreservedSegment did
2966
+ // not run).
2967
+ if (!opts?.keepAllLeaves &&
2968
+ !hasPreservedSegment &&
2969
+ !isEnvTruthy(process.env.THADDEUS_DISABLE_PRECOMPACT_SKIP) &&
2970
+ buf.length > SKIP_PRECOMPACT_THRESHOLD) {
2971
+ buf = walkChainBeforeParse(buf);
2972
+ }
2973
+ // First pass: process metadata-only lines collected during the boundary scan.
2974
+ // These populate the session-scoped maps (agentSettings, modes, prNumbers,
2975
+ // etc.) for entries written before the compact boundary. Any overlap with
2976
+ // the post-boundary buffer is harmless — later values overwrite earlier ones.
2977
+ if (metadataLines && metadataLines.length > 0) {
2978
+ const metaEntries = parseJSONL(Buffer.from(metadataLines.join('\n')));
2979
+ for (const entry of metaEntries) {
2980
+ if (entry.type === 'summary' && entry.leafUuid) {
2981
+ summaries.set(entry.leafUuid, entry.summary);
2982
+ }
2983
+ else if (entry.type === 'custom-title' && entry.sessionId) {
2984
+ customTitles.set(entry.sessionId, entry.customTitle);
2985
+ }
2986
+ else if (entry.type === 'tag' && entry.sessionId) {
2987
+ tags.set(entry.sessionId, entry.tag);
2988
+ }
2989
+ else if (entry.type === 'agent-name' && entry.sessionId) {
2990
+ agentNames.set(entry.sessionId, entry.agentName);
2991
+ }
2992
+ else if (entry.type === 'agent-color' && entry.sessionId) {
2993
+ agentColors.set(entry.sessionId, entry.agentColor);
2994
+ }
2995
+ else if (entry.type === 'agent-setting' && entry.sessionId) {
2996
+ agentSettings.set(entry.sessionId, entry.agentSetting);
2997
+ }
2998
+ else if (entry.type === 'mode' && entry.sessionId) {
2999
+ modes.set(entry.sessionId, entry.mode);
3000
+ }
3001
+ else if (entry.type === 'worktree-state' && entry.sessionId) {
3002
+ worktreeStates.set(entry.sessionId, entry.worktreeSession);
3003
+ }
3004
+ else if (entry.type === 'pr-link' && entry.sessionId) {
3005
+ prNumbers.set(entry.sessionId, entry.prNumber);
3006
+ prUrls.set(entry.sessionId, entry.prUrl);
3007
+ prRepositories.set(entry.sessionId, entry.prRepository);
3008
+ }
3009
+ }
3010
+ }
3011
+ const entries = parseJSONL(buf);
3012
+ // Bridge map for legacy progress entries: progress_uuid → progress_parent_uuid.
3013
+ // PR #24099 removed progress from isTranscriptMessage, so old transcripts with
3014
+ // progress in the parentUuid chain would truncate at buildConversationChain
3015
+ // when messages.get(progressUuid) returns undefined. Since transcripts are
3016
+ // append-only (parents before children), we record each progress→parent link
3017
+ // as we see it, chain-resolving through consecutive progress entries, then
3018
+ // rewrite any subsequent message whose parentUuid lands in the bridge.
3019
+ const progressBridge = new Map();
3020
+ for (const entry of entries) {
3021
+ // Legacy progress check runs before the Entry-typed else-if chain —
3022
+ // progress is not in the Entry union, so checking it after TypeScript
3023
+ // has narrowed `entry` intersects to `never`.
3024
+ if (isLegacyProgressEntry(entry)) {
3025
+ // Chain-resolve through consecutive progress entries so a later
3026
+ // message pointing at the tail of a progress run bridges to the
3027
+ // nearest non-progress ancestor in one lookup.
3028
+ const parent = entry.parentUuid;
3029
+ progressBridge.set(entry.uuid, parent && progressBridge.has(parent)
3030
+ ? (progressBridge.get(parent) ?? null)
3031
+ : parent);
3032
+ continue;
3033
+ }
3034
+ if (isTranscriptMessage(entry)) {
3035
+ if (entry.parentUuid && progressBridge.has(entry.parentUuid)) {
3036
+ entry.parentUuid = progressBridge.get(entry.parentUuid) ?? null;
3037
+ }
3038
+ messages.set(entry.uuid, entry);
3039
+ // Compact boundary: prior marble-origami-commit entries reference
3040
+ // messages that won't be in the post-boundary chain. The >5MB
3041
+ // backward-scan path discards them naturally by never reading the
3042
+ // pre-boundary bytes; the <5MB path reads everything, so discard
3043
+ // here. Without this, getStats().collapsedSpans in /context
3044
+ // overcounts (projectView silently skips the stale commits but
3045
+ // they're still in the log).
3046
+ if (isCompactBoundaryMessage(entry)) {
3047
+ contextCollapseCommits.length = 0;
3048
+ contextCollapseSnapshot = undefined;
3049
+ }
3050
+ }
3051
+ else if (entry.type === 'summary' && entry.leafUuid) {
3052
+ summaries.set(entry.leafUuid, entry.summary);
3053
+ }
3054
+ else if (entry.type === 'custom-title' && entry.sessionId) {
3055
+ customTitles.set(entry.sessionId, entry.customTitle);
3056
+ }
3057
+ else if (entry.type === 'tag' && entry.sessionId) {
3058
+ tags.set(entry.sessionId, entry.tag);
3059
+ }
3060
+ else if (entry.type === 'agent-name' && entry.sessionId) {
3061
+ agentNames.set(entry.sessionId, entry.agentName);
3062
+ }
3063
+ else if (entry.type === 'agent-color' && entry.sessionId) {
3064
+ agentColors.set(entry.sessionId, entry.agentColor);
3065
+ }
3066
+ else if (entry.type === 'agent-setting' && entry.sessionId) {
3067
+ agentSettings.set(entry.sessionId, entry.agentSetting);
3068
+ }
3069
+ else if (entry.type === 'mode' && entry.sessionId) {
3070
+ modes.set(entry.sessionId, entry.mode);
3071
+ }
3072
+ else if (entry.type === 'worktree-state' && entry.sessionId) {
3073
+ worktreeStates.set(entry.sessionId, entry.worktreeSession);
3074
+ }
3075
+ else if (entry.type === 'pr-link' && entry.sessionId) {
3076
+ prNumbers.set(entry.sessionId, entry.prNumber);
3077
+ prUrls.set(entry.sessionId, entry.prUrl);
3078
+ prRepositories.set(entry.sessionId, entry.prRepository);
3079
+ }
3080
+ else if (entry.type === 'file-history-snapshot') {
3081
+ fileHistorySnapshots.set(entry.messageId, entry);
3082
+ }
3083
+ else if (entry.type === 'attribution-snapshot') {
3084
+ attributionSnapshots.set(entry.messageId, entry);
3085
+ }
3086
+ else if (entry.type === 'content-replacement') {
3087
+ // Subagent decisions key by agentId (sidechain resume); main-thread
3088
+ // decisions key by sessionId (/resume).
3089
+ if (entry.agentId) {
3090
+ const existing = agentContentReplacements.get(entry.agentId) ?? [];
3091
+ agentContentReplacements.set(entry.agentId, existing);
3092
+ existing.push(...entry.replacements);
3093
+ }
3094
+ else {
3095
+ const existing = contentReplacements.get(entry.sessionId) ?? [];
3096
+ contentReplacements.set(entry.sessionId, existing);
3097
+ existing.push(...entry.replacements);
3098
+ }
3099
+ }
3100
+ else if (entry.type === 'marble-origami-commit') {
3101
+ contextCollapseCommits.push(entry);
3102
+ }
3103
+ else if (entry.type === 'marble-origami-snapshot') {
3104
+ contextCollapseSnapshot = entry;
3105
+ }
3106
+ }
3107
+ }
3108
+ catch {
3109
+ // File doesn't exist or can't be read
3110
+ }
3111
+ applyPreservedSegmentRelinks(messages);
3112
+ applySnipRemovals(messages);
3113
+ // Compute leaf UUIDs once at load time
3114
+ // Only user/assistant messages should be considered as leaves for anchoring resume.
3115
+ // Other message types (system, attachment) are metadata or auxiliary and shouldn't
3116
+ // anchor a conversation chain.
3117
+ //
3118
+ // We use standard parent relationship for main chain detection, but also need to
3119
+ // handle cases where the last message is a system/metadata message.
3120
+ // For each conversation chain (identified by following parent links), the leaf
3121
+ // is the most recent user/assistant message.
3122
+ const allMessages = [...messages.values()];
3123
+ // Standard leaf computation using parent relationships
3124
+ const parentUuids = new Set(allMessages
3125
+ .map(msg => msg.parentUuid)
3126
+ .filter((uuid) => uuid !== null));
3127
+ // Find all terminal messages (messages with no children)
3128
+ const terminalMessages = allMessages.filter(msg => !parentUuids.has(msg.uuid));
3129
+ const leafUuids = new Set();
3130
+ let hasCycle = false;
3131
+ if (getFeatureValue_CACHED_MAY_BE_STALE('thaddeus_pebble_leaf_prune', false)) {
3132
+ // Build a set of UUIDs that have user/assistant children
3133
+ // (these are mid-conversation nodes, not dead ends)
3134
+ const hasUserAssistantChild = new Set();
3135
+ for (const msg of allMessages) {
3136
+ if (msg.parentUuid && (msg.type === 'user' || msg.type === 'assistant')) {
3137
+ hasUserAssistantChild.add(msg.parentUuid);
3138
+ }
3139
+ }
3140
+ // For each terminal message, walk back to find the nearest user/assistant ancestor.
3141
+ // Skip ancestors that already have user/assistant children - those are mid-conversation
3142
+ // nodes where the conversation continued (e.g., an assistant tool_use message whose
3143
+ // progress child is terminal, but whose tool_result child continues the conversation).
3144
+ for (const terminal of terminalMessages) {
3145
+ const seen = new Set();
3146
+ let current = terminal;
3147
+ while (current) {
3148
+ if (seen.has(current.uuid)) {
3149
+ hasCycle = true;
3150
+ break;
3151
+ }
3152
+ seen.add(current.uuid);
3153
+ if (current.type === 'user' || current.type === 'assistant') {
3154
+ if (!hasUserAssistantChild.has(current.uuid)) {
3155
+ leafUuids.add(current.uuid);
3156
+ }
3157
+ break;
3158
+ }
3159
+ current = current.parentUuid
3160
+ ? messages.get(current.parentUuid)
3161
+ : undefined;
3162
+ }
3163
+ }
3164
+ }
3165
+ else {
3166
+ // Original leaf computation: walk back from terminal messages to find
3167
+ // the nearest user/assistant ancestor unconditionally
3168
+ for (const terminal of terminalMessages) {
3169
+ const seen = new Set();
3170
+ let current = terminal;
3171
+ while (current) {
3172
+ if (seen.has(current.uuid)) {
3173
+ hasCycle = true;
3174
+ break;
3175
+ }
3176
+ seen.add(current.uuid);
3177
+ if (current.type === 'user' || current.type === 'assistant') {
3178
+ leafUuids.add(current.uuid);
3179
+ break;
3180
+ }
3181
+ current = current.parentUuid
3182
+ ? messages.get(current.parentUuid)
3183
+ : undefined;
3184
+ }
3185
+ }
3186
+ }
3187
+ if (hasCycle) {
3188
+ logEvent('thaddeus_transcript_parent_cycle', {});
3189
+ }
3190
+ return {
3191
+ messages,
3192
+ summaries,
3193
+ customTitles,
3194
+ tags,
3195
+ agentNames,
3196
+ agentColors,
3197
+ agentSettings,
3198
+ prNumbers,
3199
+ prUrls,
3200
+ prRepositories,
3201
+ modes,
3202
+ worktreeStates,
3203
+ fileHistorySnapshots,
3204
+ attributionSnapshots,
3205
+ contentReplacements,
3206
+ agentContentReplacements,
3207
+ contextCollapseCommits,
3208
+ contextCollapseSnapshot,
3209
+ leafUuids,
3210
+ };
3211
+ }
3212
+ /**
3213
+ * Loads all messages, summaries, file history snapshots, and attribution snapshots from a specific session file.
3214
+ */
3215
+ async function loadSessionFile(sessionId) {
3216
+ const sessionFile = join(getSessionProjectDir() ?? getProjectDir(getOriginalCwd()), `${sessionId}.jsonl`);
3217
+ return loadTranscriptFile(sessionFile);
3218
+ }
3219
+ /**
3220
+ * Gets message UUIDs for a specific session without loading all sessions.
3221
+ * Memoized to avoid re-reading the same session file multiple times.
3222
+ */
3223
+ const getSessionMessages = memoize(async (sessionId) => {
3224
+ const { messages } = await loadSessionFile(sessionId);
3225
+ return new Set(messages.keys());
3226
+ }, (sessionId) => sessionId);
3227
+ /**
3228
+ * Clear the memoized session messages cache.
3229
+ * Call after compaction when old message UUIDs are no longer valid.
3230
+ */
3231
+ export function clearSessionMessagesCache() {
3232
+ getSessionMessages.cache.clear?.();
3233
+ }
3234
+ /**
3235
+ * Check if a message UUID exists in the session storage
3236
+ */
3237
+ export async function doesMessageExistInSession(sessionId, messageUuid) {
3238
+ const messageSet = await getSessionMessages(sessionId);
3239
+ return messageSet.has(messageUuid);
3240
+ }
3241
+ export async function getLastSessionLog(sessionId) {
3242
+ // Single read: load all session data at once instead of reading the file twice
3243
+ const { messages, summaries, customTitles, tags, agentSettings, worktreeStates, fileHistorySnapshots, attributionSnapshots, contentReplacements, contextCollapseCommits, contextCollapseSnapshot, } = await loadSessionFile(sessionId);
3244
+ if (messages.size === 0)
3245
+ return null;
3246
+ // Prime getSessionMessages cache so recordTranscript (called after REPL
3247
+ // mount on --resume) skips a second full file load. -170~227ms on large sessions.
3248
+ // Guard: only prime if cache is empty. Mid-session callers (e.g. IssueFeedback)
3249
+ // may call getLastSessionLog on the current session — overwriting a live cache
3250
+ // with a stale disk snapshot would lose unflushed UUIDs and break dedup.
3251
+ if (!getSessionMessages.cache.has(sessionId)) {
3252
+ getSessionMessages.cache.set(sessionId, Promise.resolve(new Set(messages.keys())));
3253
+ }
3254
+ // Find the most recent non-sidechain message
3255
+ const lastMessage = findLatestMessage(messages.values(), m => !m.isSidechain);
3256
+ if (!lastMessage)
3257
+ return null;
3258
+ // Build the transcript chain from the last message
3259
+ const transcript = buildConversationChain(messages, lastMessage);
3260
+ const summary = summaries.get(lastMessage.uuid);
3261
+ const customTitle = customTitles.get(lastMessage.sessionId);
3262
+ const tag = tags.get(lastMessage.sessionId);
3263
+ const agentSetting = agentSettings.get(sessionId);
3264
+ return {
3265
+ ...convertToLogOption(transcript, 0, summary, customTitle, buildFileHistorySnapshotChain(fileHistorySnapshots, transcript), tag, getTranscriptPathForSession(sessionId), buildAttributionSnapshotChain(attributionSnapshots, transcript), agentSetting, contentReplacements.get(sessionId) ?? []),
3266
+ worktreeSession: worktreeStates.get(sessionId),
3267
+ contextCollapseCommits: contextCollapseCommits.filter(e => e.sessionId === sessionId),
3268
+ contextCollapseSnapshot: contextCollapseSnapshot?.sessionId === sessionId
3269
+ ? contextCollapseSnapshot
3270
+ : undefined,
3271
+ };
3272
+ }
3273
+ /**
3274
+ * Loads the list of message logs
3275
+ * @param limit Optional limit on number of session files to load
3276
+ * @returns List of message logs sorted by date
3277
+ */
3278
+ export async function loadMessageLogs(limit) {
3279
+ const sessionLogs = await fetchLogs(limit);
3280
+ // fetchLogs returns lite (stat-only) logs — enrich them to get metadata.
3281
+ // enrichLogs already filters out sidechains, empty sessions, etc.
3282
+ const { logs: enriched } = await enrichLogs(sessionLogs, 0, sessionLogs.length);
3283
+ // enrichLogs returns fresh unshared objects — mutate in place to avoid
3284
+ // re-spreading every 30-field LogOption just to renumber the index.
3285
+ const sorted = sortLogs(enriched);
3286
+ sorted.forEach((log, i) => {
3287
+ log.value = i;
3288
+ });
3289
+ return sorted;
3290
+ }
3291
+ /**
3292
+ * Loads message logs from all project directories.
3293
+ * @param limit Optional limit on number of session files to load per project (used when no index exists)
3294
+ * @returns List of message logs sorted by date
3295
+ */
3296
+ export async function loadAllProjectsMessageLogs(limit, options) {
3297
+ if (options?.skipIndex) {
3298
+ // Load all sessions with full message data (e.g. for /insights analysis)
3299
+ return loadAllProjectsMessageLogsFull(limit);
3300
+ }
3301
+ const result = await loadAllProjectsMessageLogsProgressive(limit, options?.initialEnrichCount ?? INITIAL_ENRICH_COUNT);
3302
+ return result.logs;
3303
+ }
3304
+ async function loadAllProjectsMessageLogsFull(limit) {
3305
+ const projectsDir = getProjectsDir();
3306
+ let dirents;
3307
+ try {
3308
+ dirents = await readdir(projectsDir, { withFileTypes: true });
3309
+ }
3310
+ catch {
3311
+ return [];
3312
+ }
3313
+ const projectDirs = dirents
3314
+ .filter(dirent => dirent.isDirectory())
3315
+ .map(dirent => join(projectsDir, dirent.name));
3316
+ const logsPerProject = await Promise.all(projectDirs.map(projectDir => getLogsWithoutIndex(projectDir, limit)));
3317
+ const allLogs = logsPerProject.flat();
3318
+ // Deduplicate — same session+leaf can appear in multiple project dirs.
3319
+ // This path creates one LogOption per leaf, so use sessionId+leafUuid key.
3320
+ const deduped = new Map();
3321
+ for (const log of allLogs) {
3322
+ const key = `${log.sessionId ?? ''}:${log.leafUuid ?? ''}`;
3323
+ const existing = deduped.get(key);
3324
+ if (!existing || log.modified.getTime() > existing.modified.getTime()) {
3325
+ deduped.set(key, log);
3326
+ }
3327
+ }
3328
+ // deduped values are fresh from getLogsWithoutIndex — safe to mutate
3329
+ const sorted = sortLogs([...deduped.values()]);
3330
+ sorted.forEach((log, i) => {
3331
+ log.value = i;
3332
+ });
3333
+ return sorted;
3334
+ }
3335
+ export async function loadAllProjectsMessageLogsProgressive(limit, initialEnrichCount = INITIAL_ENRICH_COUNT) {
3336
+ const projectsDir = getProjectsDir();
3337
+ let dirents;
3338
+ try {
3339
+ dirents = await readdir(projectsDir, { withFileTypes: true });
3340
+ }
3341
+ catch {
3342
+ return { logs: [], allStatLogs: [], nextIndex: 0 };
3343
+ }
3344
+ const projectDirs = dirents
3345
+ .filter(dirent => dirent.isDirectory())
3346
+ .map(dirent => join(projectsDir, dirent.name));
3347
+ const rawLogs = [];
3348
+ for (const projectDir of projectDirs) {
3349
+ rawLogs.push(...(await getSessionFilesLite(projectDir, limit)));
3350
+ }
3351
+ // Deduplicate — same session can appear in multiple project dirs
3352
+ const sorted = deduplicateLogsBySessionId(rawLogs);
3353
+ const { logs, nextIndex } = await enrichLogs(sorted, 0, initialEnrichCount);
3354
+ // enrichLogs returns fresh unshared objects — safe to mutate in place
3355
+ logs.forEach((log, i) => {
3356
+ log.value = i;
3357
+ });
3358
+ return { logs, allStatLogs: sorted, nextIndex };
3359
+ }
3360
+ export async function loadSameRepoMessageLogs(worktreePaths, limit, initialEnrichCount = INITIAL_ENRICH_COUNT) {
3361
+ const result = await loadSameRepoMessageLogsProgressive(worktreePaths, limit, initialEnrichCount);
3362
+ return result.logs;
3363
+ }
3364
+ export async function loadSameRepoMessageLogsProgressive(worktreePaths, limit, initialEnrichCount = INITIAL_ENRICH_COUNT) {
3365
+ logForDebugging(`/resume: loading sessions for cwd=${getOriginalCwd()}, worktrees=[${worktreePaths.join(', ')}]`);
3366
+ const allStatLogs = await getStatOnlyLogsForWorktrees(worktreePaths, limit);
3367
+ logForDebugging(`/resume: found ${allStatLogs.length} session files on disk`);
3368
+ const { logs, nextIndex } = await enrichLogs(allStatLogs, 0, initialEnrichCount);
3369
+ // enrichLogs returns fresh unshared objects — safe to mutate in place
3370
+ logs.forEach((log, i) => {
3371
+ log.value = i;
3372
+ });
3373
+ return { logs, allStatLogs, nextIndex };
3374
+ }
3375
+ /**
3376
+ * Gets stat-only logs for worktree paths (no file reads).
3377
+ */
3378
+ async function getStatOnlyLogsForWorktrees(worktreePaths, limit) {
3379
+ const projectsDir = getProjectsDir();
3380
+ if (worktreePaths.length <= 1) {
3381
+ const cwd = getOriginalCwd();
3382
+ const projectDir = getProjectDir(cwd);
3383
+ return getSessionFilesLite(projectDir, undefined, cwd);
3384
+ }
3385
+ // On Windows, drive letter case can differ between git worktree list
3386
+ // output (e.g. C:/Users/...) and how paths were stored in project
3387
+ // directories (e.g. c:/Users/...). Use case-insensitive comparison.
3388
+ const caseInsensitive = process.platform === 'win32';
3389
+ // Sort worktree paths by sanitized prefix length (longest first) so
3390
+ // more specific matches take priority over shorter ones. Without this,
3391
+ // a short prefix like -code-myrepo could match -code-myrepo-worktree1
3392
+ // before the longer, more specific prefix gets a chance.
3393
+ const indexed = worktreePaths.map(wt => {
3394
+ const sanitized = sanitizePath(wt);
3395
+ return {
3396
+ path: wt,
3397
+ prefix: caseInsensitive ? sanitized.toLowerCase() : sanitized,
3398
+ };
3399
+ });
3400
+ indexed.sort((a, b) => b.prefix.length - a.prefix.length);
3401
+ const allLogs = [];
3402
+ const seenDirs = new Set();
3403
+ let allDirents;
3404
+ try {
3405
+ allDirents = await readdir(projectsDir, { withFileTypes: true });
3406
+ }
3407
+ catch (e) {
3408
+ // Fall back to current project
3409
+ logForDebugging(`Failed to read projects dir ${projectsDir}, falling back to current project: ${e}`);
3410
+ const projectDir = getProjectDir(getOriginalCwd());
3411
+ return getSessionFilesLite(projectDir, limit, getOriginalCwd());
3412
+ }
3413
+ for (const dirent of allDirents) {
3414
+ if (!dirent.isDirectory())
3415
+ continue;
3416
+ const dirName = caseInsensitive ? dirent.name.toLowerCase() : dirent.name;
3417
+ if (seenDirs.has(dirName))
3418
+ continue;
3419
+ for (const { path: wtPath, prefix } of indexed) {
3420
+ if (dirName === prefix || dirName.startsWith(prefix + '-')) {
3421
+ seenDirs.add(dirName);
3422
+ allLogs.push(...(await getSessionFilesLite(join(projectsDir, dirent.name), undefined, wtPath)));
3423
+ break;
3424
+ }
3425
+ }
3426
+ }
3427
+ // Deduplicate by sessionId — the same session can appear in multiple
3428
+ // worktree project dirs. Keep the entry with the newest modified time.
3429
+ return deduplicateLogsBySessionId(allLogs);
3430
+ }
3431
+ /**
3432
+ * Retrieves the transcript for a specific agent by agentId.
3433
+ * Directly loads the agent-specific transcript file.
3434
+ * @param agentId The agent ID to search for
3435
+ * @returns The conversation chain and budget replacement records for the agent,
3436
+ * or null if not found
3437
+ */
3438
+ export async function getAgentTranscript(agentId) {
3439
+ const agentFile = getAgentTranscriptPath(agentId);
3440
+ try {
3441
+ const { messages, agentContentReplacements } = await loadTranscriptFile(agentFile);
3442
+ // Find messages with matching agentId
3443
+ const agentMessages = Array.from(messages.values()).filter(msg => msg.agentId === agentId && msg.isSidechain);
3444
+ if (agentMessages.length === 0) {
3445
+ return null;
3446
+ }
3447
+ // Find the most recent leaf message with this agentId
3448
+ const parentUuids = new Set(agentMessages.map(msg => msg.parentUuid));
3449
+ const leafMessage = findLatestMessage(agentMessages, msg => !parentUuids.has(msg.uuid));
3450
+ if (!leafMessage) {
3451
+ return null;
3452
+ }
3453
+ // Build the conversation chain
3454
+ const transcript = buildConversationChain(messages, leafMessage);
3455
+ // Filter to only include messages with this agentId
3456
+ const agentTranscript = transcript.filter(msg => msg.agentId === agentId);
3457
+ return {
3458
+ // Convert TranscriptMessage[] to Message[]
3459
+ messages: agentTranscript.map(({ isSidechain, parentUuid, ...msg }) => msg),
3460
+ contentReplacements: agentContentReplacements.get(agentId) ?? [],
3461
+ };
3462
+ }
3463
+ catch {
3464
+ return null;
3465
+ }
3466
+ }
3467
+ /**
3468
+ * Extract agent IDs from progress messages in the conversation.
3469
+ * Agent/skill progress messages have type 'progress' with data.type
3470
+ * 'agent_progress' or 'skill_progress' and data.agentId.
3471
+ * This captures sync agents that emit progress messages during execution.
3472
+ */
3473
+ export function extractAgentIdsFromMessages(messages) {
3474
+ const agentIds = [];
3475
+ for (const message of messages) {
3476
+ if (message.type === 'progress' &&
3477
+ message.data &&
3478
+ typeof message.data === 'object' &&
3479
+ 'type' in message.data &&
3480
+ (message.data.type === 'agent_progress' ||
3481
+ message.data.type === 'skill_progress') &&
3482
+ 'agentId' in message.data &&
3483
+ typeof message.data.agentId === 'string') {
3484
+ agentIds.push(message.data.agentId);
3485
+ }
3486
+ }
3487
+ return uniq(agentIds);
3488
+ }
3489
+ /**
3490
+ * Extract teammate transcripts directly from AppState tasks.
3491
+ * In-process teammates store their messages in task.messages,
3492
+ * which is more reliable than loading from disk since each teammate turn
3493
+ * uses a random agentId for transcript storage.
3494
+ */
3495
+ export function extractTeammateTranscriptsFromTasks(tasks) {
3496
+ const transcripts = {};
3497
+ for (const task of Object.values(tasks)) {
3498
+ if (task.type === 'in_process_teammate' &&
3499
+ task.identity?.agentId &&
3500
+ task.messages &&
3501
+ task.messages.length > 0) {
3502
+ transcripts[task.identity.agentId] = task.messages;
3503
+ }
3504
+ }
3505
+ return transcripts;
3506
+ }
3507
+ /**
3508
+ * Load subagent transcripts for the given agent IDs
3509
+ */
3510
+ export async function loadSubagentTranscripts(agentIds) {
3511
+ const results = await Promise.all(agentIds.map(async (agentId) => {
3512
+ try {
3513
+ const result = await getAgentTranscript(asAgentId(agentId));
3514
+ if (result && result.messages.length > 0) {
3515
+ return { agentId, transcript: result.messages };
3516
+ }
3517
+ return null;
3518
+ }
3519
+ catch {
3520
+ // Skip if transcript can't be loaded
3521
+ return null;
3522
+ }
3523
+ }));
3524
+ const transcripts = {};
3525
+ for (const result of results) {
3526
+ if (result) {
3527
+ transcripts[result.agentId] = result.transcript;
3528
+ }
3529
+ }
3530
+ return transcripts;
3531
+ }
3532
+ // Globs the session's subagents dir directly — unlike AppState.tasks, this survives task eviction.
3533
+ export async function loadAllSubagentTranscriptsFromDisk() {
3534
+ const subagentsDir = join(getSessionProjectDir() ?? getProjectDir(getOriginalCwd()), getSessionId(), 'subagents');
3535
+ let entries;
3536
+ try {
3537
+ entries = await readdir(subagentsDir, { withFileTypes: true });
3538
+ }
3539
+ catch {
3540
+ return {};
3541
+ }
3542
+ // Filename format is the inverse of getAgentTranscriptPath() — keep in sync.
3543
+ const agentIds = entries
3544
+ .filter(d => d.isFile() && d.name.startsWith('agent-') && d.name.endsWith('.jsonl'))
3545
+ .map(d => d.name.slice('agent-'.length, -'.jsonl'.length));
3546
+ return loadSubagentTranscripts(agentIds);
3547
+ }
3548
+ // Exported so useLogMessages can sync-compute the last loggable uuid
3549
+ // without awaiting recordTranscript's return value (race-free hint tracking).
3550
+ export function isLoggableMessage(m) {
3551
+ if (m.type === 'progress')
3552
+ return false;
3553
+ // IMPORTANT: We deliberately filter out most attachments for non-ants because
3554
+ // they have sensitive info for training that we don't want exposed to the public.
3555
+ // When enabled, we allow hook_additional_context through since it contains
3556
+ // user-configured hook output that is useful for session context on resume.
3557
+ if (m.type === 'attachment' && getUserType() !== 'ant') {
3558
+ if (m.attachment.type === 'hook_additional_context' &&
3559
+ isEnvTruthy(process.env.THADDEUS_SAVE_HOOK_ADDITIONAL_CONTEXT)) {
3560
+ return true;
3561
+ }
3562
+ return false;
3563
+ }
3564
+ return true;
3565
+ }
3566
+ function collectReplIds(messages) {
3567
+ const ids = new Set();
3568
+ for (const m of messages) {
3569
+ if (m.type === 'assistant' && Array.isArray(m.message.content)) {
3570
+ for (const b of m.message.content) {
3571
+ if (b.type === 'tool_use' && b.name === REPL_TOOL_NAME) {
3572
+ ids.add(b.id);
3573
+ }
3574
+ }
3575
+ }
3576
+ }
3577
+ return ids;
3578
+ }
3579
+ /**
3580
+ * For external users, make REPL invisible in the persisted transcript: strip
3581
+ * REPL tool_use/tool_result pairs and promote isVirtual messages to real. On
3582
+ * --resume the model then sees a coherent native-tool-call history (assistant
3583
+ * called Bash, got result, called Read, got result) without the REPL wrapper.
3584
+ * Ant transcripts keep the wrapper so /share training data sees REPL usage.
3585
+ *
3586
+ * replIds is pre-collected from the FULL session array, not the slice being
3587
+ * transformed — recordTranscript receives incremental slices where the REPL
3588
+ * tool_use (earlier render) and its tool_result (later render, after async
3589
+ * execution) land in separate calls. A fresh per-call Set would miss the id
3590
+ * and leave an orphaned tool_result on disk.
3591
+ */
3592
+ function transformMessagesForExternalTranscript(messages, replIds) {
3593
+ return messages.flatMap(m => {
3594
+ if (m.type === 'assistant' && Array.isArray(m.message.content)) {
3595
+ const content = m.message.content;
3596
+ const hasRepl = content.some(b => b.type === 'tool_use' && b.name === REPL_TOOL_NAME);
3597
+ const filtered = hasRepl
3598
+ ? content.filter(b => !(b.type === 'tool_use' && b.name === REPL_TOOL_NAME))
3599
+ : content;
3600
+ if (filtered.length === 0)
3601
+ return [];
3602
+ if (m.isVirtual) {
3603
+ const { isVirtual: _omit, ...rest } = m;
3604
+ return [{ ...rest, message: { ...m.message, content: filtered } }];
3605
+ }
3606
+ if (filtered !== content) {
3607
+ return [{ ...m, message: { ...m.message, content: filtered } }];
3608
+ }
3609
+ return [m];
3610
+ }
3611
+ if (m.type === 'user' && Array.isArray(m.message.content)) {
3612
+ const content = m.message.content;
3613
+ const hasRepl = content.some(b => b.type === 'tool_result' && replIds.has(b.tool_use_id));
3614
+ const filtered = hasRepl
3615
+ ? content.filter(b => !(b.type === 'tool_result' && replIds.has(b.tool_use_id)))
3616
+ : content;
3617
+ if (filtered.length === 0)
3618
+ return [];
3619
+ if (m.isVirtual) {
3620
+ const { isVirtual: _omit, ...rest } = m;
3621
+ return [{ ...rest, message: { ...m.message, content: filtered } }];
3622
+ }
3623
+ if (filtered !== content) {
3624
+ return [{ ...m, message: { ...m.message, content: filtered } }];
3625
+ }
3626
+ return [m];
3627
+ }
3628
+ // string-content user, system, attachment
3629
+ if ('isVirtual' in m && m.isVirtual) {
3630
+ const { isVirtual: _omit, ...rest } = m;
3631
+ return [rest];
3632
+ }
3633
+ return [m];
3634
+ });
3635
+ }
3636
+ export function cleanMessagesForLogging(messages, allMessages = messages) {
3637
+ const filtered = messages.filter(isLoggableMessage);
3638
+ return getUserType() !== 'ant'
3639
+ ? transformMessagesForExternalTranscript(filtered, collectReplIds(allMessages))
3640
+ : filtered;
3641
+ }
3642
+ /**
3643
+ * Gets a log by its index
3644
+ * @param index Index in the sorted list of logs (0-based)
3645
+ * @returns Log data or null if not found
3646
+ */
3647
+ export async function getLogByIndex(index) {
3648
+ const logs = await loadMessageLogs();
3649
+ return logs[index] || null;
3650
+ }
3651
+ /**
3652
+ * Looks up unresolved tool uses in the transcript by tool_use_id.
3653
+ * Returns the assistant message containing the tool_use, or null if not found
3654
+ * or the tool call already has a tool_result.
3655
+ */
3656
+ export async function findUnresolvedToolUse(toolUseId) {
3657
+ try {
3658
+ const transcriptPath = getTranscriptPath();
3659
+ const { messages } = await loadTranscriptFile(transcriptPath);
3660
+ let toolUseMessage = null;
3661
+ // Find the tool use but make sure there's not also a result
3662
+ for (const message of messages.values()) {
3663
+ if (message.type === 'assistant') {
3664
+ const content = message.message.content;
3665
+ if (Array.isArray(content)) {
3666
+ for (const block of content) {
3667
+ if (block.type === 'tool_use' && block.id === toolUseId) {
3668
+ toolUseMessage = message;
3669
+ break;
3670
+ }
3671
+ }
3672
+ }
3673
+ }
3674
+ else if (message.type === 'user') {
3675
+ const content = message.message.content;
3676
+ if (Array.isArray(content)) {
3677
+ for (const block of content) {
3678
+ if (block.type === 'tool_result' &&
3679
+ block.tool_use_id === toolUseId) {
3680
+ // Found tool result, bail out
3681
+ return null;
3682
+ }
3683
+ }
3684
+ }
3685
+ }
3686
+ }
3687
+ return toolUseMessage;
3688
+ }
3689
+ catch {
3690
+ return null;
3691
+ }
3692
+ }
3693
+ /**
3694
+ * Gets all session JSONL files in a project directory with their stats.
3695
+ * Returns a map of sessionId → {path, mtime, ctime, size}.
3696
+ * Stats are batched via Promise.all to avoid serial syscalls in the hot loop.
3697
+ */
3698
+ export async function getSessionFilesWithMtime(projectDir) {
3699
+ const sessionFilesMap = new Map();
3700
+ let dirents;
3701
+ try {
3702
+ dirents = await readdir(projectDir, { withFileTypes: true });
3703
+ }
3704
+ catch {
3705
+ // Directory doesn't exist - return empty map
3706
+ return sessionFilesMap;
3707
+ }
3708
+ const candidates = [];
3709
+ for (const dirent of dirents) {
3710
+ if (!dirent.isFile() || !dirent.name.endsWith('.jsonl'))
3711
+ continue;
3712
+ const sessionId = validateUuid(basename(dirent.name, '.jsonl'));
3713
+ if (!sessionId)
3714
+ continue;
3715
+ candidates.push({ sessionId, filePath: join(projectDir, dirent.name) });
3716
+ }
3717
+ await Promise.all(candidates.map(async ({ sessionId, filePath }) => {
3718
+ try {
3719
+ const st = await stat(filePath);
3720
+ sessionFilesMap.set(sessionId, {
3721
+ path: filePath,
3722
+ mtime: st.mtime.getTime(),
3723
+ ctime: st.birthtime.getTime(),
3724
+ size: st.size,
3725
+ });
3726
+ }
3727
+ catch {
3728
+ logForDebugging(`Failed to stat session file: ${filePath}`);
3729
+ }
3730
+ }));
3731
+ return sessionFilesMap;
3732
+ }
3733
+ /**
3734
+ * Number of sessions to enrich on the initial load of the resume picker.
3735
+ * Each enrichment reads up to 128 KB per file (head + tail), so 50 sessions
3736
+ * means ~6.4 MB of I/O — fast on any modern filesystem while giving users
3737
+ * a much better initial view than the previous default of 10.
3738
+ */
3739
+ const INITIAL_ENRICH_COUNT = 50;
3740
+ /**
3741
+ * Loads all logs from a single session file with full message data.
3742
+ * Builds a LogOption for each leaf message in the file.
3743
+ */
3744
+ export async function loadAllLogsFromSessionFile(sessionFile, projectPathOverride) {
3745
+ const { messages, summaries, customTitles, tags, agentNames, agentColors, agentSettings, prNumbers, prUrls, prRepositories, modes, fileHistorySnapshots, attributionSnapshots, contentReplacements, leafUuids, } = await loadTranscriptFile(sessionFile, { keepAllLeaves: true });
3746
+ if (messages.size === 0)
3747
+ return [];
3748
+ const leafMessages = [];
3749
+ // Build parentUuid → children index once (O(n)), so trailing-message lookup is O(1) per leaf
3750
+ const childrenByParent = new Map();
3751
+ for (const msg of messages.values()) {
3752
+ if (leafUuids.has(msg.uuid)) {
3753
+ leafMessages.push(msg);
3754
+ }
3755
+ else if (msg.parentUuid) {
3756
+ const siblings = childrenByParent.get(msg.parentUuid);
3757
+ if (siblings) {
3758
+ siblings.push(msg);
3759
+ }
3760
+ else {
3761
+ childrenByParent.set(msg.parentUuid, [msg]);
3762
+ }
3763
+ }
3764
+ }
3765
+ const logs = [];
3766
+ for (const leafMessage of leafMessages) {
3767
+ const chain = buildConversationChain(messages, leafMessage);
3768
+ if (chain.length === 0)
3769
+ continue;
3770
+ // Append trailing messages that are children of the leaf
3771
+ const trailingMessages = childrenByParent.get(leafMessage.uuid);
3772
+ if (trailingMessages) {
3773
+ // ISO-8601 UTC timestamps are lexically sortable
3774
+ trailingMessages.sort((a, b) => a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0);
3775
+ chain.push(...trailingMessages);
3776
+ }
3777
+ const firstMessage = chain[0];
3778
+ const sessionId = leafMessage.sessionId;
3779
+ logs.push({
3780
+ date: leafMessage.timestamp,
3781
+ messages: removeExtraFields(chain),
3782
+ fullPath: sessionFile,
3783
+ value: 0,
3784
+ created: new Date(firstMessage.timestamp),
3785
+ modified: new Date(leafMessage.timestamp),
3786
+ firstPrompt: extractFirstPrompt(chain),
3787
+ messageCount: countVisibleMessages(chain),
3788
+ isSidechain: firstMessage.isSidechain ?? false,
3789
+ sessionId,
3790
+ leafUuid: leafMessage.uuid,
3791
+ summary: summaries.get(leafMessage.uuid),
3792
+ customTitle: customTitles.get(sessionId),
3793
+ tag: tags.get(sessionId),
3794
+ agentName: agentNames.get(sessionId),
3795
+ agentColor: agentColors.get(sessionId),
3796
+ agentSetting: agentSettings.get(sessionId),
3797
+ mode: modes.get(sessionId),
3798
+ prNumber: prNumbers.get(sessionId),
3799
+ prUrl: prUrls.get(sessionId),
3800
+ prRepository: prRepositories.get(sessionId),
3801
+ gitBranch: leafMessage.gitBranch,
3802
+ projectPath: projectPathOverride ?? firstMessage.cwd,
3803
+ fileHistorySnapshots: buildFileHistorySnapshotChain(fileHistorySnapshots, chain),
3804
+ attributionSnapshots: buildAttributionSnapshotChain(attributionSnapshots, chain),
3805
+ contentReplacements: contentReplacements.get(sessionId) ?? [],
3806
+ });
3807
+ }
3808
+ return logs;
3809
+ }
3810
+ /**
3811
+ * Gets logs by loading all session files fully, bypassing the session index.
3812
+ * Use this when you need full message data (e.g., for /insights analysis).
3813
+
3814
+ */
3815
+ async function getLogsWithoutIndex(projectDir, limit) {
3816
+ const sessionFilesMap = await getSessionFilesWithMtime(projectDir);
3817
+ if (sessionFilesMap.size === 0)
3818
+ return [];
3819
+ // If limit specified, only load N most recent files by mtime
3820
+ let filesToProcess;
3821
+ if (limit && sessionFilesMap.size > limit) {
3822
+ filesToProcess = [...sessionFilesMap.values()]
3823
+ .sort((a, b) => b.mtime - a.mtime)
3824
+ .slice(0, limit);
3825
+ }
3826
+ else {
3827
+ filesToProcess = [...sessionFilesMap.values()];
3828
+ }
3829
+ const logs = [];
3830
+ for (const fileInfo of filesToProcess) {
3831
+ try {
3832
+ const fileLogOptions = await loadAllLogsFromSessionFile(fileInfo.path);
3833
+ logs.push(...fileLogOptions);
3834
+ }
3835
+ catch {
3836
+ logForDebugging(`Failed to load session file: ${fileInfo.path}`);
3837
+ }
3838
+ }
3839
+ return logs;
3840
+ }
3841
+ /**
3842
+ * Reads the first and last ~64KB of a JSONL file and extracts lite metadata.
3843
+ *
3844
+ * Head (first 64KB): isSidechain, projectPath, teamName, firstPrompt.
3845
+ * Tail (last 64KB): customTitle, tag, PR link, latest gitBranch.
3846
+ *
3847
+ * Accepts a shared buffer to avoid per-file allocation overhead.
3848
+ */
3849
+ async function readLiteMetadata(filePath, fileSize, buf) {
3850
+ const { head, tail } = await readHeadAndTail(filePath, fileSize, buf);
3851
+ if (!head)
3852
+ return { firstPrompt: '', isSidechain: false };
3853
+ // Extract stable metadata from the first line via string search.
3854
+ // Works even when the first line is truncated (>64KB message).
3855
+ const isSidechain = head.includes('"isSidechain":true') || head.includes('"isSidechain": true');
3856
+ const projectPath = extractJsonStringField(head, 'cwd');
3857
+ const teamName = extractJsonStringField(head, 'teamName');
3858
+ const agentSetting = extractJsonStringField(head, 'agentSetting');
3859
+ // Prefer the last-prompt tail entry — captured by extractFirstPrompt at
3860
+ // write time (filtered, authoritative) and shows what the user was most
3861
+ // recently doing. Head scan is the fallback for sessions written before
3862
+ // last-prompt entries existed. Raw string scrapes of head are last resort
3863
+ // and catch array-format content blocks (VS Code <ide_selection> metadata).
3864
+ const firstPrompt = extractLastJsonStringField(tail, 'lastPrompt') ||
3865
+ extractFirstPromptFromChunk(head) ||
3866
+ extractJsonStringFieldPrefix(head, 'content', 200) ||
3867
+ extractJsonStringFieldPrefix(head, 'text', 200) ||
3868
+ '';
3869
+ // Extract tail metadata via string search (last occurrence wins).
3870
+ // User titles (customTitle field, from custom-title entries) win over
3871
+ // AI titles (aiTitle field, from ai-title entries). The distinct field
3872
+ // names mean extractLastJsonStringField naturally disambiguates.
3873
+ const customTitle = extractLastJsonStringField(tail, 'customTitle') ??
3874
+ extractLastJsonStringField(head, 'customTitle') ??
3875
+ extractLastJsonStringField(tail, 'aiTitle') ??
3876
+ extractLastJsonStringField(head, 'aiTitle');
3877
+ const summary = extractLastJsonStringField(tail, 'summary');
3878
+ const tag = extractLastJsonStringField(tail, 'tag');
3879
+ const gitBranch = extractLastJsonStringField(tail, 'gitBranch') ??
3880
+ extractJsonStringField(head, 'gitBranch');
3881
+ // PR link fields — prNumber is a number not a string, so try both
3882
+ const prUrl = extractLastJsonStringField(tail, 'prUrl');
3883
+ const prRepository = extractLastJsonStringField(tail, 'prRepository');
3884
+ let prNumber;
3885
+ const prNumStr = extractLastJsonStringField(tail, 'prNumber');
3886
+ if (prNumStr) {
3887
+ prNumber = parseInt(prNumStr, 10) || undefined;
3888
+ }
3889
+ if (!prNumber) {
3890
+ const prNumMatch = tail.lastIndexOf('"prNumber":');
3891
+ if (prNumMatch >= 0) {
3892
+ const afterColon = tail.slice(prNumMatch + 11, prNumMatch + 25);
3893
+ const num = parseInt(afterColon.trim(), 10);
3894
+ if (num > 0)
3895
+ prNumber = num;
3896
+ }
3897
+ }
3898
+ return {
3899
+ firstPrompt,
3900
+ gitBranch,
3901
+ isSidechain,
3902
+ projectPath,
3903
+ teamName,
3904
+ customTitle,
3905
+ summary,
3906
+ tag,
3907
+ agentSetting,
3908
+ prNumber,
3909
+ prUrl,
3910
+ prRepository,
3911
+ };
3912
+ }
3913
+ /**
3914
+ * Scans a chunk of text for the first meaningful user prompt.
3915
+ */
3916
+ function extractFirstPromptFromChunk(chunk) {
3917
+ let start = 0;
3918
+ let hasTickMessages = false;
3919
+ let firstCommandFallback = '';
3920
+ while (start < chunk.length) {
3921
+ const newlineIdx = chunk.indexOf('\n', start);
3922
+ const line = newlineIdx >= 0 ? chunk.slice(start, newlineIdx) : chunk.slice(start);
3923
+ start = newlineIdx >= 0 ? newlineIdx + 1 : chunk.length;
3924
+ if (!line.includes('"type":"user"') && !line.includes('"type": "user"')) {
3925
+ continue;
3926
+ }
3927
+ if (line.includes('"tool_result"'))
3928
+ continue;
3929
+ if (line.includes('"isMeta":true') || line.includes('"isMeta": true'))
3930
+ continue;
3931
+ try {
3932
+ const entry = jsonParse(line);
3933
+ if (entry.type !== 'user')
3934
+ continue;
3935
+ const message = entry.message;
3936
+ if (!message)
3937
+ continue;
3938
+ const content = message.content;
3939
+ // Collect all text values from the message content. For array content
3940
+ // (common in VS Code where IDE metadata tags come before the user's
3941
+ // actual prompt), iterate all text blocks so we don't miss the real
3942
+ // prompt hidden behind <ide_selection>/<ide_opened_file> blocks.
3943
+ const texts = [];
3944
+ if (typeof content === 'string') {
3945
+ texts.push(content);
3946
+ }
3947
+ else if (Array.isArray(content)) {
3948
+ for (const block of content) {
3949
+ const b = block;
3950
+ if (b.type === 'text' && typeof b.text === 'string') {
3951
+ texts.push(b.text);
3952
+ }
3953
+ }
3954
+ }
3955
+ for (const text of texts) {
3956
+ if (!text)
3957
+ continue;
3958
+ let result = text.replace(/\n/g, ' ').trim();
3959
+ // Skip command messages (slash commands) but remember the first one
3960
+ // as a fallback title. Matches skip logic in
3961
+ // getFirstMeaningfulUserMessageTextContent, but instead of discarding
3962
+ // command messages entirely, we format them cleanly (e.g. "/clear")
3963
+ // so the session still appears in the resume picker.
3964
+ const commandNameTag = extractTag(result, COMMAND_NAME_TAG);
3965
+ if (commandNameTag) {
3966
+ const name = commandNameTag.replace(/^\//, '');
3967
+ const commandArgs = extractTag(result, 'command-args')?.trim() || '';
3968
+ if (builtInCommandNames().has(name) || !commandArgs) {
3969
+ if (!firstCommandFallback) {
3970
+ firstCommandFallback = commandNameTag;
3971
+ }
3972
+ continue;
3973
+ }
3974
+ // Custom command with meaningful args — use clean display
3975
+ return commandArgs
3976
+ ? `${commandNameTag} ${commandArgs}`
3977
+ : commandNameTag;
3978
+ }
3979
+ // Format bash input with ! prefix before the generic XML skip
3980
+ const bashInput = extractTag(result, 'bash-input');
3981
+ if (bashInput)
3982
+ return `! ${bashInput}`;
3983
+ if (SKIP_FIRST_PROMPT_PATTERN.test(result)) {
3984
+ if ((feature('PROACTIVE') || feature('KAIROS')) &&
3985
+ result.startsWith(`<${TICK_TAG}>`))
3986
+ hasTickMessages = true;
3987
+ continue;
3988
+ }
3989
+ if (result.length > 200) {
3990
+ result = result.slice(0, 200).trim() + '…';
3991
+ }
3992
+ return result;
3993
+ }
3994
+ }
3995
+ catch {
3996
+ continue;
3997
+ }
3998
+ }
3999
+ // Session started with a slash command but had no subsequent real message —
4000
+ // use the clean command name so the session still appears in the resume picker
4001
+ if (firstCommandFallback)
4002
+ return firstCommandFallback;
4003
+ // Proactive sessions have only tick messages — give them a synthetic prompt
4004
+ // so they're not filtered out by enrichLogs
4005
+ if ((feature('PROACTIVE') || feature('KAIROS')) && hasTickMessages)
4006
+ return 'Proactive session';
4007
+ return '';
4008
+ }
4009
+ /**
4010
+ * Like extractJsonStringField but returns the first `maxLen` characters of the
4011
+ * value even when the closing quote is missing (truncated buffer). Newline
4012
+ * escapes are replaced with spaces and the result is trimmed.
4013
+ */
4014
+ function extractJsonStringFieldPrefix(text, key, maxLen) {
4015
+ const patterns = [`"${key}":"`, `"${key}": "`];
4016
+ for (const pattern of patterns) {
4017
+ const idx = text.indexOf(pattern);
4018
+ if (idx < 0)
4019
+ continue;
4020
+ const valueStart = idx + pattern.length;
4021
+ // Grab up to maxLen characters from the value, stopping at closing quote
4022
+ let i = valueStart;
4023
+ let collected = 0;
4024
+ while (i < text.length && collected < maxLen) {
4025
+ if (text[i] === '\\') {
4026
+ i += 2; // skip escaped char
4027
+ collected++;
4028
+ continue;
4029
+ }
4030
+ if (text[i] === '"')
4031
+ break;
4032
+ i++;
4033
+ collected++;
4034
+ }
4035
+ const raw = text.slice(valueStart, i);
4036
+ return raw.replace(/\\n/g, ' ').replace(/\\t/g, ' ').trim();
4037
+ }
4038
+ return '';
4039
+ }
4040
+ /**
4041
+ * Deduplicates logs by sessionId, keeping the entry with the newest
4042
+ * modified time. Returns sorted logs with sequential value indices.
4043
+ */
4044
+ function deduplicateLogsBySessionId(logs) {
4045
+ const deduped = new Map();
4046
+ for (const log of logs) {
4047
+ if (!log.sessionId)
4048
+ continue;
4049
+ const existing = deduped.get(log.sessionId);
4050
+ if (!existing || log.modified.getTime() > existing.modified.getTime()) {
4051
+ deduped.set(log.sessionId, log);
4052
+ }
4053
+ }
4054
+ return sortLogs([...deduped.values()]).map((log, i) => ({
4055
+ ...log,
4056
+ value: i,
4057
+ }));
4058
+ }
4059
+ /**
4060
+ * Returns lite LogOption[] from pure filesystem metadata (stat only).
4061
+ * No file reads — instant. Call `enrichLogs` to enrich
4062
+ * visible sessions with firstPrompt, gitBranch, customTitle, etc.
4063
+ */
4064
+ export async function getSessionFilesLite(projectDir, limit, projectPath) {
4065
+ const sessionFilesMap = await getSessionFilesWithMtime(projectDir);
4066
+ // Sort by mtime descending and apply limit
4067
+ let entries = [...sessionFilesMap.entries()].sort((a, b) => b[1].mtime - a[1].mtime);
4068
+ if (limit && entries.length > limit) {
4069
+ entries = entries.slice(0, limit);
4070
+ }
4071
+ const logs = [];
4072
+ for (const [sessionId, fileInfo] of entries) {
4073
+ logs.push({
4074
+ date: new Date(fileInfo.mtime).toISOString(),
4075
+ messages: [],
4076
+ isLite: true,
4077
+ fullPath: fileInfo.path,
4078
+ value: 0,
4079
+ created: new Date(fileInfo.ctime),
4080
+ modified: new Date(fileInfo.mtime),
4081
+ firstPrompt: '',
4082
+ messageCount: 0,
4083
+ fileSize: fileInfo.size,
4084
+ isSidechain: false,
4085
+ sessionId,
4086
+ projectPath,
4087
+ });
4088
+ }
4089
+ // logs are freshly pushed above — safe to mutate in place
4090
+ const sorted = sortLogs(logs);
4091
+ sorted.forEach((log, i) => {
4092
+ log.value = i;
4093
+ });
4094
+ return sorted;
4095
+ }
4096
+ /**
4097
+ * Enriches a lite log with metadata from its JSONL file.
4098
+ * Returns the enriched log, or null if the log has no meaningful content
4099
+ * (no firstPrompt, no customTitle — e.g., metadata-only session files).
4100
+ */
4101
+ async function enrichLog(log, readBuf) {
4102
+ if (!log.isLite || !log.fullPath)
4103
+ return log;
4104
+ const meta = await readLiteMetadata(log.fullPath, log.fileSize ?? 0, readBuf);
4105
+ const enriched = {
4106
+ ...log,
4107
+ isLite: false,
4108
+ firstPrompt: meta.firstPrompt,
4109
+ gitBranch: meta.gitBranch,
4110
+ isSidechain: meta.isSidechain,
4111
+ teamName: meta.teamName,
4112
+ customTitle: meta.customTitle,
4113
+ summary: meta.summary,
4114
+ tag: meta.tag,
4115
+ agentSetting: meta.agentSetting,
4116
+ prNumber: meta.prNumber,
4117
+ prUrl: meta.prUrl,
4118
+ prRepository: meta.prRepository,
4119
+ projectPath: meta.projectPath ?? log.projectPath,
4120
+ };
4121
+ // Provide a fallback title for sessions where we couldn't extract the first
4122
+ // prompt (e.g., large first messages that exceed the 16KB read buffer).
4123
+ // Previously these sessions were silently dropped, making them inaccessible
4124
+ // via /resume after crashes or large-context sessions.
4125
+ if (!enriched.firstPrompt && !enriched.customTitle) {
4126
+ enriched.firstPrompt = '(session)';
4127
+ }
4128
+ // Filter: skip sidechains and agent sessions
4129
+ if (enriched.isSidechain) {
4130
+ logForDebugging(`Session ${log.sessionId} filtered from /resume: isSidechain=true`);
4131
+ return null;
4132
+ }
4133
+ if (enriched.teamName) {
4134
+ logForDebugging(`Session ${log.sessionId} filtered from /resume: teamName=${enriched.teamName}`);
4135
+ return null;
4136
+ }
4137
+ return enriched;
4138
+ }
4139
+ /**
4140
+ * Enriches enough lite logs from `allLogs` (starting at `startIndex`) to
4141
+ * produce `count` valid results. Returns the valid enriched logs and the
4142
+ * index where scanning stopped (for progressive loading to continue from).
4143
+ */
4144
+ export async function enrichLogs(allLogs, startIndex, count) {
4145
+ const result = [];
4146
+ const readBuf = Buffer.alloc(LITE_READ_BUF_SIZE);
4147
+ let i = startIndex;
4148
+ while (i < allLogs.length && result.length < count) {
4149
+ const log = allLogs[i];
4150
+ i++;
4151
+ const enriched = await enrichLog(log, readBuf);
4152
+ if (enriched) {
4153
+ result.push(enriched);
4154
+ }
4155
+ }
4156
+ const scanned = i - startIndex;
4157
+ const filtered = scanned - result.length;
4158
+ if (filtered > 0) {
4159
+ logForDebugging(`/resume: enriched ${scanned} sessions, ${filtered} filtered out, ${result.length} visible (${allLogs.length - i} remaining on disk)`);
4160
+ }
4161
+ return { logs: result, nextIndex: i };
4162
+ }