turboflux 1.0.0

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 (490) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -0
  3. package/bin/turboflux.mjs +28 -0
  4. package/dist/cli/brand.d.ts +7 -0
  5. package/dist/cli/brand.js +33 -0
  6. package/dist/cli/brand.js.map +1 -0
  7. package/dist/cli/commands/index.d.ts +4 -0
  8. package/dist/cli/commands/index.js +671 -0
  9. package/dist/cli/commands/index.js.map +1 -0
  10. package/dist/cli/commands/registry.d.ts +24 -0
  11. package/dist/cli/commands/registry.js +124 -0
  12. package/dist/cli/commands/registry.js.map +1 -0
  13. package/dist/cli/commands/types.d.ts +69 -0
  14. package/dist/cli/commands/types.js +2 -0
  15. package/dist/cli/commands/types.js.map +1 -0
  16. package/dist/cli/components/App.d.ts +15 -0
  17. package/dist/cli/components/App.js +1738 -0
  18. package/dist/cli/components/App.js.map +1 -0
  19. package/dist/cli/components/ConversationHistory.d.ts +15 -0
  20. package/dist/cli/components/ConversationHistory.js +92 -0
  21. package/dist/cli/components/ConversationHistory.js.map +1 -0
  22. package/dist/cli/components/TranscriptViewport.d.ts +25 -0
  23. package/dist/cli/components/TranscriptViewport.js +77 -0
  24. package/dist/cli/components/TranscriptViewport.js.map +1 -0
  25. package/dist/cli/components/agentActivityModel.d.ts +24 -0
  26. package/dist/cli/components/agentActivityModel.js +45 -0
  27. package/dist/cli/components/agentActivityModel.js.map +1 -0
  28. package/dist/cli/components/appHelpers.d.ts +34 -0
  29. package/dist/cli/components/appHelpers.js +209 -0
  30. package/dist/cli/components/appHelpers.js.map +1 -0
  31. package/dist/cli/components/design-system/StatusIcon.d.ts +5 -0
  32. package/dist/cli/components/design-system/StatusIcon.js +25 -0
  33. package/dist/cli/components/design-system/StatusIcon.js.map +1 -0
  34. package/dist/cli/components/developerFlowModel.d.ts +36 -0
  35. package/dist/cli/components/developerFlowModel.js +88 -0
  36. package/dist/cli/components/developerFlowModel.js.map +1 -0
  37. package/dist/cli/components/diff/DiffCard.d.ts +16 -0
  38. package/dist/cli/components/diff/DiffCard.js +30 -0
  39. package/dist/cli/components/diff/DiffCard.js.map +1 -0
  40. package/dist/cli/components/diff/DiffHunks.d.ts +11 -0
  41. package/dist/cli/components/diff/DiffHunks.js +58 -0
  42. package/dist/cli/components/diff/DiffHunks.js.map +1 -0
  43. package/dist/cli/components/header/Header.d.ts +13 -0
  44. package/dist/cli/components/header/Header.js +25 -0
  45. package/dist/cli/components/header/Header.js.map +1 -0
  46. package/dist/cli/components/header/Mascot.d.ts +4 -0
  47. package/dist/cli/components/header/Mascot.js +53 -0
  48. package/dist/cli/components/header/Mascot.js.map +1 -0
  49. package/dist/cli/components/header/StatusLine.d.ts +21 -0
  50. package/dist/cli/components/header/StatusLine.js +110 -0
  51. package/dist/cli/components/header/StatusLine.js.map +1 -0
  52. package/dist/cli/components/input/EffortPicker.d.ts +30 -0
  53. package/dist/cli/components/input/EffortPicker.js +126 -0
  54. package/dist/cli/components/input/EffortPicker.js.map +1 -0
  55. package/dist/cli/components/input/ModelPicker.d.ts +15 -0
  56. package/dist/cli/components/input/ModelPicker.js +95 -0
  57. package/dist/cli/components/input/ModelPicker.js.map +1 -0
  58. package/dist/cli/components/input/PromptInput.d.ts +79 -0
  59. package/dist/cli/components/input/PromptInput.js +496 -0
  60. package/dist/cli/components/input/PromptInput.js.map +1 -0
  61. package/dist/cli/components/input/RewindSelector.d.ts +8 -0
  62. package/dist/cli/components/input/RewindSelector.js +45 -0
  63. package/dist/cli/components/input/RewindSelector.js.map +1 -0
  64. package/dist/cli/components/input/pasteState.d.ts +6 -0
  65. package/dist/cli/components/input/pasteState.js +42 -0
  66. package/dist/cli/components/input/pasteState.js.map +1 -0
  67. package/dist/cli/components/input/terminalInputStateMachine.d.ts +29 -0
  68. package/dist/cli/components/input/terminalInputStateMachine.js +67 -0
  69. package/dist/cli/components/input/terminalInputStateMachine.js.map +1 -0
  70. package/dist/cli/components/layout/CockpitRails.d.ts +6 -0
  71. package/dist/cli/components/layout/CockpitRails.js +13 -0
  72. package/dist/cli/components/layout/CockpitRails.js.map +1 -0
  73. package/dist/cli/components/layout/LandingView.d.ts +15 -0
  74. package/dist/cli/components/layout/LandingView.js +12 -0
  75. package/dist/cli/components/layout/LandingView.js.map +1 -0
  76. package/dist/cli/components/layout/SessionSidebar.d.ts +18 -0
  77. package/dist/cli/components/layout/SessionSidebar.js +70 -0
  78. package/dist/cli/components/layout/SessionSidebar.js.map +1 -0
  79. package/dist/cli/components/layout/StartupAnimation.d.ts +14 -0
  80. package/dist/cli/components/layout/StartupAnimation.js +30 -0
  81. package/dist/cli/components/layout/StartupAnimation.js.map +1 -0
  82. package/dist/cli/components/markdown/highlighter.d.ts +1 -0
  83. package/dist/cli/components/markdown/highlighter.js +110 -0
  84. package/dist/cli/components/markdown/highlighter.js.map +1 -0
  85. package/dist/cli/components/markdown/index.d.ts +11 -0
  86. package/dist/cli/components/markdown/index.js +86 -0
  87. package/dist/cli/components/markdown/index.js.map +1 -0
  88. package/dist/cli/components/messages/MessageList.d.ts +16 -0
  89. package/dist/cli/components/messages/MessageList.js +56 -0
  90. package/dist/cli/components/messages/MessageList.js.map +1 -0
  91. package/dist/cli/components/messages/Messages.d.ts +26 -0
  92. package/dist/cli/components/messages/Messages.js +32 -0
  93. package/dist/cli/components/messages/Messages.js.map +1 -0
  94. package/dist/cli/components/messages/ThinkingBlock.d.ts +9 -0
  95. package/dist/cli/components/messages/ThinkingBlock.js +51 -0
  96. package/dist/cli/components/messages/ThinkingBlock.js.map +1 -0
  97. package/dist/cli/components/messages/WindowedMessageList.d.ts +21 -0
  98. package/dist/cli/components/messages/WindowedMessageList.js +44 -0
  99. package/dist/cli/components/messages/WindowedMessageList.js.map +1 -0
  100. package/dist/cli/components/permissions/PermissionDialog.d.ts +23 -0
  101. package/dist/cli/components/permissions/PermissionDialog.js +83 -0
  102. package/dist/cli/components/permissions/PermissionDialog.js.map +1 -0
  103. package/dist/cli/components/spinner/SpinnerGlyph.d.ts +6 -0
  104. package/dist/cli/components/spinner/SpinnerGlyph.js +55 -0
  105. package/dist/cli/components/spinner/SpinnerGlyph.js.map +1 -0
  106. package/dist/cli/components/spinner/constants.d.ts +4 -0
  107. package/dist/cli/components/spinner/constants.js +5 -0
  108. package/dist/cli/components/spinner/constants.js.map +1 -0
  109. package/dist/cli/components/tools/ActiveWorkPanel.d.ts +22 -0
  110. package/dist/cli/components/tools/ActiveWorkPanel.js +70 -0
  111. package/dist/cli/components/tools/ActiveWorkPanel.js.map +1 -0
  112. package/dist/cli/components/tools/AgentActivityLine.d.ts +13 -0
  113. package/dist/cli/components/tools/AgentActivityLine.js +88 -0
  114. package/dist/cli/components/tools/AgentActivityLine.js.map +1 -0
  115. package/dist/cli/components/tools/QueuedPromptList.d.ts +10 -0
  116. package/dist/cli/components/tools/QueuedPromptList.js +26 -0
  117. package/dist/cli/components/tools/QueuedPromptList.js.map +1 -0
  118. package/dist/cli/components/tools/TaskFlowHud.d.ts +15 -0
  119. package/dist/cli/components/tools/TaskFlowHud.js +55 -0
  120. package/dist/cli/components/tools/TaskFlowHud.js.map +1 -0
  121. package/dist/cli/components/tools/TerminalSessionsFooter.d.ts +6 -0
  122. package/dist/cli/components/tools/TerminalSessionsFooter.js +36 -0
  123. package/dist/cli/components/tools/TerminalSessionsFooter.js.map +1 -0
  124. package/dist/cli/components/tools/ToolActivityList.d.ts +11 -0
  125. package/dist/cli/components/tools/ToolActivityList.js +73 -0
  126. package/dist/cli/components/tools/ToolActivityList.js.map +1 -0
  127. package/dist/cli/components/tools/ToolCallTree.d.ts +11 -0
  128. package/dist/cli/components/tools/ToolCallTree.js +14 -0
  129. package/dist/cli/components/tools/ToolCallTree.js.map +1 -0
  130. package/dist/cli/components/tools/toolLifecycleModel.d.ts +16 -0
  131. package/dist/cli/components/tools/toolLifecycleModel.js +30 -0
  132. package/dist/cli/components/tools/toolLifecycleModel.js.map +1 -0
  133. package/dist/cli/components/tools/toolPresentation.d.ts +9 -0
  134. package/dist/cli/components/tools/toolPresentation.js +171 -0
  135. package/dist/cli/components/tools/toolPresentation.js.map +1 -0
  136. package/dist/cli/components/tools/toolTypes.d.ts +16 -0
  137. package/dist/cli/components/tools/toolTypes.js +2 -0
  138. package/dist/cli/components/tools/toolTypes.js.map +1 -0
  139. package/dist/cli/components/transcriptWindowing.d.ts +19 -0
  140. package/dist/cli/components/transcriptWindowing.js +81 -0
  141. package/dist/cli/components/transcriptWindowing.js.map +1 -0
  142. package/dist/cli/config.d.ts +1 -0
  143. package/dist/cli/config.js +2 -0
  144. package/dist/cli/config.js.map +1 -0
  145. package/dist/cli/conversations/journalWriter.d.ts +50 -0
  146. package/dist/cli/conversations/journalWriter.js +191 -0
  147. package/dist/cli/conversations/journalWriter.js.map +1 -0
  148. package/dist/cli/conversations/manager.d.ts +65 -0
  149. package/dist/cli/conversations/manager.js +435 -0
  150. package/dist/cli/conversations/manager.js.map +1 -0
  151. package/dist/cli/conversations/recoveryExport.d.ts +18 -0
  152. package/dist/cli/conversations/recoveryExport.js +39 -0
  153. package/dist/cli/conversations/recoveryExport.js.map +1 -0
  154. package/dist/cli/conversations/recoveryMessages.d.ts +2 -0
  155. package/dist/cli/conversations/recoveryMessages.js +3 -0
  156. package/dist/cli/conversations/recoveryMessages.js.map +1 -0
  157. package/dist/cli/conversations/store.d.ts +14 -0
  158. package/dist/cli/conversations/store.js +603 -0
  159. package/dist/cli/conversations/store.js.map +1 -0
  160. package/dist/cli/conversations/types.d.ts +142 -0
  161. package/dist/cli/conversations/types.js +2 -0
  162. package/dist/cli/conversations/types.js.map +1 -0
  163. package/dist/cli/globalConfiguration.d.ts +13 -0
  164. package/dist/cli/globalConfiguration.js +61 -0
  165. package/dist/cli/globalConfiguration.js.map +1 -0
  166. package/dist/cli/hooks/useMessageCursor.d.ts +16 -0
  167. package/dist/cli/hooks/useMessageCursor.js +75 -0
  168. package/dist/cli/hooks/useMessageCursor.js.map +1 -0
  169. package/dist/cli/hooks/useOverlayStack.d.ts +9 -0
  170. package/dist/cli/hooks/useOverlayStack.js +26 -0
  171. package/dist/cli/hooks/useOverlayStack.js.map +1 -0
  172. package/dist/cli/hooks/useTerminalSize.d.ts +16 -0
  173. package/dist/cli/hooks/useTerminalSize.js +37 -0
  174. package/dist/cli/hooks/useTerminalSize.js.map +1 -0
  175. package/dist/cli/i18n/index.d.ts +15 -0
  176. package/dist/cli/i18n/index.js +32 -0
  177. package/dist/cli/i18n/index.js.map +1 -0
  178. package/dist/cli/i18n/messages.d.ts +1305 -0
  179. package/dist/cli/i18n/messages.js +1305 -0
  180. package/dist/cli/i18n/messages.js.map +1 -0
  181. package/dist/cli/imageAttachments.d.ts +22 -0
  182. package/dist/cli/imageAttachments.js +329 -0
  183. package/dist/cli/imageAttachments.js.map +1 -0
  184. package/dist/cli/index.d.ts +2 -0
  185. package/dist/cli/index.js +146 -0
  186. package/dist/cli/index.js.map +1 -0
  187. package/dist/cli/platform/terminalAttention.d.ts +46 -0
  188. package/dist/cli/platform/terminalAttention.js +152 -0
  189. package/dist/cli/platform/terminalAttention.js.map +1 -0
  190. package/dist/cli/platform/terminalTransparency.d.ts +11 -0
  191. package/dist/cli/platform/terminalTransparency.js +193 -0
  192. package/dist/cli/platform/terminalTransparency.js.map +1 -0
  193. package/dist/cli/repl.d.ts +15 -0
  194. package/dist/cli/repl.js +38 -0
  195. package/dist/cli/repl.js.map +1 -0
  196. package/dist/cli/setup.d.ts +17 -0
  197. package/dist/cli/setup.js +991 -0
  198. package/dist/cli/setup.js.map +1 -0
  199. package/dist/cli/singleShot.d.ts +30 -0
  200. package/dist/cli/singleShot.js +167 -0
  201. package/dist/cli/singleShot.js.map +1 -0
  202. package/dist/cli/state/adaptiveStreamScheduler.d.ts +53 -0
  203. package/dist/cli/state/adaptiveStreamScheduler.js +136 -0
  204. package/dist/cli/state/adaptiveStreamScheduler.js.map +1 -0
  205. package/dist/cli/state/agentFlowController.d.ts +37 -0
  206. package/dist/cli/state/agentFlowController.js +402 -0
  207. package/dist/cli/state/agentFlowController.js.map +1 -0
  208. package/dist/cli/state/approvalPresentationScheduler.d.ts +28 -0
  209. package/dist/cli/state/approvalPresentationScheduler.js +72 -0
  210. package/dist/cli/state/approvalPresentationScheduler.js.map +1 -0
  211. package/dist/cli/state/flowFeatureFlags.d.ts +18 -0
  212. package/dist/cli/state/flowFeatureFlags.js +45 -0
  213. package/dist/cli/state/flowFeatureFlags.js.map +1 -0
  214. package/dist/cli/state/flowReducer.d.ts +120 -0
  215. package/dist/cli/state/flowReducer.js +535 -0
  216. package/dist/cli/state/flowReducer.js.map +1 -0
  217. package/dist/cli/state/flowSelectors.d.ts +45 -0
  218. package/dist/cli/state/flowSelectors.js +144 -0
  219. package/dist/cli/state/flowSelectors.js.map +1 -0
  220. package/dist/cli/state/flowStore.d.ts +19 -0
  221. package/dist/cli/state/flowStore.js +51 -0
  222. package/dist/cli/state/flowStore.js.map +1 -0
  223. package/dist/cli/state/globalCommandActivity.d.ts +25 -0
  224. package/dist/cli/state/globalCommandActivity.js +57 -0
  225. package/dist/cli/state/globalCommandActivity.js.map +1 -0
  226. package/dist/cli/state/goldenTraces.d.ts +2 -0
  227. package/dist/cli/state/goldenTraces.js +15 -0
  228. package/dist/cli/state/goldenTraces.js.map +1 -0
  229. package/dist/cli/state/notificationCoordinator.d.ts +38 -0
  230. package/dist/cli/state/notificationCoordinator.js +101 -0
  231. package/dist/cli/state/notificationCoordinator.js.map +1 -0
  232. package/dist/cli/stateProvider.d.ts +5 -0
  233. package/dist/cli/stateProvider.js +7 -0
  234. package/dist/cli/stateProvider.js.map +1 -0
  235. package/dist/cli/telemetry/localFlowTelemetry.d.ts +42 -0
  236. package/dist/cli/telemetry/localFlowTelemetry.js +131 -0
  237. package/dist/cli/telemetry/localFlowTelemetry.js.map +1 -0
  238. package/dist/cli/telemetry/terminalBaseline.d.ts +93 -0
  239. package/dist/cli/telemetry/terminalBaseline.js +199 -0
  240. package/dist/cli/telemetry/terminalBaseline.js.map +1 -0
  241. package/dist/cli/telemetry/terminalLatencyTracker.d.ts +24 -0
  242. package/dist/cli/telemetry/terminalLatencyTracker.js +59 -0
  243. package/dist/cli/telemetry/terminalLatencyTracker.js.map +1 -0
  244. package/dist/cli/terminalLayout.d.ts +2 -0
  245. package/dist/cli/terminalLayout.js +16 -0
  246. package/dist/cli/terminalLayout.js.map +1 -0
  247. package/dist/cli/terminalMouse.d.ts +10 -0
  248. package/dist/cli/terminalMouse.js +29 -0
  249. package/dist/cli/terminalMouse.js.map +1 -0
  250. package/dist/cli/theme/ThemeProvider.d.ts +9 -0
  251. package/dist/cli/theme/ThemeProvider.js +26 -0
  252. package/dist/cli/theme/ThemeProvider.js.map +1 -0
  253. package/dist/cli/theme/dark.d.ts +2 -0
  254. package/dist/cli/theme/dark.js +27 -0
  255. package/dist/cli/theme/dark.js.map +1 -0
  256. package/dist/cli/theme/index.d.ts +5 -0
  257. package/dist/cli/theme/index.js +5 -0
  258. package/dist/cli/theme/index.js.map +1 -0
  259. package/dist/cli/theme/light.d.ts +2 -0
  260. package/dist/cli/theme/light.js +26 -0
  261. package/dist/cli/theme/light.js.map +1 -0
  262. package/dist/cli/theme/palette.d.ts +7 -0
  263. package/dist/cli/theme/palette.js +8 -0
  264. package/dist/cli/theme/palette.js.map +1 -0
  265. package/dist/cli/theme/resolveColor.d.ts +4 -0
  266. package/dist/cli/theme/resolveColor.js +43 -0
  267. package/dist/cli/theme/resolveColor.js.map +1 -0
  268. package/dist/cli/theme/types.d.ts +27 -0
  269. package/dist/cli/theme/types.js +2 -0
  270. package/dist/cli/theme/types.js.map +1 -0
  271. package/dist/cli/toolExecutor.d.ts +1 -0
  272. package/dist/cli/toolExecutor.js +2 -0
  273. package/dist/cli/toolExecutor.js.map +1 -0
  274. package/dist/core/agentEngine.d.ts +425 -0
  275. package/dist/core/agentEngine.js +5170 -0
  276. package/dist/core/agentEngine.js.map +1 -0
  277. package/dist/core/agents/loader.d.ts +9 -0
  278. package/dist/core/agents/loader.js +144 -0
  279. package/dist/core/agents/loader.js.map +1 -0
  280. package/dist/core/applyPatch.d.ts +24 -0
  281. package/dist/core/applyPatch.js +158 -0
  282. package/dist/core/applyPatch.js.map +1 -0
  283. package/dist/core/cacheMonitor.d.ts +24 -0
  284. package/dist/core/cacheMonitor.js +211 -0
  285. package/dist/core/cacheMonitor.js.map +1 -0
  286. package/dist/core/clientIdentity.d.ts +6 -0
  287. package/dist/core/clientIdentity.js +44 -0
  288. package/dist/core/clientIdentity.js.map +1 -0
  289. package/dist/core/config.d.ts +99 -0
  290. package/dist/core/config.js +715 -0
  291. package/dist/core/config.js.map +1 -0
  292. package/dist/core/contextCompaction.d.ts +20 -0
  293. package/dist/core/contextCompaction.js +158 -0
  294. package/dist/core/contextCompaction.js.map +1 -0
  295. package/dist/core/contextManager.d.ts +44 -0
  296. package/dist/core/contextManager.js +590 -0
  297. package/dist/core/contextManager.js.map +1 -0
  298. package/dist/core/contextPolicy.d.ts +18 -0
  299. package/dist/core/contextPolicy.js +39 -0
  300. package/dist/core/contextPolicy.js.map +1 -0
  301. package/dist/core/credentialStore.d.ts +8 -0
  302. package/dist/core/credentialStore.js +37 -0
  303. package/dist/core/credentialStore.js.map +1 -0
  304. package/dist/core/diffCompute.d.ts +31 -0
  305. package/dist/core/diffCompute.js +116 -0
  306. package/dist/core/diffCompute.js.map +1 -0
  307. package/dist/core/editHelpers.d.ts +8 -0
  308. package/dist/core/editHelpers.js +35 -0
  309. package/dist/core/editHelpers.js.map +1 -0
  310. package/dist/core/fileIO.d.ts +12 -0
  311. package/dist/core/fileIO.js +199 -0
  312. package/dist/core/fileIO.js.map +1 -0
  313. package/dist/core/gitService.d.ts +83 -0
  314. package/dist/core/gitService.js +522 -0
  315. package/dist/core/gitService.js.map +1 -0
  316. package/dist/core/index.d.ts +30 -0
  317. package/dist/core/index.js +19 -0
  318. package/dist/core/index.js.map +1 -0
  319. package/dist/core/mcp/client.d.ts +32 -0
  320. package/dist/core/mcp/client.js +186 -0
  321. package/dist/core/mcp/client.js.map +1 -0
  322. package/dist/core/mcp/settings.d.ts +2 -0
  323. package/dist/core/mcp/settings.js +26 -0
  324. package/dist/core/mcp/settings.js.map +1 -0
  325. package/dist/core/mcp/toolBridge.d.ts +20 -0
  326. package/dist/core/mcp/toolBridge.js +74 -0
  327. package/dist/core/mcp/toolBridge.js.map +1 -0
  328. package/dist/core/mcp/types.d.ts +29 -0
  329. package/dist/core/mcp/types.js +2 -0
  330. package/dist/core/mcp/types.js.map +1 -0
  331. package/dist/core/modelDiscovery.d.ts +14 -0
  332. package/dist/core/modelDiscovery.js +566 -0
  333. package/dist/core/modelDiscovery.js.map +1 -0
  334. package/dist/core/modelProtocol.d.ts +43 -0
  335. package/dist/core/modelProtocol.js +238 -0
  336. package/dist/core/modelProtocol.js.map +1 -0
  337. package/dist/core/modelRegistry.d.ts +56 -0
  338. package/dist/core/modelRegistry.js +308 -0
  339. package/dist/core/modelRegistry.js.map +1 -0
  340. package/dist/core/networkProxy.d.ts +20 -0
  341. package/dist/core/networkProxy.js +145 -0
  342. package/dist/core/networkProxy.js.map +1 -0
  343. package/dist/core/normalizeBaseUrl.d.ts +1 -0
  344. package/dist/core/normalizeBaseUrl.js +9 -0
  345. package/dist/core/normalizeBaseUrl.js.map +1 -0
  346. package/dist/core/pathUtils.d.ts +3 -0
  347. package/dist/core/pathUtils.js +46 -0
  348. package/dist/core/pathUtils.js.map +1 -0
  349. package/dist/core/permissions.d.ts +29 -0
  350. package/dist/core/permissions.js +245 -0
  351. package/dist/core/permissions.js.map +1 -0
  352. package/dist/core/persona/voiceProfile.d.ts +7 -0
  353. package/dist/core/persona/voiceProfile.js +14 -0
  354. package/dist/core/persona/voiceProfile.js.map +1 -0
  355. package/dist/core/profile.d.ts +35 -0
  356. package/dist/core/profile.js +373 -0
  357. package/dist/core/profile.js.map +1 -0
  358. package/dist/core/requestCompatibility.d.ts +13 -0
  359. package/dist/core/requestCompatibility.js +181 -0
  360. package/dist/core/requestCompatibility.js.map +1 -0
  361. package/dist/core/runtime/agentRuntime.d.ts +43 -0
  362. package/dist/core/runtime/agentRuntime.js +137 -0
  363. package/dist/core/runtime/agentRuntime.js.map +1 -0
  364. package/dist/core/runtime/approvalCoordinator.d.ts +36 -0
  365. package/dist/core/runtime/approvalCoordinator.js +113 -0
  366. package/dist/core/runtime/approvalCoordinator.js.map +1 -0
  367. package/dist/core/runtime/capabilityBoundary.d.ts +18 -0
  368. package/dist/core/runtime/capabilityBoundary.js +89 -0
  369. package/dist/core/runtime/capabilityBoundary.js.map +1 -0
  370. package/dist/core/runtime/nodeToolExecutor.d.ts +200 -0
  371. package/dist/core/runtime/nodeToolExecutor.js +2446 -0
  372. package/dist/core/runtime/nodeToolExecutor.js.map +1 -0
  373. package/dist/core/runtime/runtimeLogWriter.d.ts +32 -0
  374. package/dist/core/runtime/runtimeLogWriter.js +129 -0
  375. package/dist/core/runtime/runtimeLogWriter.js.map +1 -0
  376. package/dist/core/runtime/runtimeTaskManager.d.ts +78 -0
  377. package/dist/core/runtime/runtimeTaskManager.js +374 -0
  378. package/dist/core/runtime/runtimeTaskManager.js.map +1 -0
  379. package/dist/core/runtime/sessionRegistry.d.ts +19 -0
  380. package/dist/core/runtime/sessionRegistry.js +41 -0
  381. package/dist/core/runtime/sessionRegistry.js.map +1 -0
  382. package/dist/core/runtime/stateProvider.d.ts +69 -0
  383. package/dist/core/runtime/stateProvider.js +131 -0
  384. package/dist/core/runtime/stateProvider.js.map +1 -0
  385. package/dist/core/runtime/subAgentTaskManager.d.ts +111 -0
  386. package/dist/core/runtime/subAgentTaskManager.js +386 -0
  387. package/dist/core/runtime/subAgentTaskManager.js.map +1 -0
  388. package/dist/core/schemaValidation.d.ts +5 -0
  389. package/dist/core/schemaValidation.js +153 -0
  390. package/dist/core/schemaValidation.js.map +1 -0
  391. package/dist/core/skills/loader.d.ts +7 -0
  392. package/dist/core/skills/loader.js +91 -0
  393. package/dist/core/skills/loader.js.map +1 -0
  394. package/dist/core/skills/runtime.d.ts +19 -0
  395. package/dist/core/skills/runtime.js +61 -0
  396. package/dist/core/skills/runtime.js.map +1 -0
  397. package/dist/core/subAgent.d.ts +50 -0
  398. package/dist/core/subAgent.js +1258 -0
  399. package/dist/core/subAgent.js.map +1 -0
  400. package/dist/core/systemPrompt.d.ts +27 -0
  401. package/dist/core/systemPrompt.js +219 -0
  402. package/dist/core/systemPrompt.js.map +1 -0
  403. package/dist/core/taskManager.d.ts +105 -0
  404. package/dist/core/taskManager.js +617 -0
  405. package/dist/core/taskManager.js.map +1 -0
  406. package/dist/core/tokenCompressor.d.ts +14 -0
  407. package/dist/core/tokenCompressor.js +161 -0
  408. package/dist/core/tokenCompressor.js.map +1 -0
  409. package/dist/core/tokenCounter.d.ts +28 -0
  410. package/dist/core/tokenCounter.js +101 -0
  411. package/dist/core/tokenCounter.js.map +1 -0
  412. package/dist/core/toolDispatcher.d.ts +11 -0
  413. package/dist/core/toolDispatcher.js +38 -0
  414. package/dist/core/toolDispatcher.js.map +1 -0
  415. package/dist/core/toolExecutionLedger.d.ts +9 -0
  416. package/dist/core/toolExecutionLedger.js +84 -0
  417. package/dist/core/toolExecutionLedger.js.map +1 -0
  418. package/dist/core/toolRegistry.d.ts +19 -0
  419. package/dist/core/toolRegistry.js +820 -0
  420. package/dist/core/toolRegistry.js.map +1 -0
  421. package/dist/core/turnStrategy.d.ts +20 -0
  422. package/dist/core/turnStrategy.js +103 -0
  423. package/dist/core/turnStrategy.js.map +1 -0
  424. package/dist/platform/process.d.ts +14 -0
  425. package/dist/platform/process.js +28 -0
  426. package/dist/platform/process.js.map +1 -0
  427. package/dist/platform/runtime.d.ts +8 -0
  428. package/dist/platform/runtime.js +21 -0
  429. package/dist/platform/runtime.js.map +1 -0
  430. package/dist/server/adminPage.d.ts +1 -0
  431. package/dist/server/adminPage.js +755 -0
  432. package/dist/server/adminPage.js.map +1 -0
  433. package/dist/server/index.d.ts +22 -0
  434. package/dist/server/index.js +566 -0
  435. package/dist/server/index.js.map +1 -0
  436. package/dist/shared/agentTypes.d.ts +227 -0
  437. package/dist/shared/agentTypes.js +79 -0
  438. package/dist/shared/agentTypes.js.map +1 -0
  439. package/dist/shared/codeIndexTypes.d.ts +32 -0
  440. package/dist/shared/codeIndexTypes.js +2 -0
  441. package/dist/shared/codeIndexTypes.js.map +1 -0
  442. package/dist/shared/flowEvents.d.ts +192 -0
  443. package/dist/shared/flowEvents.js +37 -0
  444. package/dist/shared/flowEvents.js.map +1 -0
  445. package/dist/shared/memoryTypes.d.ts +100 -0
  446. package/dist/shared/memoryTypes.js +2 -0
  447. package/dist/shared/memoryTypes.js.map +1 -0
  448. package/dist/shared/pluginTypes.d.ts +345 -0
  449. package/dist/shared/pluginTypes.js +2 -0
  450. package/dist/shared/pluginTypes.js.map +1 -0
  451. package/dist/shared/runtimeTaskTypes.d.ts +43 -0
  452. package/dist/shared/runtimeTaskTypes.js +2 -0
  453. package/dist/shared/runtimeTaskTypes.js.map +1 -0
  454. package/dist/shared/skillTypes.d.ts +1 -0
  455. package/dist/shared/skillTypes.js +2 -0
  456. package/dist/shared/skillTypes.js.map +1 -0
  457. package/dist/shared/subAgentTypes.d.ts +99 -0
  458. package/dist/shared/subAgentTypes.js +2 -0
  459. package/dist/shared/subAgentTypes.js.map +1 -0
  460. package/dist/shared/terminalTypes.d.ts +102 -0
  461. package/dist/shared/terminalTypes.js +2 -0
  462. package/dist/shared/terminalTypes.js.map +1 -0
  463. package/dist/shared/toolCallMarkup.d.ts +10 -0
  464. package/dist/shared/toolCallMarkup.js +107 -0
  465. package/dist/shared/toolCallMarkup.js.map +1 -0
  466. package/dist/shared/toolTypes.d.ts +24 -0
  467. package/dist/shared/toolTypes.js +2 -0
  468. package/dist/shared/toolTypes.js.map +1 -0
  469. package/dist/shared/types.d.ts +81 -0
  470. package/dist/shared/types.js +2 -0
  471. package/dist/shared/types.js.map +1 -0
  472. package/dist/state/types.d.ts +69 -0
  473. package/dist/state/types.js +2 -0
  474. package/dist/state/types.js.map +1 -0
  475. package/dist/tools/executor.d.ts +133 -0
  476. package/dist/tools/executor.js +2 -0
  477. package/dist/tools/executor.js.map +1 -0
  478. package/dist/tools/memory/loaders.d.ts +6 -0
  479. package/dist/tools/memory/loaders.js +292 -0
  480. package/dist/tools/memory/loaders.js.map +1 -0
  481. package/dist/tools/memory/sanitizer.d.ts +6 -0
  482. package/dist/tools/memory/sanitizer.js +48 -0
  483. package/dist/tools/memory/sanitizer.js.map +1 -0
  484. package/dist/tools/memory/service.d.ts +24 -0
  485. package/dist/tools/memory/service.js +273 -0
  486. package/dist/tools/memory/service.js.map +1 -0
  487. package/dist/tools/memory/writer.d.ts +17 -0
  488. package/dist/tools/memory/writer.js +192 -0
  489. package/dist/tools/memory/writer.js.map +1 -0
  490. package/package.json +68 -0
@@ -0,0 +1,2446 @@
1
+ import { createReadStream, readFileSync, existsSync, mkdirSync, rmSync, renameSync, readdirSync, statSync } from 'fs';
2
+ import { join, dirname, relative, resolve as resolveNativePath, isAbsolute } from 'path';
3
+ import { execFile, spawn } from 'child_process';
4
+ import { promisify } from 'util';
5
+ import { createInterface } from 'readline';
6
+ const execFileAsync = promisify(execFile);
7
+ import { MemoryService } from '../../tools/memory/service';
8
+ import { hashText, writeFileAtomicSync } from '../fileIO';
9
+ import { RuntimeTaskManager } from './runtimeTaskManager';
10
+ import { getChildProcessSpawnOptions, getDefaultShellSpec, getProcessGroupSignal, usesProcessGroup } from '../../platform/process';
11
+ import { RuntimeLogWriter } from './runtimeLogWriter';
12
+ import { CapabilityBoundary } from './capabilityBoundary';
13
+ const RETRYABLE_HTTP_STATUS = new Set([408, 409, 425, 429, 500, 502, 503, 504]);
14
+ const STREAM_RETRY_DELAYS_MS = [300, 900, 1800];
15
+ const MAX_STREAM_DIAGNOSTIC_CHARS = 64 * 1024;
16
+ const MAX_STREAM_BUFFER_CHARS = 1 * 1024 * 1024;
17
+ const MAX_TERMINAL_CHUNKS = 500;
18
+ const MAX_TERMINAL_BUFFER_CHARS = 1_000_000;
19
+ const MAX_RECOVERED_TERMINAL_READ_BYTES = 2 * 1024 * 1024;
20
+ const MAX_COMMAND_OUTPUT_CHARS = 2_000_000;
21
+ const COMMAND_TERMINATION_GRACE_MS = 2000;
22
+ const RUNTIME_LOG_DIRECTORY = join('.turboflux', 'runtime-logs');
23
+ const TERMINAL_KILL_TIMEOUT_MS = 5000;
24
+ const RUNTIME_TASK_SNAPSHOT_INTERVAL_MS = 5000;
25
+ const MODEL_REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
26
+ const WEB_SEARCH_TIMEOUT_MS = 8000;
27
+ const WEB_SEARCH_USER_AGENT = 'TurboFlux/0.1 (+https://github.com/MengShengbo/TurboFluxCli)';
28
+ const DEFAULT_SHELL = getDefaultShellSpec();
29
+ const CODE_SEARCH_SKIPPED_DIRS = new Set([
30
+ '.git', '.hg', '.svn', '.claude', '.turboflux', '.vscode', '.cache', '.next', '.turbo',
31
+ '.gradle', '.m2', '.npm', '.pnpm-store', '.rustup', '.venv',
32
+ 'AppData', 'appdata', 'Library', 'library', 'node_modules', 'vendor', 'venv', 'dist', 'dist-desktop', 'build', 'out',
33
+ 'coverage', 'target', 'tmp', 'temp',
34
+ ]);
35
+ const CODE_SEARCH_EXCLUDE_GLOBS = Array.from(CODE_SEARCH_SKIPPED_DIRS, directory => `**/${directory}/**`);
36
+ const DEFAULT_SEARCH_LIMIT = 50;
37
+ const MAX_SEARCH_LIMIT = 500;
38
+ const DEFAULT_READ_RANGE_LINES = 180;
39
+ const DEFAULT_READ_RANGE_BYTES = 64 * 1024;
40
+ export class NodeToolExecutor {
41
+ workspacePath;
42
+ memoryService;
43
+ workspaceRoot;
44
+ capabilityBoundary;
45
+ backgroundTerminals = new Map();
46
+ activeStreams = new Map();
47
+ runtimeTaskManager;
48
+ constructor(workspacePath, options = {}) {
49
+ this.workspacePath = workspacePath;
50
+ this.memoryService = new MemoryService();
51
+ this.capabilityBoundary = new CapabilityBoundary(workspacePath, options.capabilityProfile);
52
+ this.workspaceRoot = this.capabilityBoundary.workspaceRoot;
53
+ this.runtimeTaskManager = options.runtimeTaskManager || new RuntimeTaskManager({
54
+ defaultOwnerSessionId: options.ownerSessionId,
55
+ });
56
+ }
57
+ getRuntimeTaskManager() {
58
+ return this.runtimeTaskManager;
59
+ }
60
+ getCapabilityProfile() {
61
+ return this.capabilityBoundary.getProfile();
62
+ }
63
+ setCapabilityProfile(profile) {
64
+ this.capabilityBoundary.setProfile(profile);
65
+ }
66
+ createRuntimeTaskLog(taskId) {
67
+ const directory = this.resolvePath(join(this.workspaceRoot, RUNTIME_LOG_DIRECTORY), 'write');
68
+ mkdirSync(directory, { recursive: true });
69
+ return this.resolvePath(join(directory, `${taskId}.jsonl`), 'write');
70
+ }
71
+ async readFile(path) {
72
+ try {
73
+ const safePath = this.resolvePath(path);
74
+ if (!existsSync(safePath))
75
+ return { success: false, error: 'File not found' };
76
+ if (!statSync(safePath).isFile())
77
+ return { success: false, error: 'Path is not a file' };
78
+ const content = readFileSync(safePath, 'utf-8');
79
+ return { success: true, data: content };
80
+ }
81
+ catch (e) {
82
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
83
+ }
84
+ }
85
+ async readFileRange(path, offset = 0, limit = DEFAULT_READ_RANGE_LINES, maxBytes = DEFAULT_READ_RANGE_BYTES) {
86
+ let stream;
87
+ let reader;
88
+ try {
89
+ const safePath = this.resolvePath(path);
90
+ if (!existsSync(safePath))
91
+ return { success: false, error: 'File not found' };
92
+ if (!statSync(safePath).isFile())
93
+ return { success: false, error: 'Path is not a file' };
94
+ const normalizedOffset = Math.max(0, Math.floor(offset));
95
+ const normalizedLimit = Math.max(1, Math.min(2_000, Math.floor(limit)));
96
+ const normalizedMaxBytes = Math.max(4_096, Math.min(2 * 1024 * 1024, Math.floor(maxBytes)));
97
+ const lines = [];
98
+ let lineIndex = 0;
99
+ let bytesRead = 0;
100
+ let truncated = false;
101
+ let partialLine = false;
102
+ stream = createReadStream(safePath, { encoding: 'utf-8' });
103
+ reader = createInterface({ input: stream, crlfDelay: Infinity });
104
+ for await (const rawLine of reader) {
105
+ if (lineIndex < normalizedOffset) {
106
+ lineIndex += 1;
107
+ continue;
108
+ }
109
+ if (lines.length >= normalizedLimit) {
110
+ truncated = true;
111
+ break;
112
+ }
113
+ const remainingBytes = normalizedMaxBytes - bytesRead;
114
+ if (remainingBytes <= 0) {
115
+ truncated = true;
116
+ break;
117
+ }
118
+ const rawBytes = Buffer.byteLength(rawLine, 'utf-8');
119
+ if (rawBytes > remainingBytes && lines.length > 0) {
120
+ truncated = true;
121
+ break;
122
+ }
123
+ const line = rawBytes > remainingBytes
124
+ ? Buffer.from(rawLine, 'utf-8').subarray(0, remainingBytes).toString('utf-8').replace(/�$/, '')
125
+ : rawLine;
126
+ lines.push(line);
127
+ bytesRead += Buffer.byteLength(line, 'utf-8') + 1;
128
+ lineIndex += 1;
129
+ if (rawBytes > remainingBytes) {
130
+ truncated = true;
131
+ partialLine = true;
132
+ break;
133
+ }
134
+ }
135
+ return {
136
+ success: true,
137
+ data: {
138
+ content: lines.join('\n'),
139
+ startLine: normalizedOffset + 1,
140
+ endLine: normalizedOffset + lines.length,
141
+ truncated,
142
+ bytesRead,
143
+ partialLine,
144
+ },
145
+ };
146
+ }
147
+ catch (e) {
148
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
149
+ }
150
+ finally {
151
+ reader?.close();
152
+ stream?.destroy();
153
+ }
154
+ }
155
+ async writeFile(path, content, metadata) {
156
+ try {
157
+ let safePath = this.resolvePath(path, 'write');
158
+ const dir = dirname(safePath);
159
+ if (!existsSync(dir))
160
+ mkdirSync(dir, { recursive: true });
161
+ safePath = this.resolvePath(safePath, 'write');
162
+ if (metadata?.expectNotExists === true && existsSync(safePath)) {
163
+ return { success: false, error: `Write conflict: file already exists: ${path}` };
164
+ }
165
+ if (typeof metadata?.expectedHash === 'string') {
166
+ if (!existsSync(safePath))
167
+ return { success: false, error: `Write conflict: file was deleted: ${path}` };
168
+ const actualHash = hashText(readFileSync(safePath, 'utf-8'));
169
+ if (actualHash !== metadata.expectedHash) {
170
+ return { success: false, error: `Write conflict: file changed since it was read: ${path}` };
171
+ }
172
+ }
173
+ writeFileAtomicSync(safePath, content);
174
+ return { success: true };
175
+ }
176
+ catch (e) {
177
+ return { success: false, error: String(e) };
178
+ }
179
+ }
180
+ async deleteFile(path, options) {
181
+ try {
182
+ const safePath = this.resolvePath(path, 'write');
183
+ if (!existsSync(safePath))
184
+ return { success: false, error: 'File not found' };
185
+ const expectedHash = options?.expectedHash;
186
+ if (typeof expectedHash === 'string') {
187
+ const actualHash = hashText(readFileSync(safePath, 'utf-8'));
188
+ if (actualHash !== expectedHash) {
189
+ return { success: false, error: `Delete conflict: file changed since it was read: ${path}` };
190
+ }
191
+ }
192
+ rmSync(safePath, { recursive: options?.recursive, force: true });
193
+ return { success: true };
194
+ }
195
+ catch (e) {
196
+ return { success: false, error: String(e) };
197
+ }
198
+ }
199
+ async moveFile(sourcePath, destinationPath, options) {
200
+ try {
201
+ const safeSourcePath = this.resolvePath(sourcePath, 'write');
202
+ const safeDestinationPath = this.resolvePath(destinationPath, 'write');
203
+ if (!existsSync(safeSourcePath))
204
+ return { success: false, error: `File not found: ${sourcePath}` };
205
+ if (statSync(safeSourcePath).isDirectory())
206
+ return { success: false, error: `Cannot move directory with moveFile: ${sourcePath}` };
207
+ if (typeof options?.expectedHash === 'string') {
208
+ const actualHash = hashText(readFileSync(safeSourcePath, 'utf-8'));
209
+ if (actualHash !== options.expectedHash) {
210
+ return { success: false, error: `Move conflict: source changed since it was read: ${sourcePath}` };
211
+ }
212
+ }
213
+ if (existsSync(safeDestinationPath)) {
214
+ if (statSync(safeDestinationPath).isDirectory())
215
+ return { success: false, error: `Cannot overwrite directory: ${destinationPath}` };
216
+ if (typeof options?.expectedDestinationHash === 'string') {
217
+ const actualHash = hashText(readFileSync(safeDestinationPath, 'utf-8'));
218
+ if (actualHash !== options.expectedDestinationHash) {
219
+ return { success: false, error: `Move conflict: destination changed since it was read: ${destinationPath}` };
220
+ }
221
+ }
222
+ }
223
+ const parent = dirname(safeDestinationPath);
224
+ if (!existsSync(parent))
225
+ mkdirSync(parent, { recursive: true });
226
+ renameSync(safeSourcePath, safeDestinationPath);
227
+ return { success: true };
228
+ }
229
+ catch (e) {
230
+ return { success: false, error: String(e) };
231
+ }
232
+ }
233
+ async listTree(path, options = {}) {
234
+ try {
235
+ const maxDepth = Math.max(0, Math.min(5, Math.floor(options.maxDepth ?? 3)));
236
+ const maxEntriesPerDirectory = Math.max(1, Math.min(500, Math.floor(options.maxEntriesPerDirectory ?? 500)));
237
+ const maxNodes = Math.max(1, Math.min(20_000, Math.floor(options.maxNodes ?? 20_000)));
238
+ const root = this.buildTree(this.resolvePath(path), maxDepth, 0, { remaining: maxNodes }, maxEntriesPerDirectory);
239
+ return { success: true, data: root };
240
+ }
241
+ catch (e) {
242
+ return { success: false, error: String(e) };
243
+ }
244
+ }
245
+ async searchFiles(pattern, basePath, options = {}) {
246
+ let safeBasePath;
247
+ try {
248
+ safeBasePath = this.resolvePath(basePath);
249
+ }
250
+ catch (e) {
251
+ return { success: false, error: String(e) };
252
+ }
253
+ const normalizedPattern = String(pattern || '').trim().replace(/\\/g, '/');
254
+ if (!normalizedPattern)
255
+ return { success: false, error: 'File search pattern is required' };
256
+ const offset = Math.max(0, Math.floor(options.offset || 0));
257
+ const limit = Math.max(1, Math.min(500, Math.floor(options.limit || 100)));
258
+ if (options.signal?.aborted) {
259
+ return { success: false, error: 'File search aborted', data: { matches: [], offset, limit, truncated: false } };
260
+ }
261
+ try {
262
+ const args = [
263
+ '--files',
264
+ '--hidden',
265
+ `--glob=${normalizedPattern}`,
266
+ ...CODE_SEARCH_EXCLUDE_GLOBS.map(pattern => `--glob=!${pattern}`),
267
+ '--glob=!**/.env',
268
+ '--glob=!**/.env.local',
269
+ '--glob=!**/.env.*.local',
270
+ '.',
271
+ ];
272
+ const { stdout } = await execFileAsync('rg', args, {
273
+ cwd: safeBasePath,
274
+ timeout: 10_000,
275
+ maxBuffer: 1024 * 1024,
276
+ signal: options.signal,
277
+ });
278
+ const matches = stdout
279
+ .split(/\r?\n/)
280
+ .map(path => path.trim())
281
+ .filter(Boolean)
282
+ .map(path => resolveNativePath(safeBasePath, path))
283
+ .sort((left, right) => {
284
+ const leftRelative = relative(safeBasePath, left).replace(/\\/g, '/');
285
+ const rightRelative = relative(safeBasePath, right).replace(/\\/g, '/');
286
+ const depthDelta = leftRelative.split('/').length - rightRelative.split('/').length;
287
+ return depthDelta || leftRelative.localeCompare(rightRelative);
288
+ });
289
+ return { success: true, data: { matches: matches.slice(offset, offset + limit), offset, limit, truncated: matches.length > offset + limit } };
290
+ }
291
+ catch (e) {
292
+ if (this.isAbortError(e) || e.code === 'ABORT_ERR') {
293
+ await this.delay(50);
294
+ return { success: false, error: 'File search aborted' };
295
+ }
296
+ if (e.code === 1 || e.exitCode === 1)
297
+ return { success: true, data: { matches: [] } };
298
+ if (e.code !== 'ENOENT') {
299
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
300
+ }
301
+ const matches = this.globSync(normalizedPattern, safeBasePath);
302
+ return { success: true, data: { matches: matches.slice(offset, offset + limit), offset, limit, truncated: matches.length > offset + limit } };
303
+ }
304
+ }
305
+ async searchContent(pattern, basePath, filePattern, caseInsensitive) {
306
+ const result = await this.searchContentPage(pattern, basePath, filePattern, caseInsensitive);
307
+ return result.success
308
+ ? { success: true, data: result.data?.hits || [] }
309
+ : { success: false, error: result.error, data: [] };
310
+ }
311
+ async searchContentBatch(requests) {
312
+ if (requests.length < 2) {
313
+ return Promise.all(requests.map(request => this.searchContentPage(request.pattern, request.basePath, request.filePattern, request.caseInsensitive, request.options)));
314
+ }
315
+ const first = requests[0];
316
+ const batchKey = (request) => JSON.stringify({
317
+ basePath: resolveNativePath(request.basePath),
318
+ filePattern: request.filePattern || '',
319
+ caseInsensitive: Boolean(request.caseInsensitive),
320
+ contextBefore: Math.max(0, Math.min(20, Math.floor(request.options?.contextBefore || 0))),
321
+ contextAfter: Math.max(0, Math.min(20, Math.floor(request.options?.contextAfter || 0))),
322
+ multiline: Boolean(request.options?.multiline),
323
+ fileType: request.options?.fileType || '',
324
+ maxColumns: Math.max(120, Math.min(2_000, Math.floor(request.options?.maxColumns || 500))),
325
+ });
326
+ const compatible = requests.every(request => batchKey(request) === batchKey(first))
327
+ && !first.options?.multiline
328
+ && requests.every(request => {
329
+ try {
330
+ void new RegExp(request.pattern, request.caseInsensitive ? 'i' : '');
331
+ return true;
332
+ }
333
+ catch {
334
+ return false;
335
+ }
336
+ });
337
+ if (!compatible) {
338
+ return Promise.all(requests.map(request => this.searchContentPage(request.pattern, request.basePath, request.filePattern, request.caseInsensitive, request.options)));
339
+ }
340
+ let safeBasePath;
341
+ try {
342
+ safeBasePath = this.resolvePath(first.basePath);
343
+ }
344
+ catch (error) {
345
+ return requests.map(request => ({
346
+ success: false,
347
+ error: String(error),
348
+ data: {
349
+ hits: [],
350
+ totalMatches: 0,
351
+ offset: Math.max(0, Math.floor(request.options?.offset || 0)),
352
+ limit: Math.max(1, Math.min(MAX_SEARCH_LIMIT, Math.floor(request.options?.limit || DEFAULT_SEARCH_LIMIT))),
353
+ truncated: false,
354
+ },
355
+ }));
356
+ }
357
+ const contextBefore = Math.max(0, Math.min(20, Math.floor(first.options?.contextBefore || 0)));
358
+ const contextAfter = Math.max(0, Math.min(20, Math.floor(first.options?.contextAfter || 0)));
359
+ const maxColumns = Math.max(120, Math.min(2_000, Math.floor(first.options?.maxColumns || 500)));
360
+ const args = [
361
+ '--json',
362
+ '--hidden',
363
+ `--max-columns=${maxColumns}`,
364
+ '--max-columns-preview',
365
+ ...CODE_SEARCH_EXCLUDE_GLOBS.map(pattern => `--glob=!${pattern}`),
366
+ '--glob=!.env',
367
+ '--glob=!.env.local',
368
+ '--glob=!.env.*.local',
369
+ ];
370
+ if (first.caseInsensitive)
371
+ args.push('--ignore-case');
372
+ if (contextBefore > 0)
373
+ args.push('-B', String(contextBefore));
374
+ if (contextAfter > 0)
375
+ args.push('-A', String(contextAfter));
376
+ if (first.options?.fileType)
377
+ args.push('--type', first.options.fileType);
378
+ if (first.filePattern)
379
+ args.push(`--glob=${first.filePattern}`);
380
+ for (const request of requests)
381
+ args.push('-e', request.pattern);
382
+ args.push('--', '.');
383
+ try {
384
+ const { stdout } = await execFileAsync('rg', args, {
385
+ cwd: safeBasePath,
386
+ timeout: 15_000,
387
+ maxBuffer: 8 * 1024 * 1024,
388
+ signal: first.options?.signal,
389
+ });
390
+ const events = [];
391
+ for (const line of stdout.split(/\r?\n/)) {
392
+ if (!line)
393
+ continue;
394
+ try {
395
+ const event = JSON.parse(line);
396
+ if (event.type !== 'match' && event.type !== 'context')
397
+ continue;
398
+ const relativePath = event.data?.path?.text;
399
+ const lineNumber = Number(event.data?.line_number);
400
+ const text = String(event.data?.lines?.text || '').replace(/\r?\n$/, '');
401
+ if (!relativePath || !Number.isFinite(lineNumber))
402
+ continue;
403
+ events.push({ type: event.type, file: resolveNativePath(safeBasePath, relativePath), line: lineNumber, text });
404
+ }
405
+ catch { }
406
+ }
407
+ const matches = events.filter(event => event.type === 'match');
408
+ return requests.map(request => {
409
+ const tester = new RegExp(request.pattern, request.caseInsensitive ? 'i' : '');
410
+ const matching = matches.filter(match => tester.test(match.text));
411
+ const offset = Math.max(0, Math.floor(request.options?.offset || 0));
412
+ const limit = Math.max(1, Math.min(MAX_SEARCH_LIMIT, Math.floor(request.options?.limit || DEFAULT_SEARCH_LIMIT)));
413
+ const selected = matching.slice(offset, offset + limit);
414
+ const hits = selected.map(match => {
415
+ const context = events
416
+ .filter(event => event.type === 'context' && event.file === match.file && event.line >= match.line - contextBefore && event.line <= match.line + contextAfter)
417
+ .map(event => `${event.line}: ${event.text}`)
418
+ .join('\n');
419
+ return { file: match.file, line: match.line, text: match.text, ...(context ? { context } : {}) };
420
+ });
421
+ return {
422
+ success: true,
423
+ data: { hits, totalMatches: matching.length, offset, limit, truncated: matching.length > offset + limit },
424
+ };
425
+ });
426
+ }
427
+ catch {
428
+ return Promise.all(requests.map(request => this.searchContentPage(request.pattern, request.basePath, request.filePattern, request.caseInsensitive, request.options)));
429
+ }
430
+ }
431
+ async searchContentPage(pattern, basePath, filePattern, caseInsensitive, options = {}) {
432
+ let safeBasePath;
433
+ try {
434
+ safeBasePath = this.resolvePath(basePath);
435
+ }
436
+ catch (e) {
437
+ return { success: false, error: String(e), data: { hits: [], totalMatches: 0, offset: 0, limit: DEFAULT_SEARCH_LIMIT, truncated: false } };
438
+ }
439
+ const offset = Math.max(0, Math.floor(options.offset || 0));
440
+ const limit = Math.max(1, Math.min(MAX_SEARCH_LIMIT, Math.floor(options.limit || DEFAULT_SEARCH_LIMIT)));
441
+ const contextBefore = Math.max(0, Math.min(20, Math.floor(options.contextBefore || 0)));
442
+ const contextAfter = Math.max(0, Math.min(20, Math.floor(options.contextAfter || 0)));
443
+ const maxColumns = Math.max(120, Math.min(2_000, Math.floor(options.maxColumns || 500)));
444
+ const maxMatchesPerFile = options.maxMatchesPerFile === undefined
445
+ ? (limit >= 100 && offset === 0 ? 24 : 0)
446
+ : Math.max(1, Math.min(100, Math.floor(options.maxMatchesPerFile)));
447
+ if (options.signal?.aborted) {
448
+ return { success: false, error: 'Content search aborted', data: { hits: [], totalMatches: 0, offset, limit, truncated: false } };
449
+ }
450
+ try {
451
+ const args = [
452
+ '--json',
453
+ '--hidden',
454
+ `--max-columns=${maxColumns}`,
455
+ '--max-columns-preview',
456
+ ...CODE_SEARCH_EXCLUDE_GLOBS.map(pattern => `--glob=!${pattern}`),
457
+ '--glob=!.env',
458
+ '--glob=!.env.local',
459
+ '--glob=!.env.*.local',
460
+ ];
461
+ if (caseInsensitive)
462
+ args.push('--ignore-case');
463
+ if (maxMatchesPerFile > 0)
464
+ args.push(`--max-count=${maxMatchesPerFile}`);
465
+ if (options.multiline)
466
+ args.push('-U', '--multiline-dotall');
467
+ if (contextBefore > 0)
468
+ args.push('-B', String(contextBefore));
469
+ if (contextAfter > 0)
470
+ args.push('-A', String(contextAfter));
471
+ if (options.fileType)
472
+ args.push('--type', options.fileType);
473
+ if (filePattern) {
474
+ args.push(`--glob=${filePattern}`);
475
+ }
476
+ args.push('--', pattern, '.');
477
+ const { stdout } = await execFileAsync('rg', args, { cwd: safeBasePath, timeout: 15_000, maxBuffer: 8 * 1024 * 1024, signal: options.signal });
478
+ const output = stdout.trim();
479
+ if (!output)
480
+ return { success: true, data: { hits: [], totalMatches: 0, offset, limit, truncated: false } };
481
+ const events = [];
482
+ for (const line of output.split(/\r?\n/)) {
483
+ try {
484
+ const event = JSON.parse(line);
485
+ if (event.type !== 'match' && event.type !== 'context')
486
+ continue;
487
+ const relativePath = event.data?.path?.text;
488
+ const lineNumber = Number(event.data?.line_number);
489
+ const text = String(event.data?.lines?.text || '').replace(/\r?\n$/, '');
490
+ if (!relativePath || !Number.isFinite(lineNumber))
491
+ continue;
492
+ events.push({
493
+ type: event.type,
494
+ file: resolveNativePath(safeBasePath, relativePath),
495
+ line: lineNumber,
496
+ text,
497
+ });
498
+ }
499
+ catch { }
500
+ }
501
+ const matches = events.filter(event => event.type === 'match');
502
+ const selected = matches.slice(offset, offset + limit);
503
+ const hits = selected.map(match => {
504
+ const context = events
505
+ .filter(event => event.type === 'context' && event.file === match.file && event.line >= match.line - contextBefore && event.line <= match.line + contextAfter)
506
+ .map(event => `${event.line}: ${event.text}`)
507
+ .join('\n');
508
+ return {
509
+ file: match.file,
510
+ line: match.line,
511
+ text: match.text,
512
+ ...(context ? { context } : {}),
513
+ };
514
+ });
515
+ return {
516
+ success: true,
517
+ data: {
518
+ hits,
519
+ totalMatches: matches.length,
520
+ offset,
521
+ limit,
522
+ truncated: matches.length > offset + limit,
523
+ },
524
+ };
525
+ }
526
+ catch (e) {
527
+ if (this.isAbortError(e) || e.code === 'ABORT_ERR') {
528
+ await this.delay(50);
529
+ return { success: false, error: 'Content search aborted', data: { hits: [], totalMatches: 0, offset, limit, truncated: false } };
530
+ }
531
+ if (e.code === 1 || e.exitCode === 1)
532
+ return { success: true, data: { hits: [], totalMatches: 0, offset, limit, truncated: false } };
533
+ if (e.code === 'ENOENT') {
534
+ const matches = this.searchContentFallback(pattern, safeBasePath, filePattern, caseInsensitive, {
535
+ contextBefore,
536
+ contextAfter,
537
+ maxMatchesPerFile,
538
+ signal: options.signal,
539
+ });
540
+ return {
541
+ success: true,
542
+ data: {
543
+ hits: matches.slice(offset, offset + limit),
544
+ totalMatches: matches.length,
545
+ offset,
546
+ limit,
547
+ truncated: matches.length > offset + limit,
548
+ },
549
+ };
550
+ }
551
+ return { success: false, error: e instanceof Error ? e.message : String(e), data: { hits: [], totalMatches: 0, offset, limit, truncated: false } };
552
+ }
553
+ }
554
+ async webSearch(query) {
555
+ const searchQuery = String(query.query || '').trim();
556
+ if (!searchQuery)
557
+ return { success: false, error: 'Search query is required' };
558
+ const limit = Math.max(1, Math.min(10, Number(query.limit) || 5));
559
+ const region = String(query.region || 'wt-wt');
560
+ const freshness = typeof query.freshness === 'string' ? query.freshness : undefined;
561
+ const domains = Array.isArray(query.domains)
562
+ ? query.domains.map(domain => this.normalizeDomainFilter(domain)).filter(Boolean)
563
+ : [];
564
+ const effectiveQuery = domains.length > 0
565
+ ? `${searchQuery} ${domains.map(domain => `site:${domain}`).join(' OR ')}`
566
+ : searchQuery;
567
+ const errors = [];
568
+ if (!freshness) {
569
+ try {
570
+ const results = await this.searchDuckDuckGoInstant(effectiveQuery, limit, region);
571
+ if (results.length > 0) {
572
+ return { success: true, data: { results, provider: 'duckduckgo_instant', query: effectiveQuery } };
573
+ }
574
+ }
575
+ catch (e) {
576
+ errors.push(`duckduckgo_instant: ${e instanceof Error ? e.message : String(e)}`);
577
+ }
578
+ }
579
+ try {
580
+ const htmlResults = await this.searchDuckDuckGoHtml(effectiveQuery, limit, region, freshness);
581
+ if (htmlResults.length > 0) {
582
+ return { success: true, data: { results: htmlResults, provider: 'duckduckgo_html', query: effectiveQuery } };
583
+ }
584
+ }
585
+ catch (e) {
586
+ errors.push(`duckduckgo_html: ${e instanceof Error ? e.message : String(e)}`);
587
+ }
588
+ try {
589
+ const bingResults = await this.searchBingHtml(effectiveQuery, limit, freshness);
590
+ if (bingResults.length > 0) {
591
+ return { success: true, data: { results: bingResults, provider: 'bing_html', query: effectiveQuery } };
592
+ }
593
+ }
594
+ catch (e) {
595
+ errors.push(`bing_html: ${e instanceof Error ? e.message : String(e)}`);
596
+ }
597
+ return errors.length > 0
598
+ ? { success: false, error: `No web search provider returned results. ${errors.join('; ')}` }
599
+ : { success: true, data: { results: [], provider: 'none', query: effectiveQuery } };
600
+ }
601
+ async searchCodeSymbols(query) {
602
+ try {
603
+ const safeRootPath = this.resolvePath(query.workspacePath);
604
+ const requestedPath = typeof query.path === 'string' && query.path.trim()
605
+ ? resolveNativePath(safeRootPath, query.path)
606
+ : safeRootPath;
607
+ const requestedFile = existsSync(requestedPath) && statSync(requestedPath).isFile();
608
+ const safeWorkspacePath = this.resolvePath(requestedFile ? dirname(requestedPath) : requestedPath);
609
+ const exactRequestedFile = requestedFile ? resolveNativePath(requestedPath).toLowerCase() : undefined;
610
+ const limit = query.limit || 10;
611
+ const escapedQuery = this.escapeRegex(query.query);
612
+ const symbolPattern = query.exact ? escapedQuery : `\\w*${escapedQuery}\\w*`;
613
+ const pattern = [
614
+ `(?:function|class|interface|type|enum|const|let|var)\\s+${symbolPattern}\\b`,
615
+ `(?:async\\s+)?def\\s+${symbolPattern}\\b`,
616
+ `(?:pub(?:\\([^)]*\\))?\\s+)?(?:async\\s+)?fn\\s+${symbolPattern}\\b`,
617
+ `func\\s+(?:\\([^)]*\\)\\s*)?${symbolPattern}\\b`,
618
+ `(?:class|interface|record|struct|enum|trait|protocol|object)\\s+${symbolPattern}\\b`,
619
+ ].map(value => `(?:${value})`).join('|');
620
+ const results = [];
621
+ let ripgrepUnavailable = false;
622
+ const args = [
623
+ '--line-number',
624
+ '--no-heading',
625
+ '--hidden',
626
+ '--ignore-case',
627
+ '--max-count=80',
628
+ '--max-columns=500',
629
+ '--max-columns-preview',
630
+ '--glob=*.{ts,tsx,js,jsx,mjs,cjs,py,pyi,rs,go,java,kt,kts,cs,c,cc,cpp,cxx,h,hpp,swift,scala,rb,php}',
631
+ ...CODE_SEARCH_EXCLUDE_GLOBS.map(exclude => `--glob=!${exclude}`),
632
+ '--',
633
+ pattern,
634
+ '.',
635
+ ];
636
+ try {
637
+ const { stdout } = await execFileAsync('rg', args, { cwd: safeWorkspacePath, timeout: 8000, maxBuffer: 512 * 1024 });
638
+ const output = stdout.trim();
639
+ if (output) {
640
+ for (const line of output.split('\n').slice(0, Math.max(40, (query.limit || 10) * 8))) {
641
+ const match = line.match(/^(.+?):(\d+):(.*)$/);
642
+ if (!match)
643
+ continue;
644
+ const filePath = resolveNativePath(safeWorkspacePath, match[1]);
645
+ if (exactRequestedFile && filePath.toLowerCase() !== exactRequestedFile)
646
+ continue;
647
+ const lineNum = parseInt(match[2]);
648
+ const text = match[3].trim();
649
+ const declaration = this.extractSymbolDeclaration(text);
650
+ if (!declaration)
651
+ continue;
652
+ const requestedKinds = Array.isArray(query.kinds)
653
+ ? query.kinds.map((kind) => String(kind))
654
+ : query.kind ? [query.kind] : [];
655
+ if (requestedKinds.length > 0 && !requestedKinds.includes(declaration.kind))
656
+ continue;
657
+ const queryLower = query.query.toLowerCase();
658
+ const symbolLower = declaration.name.toLowerCase();
659
+ const score = symbolLower === queryLower
660
+ ? 1
661
+ : symbolLower.startsWith(queryLower)
662
+ ? 0.9
663
+ : symbolLower.includes(queryLower)
664
+ ? 0.75
665
+ : 0.5;
666
+ results.push({
667
+ id: `sym_${lineNum}_${filePath.slice(-20)}`,
668
+ path: relative(safeRootPath, filePath).replace(/\\/g, '/'),
669
+ title: declaration.name,
670
+ subtitle: text.slice(0, 120),
671
+ line: lineNum,
672
+ startLine: lineNum,
673
+ endLine: lineNum + 5,
674
+ score,
675
+ source: 'symbol',
676
+ symbolKind: declaration.kind,
677
+ preview: text,
678
+ });
679
+ }
680
+ }
681
+ }
682
+ catch (e) {
683
+ if (e.code === 'ENOENT')
684
+ ripgrepUnavailable = true;
685
+ else if (e.code !== 1 && e.exitCode !== 1) {
686
+ return { success: false, error: e instanceof Error ? e.message : String(e), data: [] };
687
+ }
688
+ }
689
+ const fallback = ripgrepUnavailable
690
+ ? this.searchCodeSymbolsFallback(query.query, safeWorkspacePath, limit, safeRootPath, query.exact === true)
691
+ .filter(hit => !exactRequestedFile || resolveNativePath(safeRootPath, hit.path).toLowerCase() === exactRequestedFile)
692
+ : [];
693
+ return {
694
+ success: true,
695
+ data: results.length > 0
696
+ ? results
697
+ .filter((hit, index, all) => all.findIndex(other => other.path.toLowerCase() === hit.path.toLowerCase()
698
+ && other.title.toLowerCase() === hit.title.toLowerCase()) === index)
699
+ .sort((left, right) => (right.score || 0) - (left.score || 0) || left.path.localeCompare(right.path) || (left.line || 0) - (right.line || 0))
700
+ .slice(0, limit)
701
+ : fallback,
702
+ };
703
+ }
704
+ catch (e) {
705
+ return { success: false, error: e instanceof Error ? e.message : String(e), data: [] };
706
+ }
707
+ }
708
+ inferSymbolKind(text) {
709
+ if (/\bclass\b/.test(text))
710
+ return 'class';
711
+ if (/\binterface\b/.test(text))
712
+ return 'interface';
713
+ if (/\btype\b/.test(text))
714
+ return 'type';
715
+ if (/\benum\b/.test(text))
716
+ return 'enum';
717
+ if (/\bfunction\b/.test(text))
718
+ return 'function';
719
+ return 'constant';
720
+ }
721
+ extractSymbolDeclaration(text) {
722
+ const patterns = [
723
+ { regex: /\b(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/, kind: 'function' },
724
+ { regex: /^\s*(?:async\s+)?def\s+([A-Za-z_]\w*)/, kind: 'function' },
725
+ { regex: /\b(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+([A-Za-z_]\w*)/, kind: 'function' },
726
+ { regex: /^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/, kind: 'function' },
727
+ { regex: /\bclass\s+([A-Za-z_$][\w$]*)/, kind: 'class' },
728
+ { regex: /\binterface\s+([A-Za-z_$][\w$]*)/, kind: 'interface' },
729
+ { regex: /\b(?:type|record|struct)\s+([A-Za-z_$][\w$]*)/, kind: 'type' },
730
+ { regex: /\b(?:enum)\s+([A-Za-z_$][\w$]*)/, kind: 'enum' },
731
+ { regex: /\b(?:trait|protocol)\s+([A-Za-z_$][\w$]*)/, kind: 'interface' },
732
+ { regex: /\b(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)/, kind: 'constant' },
733
+ ];
734
+ for (const pattern of patterns) {
735
+ const match = text.match(pattern.regex);
736
+ if (match?.[1])
737
+ return { name: match[1], kind: pattern.kind };
738
+ }
739
+ return null;
740
+ }
741
+ escapeRegex(s) {
742
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
743
+ }
744
+ async getCodeMap(query) {
745
+ try {
746
+ const basePath = this.resolvePath(query.workspacePath);
747
+ for (const target of query.targetPaths || (query.path ? [query.path] : [])) {
748
+ this.resolvePath(isAbsolute(target) ? target : join(basePath, target));
749
+ }
750
+ const targetPaths = this.resolveCodeMapTargets(basePath, query);
751
+ const map = [];
752
+ for (const target of targetPaths) {
753
+ const fullPath = this.resolvePath(isAbsolute(target) ? target : join(basePath, target));
754
+ if (!existsSync(fullPath))
755
+ continue;
756
+ const node = this.buildCodeMapNode(fullPath, basePath, query.depth || 2, 0, query.maxChildrenPerPath || 24);
757
+ if (node)
758
+ map.push(node);
759
+ }
760
+ return { success: true, data: { map, source: 'filesystem' } };
761
+ }
762
+ catch (e) {
763
+ return { success: false, error: String(e), data: { map: [] } };
764
+ }
765
+ }
766
+ resolveCodeMapTargets(basePath, query) {
767
+ const explicit = query.targetPaths?.length ? query.targetPaths : query.path ? [query.path] : [];
768
+ if (explicit.length > 0)
769
+ return explicit;
770
+ const roots = ['src', 'app', 'pages', 'components', 'packages', 'frontend', 'client', 'web', 'electron', 'main', 'renderer']
771
+ .filter(target => existsSync(join(basePath, target)));
772
+ const tokens = String(query.query || '')
773
+ .toLowerCase()
774
+ .replace(/[^a-z0-9_\u4e00-\u9fa5]+/g, ' ')
775
+ .split(/\s+/)
776
+ .filter(token => token.length >= 2)
777
+ .slice(0, 12);
778
+ const scored = roots
779
+ .map(root => {
780
+ const lower = root.toLowerCase();
781
+ const score = tokens.some(token => lower.includes(token) || token.includes(lower)) ? 2 : 1;
782
+ return { root, score };
783
+ })
784
+ .sort((a, b) => b.score - a.score || a.root.localeCompare(b.root))
785
+ .map(item => item.root);
786
+ return (scored.length > 0 ? scored : ['src']).slice(0, query.maxPaths || 8);
787
+ }
788
+ buildCodeMapNode(absPath, basePath, maxDepth, depth, maxChildrenPerPath) {
789
+ const relPath = relative(basePath, absPath).replace(/\\/g, '/');
790
+ const stat = statSync(absPath);
791
+ if (stat.isFile()) {
792
+ if (!/\.(ts|tsx|js|jsx)$/.test(absPath))
793
+ return null;
794
+ const exports = this.extractExports(absPath);
795
+ if (exports.length === 0)
796
+ return null;
797
+ return {
798
+ id: `map_${relPath}`,
799
+ kind: 'symbol',
800
+ title: relPath.split('/').pop() || relPath,
801
+ path: relPath,
802
+ summary: exports.slice(0, 5).join(', '),
803
+ score: exports.length * 0.1,
804
+ children: [],
805
+ };
806
+ }
807
+ if (!stat.isDirectory() || depth >= maxDepth)
808
+ return null;
809
+ const name = relPath.split('/').pop() || relPath;
810
+ if (['node_modules', '.git', 'dist', 'build', '.turboflux'].includes(name))
811
+ return null;
812
+ const children = [];
813
+ try {
814
+ const entries = readdirSync(absPath, { withFileTypes: true })
815
+ .filter(e => !e.isSymbolicLink() && !this.shouldSkipEntry(e.name))
816
+ .sort((a, b) => (a.isDirectory() === b.isDirectory() ? a.name.localeCompare(b.name) : a.isDirectory() ? -1 : 1));
817
+ for (const entry of entries.slice(0, Math.max(1, maxChildrenPerPath))) {
818
+ const child = this.buildCodeMapNode(join(absPath, entry.name), basePath, maxDepth, depth + 1, maxChildrenPerPath);
819
+ if (child)
820
+ children.push(child);
821
+ }
822
+ }
823
+ catch { }
824
+ if (children.length === 0)
825
+ return null;
826
+ return {
827
+ id: `map_${relPath}`,
828
+ kind: 'module',
829
+ title: name,
830
+ path: relPath,
831
+ summary: `${children.length} items`,
832
+ score: children.reduce((s, c) => s + (c.score || 0), 0),
833
+ children,
834
+ };
835
+ }
836
+ extractExports(filePath) {
837
+ try {
838
+ const content = readFileSync(filePath, 'utf-8');
839
+ const exports = [];
840
+ const regex = /export\s+(?:default\s+)?(?:function|const|let|var|class|interface|type|enum)\s+(\w+)/g;
841
+ let match;
842
+ while ((match = regex.exec(content)) !== null) {
843
+ exports.push(match[1]);
844
+ }
845
+ return exports;
846
+ }
847
+ catch {
848
+ return [];
849
+ }
850
+ }
851
+ async memoryQuery(query) {
852
+ try {
853
+ const safeWorkspacePath = this.resolvePath(query.workspacePath);
854
+ const memories = await this.memoryService.query({
855
+ workspacePath: safeWorkspacePath,
856
+ query: query.query,
857
+ kind: query.kind,
858
+ scope: query.scope,
859
+ limit: query.limit,
860
+ });
861
+ const items = memories.map(m => ({
862
+ id: m.id,
863
+ text: m.text,
864
+ content: m.text,
865
+ kind: m.kind,
866
+ confidence: m.confidence,
867
+ source: m.source,
868
+ tags: m.tags,
869
+ score: 1,
870
+ }));
871
+ return { success: true, data: { items } };
872
+ }
873
+ catch (e) {
874
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
875
+ }
876
+ }
877
+ async memoryRemember(data) {
878
+ try {
879
+ const safeWorkspacePath = this.resolvePath(data.workspacePath, 'write');
880
+ const result = await this.memoryService.remember({
881
+ workspacePath: safeWorkspacePath,
882
+ text: data.content ?? data.text ?? '',
883
+ kind: data.kind,
884
+ scope: data.scope,
885
+ tags: data.tags,
886
+ confidence: data.confidence,
887
+ conversationId: data.conversationId,
888
+ messageId: data.messageId,
889
+ });
890
+ if (!result.success)
891
+ return { success: false, error: result.error };
892
+ return { success: true, data: { id: result.id || '', deduplicated: result.deduplicated } };
893
+ }
894
+ catch (e) {
895
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
896
+ }
897
+ }
898
+ async memoryForget(data) {
899
+ try {
900
+ const safeWorkspacePath = this.resolvePath(data.workspacePath, 'write');
901
+ const result = await this.memoryService.forget({ workspacePath: safeWorkspacePath, id: data.id, reason: data.reason });
902
+ if (!result.success)
903
+ return { success: false, error: result.error };
904
+ return { success: true };
905
+ }
906
+ catch (e) {
907
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
908
+ }
909
+ }
910
+ async memoryList(workspacePath) {
911
+ try {
912
+ const safeWorkspacePath = this.resolvePath(workspacePath);
913
+ const snapshot = await this.memoryService.getSnapshot(safeWorkspacePath);
914
+ const memories = await this.memoryService.query({ workspacePath: safeWorkspacePath, limit: 100 });
915
+ const items = memories.map(m => ({ id: m.id, content: m.text, kind: m.kind }));
916
+ return { success: true, data: { snapshot, items } };
917
+ }
918
+ catch (e) {
919
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
920
+ }
921
+ }
922
+ async memoryGetRelevantInjection(params) {
923
+ try {
924
+ const safeWorkspacePath = this.resolvePath(params.workspacePath);
925
+ const result = await this.memoryService.getRelevantInjection(safeWorkspacePath, params.query);
926
+ return { success: true, data: { text: result.text, tokens: result.tokens } };
927
+ }
928
+ catch (e) {
929
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
930
+ }
931
+ }
932
+ async runCommand(command, cwd, env, timeout, approved, signal) {
933
+ let safeCwd;
934
+ try {
935
+ const validation = this.validateCommandSync(command, cwd);
936
+ if (!validation.success) {
937
+ return { success: false, error: validation.error, data: { stdout: '', stderr: validation.error || '', exitCode: 1 } };
938
+ }
939
+ if (approved !== true) {
940
+ const error = 'Command execution requires an explicit permission decision';
941
+ return { success: false, error, data: { stdout: '', stderr: error, exitCode: 1 } };
942
+ }
943
+ safeCwd = validation.cwd;
944
+ }
945
+ catch (error) {
946
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
947
+ }
948
+ return this.executeCommand(command, safeCwd, env, timeout, signal);
949
+ }
950
+ async executeCommand(command, safeCwd, env, timeout, signal) {
951
+ const { shell, shellArgs } = getShellCommand(command);
952
+ const runtimeTask = this.runtimeTaskManager.createTask({
953
+ kind: 'shell',
954
+ command,
955
+ cwd: safeCwd,
956
+ interactive: false,
957
+ });
958
+ let logPath;
959
+ try {
960
+ logPath = this.createRuntimeTaskLog(runtimeTask.id);
961
+ const proc = spawn(shell, shellArgs, {
962
+ cwd: safeCwd,
963
+ env: this.buildChildEnvironment(env),
964
+ ...getChildProcessSpawnOptions(),
965
+ });
966
+ this.runtimeTaskManager.setControl(runtimeTask.id, {
967
+ stop: () => this.stopProcessAndWait(proc),
968
+ });
969
+ this.runtimeTaskManager.markRunning(runtimeTask.id, { pid: proc.pid, logPath, outputBytes: 0, outputOffset: 0 });
970
+ return this.collectProcess(proc, timeout || 30000, runtimeTask.id, logPath, signal);
971
+ }
972
+ catch (error) {
973
+ const message = error instanceof Error ? error.message : String(error);
974
+ this.runtimeTaskManager.failTask(runtimeTask.id, message, { logPath, outputBytes: 0 });
975
+ return { success: false, error: message, data: { stdout: '', stderr: message, exitCode: 1, logPath, outputBytes: 0 } };
976
+ }
977
+ }
978
+ async readOnlyProcess(command, args, cwd, env, timeout, signal) {
979
+ return this.runProcess(command, args, cwd, env, timeout, signal, 'read');
980
+ }
981
+ async runProcess(command, args, cwd, env, timeout, signal, access = 'write') {
982
+ let runtimeTaskId;
983
+ let logPath;
984
+ try {
985
+ const safeCwd = this.resolvePath(cwd, access);
986
+ const runtimeTask = this.runtimeTaskManager.createTask({
987
+ kind: 'shell',
988
+ command: [command, ...args].join(' '),
989
+ cwd: safeCwd,
990
+ interactive: false,
991
+ metadata: { executable: command, args: [...args] },
992
+ });
993
+ runtimeTaskId = runtimeTask.id;
994
+ logPath = access === 'read' && this.getCapabilityProfile() === 'read-only'
995
+ ? undefined
996
+ : this.createRuntimeTaskLog(runtimeTask.id);
997
+ const proc = spawn(command, args, {
998
+ cwd: safeCwd,
999
+ env: this.buildChildEnvironment(env),
1000
+ ...getChildProcessSpawnOptions(),
1001
+ });
1002
+ this.runtimeTaskManager.setControl(runtimeTask.id, {
1003
+ stop: () => this.stopProcessAndWait(proc),
1004
+ });
1005
+ this.runtimeTaskManager.markRunning(runtimeTask.id, { pid: proc.pid, logPath, outputBytes: 0, outputOffset: 0 });
1006
+ return await this.collectProcess(proc, timeout || 30000, runtimeTask.id, logPath, signal);
1007
+ }
1008
+ catch (error) {
1009
+ const message = error instanceof Error ? error.message : String(error);
1010
+ if (runtimeTaskId)
1011
+ this.runtimeTaskManager.failTask(runtimeTaskId, message, { logPath, outputBytes: 0 });
1012
+ return { success: false, error: message, data: { stdout: '', stderr: message, exitCode: 1, logPath, outputBytes: 0 } };
1013
+ }
1014
+ }
1015
+ collectProcess(proc, timeout, runtimeTaskId, logPath, signal) {
1016
+ return new Promise(resolve => {
1017
+ let stdout = '';
1018
+ let stderr = '';
1019
+ let truncated = false;
1020
+ let timedOut = false;
1021
+ let aborted = false;
1022
+ let settled = false;
1023
+ let outputBytes = 0;
1024
+ let logError;
1025
+ let timeoutTimer;
1026
+ let terminationGraceTimer;
1027
+ const logWriter = logPath ? new RuntimeLogWriter(logPath, {
1028
+ onDrain: () => {
1029
+ if (!proc.stdout.destroyed)
1030
+ proc.stdout.resume();
1031
+ if (!proc.stderr.destroyed)
1032
+ proc.stderr.resume();
1033
+ },
1034
+ onError: error => { logError = error.message; },
1035
+ }) : undefined;
1036
+ const append = (current, value) => {
1037
+ if (current.length >= MAX_COMMAND_OUTPUT_CHARS) {
1038
+ truncated = true;
1039
+ return current;
1040
+ }
1041
+ const text = value.toString();
1042
+ const remaining = MAX_COMMAND_OUTPUT_CHARS - current.length;
1043
+ if (text.length > remaining)
1044
+ truncated = true;
1045
+ return current + text.slice(0, remaining);
1046
+ };
1047
+ const finish = async (result) => {
1048
+ if (settled)
1049
+ return;
1050
+ settled = true;
1051
+ if (timeoutTimer)
1052
+ clearTimeout(timeoutTimer);
1053
+ if (terminationGraceTimer)
1054
+ clearTimeout(terminationGraceTimer);
1055
+ proc.stdout.off('data', onStdout);
1056
+ proc.stderr.off('data', onStderr);
1057
+ proc.off('error', onError);
1058
+ proc.off('close', onClose);
1059
+ signal?.removeEventListener('abort', onAbort);
1060
+ await logWriter?.close();
1061
+ const finalizedResult = result.data
1062
+ ? { ...result, data: { ...result.data, logPath, outputBytes } }
1063
+ : result;
1064
+ if (runtimeTaskId)
1065
+ this.finishProcessRuntimeTask(runtimeTaskId, finalizedResult, logError);
1066
+ resolve(finalizedResult);
1067
+ };
1068
+ const recordOutput = (channel, data) => {
1069
+ outputBytes += Buffer.byteLength(data);
1070
+ if (!logWriter || logError)
1071
+ return;
1072
+ if (!logWriter.append(channel, data)) {
1073
+ proc.stdout.pause();
1074
+ proc.stderr.pause();
1075
+ }
1076
+ };
1077
+ const onStdout = (data) => {
1078
+ recordOutput('stdout', data);
1079
+ stdout = append(stdout, data);
1080
+ };
1081
+ const onStderr = (data) => {
1082
+ recordOutput('stderr', data);
1083
+ stderr = append(stderr, data);
1084
+ };
1085
+ const onError = (error) => {
1086
+ recordOutput('stderr', error.message);
1087
+ void finish({ success: false, error: aborted ? 'Command aborted' : error.message, data: { stdout, stderr, exitCode: 1, timedOut, aborted, truncated } });
1088
+ };
1089
+ const onClose = (code) => {
1090
+ const exitCode = code ?? 1;
1091
+ const success = code !== null && !timedOut && !aborted;
1092
+ const error = aborted
1093
+ ? 'Command aborted'
1094
+ : timedOut
1095
+ ? `Command timed out after ${timeout}ms`
1096
+ : code === null ? 'Command terminated without an exit code' : undefined;
1097
+ void finish({ success, error, data: { stdout, stderr, exitCode, timedOut, aborted, truncated } });
1098
+ };
1099
+ const onAbort = () => {
1100
+ if (settled || aborted)
1101
+ return;
1102
+ aborted = true;
1103
+ try {
1104
+ this.terminateProcessTree(proc);
1105
+ }
1106
+ catch (error) {
1107
+ const message = error instanceof Error ? error.message : String(error);
1108
+ void finish({
1109
+ success: false,
1110
+ error: `Command aborted; process termination failed: ${message}`,
1111
+ data: { stdout, stderr, exitCode: 1, timedOut, aborted, truncated },
1112
+ });
1113
+ return;
1114
+ }
1115
+ if (!terminationGraceTimer) {
1116
+ terminationGraceTimer = setTimeout(() => {
1117
+ void finish({
1118
+ success: false,
1119
+ error: 'Command aborted; process did not exit within the termination grace period',
1120
+ data: { stdout, stderr, exitCode: 1, timedOut, aborted, truncated },
1121
+ });
1122
+ }, COMMAND_TERMINATION_GRACE_MS);
1123
+ }
1124
+ };
1125
+ proc.stdout.on('data', onStdout);
1126
+ proc.stderr.on('data', onStderr);
1127
+ proc.on('error', onError);
1128
+ proc.on('close', onClose);
1129
+ if (signal?.aborted)
1130
+ onAbort();
1131
+ else
1132
+ signal?.addEventListener('abort', onAbort, { once: true });
1133
+ timeoutTimer = setTimeout(() => {
1134
+ if (settled || aborted)
1135
+ return;
1136
+ timedOut = true;
1137
+ try {
1138
+ this.terminateProcessTree(proc);
1139
+ }
1140
+ catch (error) {
1141
+ const message = error instanceof Error ? error.message : String(error);
1142
+ void finish({
1143
+ success: false,
1144
+ error: `Command timed out after ${timeout}ms; process termination failed: ${message}`,
1145
+ data: { stdout, stderr, exitCode: 1, timedOut, truncated },
1146
+ });
1147
+ return;
1148
+ }
1149
+ if (settled)
1150
+ return;
1151
+ terminationGraceTimer = setTimeout(() => {
1152
+ void finish({
1153
+ success: false,
1154
+ error: `Command timed out after ${timeout}ms; process did not exit within the ${COMMAND_TERMINATION_GRACE_MS}ms termination grace period`,
1155
+ data: { stdout, stderr, exitCode: 1, timedOut, truncated },
1156
+ });
1157
+ }, COMMAND_TERMINATION_GRACE_MS);
1158
+ }, Math.max(1, timeout));
1159
+ });
1160
+ }
1161
+ terminateProcessTree(proc) {
1162
+ if (!proc.pid)
1163
+ return;
1164
+ if (process.platform === 'win32') {
1165
+ const killer = spawn('taskkill.exe', ['/PID', String(proc.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
1166
+ killer.unref();
1167
+ return;
1168
+ }
1169
+ try {
1170
+ process.kill(usesProcessGroup() ? -proc.pid : proc.pid, getProcessGroupSignal());
1171
+ }
1172
+ catch {
1173
+ proc.kill('SIGTERM');
1174
+ }
1175
+ }
1176
+ stopProcessAndWait(proc) {
1177
+ if (proc.exitCode !== null || proc.signalCode !== null)
1178
+ return Promise.resolve();
1179
+ return new Promise((resolve, reject) => {
1180
+ let settled = false;
1181
+ const finish = (error) => {
1182
+ if (settled)
1183
+ return;
1184
+ settled = true;
1185
+ clearTimeout(timer);
1186
+ proc.off('close', onClose);
1187
+ proc.off('error', onError);
1188
+ if (error)
1189
+ reject(error);
1190
+ else
1191
+ resolve();
1192
+ };
1193
+ const onClose = () => finish();
1194
+ const onError = (error) => finish(error);
1195
+ const timer = setTimeout(() => {
1196
+ finish(new Error(`Process did not exit within ${COMMAND_TERMINATION_GRACE_MS}ms`));
1197
+ }, COMMAND_TERMINATION_GRACE_MS);
1198
+ proc.once('close', onClose);
1199
+ proc.once('error', onError);
1200
+ try {
1201
+ this.terminateProcessTree(proc);
1202
+ }
1203
+ catch (error) {
1204
+ finish(error instanceof Error ? error : new Error(String(error)));
1205
+ }
1206
+ });
1207
+ }
1208
+ finishProcessRuntimeTask(taskId, result, logError) {
1209
+ const output = result.data;
1210
+ const patch = {
1211
+ exitCode: output?.exitCode ?? 1,
1212
+ outputBytes: output?.outputBytes || 0,
1213
+ logPath: output?.logPath,
1214
+ metadata: {
1215
+ timedOut: output?.timedOut === true,
1216
+ truncated: output?.truncated === true,
1217
+ ...(logError ? { logError } : {}),
1218
+ },
1219
+ };
1220
+ if (result.success && (output?.exitCode ?? 0) === 0) {
1221
+ this.runtimeTaskManager.completeTask(taskId, patch);
1222
+ return;
1223
+ }
1224
+ this.runtimeTaskManager.failTask(taskId, result.error || `Process exited with code ${patch.exitCode}`, patch);
1225
+ }
1226
+ async validateCommand(command, cwd) {
1227
+ const validation = this.validateCommandSync(command, cwd);
1228
+ if (!validation.success)
1229
+ return { success: false, error: validation.error };
1230
+ return { success: true };
1231
+ }
1232
+ validateCommandSync(command, cwd) {
1233
+ try {
1234
+ this.capabilityBoundary.assertCommandAllowed();
1235
+ const safeCwd = this.resolvePath(cwd, 'write');
1236
+ return { success: true, cwd: safeCwd };
1237
+ }
1238
+ catch (error) {
1239
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1240
+ }
1241
+ }
1242
+ async startBackgroundCommand(command, cwd, env, approved) {
1243
+ const validation = this.validateCommandSync(command, cwd);
1244
+ if (!validation.success)
1245
+ return { success: false, error: validation.error };
1246
+ if (approved !== true)
1247
+ return { success: false, error: 'Command execution requires an explicit permission decision' };
1248
+ const { shell, shellArgs } = getShellCommand(command);
1249
+ return this.spawnTerminalSession({
1250
+ shell,
1251
+ shellArgs,
1252
+ shellId: DEFAULT_SHELL.id,
1253
+ shellLabel: DEFAULT_SHELL.label,
1254
+ command,
1255
+ cwd: validation.cwd,
1256
+ env,
1257
+ commandSession: true,
1258
+ });
1259
+ }
1260
+ async ptyCreate(options) {
1261
+ let safeCwd;
1262
+ try {
1263
+ this.capabilityBoundary.assertCommandAllowed();
1264
+ safeCwd = this.resolvePath(options?.cwd || this.workspaceRoot, 'write');
1265
+ }
1266
+ catch (error) {
1267
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1268
+ }
1269
+ const shell = options?.shell || DEFAULT_SHELL.command;
1270
+ return this.spawnTerminalSession({
1271
+ shell,
1272
+ shellArgs: options?.shell ? [] : DEFAULT_SHELL.args,
1273
+ shellId: options?.shell ? 'custom' : DEFAULT_SHELL.id,
1274
+ shellLabel: options?.shell ? shell : DEFAULT_SHELL.label,
1275
+ command: shell,
1276
+ cwd: safeCwd,
1277
+ env: options?.env,
1278
+ commandSession: false,
1279
+ });
1280
+ }
1281
+ async spawnTerminalSession(options) {
1282
+ let runtimeTaskId;
1283
+ let proc;
1284
+ try {
1285
+ const now = Date.now();
1286
+ const sessionId = `term_${now.toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
1287
+ const runtimeTask = this.runtimeTaskManager.createTask({
1288
+ kind: 'terminal',
1289
+ command: options.command,
1290
+ cwd: options.cwd,
1291
+ interactive: true,
1292
+ metadata: { sessionId, shellId: options.shellId, commandSession: options.commandSession },
1293
+ });
1294
+ runtimeTaskId = runtimeTask.id;
1295
+ const logPath = this.createRuntimeTaskLog(runtimeTask.id);
1296
+ proc = spawn(options.shell, options.shellArgs, {
1297
+ cwd: options.cwd,
1298
+ env: this.buildChildEnvironment(options.env),
1299
+ ...getChildProcessSpawnOptions(),
1300
+ });
1301
+ const info = {
1302
+ id: sessionId,
1303
+ pid: proc.pid ?? 0,
1304
+ shell: options.shell,
1305
+ shellId: options.shellId,
1306
+ shellLabel: options.shellLabel,
1307
+ cwd: options.cwd,
1308
+ status: 'running',
1309
+ createdAt: now,
1310
+ updatedAt: now,
1311
+ isAgentSession: true,
1312
+ title: options.command,
1313
+ command: options.command,
1314
+ runtimeTaskId: runtimeTask.id,
1315
+ logPath,
1316
+ outputBytes: 0,
1317
+ omittedBytes: 0,
1318
+ firstSeq: 1,
1319
+ lastSeq: 0,
1320
+ canWrite: true,
1321
+ };
1322
+ const writer = new RuntimeLogWriter(logPath, {
1323
+ onDrain: () => {
1324
+ const active = this.backgroundTerminals.get(sessionId);
1325
+ if (!active?.pausedForLog)
1326
+ return;
1327
+ active.pausedForLog = false;
1328
+ if (!active.proc.stdout.destroyed)
1329
+ active.proc.stdout.resume();
1330
+ if (!active.proc.stderr.destroyed)
1331
+ active.proc.stderr.resume();
1332
+ },
1333
+ onError: error => {
1334
+ const active = this.backgroundTerminals.get(sessionId);
1335
+ if (active)
1336
+ active.logError = error.message;
1337
+ },
1338
+ });
1339
+ const session = {
1340
+ info,
1341
+ proc,
1342
+ chunks: [],
1343
+ nextSeq: 1,
1344
+ bufferChars: 0,
1345
+ runtimeTaskId: runtimeTask.id,
1346
+ logPath,
1347
+ outputBytes: 0,
1348
+ omittedBytes: 0,
1349
+ writer,
1350
+ commandSession: options.commandSession,
1351
+ pausedForLog: false,
1352
+ lastRuntimeSnapshotAt: now,
1353
+ stopRequested: false,
1354
+ };
1355
+ this.backgroundTerminals.set(sessionId, session);
1356
+ this.runtimeTaskManager.setControl(runtimeTask.id, {
1357
+ stop: async () => {
1358
+ const result = await this.ptyKill(sessionId);
1359
+ if (!result.success)
1360
+ throw new Error(result.error || `Failed to stop terminal ${sessionId}`);
1361
+ },
1362
+ write: async (data) => {
1363
+ const result = await this.ptyWrite(sessionId, data);
1364
+ if (!result.success)
1365
+ throw new Error(result.error || `Failed to write terminal ${sessionId}`);
1366
+ },
1367
+ });
1368
+ this.runtimeTaskManager.markRunning(runtimeTask.id, {
1369
+ pid: proc.pid,
1370
+ logPath,
1371
+ outputBytes: 0,
1372
+ outputOffset: 0,
1373
+ });
1374
+ const append = (channel, data) => {
1375
+ const text = data.toString();
1376
+ if (!text)
1377
+ return;
1378
+ const seq = session.nextSeq++;
1379
+ session.outputBytes += Buffer.byteLength(data);
1380
+ if (!session.logError && !session.writer.append(channel, data, seq)) {
1381
+ session.pausedForLog = true;
1382
+ session.proc.stdout.pause();
1383
+ session.proc.stderr.pause();
1384
+ }
1385
+ session.chunks.push({
1386
+ seq,
1387
+ data: text,
1388
+ timestamp: Date.now(),
1389
+ });
1390
+ session.bufferChars += text.length;
1391
+ if (session.chunks.length > MAX_TERMINAL_CHUNKS) {
1392
+ const removed = session.chunks.splice(0, session.chunks.length - MAX_TERMINAL_CHUNKS);
1393
+ session.bufferChars -= removed.reduce((sum, chunk) => sum + chunk.data.length, 0);
1394
+ session.omittedBytes += removed.reduce((sum, chunk) => sum + Buffer.byteLength(chunk.data), 0);
1395
+ }
1396
+ while (session.bufferChars > MAX_TERMINAL_BUFFER_CHARS && session.chunks.length > 1) {
1397
+ const removed = session.chunks.shift();
1398
+ if (removed) {
1399
+ session.bufferChars -= removed.data.length;
1400
+ session.omittedBytes += Buffer.byteLength(removed.data);
1401
+ }
1402
+ }
1403
+ const updatedAt = Date.now();
1404
+ session.info.updatedAt = updatedAt;
1405
+ session.info.outputBytes = session.outputBytes;
1406
+ session.info.omittedBytes = session.omittedBytes;
1407
+ session.info.firstSeq = session.chunks[0]?.seq ?? session.nextSeq;
1408
+ session.info.lastSeq = session.nextSeq - 1;
1409
+ if (updatedAt - session.lastRuntimeSnapshotAt >= RUNTIME_TASK_SNAPSHOT_INTERVAL_MS) {
1410
+ session.lastRuntimeSnapshotAt = updatedAt;
1411
+ this.runtimeTaskManager.updateTask(session.runtimeTaskId, {
1412
+ outputBytes: session.outputBytes,
1413
+ outputOffset: session.outputBytes,
1414
+ metadata: {
1415
+ firstSeq: session.info.firstSeq,
1416
+ lastSeq: session.info.lastSeq,
1417
+ omittedBytes: session.omittedBytes,
1418
+ },
1419
+ });
1420
+ }
1421
+ };
1422
+ proc.stdout.on('data', data => append('stdout', data));
1423
+ proc.stderr.on('data', data => append('stderr', data));
1424
+ let processError;
1425
+ proc.on('error', (err) => {
1426
+ processError = err.message;
1427
+ session.info.error = err.message;
1428
+ session.info.updatedAt = Date.now();
1429
+ append('stderr', `\n[terminal error] ${err.message}\n`);
1430
+ });
1431
+ proc.on('close', async (code, signal) => {
1432
+ await session.writer.close();
1433
+ const stopped = session.stopRequested
1434
+ || ['stopping', 'stopped'].includes(this.runtimeTaskManager.getTask(session.runtimeTaskId)?.status || '');
1435
+ const failed = !stopped && Boolean(processError || signal || (code ?? 1) !== 0);
1436
+ session.info.status = failed ? 'error' : 'exited';
1437
+ session.info.exitCode = code;
1438
+ session.info.exitSignal = signal ?? null;
1439
+ session.info.canWrite = false;
1440
+ session.info.error = failed
1441
+ ? processError || (signal ? `Terminal exited with signal ${signal}` : `Terminal exited with code ${code ?? 1}`)
1442
+ : undefined;
1443
+ session.info.updatedAt = Date.now();
1444
+ const patch = {
1445
+ exitCode: code,
1446
+ outputBytes: session.outputBytes,
1447
+ outputOffset: session.outputBytes,
1448
+ logPath: session.logPath,
1449
+ error: undefined,
1450
+ metadata: {
1451
+ exitSignal: signal ?? null,
1452
+ durationMs: Date.now() - session.info.createdAt,
1453
+ omittedBytes: session.omittedBytes,
1454
+ firstSeq: session.info.firstSeq,
1455
+ lastSeq: session.info.lastSeq,
1456
+ ...(session.logError ? { logError: session.logError } : {}),
1457
+ },
1458
+ };
1459
+ if (stopped)
1460
+ this.runtimeTaskManager.markStopped(session.runtimeTaskId, 'Terminal stopped', patch);
1461
+ else if (!failed)
1462
+ this.runtimeTaskManager.completeTask(session.runtimeTaskId, patch);
1463
+ else
1464
+ this.runtimeTaskManager.failTask(session.runtimeTaskId, session.info.error, patch);
1465
+ this.backgroundTerminals.delete(sessionId);
1466
+ });
1467
+ return { success: true, data: { sessionId, session: info }, session, sessionId };
1468
+ }
1469
+ catch (e) {
1470
+ const message = e instanceof Error ? e.message : String(e);
1471
+ if (proc)
1472
+ this.terminateProcessTree(proc);
1473
+ if (runtimeTaskId)
1474
+ this.runtimeTaskManager.failTask(runtimeTaskId, message);
1475
+ return { success: false, error: message };
1476
+ }
1477
+ }
1478
+ async ptyWrite(sessionId, data) {
1479
+ const session = this.backgroundTerminals.get(sessionId);
1480
+ if (!session)
1481
+ return { success: false, error: `Terminal not found: ${sessionId}` };
1482
+ if (session.info.status !== 'running')
1483
+ return { success: false, error: `Terminal ${sessionId} is ${session.info.status}` };
1484
+ try {
1485
+ session.proc.stdin.write(data);
1486
+ session.info.updatedAt = Date.now();
1487
+ const firstLine = data.split(/\r?\n/).find(line => line.trim());
1488
+ if (firstLine && !session.commandSession) {
1489
+ session.info.title = firstLine.trim();
1490
+ this.runtimeTaskManager.updateTask(session.runtimeTaskId, { command: firstLine.trim() });
1491
+ }
1492
+ return { success: true };
1493
+ }
1494
+ catch (e) {
1495
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
1496
+ }
1497
+ }
1498
+ async ptyGetBuffer(sessionId, sinceSeq = 0) {
1499
+ const session = this.backgroundTerminals.get(sessionId);
1500
+ if (!session)
1501
+ return this.readRecoveredTerminal(sessionId, sinceSeq);
1502
+ const chunks = sinceSeq > 0
1503
+ ? session.chunks.filter(chunk => chunk.seq > sinceSeq)
1504
+ : session.chunks;
1505
+ const firstSeq = session.chunks[0]?.seq ?? session.nextSeq;
1506
+ const lastSeq = session.nextSeq - 1;
1507
+ return {
1508
+ success: true,
1509
+ data: chunks.map(chunk => chunk.data).join(''),
1510
+ chunks: [...chunks],
1511
+ session: { ...session.info },
1512
+ firstSeq,
1513
+ lastSeq,
1514
+ omittedBytes: sinceSeq < firstSeq - 1 ? session.omittedBytes : 0,
1515
+ };
1516
+ }
1517
+ async ptyInterruptCommand(sessionId) {
1518
+ const session = this.backgroundTerminals.get(sessionId);
1519
+ if (!session)
1520
+ return { success: false, error: `Terminal not found: ${sessionId}` };
1521
+ if (session.info.status !== 'running')
1522
+ return { success: false, error: `Terminal ${sessionId} is ${session.info.status}` };
1523
+ try {
1524
+ if (session.commandSession || process.platform === 'win32') {
1525
+ session.stopRequested = true;
1526
+ }
1527
+ if (process.platform === 'win32') {
1528
+ await this.killTerminalProcessTree(session);
1529
+ }
1530
+ else {
1531
+ try {
1532
+ process.kill(-session.info.pid, 'SIGINT');
1533
+ }
1534
+ catch {
1535
+ session.proc.kill('SIGINT');
1536
+ }
1537
+ }
1538
+ if (session.stopRequested)
1539
+ this.runtimeTaskManager.markStopping(session.runtimeTaskId);
1540
+ session.info.updatedAt = Date.now();
1541
+ return { success: true };
1542
+ }
1543
+ catch (e) {
1544
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
1545
+ }
1546
+ }
1547
+ async ptyKill(sessionId) {
1548
+ const session = this.backgroundTerminals.get(sessionId);
1549
+ if (!session)
1550
+ return { success: false, error: `Terminal not found: ${sessionId}` };
1551
+ try {
1552
+ if (session.info.status === 'running') {
1553
+ session.stopRequested = true;
1554
+ this.runtimeTaskManager.markStopping(session.runtimeTaskId);
1555
+ const closed = this.waitForTerminalClose(session, TERMINAL_KILL_TIMEOUT_MS);
1556
+ if (!session.proc.stdin.destroyed) {
1557
+ session.proc.stdin.end();
1558
+ }
1559
+ const exitedAfterStdinClose = await this.waitForTerminalClose(session, 1000);
1560
+ if (!exitedAfterStdinClose) {
1561
+ await this.killTerminalProcessTree(session);
1562
+ }
1563
+ const didClose = await closed;
1564
+ if (!didClose && session.info.status === 'running') {
1565
+ this.runtimeTaskManager.updateTask(session.runtimeTaskId, {
1566
+ error: `Terminal did not exit within ${TERMINAL_KILL_TIMEOUT_MS}ms`,
1567
+ });
1568
+ return { success: false, error: `Terminal ${sessionId} did not exit within ${TERMINAL_KILL_TIMEOUT_MS}ms` };
1569
+ }
1570
+ await session.writer.close();
1571
+ }
1572
+ session.info.status = 'exited';
1573
+ session.info.canWrite = false;
1574
+ session.info.error = undefined;
1575
+ session.info.updatedAt = Date.now();
1576
+ this.runtimeTaskManager.markStopped(session.runtimeTaskId, 'Terminal stopped', {
1577
+ exitCode: session.info.exitCode,
1578
+ outputBytes: session.outputBytes,
1579
+ outputOffset: session.outputBytes,
1580
+ logPath: session.logPath,
1581
+ error: undefined,
1582
+ metadata: {
1583
+ omittedBytes: session.omittedBytes,
1584
+ firstSeq: session.info.firstSeq,
1585
+ lastSeq: session.info.lastSeq,
1586
+ ...(session.logError ? { logError: session.logError } : {}),
1587
+ },
1588
+ });
1589
+ return { success: true };
1590
+ }
1591
+ catch (e) {
1592
+ this.runtimeTaskManager.updateTask(session.runtimeTaskId, {
1593
+ error: e instanceof Error ? e.message : String(e),
1594
+ });
1595
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
1596
+ }
1597
+ }
1598
+ async ptyList() {
1599
+ const sessions = Array.from(this.backgroundTerminals.values())
1600
+ .map(session => ({ ...session.info }));
1601
+ const knownIds = new Set(sessions.map(session => session.id));
1602
+ for (const task of this.runtimeTaskManager.listTasks({ kind: 'terminal' })) {
1603
+ const sessionId = typeof task.metadata?.sessionId === 'string' ? task.metadata.sessionId : undefined;
1604
+ if (!sessionId || knownIds.has(sessionId))
1605
+ continue;
1606
+ const shellId = typeof task.metadata?.shellId === 'string' ? task.metadata.shellId : 'recovered';
1607
+ sessions.push({
1608
+ id: sessionId,
1609
+ pid: task.pid ?? 0,
1610
+ shell: shellId,
1611
+ shellId,
1612
+ shellLabel: shellId,
1613
+ cwd: task.cwd || this.workspaceRoot,
1614
+ status: task.status === 'running' || task.status === 'starting'
1615
+ ? 'running'
1616
+ : task.status === 'failed' || task.status === 'orphaned' ? 'error' : 'exited',
1617
+ createdAt: task.startedAt,
1618
+ updatedAt: task.updatedAt,
1619
+ isAgentSession: true,
1620
+ title: task.command || shellId,
1621
+ command: task.command,
1622
+ runtimeTaskId: task.id,
1623
+ logPath: task.logPath,
1624
+ outputBytes: task.outputBytes,
1625
+ omittedBytes: typeof task.metadata?.omittedBytes === 'number' ? task.metadata.omittedBytes : 0,
1626
+ firstSeq: typeof task.metadata?.firstSeq === 'number' ? task.metadata.firstSeq : undefined,
1627
+ lastSeq: typeof task.metadata?.lastSeq === 'number' ? task.metadata.lastSeq : undefined,
1628
+ exitCode: task.exitCode,
1629
+ error: task.error,
1630
+ recovered: task.metadata?.recovered === true,
1631
+ canWrite: false,
1632
+ });
1633
+ }
1634
+ sessions
1635
+ .sort((a, b) => a.createdAt - b.createdAt);
1636
+ return { success: true, data: sessions, sessions };
1637
+ }
1638
+ readRecoveredTerminal(sessionId, sinceSeq) {
1639
+ const task = this.runtimeTaskManager.listTasks({ kind: 'terminal' }).find(item => item.metadata?.sessionId === sessionId);
1640
+ if (!task)
1641
+ return { success: false, error: `Terminal not found: ${sessionId}` };
1642
+ const listed = this.runtimeTaskToSession(task);
1643
+ if (!task.logPath || !existsSync(task.logPath)) {
1644
+ return { success: true, data: '', chunks: [], session: listed, firstSeq: 1, lastSeq: 0, omittedBytes: 0 };
1645
+ }
1646
+ try {
1647
+ const fileSize = statSync(task.logPath).size;
1648
+ const requestedOffset = Math.max(0, fileSize - MAX_RECOVERED_TERMINAL_READ_BYTES);
1649
+ const output = this.runtimeTaskManager.readTaskOutput(task.id, requestedOffset, MAX_RECOVERED_TERMINAL_READ_BYTES);
1650
+ const lines = output.content.split(/\r?\n/);
1651
+ if (requestedOffset > 0)
1652
+ lines.shift();
1653
+ const records = lines.filter(Boolean).flatMap((line, index) => {
1654
+ try {
1655
+ const record = JSON.parse(line);
1656
+ return [{
1657
+ seq: typeof record.seq === 'number' ? record.seq : index + 1,
1658
+ timestamp: record.timestamp || task.startedAt,
1659
+ data: String(record.data || ''),
1660
+ }];
1661
+ }
1662
+ catch {
1663
+ return [];
1664
+ }
1665
+ });
1666
+ const chunks = sinceSeq > 0 ? records.filter(record => record.seq > sinceSeq) : records;
1667
+ const firstSeq = records[0]?.seq ?? 1;
1668
+ const currentOutputBytes = records.reduce((total, record) => total + Buffer.byteLength(record.data), 0);
1669
+ const knownOmittedBytes = Math.max(typeof task.metadata?.omittedBytes === 'number' ? task.metadata.omittedBytes : 0, Math.max(0, (task.outputBytes || 0) - currentOutputBytes));
1670
+ return {
1671
+ success: true,
1672
+ data: chunks.map(chunk => chunk.data).join(''),
1673
+ chunks,
1674
+ session: listed,
1675
+ firstSeq,
1676
+ lastSeq: records.at(-1)?.seq ?? 0,
1677
+ omittedBytes: sinceSeq < firstSeq - 1 ? knownOmittedBytes : 0,
1678
+ };
1679
+ }
1680
+ catch (error) {
1681
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1682
+ }
1683
+ }
1684
+ runtimeTaskToSession(task) {
1685
+ const sessionId = typeof task.metadata?.sessionId === 'string' ? task.metadata.sessionId : task.id;
1686
+ const shellId = typeof task.metadata?.shellId === 'string' ? task.metadata.shellId : 'recovered';
1687
+ return {
1688
+ id: sessionId,
1689
+ pid: task.pid ?? 0,
1690
+ shell: shellId,
1691
+ shellId,
1692
+ shellLabel: shellId,
1693
+ cwd: task.cwd || this.workspaceRoot,
1694
+ status: task.status === 'running' || task.status === 'starting'
1695
+ ? 'running'
1696
+ : task.status === 'failed' || task.status === 'orphaned' ? 'error' : 'exited',
1697
+ createdAt: task.startedAt,
1698
+ updatedAt: task.updatedAt,
1699
+ isAgentSession: true,
1700
+ title: task.command || shellId,
1701
+ command: task.command,
1702
+ runtimeTaskId: task.id,
1703
+ logPath: task.logPath,
1704
+ outputBytes: task.outputBytes,
1705
+ firstSeq: typeof task.metadata?.firstSeq === 'number' ? task.metadata.firstSeq : undefined,
1706
+ lastSeq: typeof task.metadata?.lastSeq === 'number' ? task.metadata.lastSeq : undefined,
1707
+ exitCode: task.exitCode,
1708
+ error: task.error,
1709
+ recovered: task.metadata?.recovered === true,
1710
+ canWrite: false,
1711
+ };
1712
+ }
1713
+ async ptyKillAll() {
1714
+ const errors = [];
1715
+ for (const sessionId of this.backgroundTerminals.keys()) {
1716
+ const result = await this.ptyKill(sessionId);
1717
+ if (!result.success)
1718
+ errors.push(`${sessionId}: ${result.error || 'unknown error'}`);
1719
+ }
1720
+ if (errors.length > 0)
1721
+ return { success: false, error: errors.join('\n') };
1722
+ return { success: true };
1723
+ }
1724
+ async killTerminalProcessTree(session) {
1725
+ const pid = session.info.pid;
1726
+ if (!pid) {
1727
+ session.proc.kill();
1728
+ return;
1729
+ }
1730
+ if (process.platform === 'win32') {
1731
+ try {
1732
+ await execFileAsync('taskkill', ['/PID', String(pid), '/T', '/F']);
1733
+ return;
1734
+ }
1735
+ catch {
1736
+ session.proc.kill();
1737
+ return;
1738
+ }
1739
+ }
1740
+ try {
1741
+ process.kill(usesProcessGroup() ? -pid : pid, getProcessGroupSignal());
1742
+ return;
1743
+ }
1744
+ catch {
1745
+ session.proc.kill('SIGTERM');
1746
+ }
1747
+ }
1748
+ waitForTerminalClose(session, timeoutMs) {
1749
+ if (session.info.status !== 'running')
1750
+ return Promise.resolve(true);
1751
+ return new Promise(resolve => {
1752
+ let settled = false;
1753
+ const done = (closed) => {
1754
+ if (settled)
1755
+ return;
1756
+ settled = true;
1757
+ clearTimeout(timer);
1758
+ resolve(closed);
1759
+ };
1760
+ const timer = setTimeout(() => done(false), timeoutMs);
1761
+ session.proc.once('close', () => done(true));
1762
+ });
1763
+ }
1764
+ async sendMessage(url, headers, body, options = {}) {
1765
+ const request = this.createRequestController(options);
1766
+ try {
1767
+ for (let attempt = 0; attempt <= STREAM_RETRY_DELAYS_MS.length; attempt += 1) {
1768
+ try {
1769
+ const response = await fetch(url, {
1770
+ method: 'POST',
1771
+ headers: { 'Content-Type': 'application/json', ...headers },
1772
+ body,
1773
+ signal: request.controller.signal,
1774
+ });
1775
+ const text = await response.text();
1776
+ if (!response.ok) {
1777
+ const error = this.formatHttpError(url, response.status, text);
1778
+ if (attempt < STREAM_RETRY_DELAYS_MS.length && RETRYABLE_HTTP_STATUS.has(response.status)) {
1779
+ await this.delay(STREAM_RETRY_DELAYS_MS[attempt], request.controller.signal);
1780
+ continue;
1781
+ }
1782
+ return { success: false, error, status: response.status };
1783
+ }
1784
+ return { success: true, data: text };
1785
+ }
1786
+ catch (error) {
1787
+ if (request.controller.signal.aborted || this.isAbortError(error)) {
1788
+ return {
1789
+ success: false,
1790
+ error: request.timedOut() && !options.signal?.aborted
1791
+ ? `Request timed out after ${request.timeoutMs}ms`
1792
+ : 'Request aborted',
1793
+ };
1794
+ }
1795
+ if (attempt < STREAM_RETRY_DELAYS_MS.length) {
1796
+ await this.delay(STREAM_RETRY_DELAYS_MS[attempt], request.controller.signal);
1797
+ continue;
1798
+ }
1799
+ return { success: false, error: this.formatNetworkError(url, error) };
1800
+ }
1801
+ }
1802
+ return { success: false, error: 'Request failed' };
1803
+ }
1804
+ finally {
1805
+ request.cleanup();
1806
+ }
1807
+ }
1808
+ async streamMessage(url, headers, body, onLine, options = {}) {
1809
+ const request = this.createRequestController(options);
1810
+ if (options.streamId !== undefined) {
1811
+ this.activeStreams.get(options.streamId)?.abort();
1812
+ this.activeStreams.set(options.streamId, request.controller);
1813
+ }
1814
+ try {
1815
+ for (let attempt = 0; attempt <= STREAM_RETRY_DELAYS_MS.length; attempt += 1) {
1816
+ let emittedAnyLine = false;
1817
+ let receivedAnyBytes = false;
1818
+ let buffer = '';
1819
+ const diagnosticChunks = [];
1820
+ let diagnosticChars = 0;
1821
+ const recordDiagnostic = (line) => {
1822
+ if (diagnosticChars >= MAX_STREAM_DIAGNOSTIC_CHARS)
1823
+ return;
1824
+ const remaining = MAX_STREAM_DIAGNOSTIC_CHARS - diagnosticChars;
1825
+ const bounded = line.slice(0, remaining);
1826
+ diagnosticChunks.push(bounded);
1827
+ diagnosticChars += bounded.length;
1828
+ };
1829
+ try {
1830
+ const response = await fetch(url, {
1831
+ method: 'POST',
1832
+ headers: { 'Content-Type': 'application/json', ...headers },
1833
+ body,
1834
+ signal: request.controller.signal,
1835
+ });
1836
+ if (!response.ok) {
1837
+ const text = await response.text();
1838
+ const error = this.formatHttpError(url, response.status, text);
1839
+ if (attempt < STREAM_RETRY_DELAYS_MS.length && RETRYABLE_HTTP_STATUS.has(response.status)) {
1840
+ await this.delay(STREAM_RETRY_DELAYS_MS[attempt], request.controller.signal);
1841
+ continue;
1842
+ }
1843
+ return { success: false, error, status: response.status };
1844
+ }
1845
+ const reader = response.body?.getReader();
1846
+ if (!reader)
1847
+ return { success: false, error: 'No response body' };
1848
+ const decoder = new TextDecoder();
1849
+ while (true) {
1850
+ const { done, value } = await reader.read();
1851
+ if (done)
1852
+ break;
1853
+ if (value.byteLength > 0)
1854
+ receivedAnyBytes = true;
1855
+ buffer += decoder.decode(value, { stream: true });
1856
+ if (buffer.length > MAX_STREAM_BUFFER_CHARS) {
1857
+ const lastNewline = buffer.lastIndexOf('\n');
1858
+ buffer = lastNewline >= 0 && lastNewline <= MAX_STREAM_BUFFER_CHARS
1859
+ ? buffer.slice(0, lastNewline + 1)
1860
+ : '';
1861
+ }
1862
+ const lines = buffer.split('\n');
1863
+ buffer = lines.pop() || '';
1864
+ for (const line of lines) {
1865
+ if (line.trim()) {
1866
+ emittedAnyLine = true;
1867
+ onLine(line);
1868
+ recordDiagnostic(`${line}\n`);
1869
+ }
1870
+ }
1871
+ }
1872
+ if (buffer.trim()) {
1873
+ emittedAnyLine = true;
1874
+ onLine(buffer);
1875
+ recordDiagnostic(buffer);
1876
+ }
1877
+ return { success: true };
1878
+ }
1879
+ catch (error) {
1880
+ if (request.controller.signal.aborted || this.isAbortError(error)) {
1881
+ return {
1882
+ success: false,
1883
+ error: request.timedOut() && !options.signal?.aborted
1884
+ ? `Request timed out after ${request.timeoutMs}ms`
1885
+ : 'Request aborted',
1886
+ };
1887
+ }
1888
+ if (buffer.trim()) {
1889
+ emittedAnyLine = true;
1890
+ onLine(buffer);
1891
+ recordDiagnostic(buffer);
1892
+ buffer = '';
1893
+ }
1894
+ if (!emittedAnyLine && !receivedAnyBytes && attempt < STREAM_RETRY_DELAYS_MS.length) {
1895
+ await this.delay(STREAM_RETRY_DELAYS_MS[attempt], request.controller.signal);
1896
+ continue;
1897
+ }
1898
+ return {
1899
+ success: false,
1900
+ error: this.formatNetworkError(url, error),
1901
+ ...(diagnosticChunks.length > 0 ? { data: diagnosticChunks.join('') } : {}),
1902
+ };
1903
+ }
1904
+ }
1905
+ return { success: false, error: 'Stream request failed' };
1906
+ }
1907
+ finally {
1908
+ if (options.streamId !== undefined && this.activeStreams.get(options.streamId) === request.controller) {
1909
+ this.activeStreams.delete(options.streamId);
1910
+ }
1911
+ request.cleanup();
1912
+ }
1913
+ }
1914
+ async streamAbort(streamId) {
1915
+ this.activeStreams.get(streamId)?.abort();
1916
+ }
1917
+ createRequestController(options) {
1918
+ const controller = new AbortController();
1919
+ const timeoutMs = options.timeoutMs || MODEL_REQUEST_TIMEOUT_MS;
1920
+ let timedOut = false;
1921
+ const abortFromParent = () => controller.abort();
1922
+ if (options.signal?.aborted)
1923
+ controller.abort();
1924
+ else
1925
+ options.signal?.addEventListener('abort', abortFromParent, { once: true });
1926
+ const timer = setTimeout(() => {
1927
+ timedOut = true;
1928
+ controller.abort();
1929
+ }, timeoutMs);
1930
+ return {
1931
+ controller,
1932
+ timeoutMs,
1933
+ timedOut: () => timedOut,
1934
+ cleanup: () => {
1935
+ clearTimeout(timer);
1936
+ options.signal?.removeEventListener('abort', abortFromParent);
1937
+ },
1938
+ };
1939
+ }
1940
+ isAbortError(error) {
1941
+ return error instanceof Error && error.name === 'AbortError';
1942
+ }
1943
+ async searchDuckDuckGoInstant(query, limit, region) {
1944
+ const url = new URL('https://api.duckduckgo.com/');
1945
+ url.searchParams.set('q', query);
1946
+ url.searchParams.set('format', 'json');
1947
+ url.searchParams.set('no_html', '1');
1948
+ url.searchParams.set('skip_disambig', '1');
1949
+ url.searchParams.set('kl', region);
1950
+ const json = await this.fetchJsonWithTimeout(url.toString());
1951
+ const results = [];
1952
+ if (typeof json.AbstractURL === 'string' && json.AbstractURL && typeof json.AbstractText === 'string' && json.AbstractText) {
1953
+ results.push({
1954
+ title: this.cleanText(json.Heading || query),
1955
+ url: json.AbstractURL,
1956
+ snippet: this.cleanText(json.AbstractText),
1957
+ source: json.AbstractSource || 'DuckDuckGo',
1958
+ });
1959
+ }
1960
+ const collectTopic = (topic) => {
1961
+ if (!topic || typeof topic !== 'object')
1962
+ return;
1963
+ if (typeof topic.FirstURL === 'string' && typeof topic.Text === 'string') {
1964
+ results.push({
1965
+ title: this.cleanText(topic.Text.split(' - ')[0] || topic.Text).slice(0, 120),
1966
+ url: topic.FirstURL,
1967
+ snippet: this.cleanText(topic.Text),
1968
+ source: 'DuckDuckGo',
1969
+ });
1970
+ }
1971
+ if (Array.isArray(topic.Topics)) {
1972
+ for (const child of topic.Topics)
1973
+ collectTopic(child);
1974
+ }
1975
+ };
1976
+ for (const topic of Array.isArray(json.RelatedTopics) ? json.RelatedTopics : [])
1977
+ collectTopic(topic);
1978
+ return this.dedupeWebResults(results).slice(0, limit);
1979
+ }
1980
+ async searchDuckDuckGoHtml(query, limit, region, freshness) {
1981
+ const url = new URL('https://duckduckgo.com/html/');
1982
+ url.searchParams.set('q', query);
1983
+ url.searchParams.set('kl', region);
1984
+ const df = this.freshnessToDuckDuckGoDf(freshness);
1985
+ if (df)
1986
+ url.searchParams.set('df', df);
1987
+ const html = await this.fetchTextWithTimeout(url.toString(), {
1988
+ 'User-Agent': WEB_SEARCH_USER_AGENT,
1989
+ 'Accept': 'text/html,application/xhtml+xml',
1990
+ });
1991
+ const results = [];
1992
+ const blockRe = /<div[^>]+class="result[^"]*"[^>]*>([\s\S]*?)<\/div>\s*<\/div>/gi;
1993
+ let match;
1994
+ while ((match = blockRe.exec(html)) && results.length < limit * 2) {
1995
+ const block = match[1];
1996
+ const link = block.match(/<a[^>]+class="result__a"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/i);
1997
+ if (!link)
1998
+ continue;
1999
+ const rawUrl = this.decodeHtml(link[1]);
2000
+ const title = this.cleanText(this.stripHtml(link[2]));
2001
+ const snippetMatch = block.match(/<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/i)
2002
+ || block.match(/<div[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/div>/i);
2003
+ const snippet = snippetMatch ? this.cleanText(this.stripHtml(snippetMatch[1])) : '';
2004
+ const normalizedUrl = this.normalizeDuckDuckGoUrl(rawUrl);
2005
+ if (!title || !normalizedUrl)
2006
+ continue;
2007
+ results.push({ title, url: normalizedUrl, snippet, source: 'DuckDuckGo' });
2008
+ }
2009
+ return this.dedupeWebResults(results).slice(0, limit);
2010
+ }
2011
+ async searchBingHtml(query, limit, freshness) {
2012
+ const url = new URL('https://www.bing.com/search');
2013
+ url.searchParams.set('q', query);
2014
+ url.searchParams.set('count', String(Math.max(limit, 5)));
2015
+ const freshnessFilter = this.freshnessToBingFilter(freshness);
2016
+ if (freshnessFilter)
2017
+ url.searchParams.set('qft', freshnessFilter);
2018
+ const html = await this.fetchTextWithTimeout(url.toString(), {
2019
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) TurboFlux/0.1',
2020
+ 'Accept': 'text/html,application/xhtml+xml',
2021
+ 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.7',
2022
+ });
2023
+ const results = [];
2024
+ const blockRe = /<li[^>]+class="b_algo"[^>]*>([\s\S]*?)<\/li>/gi;
2025
+ let match;
2026
+ while ((match = blockRe.exec(html)) && results.length < limit * 2) {
2027
+ const block = match[1];
2028
+ const link = block.match(/<h2[^>]*>\s*<a[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>\s*<\/h2>/i)
2029
+ || block.match(/<a[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/i);
2030
+ if (!link)
2031
+ continue;
2032
+ const title = this.cleanText(this.stripHtml(link[2]));
2033
+ const normalizedUrl = this.normalizeBingUrl(this.decodeHtml(link[1]));
2034
+ const snippetMatch = block.match(/<p[^>]*>([\s\S]*?)<\/p>/i)
2035
+ || block.match(/<div[^>]+class="b_caption"[^>]*>([\s\S]*?)<\/div>/i);
2036
+ const snippet = snippetMatch ? this.cleanText(this.stripHtml(snippetMatch[1])) : '';
2037
+ if (!title || !normalizedUrl || normalizedUrl.startsWith('javascript:'))
2038
+ continue;
2039
+ results.push({ title, url: normalizedUrl, snippet, source: 'Bing' });
2040
+ }
2041
+ return this.dedupeWebResults(results).slice(0, limit);
2042
+ }
2043
+ async fetchJsonWithTimeout(url) {
2044
+ const text = await this.fetchTextWithTimeout(url, {
2045
+ 'User-Agent': WEB_SEARCH_USER_AGENT,
2046
+ 'Accept': 'application/json',
2047
+ });
2048
+ return JSON.parse(text);
2049
+ }
2050
+ async fetchTextWithTimeout(url, headers) {
2051
+ const controller = new AbortController();
2052
+ const timer = setTimeout(() => controller.abort(), WEB_SEARCH_TIMEOUT_MS);
2053
+ try {
2054
+ const response = await fetch(url, { headers, signal: controller.signal });
2055
+ const text = await response.text();
2056
+ if (!response.ok)
2057
+ throw new Error(this.formatHttpError(url, response.status, text));
2058
+ return text;
2059
+ }
2060
+ finally {
2061
+ clearTimeout(timer);
2062
+ }
2063
+ }
2064
+ dedupeWebResults(results) {
2065
+ const seen = new Set();
2066
+ const deduped = [];
2067
+ for (const result of results) {
2068
+ const url = result.url.trim();
2069
+ if (!url || seen.has(url))
2070
+ continue;
2071
+ seen.add(url);
2072
+ deduped.push({
2073
+ title: this.cleanText(result.title).slice(0, 160),
2074
+ url,
2075
+ snippet: this.cleanText(result.snippet).slice(0, 500),
2076
+ source: result.source,
2077
+ publishedDate: result.publishedDate,
2078
+ });
2079
+ }
2080
+ return deduped;
2081
+ }
2082
+ normalizeDuckDuckGoUrl(value) {
2083
+ try {
2084
+ const decoded = this.decodeHtml(value);
2085
+ const url = new URL(decoded, 'https://duckduckgo.com');
2086
+ const uddg = url.searchParams.get('uddg');
2087
+ return uddg ? decodeURIComponent(uddg) : url.toString();
2088
+ }
2089
+ catch {
2090
+ return value;
2091
+ }
2092
+ }
2093
+ normalizeBingUrl(value) {
2094
+ try {
2095
+ const decoded = this.decodeHtml(value);
2096
+ const url = new URL(decoded, 'https://www.bing.com');
2097
+ const rawU = url.searchParams.get('u');
2098
+ if (url.hostname.endsWith('bing.com') && rawU) {
2099
+ const target = this.decodeBingEncodedUrl(rawU);
2100
+ if (target)
2101
+ return target;
2102
+ }
2103
+ return url.toString();
2104
+ }
2105
+ catch {
2106
+ return value;
2107
+ }
2108
+ }
2109
+ decodeBingEncodedUrl(value) {
2110
+ try {
2111
+ const raw = value.startsWith('a1') ? value.slice(2) : value;
2112
+ const padded = raw.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(raw.length / 4) * 4, '=');
2113
+ const decoded = Buffer.from(padded, 'base64').toString('utf-8');
2114
+ return /^https?:\/\//i.test(decoded) ? decoded : '';
2115
+ }
2116
+ catch {
2117
+ return '';
2118
+ }
2119
+ }
2120
+ normalizeDomainFilter(value) {
2121
+ const raw = String(value || '').trim();
2122
+ if (!raw)
2123
+ return '';
2124
+ try {
2125
+ const withProtocol = /^[a-z]+:\/\//i.test(raw) ? raw : `https://${raw}`;
2126
+ return new URL(withProtocol).hostname.replace(/^www\./i, '');
2127
+ }
2128
+ catch {
2129
+ return raw.replace(/^https?:\/\//i, '').replace(/^www\./i, '').split(/[/?#]/)[0];
2130
+ }
2131
+ }
2132
+ freshnessToDuckDuckGoDf(freshness) {
2133
+ switch (freshness) {
2134
+ case 'day': return 'd';
2135
+ case 'week': return 'w';
2136
+ case 'month': return 'm';
2137
+ case 'year': return 'y';
2138
+ default: return '';
2139
+ }
2140
+ }
2141
+ freshnessToBingFilter(freshness) {
2142
+ switch (freshness) {
2143
+ case 'day': return '+filterui:age-lt1440';
2144
+ case 'week': return '+filterui:age-lt10080';
2145
+ case 'month': return '+filterui:age-lt43200';
2146
+ case 'year': return '+filterui:age-lt525600';
2147
+ default: return '';
2148
+ }
2149
+ }
2150
+ stripHtml(value) {
2151
+ return value.replace(/<[^>]+>/g, ' ');
2152
+ }
2153
+ decodeHtml(value) {
2154
+ return value
2155
+ .replace(/&amp;/g, '&')
2156
+ .replace(/&lt;/g, '<')
2157
+ .replace(/&gt;/g, '>')
2158
+ .replace(/&quot;/g, '"')
2159
+ .replace(/&nbsp;|&ensp;|&emsp;/g, ' ')
2160
+ .replace(/&#39;/g, "'")
2161
+ .replace(/&#x27;/g, "'")
2162
+ .replace(/&#(\d+);/g, (_match, code) => {
2163
+ try {
2164
+ return String.fromCodePoint(Number(code));
2165
+ }
2166
+ catch {
2167
+ return _match;
2168
+ }
2169
+ })
2170
+ .replace(/&#x([0-9a-f]+);/gi, (_match, code) => {
2171
+ try {
2172
+ return String.fromCodePoint(parseInt(code, 16));
2173
+ }
2174
+ catch {
2175
+ return _match;
2176
+ }
2177
+ });
2178
+ }
2179
+ cleanText(value) {
2180
+ return this.decodeHtml(String(value || '').replace(/\s+/g, ' ').trim());
2181
+ }
2182
+ resolvePath(path, access = 'read') {
2183
+ return this.capabilityBoundary.resolvePath(path, access);
2184
+ }
2185
+ buildTree(dirPath, maxDepth, depth = 0, budget = { remaining: 20_000 }, maxEntriesPerDirectory = 500) {
2186
+ const name = depth === 0 ? dirPath : dirPath.split(/[\\/]/).pop() || dirPath;
2187
+ const node = { name, type: 'directory', children: [] };
2188
+ budget.remaining = Math.max(0, budget.remaining - 1);
2189
+ if (depth >= maxDepth || budget.remaining <= 0)
2190
+ return node;
2191
+ try {
2192
+ const entries = readdirSync(dirPath, { withFileTypes: true })
2193
+ .filter(entry => !entry.isSymbolicLink() && !this.shouldSkipEntry(entry.name))
2194
+ .sort((left, right) => left.name.localeCompare(right.name))
2195
+ .slice(0, maxEntriesPerDirectory);
2196
+ for (const entry of entries) {
2197
+ if (budget.remaining <= 0)
2198
+ break;
2199
+ const fullPath = join(dirPath, entry.name);
2200
+ if (entry.isDirectory()) {
2201
+ node.children.push(this.buildTree(fullPath, maxDepth, depth + 1, budget, maxEntriesPerDirectory));
2202
+ }
2203
+ else {
2204
+ budget.remaining -= 1;
2205
+ node.children.push({ name: entry.name, type: 'file' });
2206
+ }
2207
+ }
2208
+ }
2209
+ catch { }
2210
+ return node;
2211
+ }
2212
+ globSync(pattern, basePath) {
2213
+ const results = [];
2214
+ const regex = this.globToRegex(pattern);
2215
+ this.walkDir(basePath, (filePath) => {
2216
+ const rel = relative(basePath, filePath).replace(/\\/g, '/');
2217
+ if (regex.test(rel))
2218
+ results.push(filePath);
2219
+ });
2220
+ return results;
2221
+ }
2222
+ searchContentFallback(pattern, basePath, filePattern, caseInsensitive, options = {}) {
2223
+ const matcher = new RegExp(pattern, caseInsensitive ? 'i' : '');
2224
+ const fileMatcher = filePattern ? this.globToRegex(filePattern) : null;
2225
+ const contextBefore = Math.max(0, Math.min(20, Math.floor(options.contextBefore || 0)));
2226
+ const contextAfter = Math.max(0, Math.min(20, Math.floor(options.contextAfter || 0)));
2227
+ const maxMatchesPerFile = options.maxMatchesPerFile === undefined
2228
+ ? 0
2229
+ : Math.max(1, Math.min(100, Math.floor(options.maxMatchesPerFile)));
2230
+ const results = [];
2231
+ this.walkDir(basePath, filePath => {
2232
+ if (options.signal?.aborted || results.length >= 500)
2233
+ return;
2234
+ const rel = relative(basePath, filePath).replace(/\\/g, '/');
2235
+ if (fileMatcher && !fileMatcher.test(filePattern?.includes('/') ? rel : filePath.split(/[\\/]/).pop() || rel))
2236
+ return;
2237
+ try {
2238
+ const lines = readFileSync(filePath, 'utf-8').split(/\r?\n/);
2239
+ let fileMatches = 0;
2240
+ for (let index = 0; index < lines.length && results.length < 500; index += 1) {
2241
+ if (options.signal?.aborted)
2242
+ return;
2243
+ if (!matcher.test(lines[index])) {
2244
+ matcher.lastIndex = 0;
2245
+ continue;
2246
+ }
2247
+ matcher.lastIndex = 0;
2248
+ if (maxMatchesPerFile > 0 && fileMatches >= maxMatchesPerFile)
2249
+ continue;
2250
+ fileMatches += 1;
2251
+ const contextLines = [];
2252
+ const start = Math.max(0, index - contextBefore);
2253
+ const end = Math.min(lines.length - 1, index + contextAfter);
2254
+ for (let contextIndex = start; contextIndex <= end; contextIndex += 1) {
2255
+ if (contextIndex === index)
2256
+ continue;
2257
+ contextLines.push(`${contextIndex + 1}: ${lines[contextIndex]}`);
2258
+ }
2259
+ results.push({
2260
+ file: filePath,
2261
+ line: index + 1,
2262
+ text: lines[index],
2263
+ ...(contextLines.length > 0 ? { context: contextLines.join('\n') } : {}),
2264
+ });
2265
+ }
2266
+ }
2267
+ catch { }
2268
+ });
2269
+ return results;
2270
+ }
2271
+ searchCodeSymbolsFallback(query, basePath, limit, workspacePath = basePath, exact = false) {
2272
+ const results = [];
2273
+ const queryLower = query.toLowerCase();
2274
+ this.walkDir(basePath, filePath => {
2275
+ if (results.length >= limit || !/\.(?:ts|tsx|js|jsx|mjs|cjs|py|pyi|rs|go|java|kt|kts|cs|c|cc|cpp|cxx|h|hpp|swift|scala|rb|php)$/i.test(filePath))
2276
+ return;
2277
+ try {
2278
+ const lines = readFileSync(filePath, 'utf-8').split(/\r?\n/);
2279
+ for (let index = 0; index < lines.length && results.length < limit; index += 1) {
2280
+ const declaration = this.extractSymbolDeclaration(lines[index]);
2281
+ if (!declaration || (exact
2282
+ ? declaration.name.toLowerCase() !== queryLower
2283
+ : !declaration.name.toLowerCase().includes(queryLower)))
2284
+ continue;
2285
+ const text = lines[index].trim();
2286
+ results.push({
2287
+ id: `sym_${index + 1}_${filePath.slice(-20)}`,
2288
+ path: relative(workspacePath, filePath).replace(/\\/g, '/'),
2289
+ title: declaration.name,
2290
+ subtitle: text.slice(0, 120),
2291
+ line: index + 1,
2292
+ startLine: index + 1,
2293
+ endLine: index + 6,
2294
+ score: 1,
2295
+ source: 'symbol',
2296
+ symbolKind: declaration.kind,
2297
+ preview: text,
2298
+ });
2299
+ }
2300
+ }
2301
+ catch { }
2302
+ });
2303
+ return results;
2304
+ }
2305
+ walkDir(dir, callback, depth = 0) {
2306
+ if (depth > 10)
2307
+ return;
2308
+ try {
2309
+ const entries = readdirSync(dir, { withFileTypes: true });
2310
+ for (const entry of entries) {
2311
+ if (entry.isSymbolicLink() || this.shouldSkipEntry(entry.name))
2312
+ continue;
2313
+ const fullPath = join(dir, entry.name);
2314
+ if (entry.isDirectory()) {
2315
+ this.walkDir(fullPath, callback, depth + 1);
2316
+ }
2317
+ else {
2318
+ callback(fullPath);
2319
+ }
2320
+ }
2321
+ }
2322
+ catch { }
2323
+ }
2324
+ globToRegex(pattern) {
2325
+ const alternatives = this.expandGlobBraces(pattern.replace(/\\/g, '/'))
2326
+ .map(alternative => {
2327
+ let regex = '';
2328
+ for (let index = 0; index < alternative.length; index += 1) {
2329
+ const character = alternative[index];
2330
+ if (character === '*' && alternative[index + 1] === '*') {
2331
+ if (alternative[index + 2] === '/') {
2332
+ regex += '(?:.*/)?';
2333
+ index += 2;
2334
+ }
2335
+ else {
2336
+ regex += '.*';
2337
+ index += 1;
2338
+ }
2339
+ }
2340
+ else if (character === '*') {
2341
+ regex += '[^/]*';
2342
+ }
2343
+ else if (character === '?') {
2344
+ regex += '[^/]';
2345
+ }
2346
+ else {
2347
+ regex += character.replace(/[.+^$()|[\]{}\\]/g, '\\$&');
2348
+ }
2349
+ }
2350
+ return regex;
2351
+ });
2352
+ return new RegExp(`^(?:${alternatives.join('|')})$`, 'i');
2353
+ }
2354
+ expandGlobBraces(pattern) {
2355
+ const open = pattern.indexOf('{');
2356
+ if (open < 0)
2357
+ return [pattern];
2358
+ const close = pattern.indexOf('}', open + 1);
2359
+ if (close < 0)
2360
+ return [pattern];
2361
+ const choices = pattern.slice(open + 1, close).split(',').filter(Boolean);
2362
+ if (choices.length < 2)
2363
+ return [pattern];
2364
+ const prefix = pattern.slice(0, open);
2365
+ const suffix = pattern.slice(close + 1);
2366
+ return choices.flatMap(choice => this.expandGlobBraces(`${prefix}${choice}${suffix}`));
2367
+ }
2368
+ shouldSkipEntry(name) {
2369
+ if (CODE_SEARCH_SKIPPED_DIRS.has(name.toLowerCase()))
2370
+ return true;
2371
+ return name === '.env' || name === '.env.local' || /^\.env\..+\.local$/i.test(name);
2372
+ }
2373
+ buildChildEnvironment(overrides) {
2374
+ return { ...process.env, ...overrides };
2375
+ }
2376
+ delay(ms, signal) {
2377
+ if (signal?.aborted)
2378
+ return Promise.resolve();
2379
+ return new Promise(resolve => {
2380
+ const finish = () => {
2381
+ clearTimeout(timer);
2382
+ signal?.removeEventListener('abort', finish);
2383
+ resolve();
2384
+ };
2385
+ const timer = setTimeout(finish, ms);
2386
+ signal?.addEventListener('abort', finish, { once: true });
2387
+ });
2388
+ }
2389
+ formatHttpError(url, status, text) {
2390
+ const detail = text.trim() || 'empty response';
2391
+ return `HTTP ${status}: ${detail}`;
2392
+ }
2393
+ formatNetworkError(url, error) {
2394
+ const parts = [];
2395
+ const seen = new Set();
2396
+ let current = error;
2397
+ for (let depth = 0; current !== undefined && current !== null && depth < 5; depth += 1) {
2398
+ if (seen.has(current))
2399
+ break;
2400
+ seen.add(current);
2401
+ if (current instanceof Error) {
2402
+ const record = current;
2403
+ const metadata = [
2404
+ record.code ? `code=${String(record.code)}` : '',
2405
+ record.errno ? `errno=${String(record.errno)}` : '',
2406
+ record.syscall ? `syscall=${String(record.syscall)}` : '',
2407
+ record.address ? `address=${String(record.address)}` : '',
2408
+ record.port ? `port=${String(record.port)}` : '',
2409
+ ].filter(Boolean);
2410
+ parts.push(`${record.message || record.name}${metadata.length > 0 ? ` (${metadata.join(', ')})` : ''}`);
2411
+ current = record.cause;
2412
+ continue;
2413
+ }
2414
+ if (typeof current === 'object') {
2415
+ const record = current;
2416
+ const metadata = ['code', 'errno', 'syscall', 'address', 'port']
2417
+ .filter(key => record[key] !== undefined)
2418
+ .map(key => `${key}=${String(record[key])}`);
2419
+ const message = typeof record.message === 'string' ? record.message : String(current);
2420
+ parts.push(`${message}${metadata.length > 0 ? ` (${metadata.join(', ')})` : ''}`);
2421
+ current = record.cause;
2422
+ continue;
2423
+ }
2424
+ parts.push(String(current));
2425
+ break;
2426
+ }
2427
+ return `Network request to ${url} failed: ${parts.filter(Boolean).join(' <- caused by: ') || 'unknown network error'}`;
2428
+ }
2429
+ }
2430
+ function getShellCommand(command) {
2431
+ if (process.platform !== 'win32') {
2432
+ return { shell: DEFAULT_SHELL.command, shellArgs: ['-lc', command] };
2433
+ }
2434
+ const wrapped = [
2435
+ '[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); $OutputEncoding = [Console]::OutputEncoding',
2436
+ command,
2437
+ '$turbofluxSucceeded = $?',
2438
+ '$turbofluxExitCode = $LASTEXITCODE',
2439
+ 'if (-not $turbofluxSucceeded) { if ($null -ne $turbofluxExitCode) { exit $turbofluxExitCode }; exit 1 }',
2440
+ ].join('\n');
2441
+ return {
2442
+ shell: DEFAULT_SHELL.command,
2443
+ shellArgs: ['-NoLogo', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', wrapped],
2444
+ };
2445
+ }
2446
+ //# sourceMappingURL=nodeToolExecutor.js.map