@trenchwork/erosolar 1.1.40

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 (600) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +225 -0
  3. package/agents/erosolar-code.rules.json +199 -0
  4. package/dist/bin/cliMode.d.ts +8 -0
  5. package/dist/bin/cliMode.d.ts.map +1 -0
  6. package/dist/bin/cliMode.js +20 -0
  7. package/dist/bin/cliMode.js.map +1 -0
  8. package/dist/bin/deepseek.d.ts +3 -0
  9. package/dist/bin/deepseek.d.ts.map +1 -0
  10. package/dist/bin/deepseek.js +203 -0
  11. package/dist/bin/deepseek.js.map +1 -0
  12. package/dist/bin/erosolar.d.ts +7 -0
  13. package/dist/bin/erosolar.d.ts.map +1 -0
  14. package/dist/bin/erosolar.js +7 -0
  15. package/dist/bin/erosolar.js.map +1 -0
  16. package/dist/bin/selfTest.d.ts +14 -0
  17. package/dist/bin/selfTest.d.ts.map +1 -0
  18. package/dist/bin/selfTest.js +298 -0
  19. package/dist/bin/selfTest.js.map +1 -0
  20. package/dist/capabilities/baseCapability.d.ts +72 -0
  21. package/dist/capabilities/baseCapability.d.ts.map +1 -0
  22. package/dist/capabilities/baseCapability.js +183 -0
  23. package/dist/capabilities/baseCapability.js.map +1 -0
  24. package/dist/capabilities/bashCapability.d.ts +13 -0
  25. package/dist/capabilities/bashCapability.d.ts.map +1 -0
  26. package/dist/capabilities/bashCapability.js +24 -0
  27. package/dist/capabilities/bashCapability.js.map +1 -0
  28. package/dist/capabilities/editCapability.d.ts +17 -0
  29. package/dist/capabilities/editCapability.d.ts.map +1 -0
  30. package/dist/capabilities/editCapability.js +27 -0
  31. package/dist/capabilities/editCapability.js.map +1 -0
  32. package/dist/capabilities/enhancedGitCapability.d.ts +7 -0
  33. package/dist/capabilities/enhancedGitCapability.d.ts.map +1 -0
  34. package/dist/capabilities/enhancedGitCapability.js +220 -0
  35. package/dist/capabilities/enhancedGitCapability.js.map +1 -0
  36. package/dist/capabilities/filesystemCapability.d.ts +13 -0
  37. package/dist/capabilities/filesystemCapability.d.ts.map +1 -0
  38. package/dist/capabilities/filesystemCapability.js +24 -0
  39. package/dist/capabilities/filesystemCapability.js.map +1 -0
  40. package/dist/capabilities/gitHistoryCapability.d.ts +6 -0
  41. package/dist/capabilities/gitHistoryCapability.d.ts.map +1 -0
  42. package/dist/capabilities/gitHistoryCapability.js +184 -0
  43. package/dist/capabilities/gitHistoryCapability.js.map +1 -0
  44. package/dist/capabilities/hitlCapability.d.ts +18 -0
  45. package/dist/capabilities/hitlCapability.d.ts.map +1 -0
  46. package/dist/capabilities/hitlCapability.js +29 -0
  47. package/dist/capabilities/hitlCapability.js.map +1 -0
  48. package/dist/capabilities/index.d.ts +11 -0
  49. package/dist/capabilities/index.d.ts.map +1 -0
  50. package/dist/capabilities/index.js +16 -0
  51. package/dist/capabilities/index.js.map +1 -0
  52. package/dist/capabilities/memoryCapability.d.ts +10 -0
  53. package/dist/capabilities/memoryCapability.d.ts.map +1 -0
  54. package/dist/capabilities/memoryCapability.js +22 -0
  55. package/dist/capabilities/memoryCapability.js.map +1 -0
  56. package/dist/capabilities/notebookCapability.d.ts +6 -0
  57. package/dist/capabilities/notebookCapability.d.ts.map +1 -0
  58. package/dist/capabilities/notebookCapability.js +17 -0
  59. package/dist/capabilities/notebookCapability.js.map +1 -0
  60. package/dist/capabilities/searchCapability.d.ts +19 -0
  61. package/dist/capabilities/searchCapability.d.ts.map +1 -0
  62. package/dist/capabilities/searchCapability.js +29 -0
  63. package/dist/capabilities/searchCapability.js.map +1 -0
  64. package/dist/capabilities/skillCapability.d.ts +6 -0
  65. package/dist/capabilities/skillCapability.d.ts.map +1 -0
  66. package/dist/capabilities/skillCapability.js +17 -0
  67. package/dist/capabilities/skillCapability.js.map +1 -0
  68. package/dist/capabilities/todoCapability.d.ts +11 -0
  69. package/dist/capabilities/todoCapability.d.ts.map +1 -0
  70. package/dist/capabilities/todoCapability.js +22 -0
  71. package/dist/capabilities/todoCapability.js.map +1 -0
  72. package/dist/capabilities/toolManifest.d.ts +3 -0
  73. package/dist/capabilities/toolManifest.d.ts.map +1 -0
  74. package/dist/capabilities/toolManifest.js +163 -0
  75. package/dist/capabilities/toolManifest.js.map +1 -0
  76. package/dist/capabilities/toolRegistry.d.ts +25 -0
  77. package/dist/capabilities/toolRegistry.d.ts.map +1 -0
  78. package/dist/capabilities/toolRegistry.js +150 -0
  79. package/dist/capabilities/toolRegistry.js.map +1 -0
  80. package/dist/capabilities/unifiedCodingCapability.d.ts +62 -0
  81. package/dist/capabilities/unifiedCodingCapability.d.ts.map +1 -0
  82. package/dist/capabilities/unifiedCodingCapability.js +790 -0
  83. package/dist/capabilities/unifiedCodingCapability.js.map +1 -0
  84. package/dist/capabilities/webCapability.d.ts +23 -0
  85. package/dist/capabilities/webCapability.d.ts.map +1 -0
  86. package/dist/capabilities/webCapability.js +33 -0
  87. package/dist/capabilities/webCapability.js.map +1 -0
  88. package/dist/config.d.ts +25 -0
  89. package/dist/config.d.ts.map +1 -0
  90. package/dist/config.js +184 -0
  91. package/dist/config.js.map +1 -0
  92. package/dist/contracts/agent-profiles.schema.json +43 -0
  93. package/dist/contracts/agent-schemas.json +470 -0
  94. package/dist/contracts/models.schema.json +9 -0
  95. package/dist/contracts/module-schema.json +367 -0
  96. package/dist/contracts/schemas/agent-profile.schema.json +157 -0
  97. package/dist/contracts/schemas/agent-rules.schema.json +238 -0
  98. package/dist/contracts/schemas/agent-schemas.schema.json +528 -0
  99. package/dist/contracts/schemas/agent.schema.json +90 -0
  100. package/dist/contracts/schemas/tool-selection.schema.json +174 -0
  101. package/dist/contracts/tools.schema.json +42 -0
  102. package/dist/contracts/unified-schema.json +660 -0
  103. package/dist/contracts/v1/agent.d.ts +179 -0
  104. package/dist/contracts/v1/agent.d.ts.map +1 -0
  105. package/dist/contracts/v1/agent.js +8 -0
  106. package/dist/contracts/v1/agent.js.map +1 -0
  107. package/dist/contracts/v1/agentProfileManifest.d.ts +60 -0
  108. package/dist/contracts/v1/agentProfileManifest.d.ts.map +1 -0
  109. package/dist/contracts/v1/agentProfileManifest.js +9 -0
  110. package/dist/contracts/v1/agentProfileManifest.js.map +1 -0
  111. package/dist/contracts/v1/agentRules.d.ts +60 -0
  112. package/dist/contracts/v1/agentRules.d.ts.map +1 -0
  113. package/dist/contracts/v1/agentRules.js +10 -0
  114. package/dist/contracts/v1/agentRules.js.map +1 -0
  115. package/dist/contracts/v1/provider.d.ts +149 -0
  116. package/dist/contracts/v1/provider.d.ts.map +1 -0
  117. package/dist/contracts/v1/provider.js +7 -0
  118. package/dist/contracts/v1/provider.js.map +1 -0
  119. package/dist/contracts/v1/tool.d.ts +136 -0
  120. package/dist/contracts/v1/tool.d.ts.map +1 -0
  121. package/dist/contracts/v1/tool.js +7 -0
  122. package/dist/contracts/v1/tool.js.map +1 -0
  123. package/dist/contracts/v1/toolAccess.d.ts +43 -0
  124. package/dist/contracts/v1/toolAccess.d.ts.map +1 -0
  125. package/dist/contracts/v1/toolAccess.js +9 -0
  126. package/dist/contracts/v1/toolAccess.js.map +1 -0
  127. package/dist/core/adversarial.d.ts +38 -0
  128. package/dist/core/adversarial.d.ts.map +1 -0
  129. package/dist/core/adversarial.js +106 -0
  130. package/dist/core/adversarial.js.map +1 -0
  131. package/dist/core/agent.d.ts +329 -0
  132. package/dist/core/agent.d.ts.map +1 -0
  133. package/dist/core/agent.js +1668 -0
  134. package/dist/core/agent.js.map +1 -0
  135. package/dist/core/agentProfileManifest.d.ts +3 -0
  136. package/dist/core/agentProfileManifest.d.ts.map +1 -0
  137. package/dist/core/agentProfileManifest.js +188 -0
  138. package/dist/core/agentProfileManifest.js.map +1 -0
  139. package/dist/core/agentProfiles.d.ts +22 -0
  140. package/dist/core/agentProfiles.d.ts.map +1 -0
  141. package/dist/core/agentProfiles.js +35 -0
  142. package/dist/core/agentProfiles.js.map +1 -0
  143. package/dist/core/agentRegistry.d.ts +111 -0
  144. package/dist/core/agentRegistry.d.ts.map +1 -0
  145. package/dist/core/agentRegistry.js +229 -0
  146. package/dist/core/agentRegistry.js.map +1 -0
  147. package/dist/core/agentRulebook.d.ts +11 -0
  148. package/dist/core/agentRulebook.d.ts.map +1 -0
  149. package/dist/core/agentRulebook.js +136 -0
  150. package/dist/core/agentRulebook.js.map +1 -0
  151. package/dist/core/agentSchemaLoader.d.ts +131 -0
  152. package/dist/core/agentSchemaLoader.d.ts.map +1 -0
  153. package/dist/core/agentSchemaLoader.js +235 -0
  154. package/dist/core/agentSchemaLoader.js.map +1 -0
  155. package/dist/core/aiErrorFixer.d.ts +57 -0
  156. package/dist/core/aiErrorFixer.d.ts.map +1 -0
  157. package/dist/core/aiErrorFixer.js +214 -0
  158. package/dist/core/aiErrorFixer.js.map +1 -0
  159. package/dist/core/bashCommandGuidance.d.ts +16 -0
  160. package/dist/core/bashCommandGuidance.d.ts.map +1 -0
  161. package/dist/core/bashCommandGuidance.js +40 -0
  162. package/dist/core/bashCommandGuidance.js.map +1 -0
  163. package/dist/core/constants.d.ts +31 -0
  164. package/dist/core/constants.d.ts.map +1 -0
  165. package/dist/core/constants.js +62 -0
  166. package/dist/core/constants.js.map +1 -0
  167. package/dist/core/contextManager.d.ts +271 -0
  168. package/dist/core/contextManager.d.ts.map +1 -0
  169. package/dist/core/contextManager.js +1073 -0
  170. package/dist/core/contextManager.js.map +1 -0
  171. package/dist/core/contextWindow.d.ts +42 -0
  172. package/dist/core/contextWindow.d.ts.map +1 -0
  173. package/dist/core/contextWindow.js +123 -0
  174. package/dist/core/contextWindow.js.map +1 -0
  175. package/dist/core/customCommands.d.ts +19 -0
  176. package/dist/core/customCommands.d.ts.map +1 -0
  177. package/dist/core/customCommands.js +85 -0
  178. package/dist/core/customCommands.js.map +1 -0
  179. package/dist/core/errors/apiKeyErrors.d.ts +11 -0
  180. package/dist/core/errors/apiKeyErrors.d.ts.map +1 -0
  181. package/dist/core/errors/apiKeyErrors.js +159 -0
  182. package/dist/core/errors/apiKeyErrors.js.map +1 -0
  183. package/dist/core/errors/errorTypes.d.ts +111 -0
  184. package/dist/core/errors/errorTypes.d.ts.map +1 -0
  185. package/dist/core/errors/errorTypes.js +345 -0
  186. package/dist/core/errors/errorTypes.js.map +1 -0
  187. package/dist/core/errors/index.d.ts +50 -0
  188. package/dist/core/errors/index.d.ts.map +1 -0
  189. package/dist/core/errors/index.js +156 -0
  190. package/dist/core/errors/index.js.map +1 -0
  191. package/dist/core/errors/networkErrors.d.ts +14 -0
  192. package/dist/core/errors/networkErrors.d.ts.map +1 -0
  193. package/dist/core/errors/networkErrors.js +53 -0
  194. package/dist/core/errors/networkErrors.js.map +1 -0
  195. package/dist/core/errors/safetyValidator.d.ts +109 -0
  196. package/dist/core/errors/safetyValidator.d.ts.map +1 -0
  197. package/dist/core/errors/safetyValidator.js +272 -0
  198. package/dist/core/errors/safetyValidator.js.map +1 -0
  199. package/dist/core/errors.d.ts +4 -0
  200. package/dist/core/errors.d.ts.map +1 -0
  201. package/dist/core/errors.js +33 -0
  202. package/dist/core/errors.js.map +1 -0
  203. package/dist/core/finalResponseFormatter.d.ts +10 -0
  204. package/dist/core/finalResponseFormatter.d.ts.map +1 -0
  205. package/dist/core/finalResponseFormatter.js +14 -0
  206. package/dist/core/finalResponseFormatter.js.map +1 -0
  207. package/dist/core/guardrails.d.ts +146 -0
  208. package/dist/core/guardrails.d.ts.map +1 -0
  209. package/dist/core/guardrails.js +361 -0
  210. package/dist/core/guardrails.js.map +1 -0
  211. package/dist/core/hitl.d.ts +119 -0
  212. package/dist/core/hitl.d.ts.map +1 -0
  213. package/dist/core/hitl.js +387 -0
  214. package/dist/core/hitl.js.map +1 -0
  215. package/dist/core/hooks.d.ts +95 -0
  216. package/dist/core/hooks.d.ts.map +1 -0
  217. package/dist/core/hooks.js +236 -0
  218. package/dist/core/hooks.js.map +1 -0
  219. package/dist/core/index.d.ts +7 -0
  220. package/dist/core/index.d.ts.map +1 -0
  221. package/dist/core/index.js +7 -0
  222. package/dist/core/index.js.map +1 -0
  223. package/dist/core/inputProtection.d.ts +122 -0
  224. package/dist/core/inputProtection.d.ts.map +1 -0
  225. package/dist/core/inputProtection.js +422 -0
  226. package/dist/core/inputProtection.js.map +1 -0
  227. package/dist/core/modelDiscovery.d.ts +105 -0
  228. package/dist/core/modelDiscovery.d.ts.map +1 -0
  229. package/dist/core/modelDiscovery.js +768 -0
  230. package/dist/core/modelDiscovery.js.map +1 -0
  231. package/dist/core/multilinePasteHandler.d.ts +35 -0
  232. package/dist/core/multilinePasteHandler.d.ts.map +1 -0
  233. package/dist/core/multilinePasteHandler.js +81 -0
  234. package/dist/core/multilinePasteHandler.js.map +1 -0
  235. package/dist/core/preferences.d.ts +67 -0
  236. package/dist/core/preferences.d.ts.map +1 -0
  237. package/dist/core/preferences.js +315 -0
  238. package/dist/core/preferences.js.map +1 -0
  239. package/dist/core/providerKeys.d.ts +20 -0
  240. package/dist/core/providerKeys.d.ts.map +1 -0
  241. package/dist/core/providerKeys.js +40 -0
  242. package/dist/core/providerKeys.js.map +1 -0
  243. package/dist/core/refusalDetection.d.ts +2 -0
  244. package/dist/core/refusalDetection.d.ts.map +1 -0
  245. package/dist/core/refusalDetection.js +51 -0
  246. package/dist/core/refusalDetection.js.map +1 -0
  247. package/dist/core/resultVerification.d.ts +47 -0
  248. package/dist/core/resultVerification.d.ts.map +1 -0
  249. package/dist/core/resultVerification.js +126 -0
  250. package/dist/core/resultVerification.js.map +1 -0
  251. package/dist/core/schemaValidator.d.ts +49 -0
  252. package/dist/core/schemaValidator.d.ts.map +1 -0
  253. package/dist/core/schemaValidator.js +234 -0
  254. package/dist/core/schemaValidator.js.map +1 -0
  255. package/dist/core/secretStore.d.ts +48 -0
  256. package/dist/core/secretStore.d.ts.map +1 -0
  257. package/dist/core/secretStore.js +266 -0
  258. package/dist/core/secretStore.js.map +1 -0
  259. package/dist/core/sessionStorage.d.ts +10 -0
  260. package/dist/core/sessionStorage.d.ts.map +1 -0
  261. package/dist/core/sessionStorage.js +46 -0
  262. package/dist/core/sessionStorage.js.map +1 -0
  263. package/dist/core/sessionStore.d.ts +35 -0
  264. package/dist/core/sessionStore.d.ts.map +1 -0
  265. package/dist/core/sessionStore.js +191 -0
  266. package/dist/core/sessionStore.js.map +1 -0
  267. package/dist/core/shutdown.d.ts +34 -0
  268. package/dist/core/shutdown.d.ts.map +1 -0
  269. package/dist/core/shutdown.js +186 -0
  270. package/dist/core/shutdown.js.map +1 -0
  271. package/dist/core/sudoPasswordManager.d.ts +52 -0
  272. package/dist/core/sudoPasswordManager.d.ts.map +1 -0
  273. package/dist/core/sudoPasswordManager.js +115 -0
  274. package/dist/core/sudoPasswordManager.js.map +1 -0
  275. package/dist/core/taskCompletionDetector.d.ts +117 -0
  276. package/dist/core/taskCompletionDetector.d.ts.map +1 -0
  277. package/dist/core/taskCompletionDetector.js +532 -0
  278. package/dist/core/taskCompletionDetector.js.map +1 -0
  279. package/dist/core/testFailureMonitor.d.ts +67 -0
  280. package/dist/core/testFailureMonitor.d.ts.map +1 -0
  281. package/dist/core/testFailureMonitor.js +262 -0
  282. package/dist/core/testFailureMonitor.js.map +1 -0
  283. package/dist/core/toolPreconditions.d.ts +34 -0
  284. package/dist/core/toolPreconditions.d.ts.map +1 -0
  285. package/dist/core/toolPreconditions.js +242 -0
  286. package/dist/core/toolPreconditions.js.map +1 -0
  287. package/dist/core/toolRuntime.d.ts +192 -0
  288. package/dist/core/toolRuntime.d.ts.map +1 -0
  289. package/dist/core/toolRuntime.js +477 -0
  290. package/dist/core/toolRuntime.js.map +1 -0
  291. package/dist/core/types/utilityTypes.d.ts +183 -0
  292. package/dist/core/types/utilityTypes.d.ts.map +1 -0
  293. package/dist/core/types/utilityTypes.js +273 -0
  294. package/dist/core/types/utilityTypes.js.map +1 -0
  295. package/dist/core/types.d.ts +334 -0
  296. package/dist/core/types.d.ts.map +1 -0
  297. package/dist/core/types.js +76 -0
  298. package/dist/core/types.js.map +1 -0
  299. package/dist/core/updateChecker.d.ts +148 -0
  300. package/dist/core/updateChecker.d.ts.map +1 -0
  301. package/dist/core/updateChecker.js +599 -0
  302. package/dist/core/updateChecker.js.map +1 -0
  303. package/dist/headless/interactiveShell.d.ts +39 -0
  304. package/dist/headless/interactiveShell.d.ts.map +1 -0
  305. package/dist/headless/interactiveShell.js +2052 -0
  306. package/dist/headless/interactiveShell.js.map +1 -0
  307. package/dist/headless/printMode.d.ts +17 -0
  308. package/dist/headless/printMode.d.ts.map +1 -0
  309. package/dist/headless/printMode.js +40 -0
  310. package/dist/headless/printMode.js.map +1 -0
  311. package/dist/leanAgent.d.ts +73 -0
  312. package/dist/leanAgent.d.ts.map +1 -0
  313. package/dist/leanAgent.js +177 -0
  314. package/dist/leanAgent.js.map +1 -0
  315. package/dist/plugins/index.d.ts +49 -0
  316. package/dist/plugins/index.d.ts.map +1 -0
  317. package/dist/plugins/index.js +104 -0
  318. package/dist/plugins/index.js.map +1 -0
  319. package/dist/plugins/providers/anthropic/index.d.ts +9 -0
  320. package/dist/plugins/providers/anthropic/index.d.ts.map +1 -0
  321. package/dist/plugins/providers/anthropic/index.js +48 -0
  322. package/dist/plugins/providers/anthropic/index.js.map +1 -0
  323. package/dist/plugins/providers/deepseek/index.d.ts +11 -0
  324. package/dist/plugins/providers/deepseek/index.d.ts.map +1 -0
  325. package/dist/plugins/providers/deepseek/index.js +59 -0
  326. package/dist/plugins/providers/deepseek/index.js.map +1 -0
  327. package/dist/plugins/providers/index.d.ts +2 -0
  328. package/dist/plugins/providers/index.d.ts.map +1 -0
  329. package/dist/plugins/providers/index.js +17 -0
  330. package/dist/plugins/providers/index.js.map +1 -0
  331. package/dist/plugins/providers/openai/index.d.ts +10 -0
  332. package/dist/plugins/providers/openai/index.d.ts.map +1 -0
  333. package/dist/plugins/providers/openai/index.js +47 -0
  334. package/dist/plugins/providers/openai/index.js.map +1 -0
  335. package/dist/plugins/providers/xai/index.d.ts +10 -0
  336. package/dist/plugins/providers/xai/index.d.ts.map +1 -0
  337. package/dist/plugins/providers/xai/index.js +47 -0
  338. package/dist/plugins/providers/xai/index.js.map +1 -0
  339. package/dist/plugins/tools/agentSpawning/agentSpawningPlugin.d.ts +10 -0
  340. package/dist/plugins/tools/agentSpawning/agentSpawningPlugin.d.ts.map +1 -0
  341. package/dist/plugins/tools/agentSpawning/agentSpawningPlugin.js +110 -0
  342. package/dist/plugins/tools/agentSpawning/agentSpawningPlugin.js.map +1 -0
  343. package/dist/plugins/tools/bash/localBashPlugin.d.ts +3 -0
  344. package/dist/plugins/tools/bash/localBashPlugin.d.ts.map +1 -0
  345. package/dist/plugins/tools/bash/localBashPlugin.js +14 -0
  346. package/dist/plugins/tools/bash/localBashPlugin.js.map +1 -0
  347. package/dist/plugins/tools/edit/editPlugin.d.ts +9 -0
  348. package/dist/plugins/tools/edit/editPlugin.d.ts.map +1 -0
  349. package/dist/plugins/tools/edit/editPlugin.js +15 -0
  350. package/dist/plugins/tools/edit/editPlugin.js.map +1 -0
  351. package/dist/plugins/tools/enhancedGit/enhancedGitPlugin.d.ts +3 -0
  352. package/dist/plugins/tools/enhancedGit/enhancedGitPlugin.d.ts.map +1 -0
  353. package/dist/plugins/tools/enhancedGit/enhancedGitPlugin.js +9 -0
  354. package/dist/plugins/tools/enhancedGit/enhancedGitPlugin.js.map +1 -0
  355. package/dist/plugins/tools/filesystem/localFilesystemPlugin.d.ts +3 -0
  356. package/dist/plugins/tools/filesystem/localFilesystemPlugin.d.ts.map +1 -0
  357. package/dist/plugins/tools/filesystem/localFilesystemPlugin.js +14 -0
  358. package/dist/plugins/tools/filesystem/localFilesystemPlugin.js.map +1 -0
  359. package/dist/plugins/tools/gitHistory/gitHistoryPlugin.d.ts +3 -0
  360. package/dist/plugins/tools/gitHistory/gitHistoryPlugin.d.ts.map +1 -0
  361. package/dist/plugins/tools/gitHistory/gitHistoryPlugin.js +9 -0
  362. package/dist/plugins/tools/gitHistory/gitHistoryPlugin.js.map +1 -0
  363. package/dist/plugins/tools/index.d.ts +3 -0
  364. package/dist/plugins/tools/index.d.ts.map +1 -0
  365. package/dist/plugins/tools/index.js +3 -0
  366. package/dist/plugins/tools/index.js.map +1 -0
  367. package/dist/plugins/tools/integrity/integrityPlugin.d.ts +3 -0
  368. package/dist/plugins/tools/integrity/integrityPlugin.d.ts.map +1 -0
  369. package/dist/plugins/tools/integrity/integrityPlugin.js +31 -0
  370. package/dist/plugins/tools/integrity/integrityPlugin.js.map +1 -0
  371. package/dist/plugins/tools/mcp/mcpPlugin.d.ts +3 -0
  372. package/dist/plugins/tools/mcp/mcpPlugin.d.ts.map +1 -0
  373. package/dist/plugins/tools/mcp/mcpPlugin.js +27 -0
  374. package/dist/plugins/tools/mcp/mcpPlugin.js.map +1 -0
  375. package/dist/plugins/tools/nodeDefaults.d.ts +13 -0
  376. package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -0
  377. package/dist/plugins/tools/nodeDefaults.js +33 -0
  378. package/dist/plugins/tools/nodeDefaults.js.map +1 -0
  379. package/dist/plugins/tools/orchestration/orchestrationPlugin.d.ts +3 -0
  380. package/dist/plugins/tools/orchestration/orchestrationPlugin.d.ts.map +1 -0
  381. package/dist/plugins/tools/orchestration/orchestrationPlugin.js +340 -0
  382. package/dist/plugins/tools/orchestration/orchestrationPlugin.js.map +1 -0
  383. package/dist/plugins/tools/registry.d.ts +22 -0
  384. package/dist/plugins/tools/registry.d.ts.map +1 -0
  385. package/dist/plugins/tools/registry.js +58 -0
  386. package/dist/plugins/tools/registry.js.map +1 -0
  387. package/dist/plugins/tools/search/localSearchPlugin.d.ts +3 -0
  388. package/dist/plugins/tools/search/localSearchPlugin.d.ts.map +1 -0
  389. package/dist/plugins/tools/search/localSearchPlugin.js +14 -0
  390. package/dist/plugins/tools/search/localSearchPlugin.js.map +1 -0
  391. package/dist/plugins/tools/skills/skillPlugin.d.ts +3 -0
  392. package/dist/plugins/tools/skills/skillPlugin.d.ts.map +1 -0
  393. package/dist/plugins/tools/skills/skillPlugin.js +27 -0
  394. package/dist/plugins/tools/skills/skillPlugin.js.map +1 -0
  395. package/dist/plugins/tools/todo/todoPlugin.d.ts +3 -0
  396. package/dist/plugins/tools/todo/todoPlugin.d.ts.map +1 -0
  397. package/dist/plugins/tools/todo/todoPlugin.js +10 -0
  398. package/dist/plugins/tools/todo/todoPlugin.js.map +1 -0
  399. package/dist/providers/baseProvider.d.ts +148 -0
  400. package/dist/providers/baseProvider.d.ts.map +1 -0
  401. package/dist/providers/baseProvider.js +284 -0
  402. package/dist/providers/baseProvider.js.map +1 -0
  403. package/dist/providers/openaiChatCompletionsProvider.d.ts +70 -0
  404. package/dist/providers/openaiChatCompletionsProvider.d.ts.map +1 -0
  405. package/dist/providers/openaiChatCompletionsProvider.js +1043 -0
  406. package/dist/providers/openaiChatCompletionsProvider.js.map +1 -0
  407. package/dist/providers/providerFactory.d.ts +22 -0
  408. package/dist/providers/providerFactory.d.ts.map +1 -0
  409. package/dist/providers/providerFactory.js +25 -0
  410. package/dist/providers/providerFactory.js.map +1 -0
  411. package/dist/providers/resilientProvider.d.ts +103 -0
  412. package/dist/providers/resilientProvider.d.ts.map +1 -0
  413. package/dist/providers/resilientProvider.js +468 -0
  414. package/dist/providers/resilientProvider.js.map +1 -0
  415. package/dist/runtime/agentController.d.ts +121 -0
  416. package/dist/runtime/agentController.d.ts.map +1 -0
  417. package/dist/runtime/agentController.js +739 -0
  418. package/dist/runtime/agentController.js.map +1 -0
  419. package/dist/runtime/agentHost.d.ts +61 -0
  420. package/dist/runtime/agentHost.d.ts.map +1 -0
  421. package/dist/runtime/agentHost.js +158 -0
  422. package/dist/runtime/agentHost.js.map +1 -0
  423. package/dist/runtime/agentSession.d.ts +49 -0
  424. package/dist/runtime/agentSession.d.ts.map +1 -0
  425. package/dist/runtime/agentSession.js +218 -0
  426. package/dist/runtime/agentSession.js.map +1 -0
  427. package/dist/runtime/agentSpawningWiring.d.ts +23 -0
  428. package/dist/runtime/agentSpawningWiring.d.ts.map +1 -0
  429. package/dist/runtime/agentSpawningWiring.js +119 -0
  430. package/dist/runtime/agentSpawningWiring.js.map +1 -0
  431. package/dist/runtime/agentWorkerPool.d.ts +167 -0
  432. package/dist/runtime/agentWorkerPool.d.ts.map +1 -0
  433. package/dist/runtime/agentWorkerPool.js +435 -0
  434. package/dist/runtime/agentWorkerPool.js.map +1 -0
  435. package/dist/runtime/node.d.ts +7 -0
  436. package/dist/runtime/node.d.ts.map +1 -0
  437. package/dist/runtime/node.js +50 -0
  438. package/dist/runtime/node.js.map +1 -0
  439. package/dist/runtime/universal.d.ts +18 -0
  440. package/dist/runtime/universal.d.ts.map +1 -0
  441. package/dist/runtime/universal.js +21 -0
  442. package/dist/runtime/universal.js.map +1 -0
  443. package/dist/shell/autoExecutor.d.ts +70 -0
  444. package/dist/shell/autoExecutor.d.ts.map +1 -0
  445. package/dist/shell/autoExecutor.js +320 -0
  446. package/dist/shell/autoExecutor.js.map +1 -0
  447. package/dist/shell/commandRegistry.d.ts +122 -0
  448. package/dist/shell/commandRegistry.d.ts.map +1 -0
  449. package/dist/shell/commandRegistry.js +355 -0
  450. package/dist/shell/commandRegistry.js.map +1 -0
  451. package/dist/shell/composableMessage.d.ts +178 -0
  452. package/dist/shell/composableMessage.d.ts.map +1 -0
  453. package/dist/shell/composableMessage.js +384 -0
  454. package/dist/shell/composableMessage.js.map +1 -0
  455. package/dist/shell/liveStatus.d.ts +27 -0
  456. package/dist/shell/liveStatus.d.ts.map +1 -0
  457. package/dist/shell/liveStatus.js +53 -0
  458. package/dist/shell/liveStatus.js.map +1 -0
  459. package/dist/shell/systemPrompt.d.ts +12 -0
  460. package/dist/shell/systemPrompt.d.ts.map +1 -0
  461. package/dist/shell/systemPrompt.js +16 -0
  462. package/dist/shell/systemPrompt.js.map +1 -0
  463. package/dist/shell/vimMode.d.ts +66 -0
  464. package/dist/shell/vimMode.d.ts.map +1 -0
  465. package/dist/shell/vimMode.js +435 -0
  466. package/dist/shell/vimMode.js.map +1 -0
  467. package/dist/tools/bashTools.d.ts +11 -0
  468. package/dist/tools/bashTools.d.ts.map +1 -0
  469. package/dist/tools/bashTools.js +779 -0
  470. package/dist/tools/bashTools.js.map +1 -0
  471. package/dist/tools/diffUtils.d.ts +43 -0
  472. package/dist/tools/diffUtils.d.ts.map +1 -0
  473. package/dist/tools/diffUtils.js +607 -0
  474. package/dist/tools/diffUtils.js.map +1 -0
  475. package/dist/tools/editTools.d.ts +29 -0
  476. package/dist/tools/editTools.d.ts.map +1 -0
  477. package/dist/tools/editTools.js +792 -0
  478. package/dist/tools/editTools.js.map +1 -0
  479. package/dist/tools/fileChangeTracker.d.ts +47 -0
  480. package/dist/tools/fileChangeTracker.d.ts.map +1 -0
  481. package/dist/tools/fileChangeTracker.js +154 -0
  482. package/dist/tools/fileChangeTracker.js.map +1 -0
  483. package/dist/tools/fileReadTracker.d.ts +69 -0
  484. package/dist/tools/fileReadTracker.d.ts.map +1 -0
  485. package/dist/tools/fileReadTracker.js +213 -0
  486. package/dist/tools/fileReadTracker.js.map +1 -0
  487. package/dist/tools/fileTools.d.ts +3 -0
  488. package/dist/tools/fileTools.d.ts.map +1 -0
  489. package/dist/tools/fileTools.js +389 -0
  490. package/dist/tools/fileTools.js.map +1 -0
  491. package/dist/tools/grepTools.d.ts +3 -0
  492. package/dist/tools/grepTools.d.ts.map +1 -0
  493. package/dist/tools/grepTools.js +128 -0
  494. package/dist/tools/grepTools.js.map +1 -0
  495. package/dist/tools/hitlTools.d.ts +7 -0
  496. package/dist/tools/hitlTools.d.ts.map +1 -0
  497. package/dist/tools/hitlTools.js +185 -0
  498. package/dist/tools/hitlTools.js.map +1 -0
  499. package/dist/tools/localExplore.d.ts +38 -0
  500. package/dist/tools/localExplore.d.ts.map +1 -0
  501. package/dist/tools/localExplore.js +30 -0
  502. package/dist/tools/localExplore.js.map +1 -0
  503. package/dist/tools/memoryTools.d.ts +20 -0
  504. package/dist/tools/memoryTools.d.ts.map +1 -0
  505. package/dist/tools/memoryTools.js +180 -0
  506. package/dist/tools/memoryTools.js.map +1 -0
  507. package/dist/tools/notebookTools.d.ts +20 -0
  508. package/dist/tools/notebookTools.d.ts.map +1 -0
  509. package/dist/tools/notebookTools.js +140 -0
  510. package/dist/tools/notebookTools.js.map +1 -0
  511. package/dist/tools/searchTools.d.ts +12 -0
  512. package/dist/tools/searchTools.d.ts.map +1 -0
  513. package/dist/tools/searchTools.js +413 -0
  514. package/dist/tools/searchTools.js.map +1 -0
  515. package/dist/tools/skillTools.d.ts +24 -0
  516. package/dist/tools/skillTools.d.ts.map +1 -0
  517. package/dist/tools/skillTools.js +140 -0
  518. package/dist/tools/skillTools.js.map +1 -0
  519. package/dist/tools/todoTools.d.ts +24 -0
  520. package/dist/tools/todoTools.d.ts.map +1 -0
  521. package/dist/tools/todoTools.js +101 -0
  522. package/dist/tools/todoTools.js.map +1 -0
  523. package/dist/tools/webTools.d.ts +26 -0
  524. package/dist/tools/webTools.d.ts.map +1 -0
  525. package/dist/tools/webTools.js +383 -0
  526. package/dist/tools/webTools.js.map +1 -0
  527. package/dist/ui/ink/App.d.ts +42 -0
  528. package/dist/ui/ink/App.d.ts.map +1 -0
  529. package/dist/ui/ink/App.js +10 -0
  530. package/dist/ui/ink/App.js.map +1 -0
  531. package/dist/ui/ink/ChatStatic.d.ts +29 -0
  532. package/dist/ui/ink/ChatStatic.d.ts.map +1 -0
  533. package/dist/ui/ink/ChatStatic.js +14 -0
  534. package/dist/ui/ink/ChatStatic.js.map +1 -0
  535. package/dist/ui/ink/InkPromptController.d.ts +289 -0
  536. package/dist/ui/ink/InkPromptController.d.ts.map +1 -0
  537. package/dist/ui/ink/InkPromptController.js +552 -0
  538. package/dist/ui/ink/InkPromptController.js.map +1 -0
  539. package/dist/ui/ink/Menu.d.ts +21 -0
  540. package/dist/ui/ink/Menu.d.ts.map +1 -0
  541. package/dist/ui/ink/Menu.js +61 -0
  542. package/dist/ui/ink/Menu.js.map +1 -0
  543. package/dist/ui/ink/Prompt.d.ts +31 -0
  544. package/dist/ui/ink/Prompt.d.ts.map +1 -0
  545. package/dist/ui/ink/Prompt.js +197 -0
  546. package/dist/ui/ink/Prompt.js.map +1 -0
  547. package/dist/ui/ink/StatusLine.d.ts +21 -0
  548. package/dist/ui/ink/StatusLine.d.ts.map +1 -0
  549. package/dist/ui/ink/StatusLine.js +11 -0
  550. package/dist/ui/ink/StatusLine.js.map +1 -0
  551. package/dist/ui/theme.d.ts +351 -0
  552. package/dist/ui/theme.d.ts.map +1 -0
  553. package/dist/ui/theme.js +437 -0
  554. package/dist/ui/theme.js.map +1 -0
  555. package/dist/utils/analytics.d.ts +2 -0
  556. package/dist/utils/analytics.d.ts.map +1 -0
  557. package/dist/utils/analytics.js +51 -0
  558. package/dist/utils/analytics.js.map +1 -0
  559. package/dist/utils/asyncUtils.d.ts +95 -0
  560. package/dist/utils/asyncUtils.d.ts.map +1 -0
  561. package/dist/utils/asyncUtils.js +286 -0
  562. package/dist/utils/asyncUtils.js.map +1 -0
  563. package/dist/utils/debugLogger.d.ts +6 -0
  564. package/dist/utils/debugLogger.d.ts.map +1 -0
  565. package/dist/utils/debugLogger.js +39 -0
  566. package/dist/utils/debugLogger.js.map +1 -0
  567. package/dist/utils/errorUtils.d.ts +12 -0
  568. package/dist/utils/errorUtils.d.ts.map +1 -0
  569. package/dist/utils/errorUtils.js +83 -0
  570. package/dist/utils/errorUtils.js.map +1 -0
  571. package/dist/utils/frontmatter.d.ts +10 -0
  572. package/dist/utils/frontmatter.d.ts.map +1 -0
  573. package/dist/utils/frontmatter.js +78 -0
  574. package/dist/utils/frontmatter.js.map +1 -0
  575. package/dist/utils/packageInfo.d.ts +14 -0
  576. package/dist/utils/packageInfo.d.ts.map +1 -0
  577. package/dist/utils/packageInfo.js +45 -0
  578. package/dist/utils/packageInfo.js.map +1 -0
  579. package/dist/utils/planFormatter.d.ts +34 -0
  580. package/dist/utils/planFormatter.d.ts.map +1 -0
  581. package/dist/utils/planFormatter.js +141 -0
  582. package/dist/utils/planFormatter.js.map +1 -0
  583. package/dist/utils/securityUtils.d.ts +132 -0
  584. package/dist/utils/securityUtils.d.ts.map +1 -0
  585. package/dist/utils/securityUtils.js +324 -0
  586. package/dist/utils/securityUtils.js.map +1 -0
  587. package/dist/utils/statusReporter.d.ts +6 -0
  588. package/dist/utils/statusReporter.d.ts.map +1 -0
  589. package/dist/utils/statusReporter.js +26 -0
  590. package/dist/utils/statusReporter.js.map +1 -0
  591. package/dist/workspace.d.ts +8 -0
  592. package/dist/workspace.d.ts.map +1 -0
  593. package/dist/workspace.js +135 -0
  594. package/dist/workspace.js.map +1 -0
  595. package/dist/workspace.validator.d.ts +49 -0
  596. package/dist/workspace.validator.d.ts.map +1 -0
  597. package/dist/workspace.validator.js +215 -0
  598. package/dist/workspace.validator.js.map +1 -0
  599. package/package.json +123 -0
  600. package/scripts/postinstall.cjs +58 -0
@@ -0,0 +1,1668 @@
1
+ import path from 'node:path';
2
+ import { isMultilinePaste, processPaste } from './multilinePasteHandler.js';
3
+ import { safeErrorMessage } from './secretStore.js';
4
+ import { logDebug, debugSnippet } from '../utils/debugLogger.js';
5
+ import { ensureNextSteps } from './finalResponseFormatter.js';
6
+ import { isAdversarialEnabled, reviewDraft } from './adversarial.js';
7
+ /**
8
+ * Maximum number of context overflow recovery attempts
9
+ */
10
+ const MAX_CONTEXT_RECOVERY_ATTEMPTS = 3;
11
+ // Streaming runs without timeouts - we let the model take as long as it needs
12
+ /**
13
+ * Check if an error is a context overflow error
14
+ */
15
+ function isContextOverflowError(error) {
16
+ if (!(error instanceof Error))
17
+ return false;
18
+ const message = error.message.toLowerCase();
19
+ return (message.includes('context length') ||
20
+ message.includes('token') && (message.includes('limit') || message.includes('exceed') || message.includes('maximum')) ||
21
+ message.includes('too long') ||
22
+ message.includes('too many tokens') ||
23
+ message.includes('max_tokens') ||
24
+ message.includes('context window'));
25
+ }
26
+ /**
27
+ * Check if an error is a transient/retryable error (network issues, rate limits, server errors)
28
+ */
29
+ function isTransientError(error) {
30
+ if (!(error instanceof Error))
31
+ return false;
32
+ const message = error.message.toLowerCase();
33
+ // Network errors
34
+ const networkPatterns = [
35
+ 'econnrefused', 'econnreset', 'enotfound', 'etimedout', 'epipe',
36
+ 'network error', 'connection error', 'fetch failed', 'socket hang up',
37
+ 'network is unreachable', 'connection refused', 'connection reset',
38
+ ];
39
+ if (networkPatterns.some(p => message.includes(p))) {
40
+ return true;
41
+ }
42
+ // Rate limit errors
43
+ if (message.includes('rate limit') || message.includes('429') || message.includes('too many requests')) {
44
+ return true;
45
+ }
46
+ // Server errors (5xx)
47
+ if (message.includes('500') || message.includes('502') || message.includes('503') || message.includes('504')) {
48
+ return true;
49
+ }
50
+ // Temporary service errors
51
+ if (message.includes('service unavailable') || message.includes('temporarily unavailable') ||
52
+ message.includes('overloaded') || message.includes('server error')) {
53
+ return true;
54
+ }
55
+ return false;
56
+ }
57
+ /**
58
+ * Maximum number of transient error retries
59
+ */
60
+ const MAX_TRANSIENT_RETRIES = 3;
61
+ /**
62
+ * Delay before retry (in ms), with exponential backoff
63
+ */
64
+ function getRetryDelay(attempt) {
65
+ // Base delay of 1 second, doubles each attempt: 1s, 2s, 4s
66
+ return Math.min(1000 * Math.pow(2, attempt - 1), 10000);
67
+ }
68
+ /**
69
+ * Sleep for the specified milliseconds
70
+ */
71
+ function sleep(ms) {
72
+ return new Promise(resolve => setTimeout(resolve, ms));
73
+ }
74
+ export class AgentRuntime {
75
+ messages = [];
76
+ provider;
77
+ toolRuntime;
78
+ callbacks;
79
+ contextManager;
80
+ activeRun = null;
81
+ baseSystemPrompt;
82
+ providerId;
83
+ modelId;
84
+ workingDirectory;
85
+ explainEdits;
86
+ cancellationRequested = false;
87
+ // Loop detection: track last tool calls to detect stuck loops
88
+ lastToolCallSignature = null;
89
+ repeatedToolCallCount = 0;
90
+ static MAX_REPEATED_TOOL_CALLS = 5; // Stop on 5th identical call (4 allowed)
91
+ // Session-level context recovery tracking to prevent endless recovery loops
92
+ totalContextRecoveries = 0;
93
+ static MAX_TOTAL_RECOVERIES = 5; // Max recoveries across entire session
94
+ // Behavioral loop detection: track recent tool calls to catch repetitive patterns
95
+ // e.g., calling "execute_bash" with "git status" 5 times even if output differs slightly
96
+ recentToolCalls = [];
97
+ static TOOL_HISTORY_SIZE = 12;
98
+ static BEHAVIORAL_LOOP_THRESHOLD = 3; // Same tool+cmd 3+ times in last 12 = stuck
99
+ // Consecutive failure detection: track failed commands to prevent stuck loops
100
+ consecutiveFailures = 0;
101
+ static MAX_CONSECUTIVE_FAILURES = 3; // Stop after 3 consecutive failed commands
102
+ static EDIT_CONTEXT_CHAR_LIMIT = 4000;
103
+ // Never cache stateful tools - they must always execute to reflect current system state
104
+ static NON_CACHEABLE_TOOL_NAMES = new Set([
105
+ 'bash',
106
+ 'execute_bash',
107
+ 'execute_command',
108
+ 'run_command',
109
+ 'edit',
110
+ 'edit_file',
111
+ 'write',
112
+ 'write_file',
113
+ 'notebookedit',
114
+ 'read',
115
+ 'read_file',
116
+ 'read_files',
117
+ 'list_files',
118
+ 'list_dir',
119
+ 'glob',
120
+ 'grep',
121
+ 'search',
122
+ 'search_text',
123
+ 'git_status',
124
+ 'git_diff',
125
+ 'git_log',
126
+ 'git_commit',
127
+ 'git_push',
128
+ ]);
129
+ // Skip loop short-circuiting for direct execution tools to avoid blocking user commands
130
+ static LOOP_EXEMPT_TOOL_NAMES = new Set([
131
+ 'bash',
132
+ 'execute_bash',
133
+ 'execute_command',
134
+ 'run_command',
135
+ 'edit',
136
+ 'edit_file',
137
+ 'write',
138
+ 'write_file',
139
+ 'notebookedit',
140
+ // Read/search tools are noise-prone and often repeated legitimately
141
+ 'read',
142
+ 'read_file',
143
+ 'read_files',
144
+ 'list_files',
145
+ 'list_dir',
146
+ 'glob',
147
+ 'glob_search',
148
+ 'grep',
149
+ 'search',
150
+ ]);
151
+ // Tool result cache: prevent duplicate identical tool calls by returning cached results
152
+ // Key: tool signature (name + JSON args), Value: result string
153
+ toolResultCache = new Map();
154
+ static TOOL_CACHE_MAX_SIZE = 50; // Keep last 50 tool results
155
+ // Track tool history position per send() call for accurate progress detection
156
+ toolHistoryCursor = 0;
157
+ // Cached model info from provider API (real context window limits)
158
+ modelInfo = null;
159
+ modelInfoFetched = false;
160
+ constructor(options) {
161
+ this.provider = options.provider;
162
+ this.toolRuntime = options.toolRuntime;
163
+ this.callbacks = options.callbacks ?? {};
164
+ this.contextManager = options.contextManager ?? null;
165
+ this.providerId = options.providerId ?? 'unknown';
166
+ this.modelId = options.modelId ?? 'unknown';
167
+ this.workingDirectory = options.workingDirectory ?? process.cwd();
168
+ this.explainEdits = options.explainEdits ?? false;
169
+ const trimmedPrompt = options.systemPrompt.trim();
170
+ this.baseSystemPrompt = trimmedPrompt || null;
171
+ if (trimmedPrompt) {
172
+ this.messages.push({ role: 'system', content: trimmedPrompt });
173
+ }
174
+ }
175
+ /**
176
+ * Request cancellation of the current operation.
177
+ * The agent will stop at the next safe point (after current tool completes).
178
+ */
179
+ requestCancellation() {
180
+ this.cancellationRequested = true;
181
+ }
182
+ /**
183
+ * Check if cancellation has been requested.
184
+ */
185
+ isCancellationRequested() {
186
+ return this.cancellationRequested;
187
+ }
188
+ /**
189
+ * Check if the agent is currently processing a request.
190
+ */
191
+ isRunning() {
192
+ return this.activeRun !== null;
193
+ }
194
+ /**
195
+ * Check if any of the tool calls are edit operations (Edit, Write)
196
+ */
197
+ isEditToolCall(toolName) {
198
+ const name = toolName.toLowerCase();
199
+ return name === 'edit' || name === 'edit_file' || name === 'write' || name === 'write_file';
200
+ }
201
+ /**
202
+ * Extract a display-friendly file path from a tool call (prefers workspace-relative path)
203
+ */
204
+ getEditedFilePath(call) {
205
+ const args = call.arguments;
206
+ const rawPath = typeof args['file_path'] === 'string'
207
+ ? args['file_path']
208
+ : typeof args['path'] === 'string'
209
+ ? args['path']
210
+ : null;
211
+ if (!rawPath) {
212
+ return null;
213
+ }
214
+ const relativePath = path.relative(this.workingDirectory, rawPath);
215
+ if (relativePath && !relativePath.startsWith('..') && relativePath !== '') {
216
+ return relativePath;
217
+ }
218
+ return rawPath;
219
+ }
220
+ /**
221
+ * Get the file paths from edit tool calls for the explanation prompt
222
+ */
223
+ getEditedFiles(toolCalls) {
224
+ const files = [];
225
+ for (const call of toolCalls) {
226
+ if (this.isEditToolCall(call.name)) {
227
+ const filePath = this.getEditedFilePath(call);
228
+ if (filePath) {
229
+ files.push(filePath);
230
+ }
231
+ }
232
+ }
233
+ return files;
234
+ }
235
+ async send(text, useStreaming = false) {
236
+ const prompt = text.trim();
237
+ if (!prompt) {
238
+ return '';
239
+ }
240
+ // Notify UI immediately so it can reflect activity without waiting for generation
241
+ this.callbacks.onRequestReceived?.(prompt.slice(0, 400));
242
+ // Reset cancellation flag and loop tracking at start of new request
243
+ this.cancellationRequested = false;
244
+ this.resetBehavioralLoopTracking();
245
+ // Track tool history position for this run
246
+ this.toolHistoryCursor = this.toolRuntime.getToolHistory().length;
247
+ // Handle multi-line paste: show actual content to user, send full content to AI
248
+ if (isMultilinePaste(prompt)) {
249
+ const processed = processPaste(prompt);
250
+ // Notify UI about the paste with the actual content, not just summary
251
+ this.callbacks.onMultilinePaste?.(processed.fullContent, processed.metadata);
252
+ // Send the full content to the AI
253
+ this.messages.push({ role: 'user', content: processed.fullContent });
254
+ }
255
+ else {
256
+ // Single-line or short text: send as-is
257
+ this.messages.push({ role: 'user', content: prompt });
258
+ }
259
+ const run = { startedAt: Date.now() };
260
+ this.activeRun = run;
261
+ try {
262
+ // Always use streaming when available - no fallback
263
+ if (useStreaming && this.provider.generateStream) {
264
+ return await this.processConversationStreaming();
265
+ }
266
+ return await this.processConversation();
267
+ }
268
+ finally {
269
+ if (this.activeRun === run) {
270
+ this.activeRun = null;
271
+ }
272
+ // Reset cancellation flag when done
273
+ this.cancellationRequested = false;
274
+ }
275
+ }
276
+ async processConversation() {
277
+ let contextRecoveryAttempts = 0;
278
+ let transientRetryAttempts = 0;
279
+ // eslint-disable-next-line no-constant-condition
280
+ while (true) {
281
+ // Check for cancellation at start of each iteration
282
+ if (this.cancellationRequested) {
283
+ this.callbacks.onCancelled?.();
284
+ return '[Operation cancelled by user]';
285
+ }
286
+ // Prune messages if approaching context limit (BEFORE generation)
287
+ await this.pruneMessagesIfNeeded();
288
+ try {
289
+ const response = await this.provider.generate(this.messages, this.providerTools);
290
+ const usage = response.usage ?? null;
291
+ const contextStats = this.getContextStats();
292
+ // Reset recovery attempts on successful generation
293
+ contextRecoveryAttempts = 0;
294
+ if (response.type === 'tool_calls') {
295
+ // BEHAVIORAL LOOP DETECTION: Check if model is stuck calling same tool repeatedly
296
+ const behavioralLoopResult = this.checkBehavioralLoop(response.toolCalls);
297
+ if (behavioralLoopResult) {
298
+ this.emitAssistantMessage(behavioralLoopResult, { isFinal: true, usage, contextStats });
299
+ this.messages.push({ role: 'assistant', content: behavioralLoopResult });
300
+ return behavioralLoopResult;
301
+ }
302
+ // Loop detection: check if same tool calls are being repeated (exact signature match)
303
+ const signatureCalls = response.toolCalls.filter(call => !this.shouldSkipLoopDetection(call));
304
+ const toolSignature = signatureCalls.length
305
+ ? signatureCalls
306
+ .map((t) => `${t.name}:${JSON.stringify(t.arguments)}`)
307
+ .sort()
308
+ .join('|')
309
+ : null;
310
+ if (toolSignature && toolSignature === this.lastToolCallSignature) {
311
+ this.repeatedToolCallCount++;
312
+ if (this.repeatedToolCallCount >= AgentRuntime.MAX_REPEATED_TOOL_CALLS) {
313
+ // Break out of loop - model is stuck
314
+ const loopMsg = `Tool loop detected: same tools called ${this.repeatedToolCallCount} times. Please try a different approach or provide more specific instructions.`;
315
+ this.emitAssistantMessage(loopMsg, { isFinal: true, usage, contextStats });
316
+ this.messages.push({ role: 'assistant', content: loopMsg });
317
+ this.lastToolCallSignature = null;
318
+ this.repeatedToolCallCount = 0;
319
+ return loopMsg;
320
+ }
321
+ }
322
+ else if (toolSignature) {
323
+ this.lastToolCallSignature = toolSignature;
324
+ this.repeatedToolCallCount = 1;
325
+ }
326
+ else {
327
+ this.lastToolCallSignature = null;
328
+ this.repeatedToolCallCount = 0;
329
+ }
330
+ // Emit narration if present - it shows the AI's thought process before tools
331
+ const narration = response.content?.trim();
332
+ if (narration) {
333
+ this.emitAssistantMessage(narration, {
334
+ isFinal: false,
335
+ usage,
336
+ contextStats,
337
+ });
338
+ }
339
+ this.maybeAckToolCalls(response.toolCalls, Boolean(narration?.length), usage, contextStats);
340
+ const assistantMessage = {
341
+ role: 'assistant',
342
+ content: response.content ?? '',
343
+ };
344
+ if (response.toolCalls?.length) {
345
+ assistantMessage.toolCalls = response.toolCalls;
346
+ }
347
+ this.messages.push(assistantMessage);
348
+ await this.resolveToolCalls(response.toolCalls);
349
+ continue;
350
+ }
351
+ const reply = response.content?.trim() ?? '';
352
+ const { output: ensuredReply } = ensureNextSteps(reply);
353
+ let finalReply = ensuredReply;
354
+ // Reset loop detection when we get a text response (not just tool calls)
355
+ if (finalReply.length >= 10) {
356
+ this.lastToolCallSignature = null;
357
+ this.repeatedToolCallCount = 0;
358
+ }
359
+ finalReply = await this.maybeAdversarialReview(finalReply);
360
+ if (finalReply) {
361
+ this.emitAssistantMessage(finalReply, { isFinal: true, usage, contextStats });
362
+ }
363
+ this.messages.push({ role: 'assistant', content: finalReply });
364
+ // Trigger verification for final responses with verifiable claims
365
+ this.triggerVerificationIfNeeded(finalReply);
366
+ return finalReply;
367
+ }
368
+ catch (error) {
369
+ // Auto-recover from context overflow errors (with session-level limit)
370
+ const canRecover = contextRecoveryAttempts < MAX_CONTEXT_RECOVERY_ATTEMPTS &&
371
+ this.totalContextRecoveries < AgentRuntime.MAX_TOTAL_RECOVERIES;
372
+ if (isContextOverflowError(error) && canRecover) {
373
+ contextRecoveryAttempts++;
374
+ this.totalContextRecoveries++;
375
+ const recovered = await this.recoverFromContextOverflow(contextRecoveryAttempts);
376
+ if (recovered) {
377
+ // Notify UI that we're continuing after recovery
378
+ this.callbacks.onContinueAfterRecovery?.();
379
+ // Retry the generation with reduced context
380
+ continue;
381
+ }
382
+ }
383
+ // Auto-retry transient errors (network issues, rate limits, server errors)
384
+ if (isTransientError(error) && transientRetryAttempts < MAX_TRANSIENT_RETRIES) {
385
+ transientRetryAttempts++;
386
+ const delayMs = getRetryDelay(transientRetryAttempts);
387
+ this.callbacks.onRetrying?.(transientRetryAttempts, MAX_TRANSIENT_RETRIES, error);
388
+ await sleep(delayMs);
389
+ continue;
390
+ }
391
+ // Re-throw if not recoverable or recovery failed
392
+ throw error;
393
+ }
394
+ }
395
+ }
396
+ async processConversationStreaming() {
397
+ if (!this.provider.generateStream) {
398
+ return this.processConversation();
399
+ }
400
+ let contextRecoveryAttempts = 0;
401
+ let transientRetryAttempts = 0;
402
+ const STREAM_HARD_CHAR_LIMIT = 120000; // Hard guardrail to prevent runaway provider output
403
+ let totalCharsReceived = 0;
404
+ let truncatedResponse = false;
405
+ // eslint-disable-next-line no-constant-condition
406
+ while (true) {
407
+ // Check for cancellation at start of each iteration
408
+ if (this.cancellationRequested) {
409
+ this.callbacks.onCancelled?.();
410
+ return '[Operation cancelled by user]';
411
+ }
412
+ // Prune messages if approaching context limit (BEFORE generation)
413
+ await this.pruneMessagesIfNeeded();
414
+ try {
415
+ let fullContent = '';
416
+ let reasoningContent = '';
417
+ const toolCalls = [];
418
+ let usage = null;
419
+ const suppressStreamNarration = this.shouldSuppressToolNarration();
420
+ let bufferedContent = '';
421
+ const stream = this.provider.generateStream(this.messages, this.providerTools);
422
+ const iterator = stream[Symbol.asyncIterator]();
423
+ let streamClosed = false;
424
+ const closeStream = async () => {
425
+ if (streamClosed) {
426
+ return;
427
+ }
428
+ streamClosed = true;
429
+ if (typeof iterator.return === 'function') {
430
+ try {
431
+ await iterator.return();
432
+ }
433
+ catch (closeError) {
434
+ logDebug(`[agent] Failed to close stream cleanly: ${safeErrorMessage(closeError)}`);
435
+ }
436
+ }
437
+ };
438
+ const describeChunk = (chunk) => {
439
+ if (!chunk) {
440
+ return 'unknown chunk';
441
+ }
442
+ switch (chunk.type) {
443
+ case 'content':
444
+ case 'reasoning': {
445
+ const snippet = debugSnippet(chunk.content);
446
+ return snippet ? `${chunk.type} → ${snippet}` : chunk.type;
447
+ }
448
+ case 'tool_call':
449
+ return chunk.toolCall ? `tool_call ${chunk.toolCall.name}` : 'tool_call';
450
+ case 'usage':
451
+ if (chunk.usage?.totalTokens != null) {
452
+ return `usage tokens=${chunk.usage.totalTokens}`;
453
+ }
454
+ return 'usage';
455
+ case 'done':
456
+ return 'done';
457
+ default:
458
+ return chunk.type;
459
+ }
460
+ };
461
+ // Simple streaming loop - no timeouts, let the stream run until done
462
+ try {
463
+ let chunkCount = 0;
464
+ // eslint-disable-next-line no-constant-condition
465
+ while (true) {
466
+ const result = await iterator.next();
467
+ chunkCount++;
468
+ // Only log significant chunks (tool calls, done), not every content/reasoning token
469
+ if (result.done || result.value?.type === 'tool_call') {
470
+ const chunkLabel = result.done ? 'done' : describeChunk(result.value);
471
+ logDebug(`[agent] chunk ${chunkCount}: ${chunkLabel}`);
472
+ }
473
+ // Check for cancellation during streaming
474
+ if (this.cancellationRequested) {
475
+ await closeStream();
476
+ this.callbacks.onCancelled?.();
477
+ const partial = (fullContent || reasoningContent).trim();
478
+ if (partial) {
479
+ this.messages.push({ role: 'assistant', content: `${partial}\n\n[Cancelled by user]` });
480
+ }
481
+ return '[Operation cancelled by user]';
482
+ }
483
+ if (result.done) {
484
+ break;
485
+ }
486
+ const chunk = result.value;
487
+ if (chunk.type === 'reasoning' && chunk.content) {
488
+ // Buffer reasoning content - don't stream token-by-token
489
+ // It will be emitted as a complete block when ready
490
+ const next = reasoningContent + chunk.content;
491
+ totalCharsReceived += chunk.content.length;
492
+ // Hard cap buffered reasoning to protect memory
493
+ if (next.length > 24000) {
494
+ reasoningContent = next.slice(-24000);
495
+ }
496
+ else {
497
+ reasoningContent = next;
498
+ }
499
+ if (totalCharsReceived > STREAM_HARD_CHAR_LIMIT) {
500
+ truncatedResponse = true;
501
+ await closeStream();
502
+ break;
503
+ }
504
+ continue;
505
+ }
506
+ if (chunk.type === 'content' && chunk.content) {
507
+ const nextContent = fullContent + chunk.content;
508
+ totalCharsReceived += chunk.content.length;
509
+ // Cap buffered content to avoid OOM from runaway outputs
510
+ fullContent = nextContent.length > 48000 ? nextContent.slice(-48000) : nextContent;
511
+ if (suppressStreamNarration) {
512
+ const nextBuffered = bufferedContent + chunk.content;
513
+ bufferedContent = nextBuffered.length > 24000 ? nextBuffered.slice(-24000) : nextBuffered;
514
+ }
515
+ else {
516
+ this.callbacks.onStreamChunk?.(chunk.content, 'content');
517
+ }
518
+ if (totalCharsReceived > STREAM_HARD_CHAR_LIMIT) {
519
+ truncatedResponse = true;
520
+ await closeStream();
521
+ break;
522
+ }
523
+ }
524
+ else if (chunk.type === 'tool_call' && chunk.toolCall) {
525
+ // On first tool call, flush any buffered content
526
+ if (toolCalls.length === 0) {
527
+ // Emit complete reasoning block first
528
+ if (reasoningContent.trim()) {
529
+ this.callbacks.onStreamChunk?.(reasoningContent, 'reasoning');
530
+ }
531
+ // Then emit buffered narration content
532
+ if (suppressStreamNarration && bufferedContent) {
533
+ this.callbacks.onStreamChunk?.(bufferedContent, 'content');
534
+ bufferedContent = '';
535
+ }
536
+ }
537
+ toolCalls.push(chunk.toolCall);
538
+ }
539
+ else if (chunk.type === 'usage' && chunk.usage) {
540
+ usage = chunk.usage;
541
+ // Emit real token usage during streaming
542
+ this.callbacks.onUsage?.(chunk.usage);
543
+ }
544
+ }
545
+ }
546
+ finally {
547
+ await closeStream();
548
+ }
549
+ // Reset recovery attempts on successful generation
550
+ contextRecoveryAttempts = 0;
551
+ const contextStats = this.getContextStats();
552
+ // IMPORTANT: Only use fullContent for user-visible output
553
+ // reasoningContent is internal model thinking and should NEVER be shown to users
554
+ // We keep it for conversation history (helps the model) but not for display
555
+ const combinedContent = fullContent;
556
+ if (truncatedResponse) {
557
+ const notice = '\n\n[Response truncated: reached safety limit of 120k characters to prevent OOM.]';
558
+ const updated = combinedContent ? `${combinedContent}${notice}` : notice.trim();
559
+ fullContent = updated;
560
+ reasoningContent = '';
561
+ // Partial tool calls are unsafe when truncated; drop them
562
+ toolCalls.length = 0;
563
+ }
564
+ // If no tool calls were issued, emit reasoning and buffered content as complete blocks
565
+ if (toolCalls.length === 0) {
566
+ // Emit complete reasoning block if we have one
567
+ if (reasoningContent.trim()) {
568
+ this.callbacks.onStreamChunk?.(reasoningContent, 'reasoning');
569
+ }
570
+ // Emit buffered narration content
571
+ if (suppressStreamNarration && bufferedContent) {
572
+ this.callbacks.onStreamChunk?.(bufferedContent, 'content');
573
+ bufferedContent = '';
574
+ }
575
+ }
576
+ // Check if we got tool calls
577
+ if (toolCalls.length > 0) {
578
+ // BEHAVIORAL LOOP DETECTION: Check if model is stuck calling same tool repeatedly
579
+ // This catches patterns like "git status" called 5 times even with slightly different outputs
580
+ const behavioralLoopResult = this.checkBehavioralLoop(toolCalls);
581
+ if (behavioralLoopResult) {
582
+ this.emitAssistantMessage(behavioralLoopResult, { isFinal: true, usage, contextStats, wasStreamed: true });
583
+ this.messages.push({ role: 'assistant', content: behavioralLoopResult });
584
+ return behavioralLoopResult;
585
+ }
586
+ // Loop detection: check if same tool calls are being repeated (exact signature match)
587
+ const signatureCalls = toolCalls.filter(call => !this.shouldSkipLoopDetection(call));
588
+ const toolSignature = signatureCalls.length
589
+ ? signatureCalls
590
+ .map((t) => `${t.name}:${JSON.stringify(t.arguments)}`)
591
+ .sort()
592
+ .join('|')
593
+ : null;
594
+ if (toolSignature && toolSignature === this.lastToolCallSignature) {
595
+ this.repeatedToolCallCount++;
596
+ if (this.repeatedToolCallCount >= AgentRuntime.MAX_REPEATED_TOOL_CALLS) {
597
+ // Break out of loop - model is stuck
598
+ const loopMsg = `Tool loop detected: same tools called ${this.repeatedToolCallCount} times. Please try a different approach or provide more specific instructions.`;
599
+ this.emitAssistantMessage(loopMsg, { isFinal: true, usage, contextStats, wasStreamed: true });
600
+ this.messages.push({ role: 'assistant', content: loopMsg });
601
+ this.lastToolCallSignature = null;
602
+ this.repeatedToolCallCount = 0;
603
+ return loopMsg;
604
+ }
605
+ }
606
+ else if (toolSignature) {
607
+ this.lastToolCallSignature = toolSignature;
608
+ this.repeatedToolCallCount = 1;
609
+ }
610
+ else {
611
+ this.lastToolCallSignature = null;
612
+ this.repeatedToolCallCount = 0;
613
+ }
614
+ // Content was already streamed via onStreamChunk, just record it for context
615
+ // (wasStreamed=true prevents duplicate display)
616
+ // Note: Acknowledgement injection happens during streaming (when first tool_call chunk arrives)
617
+ const narration = combinedContent.trim();
618
+ if (narration) {
619
+ this.emitAssistantMessage(narration, {
620
+ isFinal: false,
621
+ usage,
622
+ contextStats,
623
+ wasStreamed: true,
624
+ });
625
+ }
626
+ this.maybeAckToolCalls(toolCalls, Boolean(narration.length), usage, contextStats);
627
+ const assistantMessage = {
628
+ role: 'assistant',
629
+ content: combinedContent,
630
+ toolCalls,
631
+ };
632
+ this.messages.push(assistantMessage);
633
+ await this.resolveToolCalls(toolCalls);
634
+ continue;
635
+ }
636
+ let reply = combinedContent.trim();
637
+ // For reasoning models: if no content but we have reasoning, use reasoning as the response
638
+ // This handles models like deepseek-v4-pro that put their entire response in reasoning_content
639
+ // The reasoning has already been streamed as 'thought' events showing the AI's thinking
640
+ if (!reply && reasoningContent.trim()) {
641
+ // Use reasoning as the reply - it contains the model's answer
642
+ reply = reasoningContent.trim();
643
+ // Stream the content so it appears as the actual response (not just thoughts)
644
+ this.callbacks.onStreamChunk?.(reply, 'content');
645
+ }
646
+ const { output: ensuredReply, appended } = ensureNextSteps(reply);
647
+ let finalReply = ensuredReply;
648
+ // Reset loop detection when we get a text response (not just tool calls)
649
+ if (finalReply.length >= 10) {
650
+ this.lastToolCallSignature = null;
651
+ this.repeatedToolCallCount = 0;
652
+ }
653
+ // If we appended a required Next steps section, stream just the delta
654
+ if (appended) {
655
+ this.callbacks.onStreamChunk?.(appended, 'content');
656
+ }
657
+ finalReply = await this.maybeAdversarialReview(finalReply);
658
+ // Final message - mark as streamed to avoid double-display in UI
659
+ if (finalReply) {
660
+ this.emitAssistantMessage(finalReply, { isFinal: true, usage, contextStats, wasStreamed: true });
661
+ }
662
+ this.messages.push({ role: 'assistant', content: finalReply });
663
+ // Trigger verification for final responses with verifiable claims
664
+ this.triggerVerificationIfNeeded(finalReply);
665
+ return finalReply;
666
+ }
667
+ catch (error) {
668
+ // Auto-recover from context overflow errors (with session-level limit)
669
+ const canRecover = contextRecoveryAttempts < MAX_CONTEXT_RECOVERY_ATTEMPTS &&
670
+ this.totalContextRecoveries < AgentRuntime.MAX_TOTAL_RECOVERIES;
671
+ if (isContextOverflowError(error) && canRecover) {
672
+ contextRecoveryAttempts++;
673
+ this.totalContextRecoveries++;
674
+ const recovered = await this.recoverFromContextOverflow(contextRecoveryAttempts);
675
+ if (recovered) {
676
+ // Notify UI that we're continuing after recovery
677
+ this.callbacks.onContinueAfterRecovery?.();
678
+ // Retry the generation with reduced context
679
+ continue;
680
+ }
681
+ }
682
+ // Auto-retry transient errors (network issues, rate limits, server errors)
683
+ if (isTransientError(error) && transientRetryAttempts < MAX_TRANSIENT_RETRIES) {
684
+ transientRetryAttempts++;
685
+ const delayMs = getRetryDelay(transientRetryAttempts);
686
+ this.callbacks.onRetrying?.(transientRetryAttempts, MAX_TRANSIENT_RETRIES, error);
687
+ await sleep(delayMs);
688
+ continue;
689
+ }
690
+ // Re-throw if not recoverable or recovery failed
691
+ throw error;
692
+ }
693
+ }
694
+ }
695
+ /**
696
+ * Execute tool calls with optimized concurrency
697
+ *
698
+ * PERF: Uses Promise.all for parallel execution with early result handling.
699
+ * Results are collected in order but execution happens concurrently.
700
+ * For very large batches (>10 tools), uses chunked execution to prevent
701
+ * overwhelming system resources.
702
+ */
703
+ async resolveToolCalls(toolCalls) {
704
+ const numCalls = toolCalls.length;
705
+ const executedEdits = [];
706
+ // Check for cancellation before starting tool execution
707
+ if (this.cancellationRequested) {
708
+ // Add cancellation message for each pending tool call
709
+ for (const call of toolCalls) {
710
+ this.messages.push({
711
+ role: 'tool',
712
+ name: call.name,
713
+ toolCallId: call.id,
714
+ content: '[Tool execution cancelled by user]',
715
+ });
716
+ }
717
+ return;
718
+ }
719
+ // Fast path: single tool call
720
+ if (numCalls === 1) {
721
+ const call = toolCalls[0];
722
+ // Check cache first - prevent duplicate identical tool calls
723
+ const cached = this.getCachedToolResult(call);
724
+ if (cached !== null) {
725
+ // Return cached result with indicator that it was from cache
726
+ this.messages.push({
727
+ role: 'tool',
728
+ name: call.name,
729
+ toolCallId: call.id,
730
+ content: `[Cached result - identical call already executed]\n\n${cached}`,
731
+ });
732
+ return;
733
+ }
734
+ this.callbacks.onToolExecution?.(call.name, true);
735
+ const output = await this.toolRuntime.execute(call);
736
+ this.callbacks.onToolExecution?.(call.name, false);
737
+ // Track consecutive failures
738
+ const failureMsg = this.trackToolResult(output);
739
+ // Cache the result for future identical calls
740
+ this.cacheToolResult(call, output);
741
+ if (this.isEditToolCall(call.name)) {
742
+ executedEdits.push({ call, output, fromCache: false });
743
+ }
744
+ // Add tool result to messages
745
+ const toolContent = failureMsg ? `${output}\n\n[SYSTEM: ${failureMsg}]` : output;
746
+ this.messages.push({
747
+ role: 'tool',
748
+ name: call.name,
749
+ toolCallId: call.id,
750
+ content: toolContent,
751
+ });
752
+ await this.maybeExplainEdits(executedEdits);
753
+ return;
754
+ }
755
+ // PERF: For reasonable batch sizes, execute all in parallel
756
+ // Check cache for each call and only execute non-cached ones
757
+ if (numCalls <= 10) {
758
+ const cachedResults = [];
759
+ const toExecute = [];
760
+ // Separate cached from non-cached calls
761
+ for (const call of toolCalls) {
762
+ const cached = this.getCachedToolResult(call);
763
+ if (cached !== null) {
764
+ cachedResults.push({ call, output: cached, fromCache: true });
765
+ if (this.isEditToolCall(call.name)) {
766
+ executedEdits.push({ call, output: cached, fromCache: true });
767
+ }
768
+ }
769
+ else {
770
+ toExecute.push(call);
771
+ }
772
+ }
773
+ // Execute non-cached calls in parallel
774
+ if (toExecute.length > 0) {
775
+ const toolNames = toExecute.map(c => c.name).join(', ');
776
+ this.callbacks.onToolExecution?.(toolNames, true);
777
+ const executed = await Promise.all(toExecute.map(async (call) => {
778
+ const output = await this.toolRuntime.execute(call);
779
+ this.cacheToolResult(call, output);
780
+ if (this.isEditToolCall(call.name)) {
781
+ executedEdits.push({ call, output, fromCache: false });
782
+ }
783
+ return { call, output, fromCache: false };
784
+ }));
785
+ this.callbacks.onToolExecution?.(toolNames, false);
786
+ cachedResults.push(...executed);
787
+ }
788
+ // Add all results to messages in the original order and track failures
789
+ let failureMsg = null;
790
+ for (const originalCall of toolCalls) {
791
+ const result = cachedResults.find(r => r.call.id === originalCall.id);
792
+ if (result) {
793
+ // Track consecutive failures for non-cached results
794
+ if (!result.fromCache) {
795
+ failureMsg = this.trackToolResult(result.output) ?? failureMsg;
796
+ }
797
+ let content = result.fromCache
798
+ ? `[Cached result - identical call already executed]\n\n${result.output}`
799
+ : result.output;
800
+ // Append failure message to last tool result if detected
801
+ if (failureMsg && originalCall === toolCalls[toolCalls.length - 1]) {
802
+ content = `${content}\n\n[SYSTEM: ${failureMsg}]`;
803
+ }
804
+ this.messages.push({
805
+ role: 'tool',
806
+ name: result.call.name,
807
+ toolCallId: result.call.id,
808
+ content,
809
+ });
810
+ }
811
+ }
812
+ await this.maybeExplainEdits(executedEdits);
813
+ return;
814
+ }
815
+ // PERF: For large batches, use chunked parallel execution with caching
816
+ const CHUNK_SIZE = 8;
817
+ const allResults = [];
818
+ for (let i = 0; i < numCalls; i += CHUNK_SIZE) {
819
+ const chunk = toolCalls.slice(i, i + CHUNK_SIZE);
820
+ const cachedInChunk = [];
821
+ const toExecuteInChunk = [];
822
+ for (const call of chunk) {
823
+ const cached = this.getCachedToolResult(call);
824
+ if (cached !== null) {
825
+ cachedInChunk.push({ call, output: cached, fromCache: true });
826
+ if (this.isEditToolCall(call.name)) {
827
+ executedEdits.push({ call, output: cached, fromCache: true });
828
+ }
829
+ }
830
+ else {
831
+ toExecuteInChunk.push(call);
832
+ }
833
+ }
834
+ if (toExecuteInChunk.length > 0) {
835
+ const chunkNames = toExecuteInChunk.map(c => c.name).join(', ');
836
+ this.callbacks.onToolExecution?.(chunkNames, true);
837
+ const executed = await Promise.all(toExecuteInChunk.map(async (call) => {
838
+ const output = await this.toolRuntime.execute(call);
839
+ this.cacheToolResult(call, output);
840
+ if (this.isEditToolCall(call.name)) {
841
+ executedEdits.push({ call, output, fromCache: false });
842
+ }
843
+ return { call, output, fromCache: false };
844
+ }));
845
+ this.callbacks.onToolExecution?.(chunkNames, false);
846
+ cachedInChunk.push(...executed);
847
+ }
848
+ allResults.push(...cachedInChunk);
849
+ }
850
+ // Add results to messages in original order and track failures
851
+ let failureMsg = null;
852
+ for (const originalCall of toolCalls) {
853
+ const result = allResults.find(r => r.call.id === originalCall.id);
854
+ if (result) {
855
+ // Track consecutive failures for non-cached results
856
+ if (!result.fromCache) {
857
+ failureMsg = this.trackToolResult(result.output) ?? failureMsg;
858
+ }
859
+ let content = result.fromCache
860
+ ? `[Cached result - identical call already executed]\n\n${result.output}`
861
+ : result.output;
862
+ // Append failure message to last tool result if detected
863
+ if (failureMsg && originalCall === toolCalls[toolCalls.length - 1]) {
864
+ content = `${content}\n\n[SYSTEM: ${failureMsg}]`;
865
+ }
866
+ this.messages.push({
867
+ role: 'tool',
868
+ name: result.call.name,
869
+ toolCallId: result.call.id,
870
+ content,
871
+ });
872
+ }
873
+ }
874
+ await this.maybeExplainEdits(executedEdits);
875
+ }
876
+ truncateEditOutput(output) {
877
+ if (!output) {
878
+ return '[no tool output available]';
879
+ }
880
+ const limit = AgentRuntime.EDIT_CONTEXT_CHAR_LIMIT;
881
+ if (output.length <= limit) {
882
+ return output;
883
+ }
884
+ const head = output.slice(0, Math.floor(limit * 0.7));
885
+ const tail = output.slice(-Math.floor(limit * 0.2));
886
+ const omitted = output.length - head.length - tail.length;
887
+ return `${head}\n... [truncated ${omitted} chars] ...\n${tail}`;
888
+ }
889
+ buildEditExplanationPrompt(toolName, files, toolOutput) {
890
+ const fileNames = files.map(f => f.split('/').pop()).join(', ');
891
+ const userContent = [
892
+ `Summarize this ${toolName} operation in 1-2 sentences for the UI status line.`,
893
+ `Files: ${fileNames || 'unknown'}`,
894
+ '',
895
+ 'Output:',
896
+ toolOutput.slice(0, 500), // Limit context to reduce hallucination
897
+ ].join('\n');
898
+ return [
899
+ {
900
+ role: 'system',
901
+ content: 'You write brief UI status messages. Reply with ONLY a 1-2 sentence summary. No analysis, no reasoning, no explanations of your process.',
902
+ },
903
+ { role: 'user', content: userContent },
904
+ // Prefill assistant response to guide format
905
+ { role: 'assistant', content: '' },
906
+ ];
907
+ }
908
+ /**
909
+ * Extract clean explanation from model output that may contain reasoning.
910
+ * Reasoning models like deepseek-v4-pro output chain-of-thought which we need to filter.
911
+ */
912
+ extractCleanExplanation(rawOutput) {
913
+ if (!rawOutput)
914
+ return '';
915
+ // Check for common reasoning patterns and extract final output
916
+ const patterns = [
917
+ // "Final explanation:" or "Final concise explanation:" patterns
918
+ /(?:final\s+(?:concise\s+)?explanation\s*:?\s*["']?)([^"'\n]+(?:["']|$))/i,
919
+ // Quoted final output
920
+ /"([^"]{20,})"(?:\s*\([^)]+\))?$/,
921
+ // Last paragraph after deliberation markers
922
+ /(?:draft|summary|output|result)\s*:?\s*\n?\s*["']?([^"'\n]+)/i,
923
+ ];
924
+ for (const pattern of patterns) {
925
+ const match = rawOutput.match(pattern);
926
+ if (match?.[1]) {
927
+ // Clean up the extracted text
928
+ return match[1].replace(/^["']|["']$/g, '').trim();
929
+ }
930
+ }
931
+ // Check if the output looks like reasoning (contains deliberation markers)
932
+ const reasoningMarkers = [
933
+ /^first,?\s+(the user|i need|let me|looking at)/i,
934
+ /^(from this|based on|analyzing|the tool output shows)/i,
935
+ /^(intent:|impact:|user-visible changes:)/im,
936
+ /^(now,?\s+i (?:need|should|will)|let me (?:craft|think|analyze))/i,
937
+ /\b(draft:|final (?:draft|explanation):)/i,
938
+ ];
939
+ const hasReasoning = reasoningMarkers.some(marker => marker.test(rawOutput));
940
+ if (hasReasoning) {
941
+ // Try to extract the last meaningful sentence/paragraph
942
+ const lines = rawOutput.split('\n').filter(l => l.trim());
943
+ // Look for the last line that looks like a summary (not a reasoning line)
944
+ for (let i = lines.length - 1; i >= 0; i--) {
945
+ const line = lines[i].trim();
946
+ // Skip lines that look like reasoning
947
+ if (reasoningMarkers.some(m => m.test(line)))
948
+ continue;
949
+ // Skip very short lines or lines that are just labels
950
+ if (line.length < 30 || /^[\w\s]+:$/.test(line))
951
+ continue;
952
+ // Found a good candidate
953
+ return line.replace(/^["']|["']$/g, '').replace(/\([^)]+\)$/, '').trim();
954
+ }
955
+ // Fallback: take last 200 chars and try to find a sentence
956
+ const tail = rawOutput.slice(-300);
957
+ const lastSentence = tail.match(/[A-Z][^.!?]*[.!?](?:\s|$)/g);
958
+ if (lastSentence?.length) {
959
+ return lastSentence[lastSentence.length - 1].trim();
960
+ }
961
+ }
962
+ // No reasoning detected, return as-is but truncate if too long
963
+ if (rawOutput.length > 500) {
964
+ // Find a sentence break near the end
965
+ const truncated = rawOutput.slice(0, 500);
966
+ const lastPeriod = truncated.lastIndexOf('.');
967
+ if (lastPeriod > 200) {
968
+ return truncated.slice(0, lastPeriod + 1);
969
+ }
970
+ }
971
+ return rawOutput.trim();
972
+ }
973
+ async maybeExplainEdits(results) {
974
+ if (!this.explainEdits || results.length === 0 || this.cancellationRequested) {
975
+ return;
976
+ }
977
+ for (const result of results) {
978
+ if (result.fromCache || !this.isEditToolCall(result.call.name)) {
979
+ continue;
980
+ }
981
+ const files = this.getEditedFiles([result.call]);
982
+ const truncatedOutput = this.truncateEditOutput(result.output);
983
+ const prompt = this.buildEditExplanationPrompt(result.call.name, files, truncatedOutput);
984
+ try {
985
+ const response = await this.provider.generate(prompt, []);
986
+ if (response.type !== 'message') {
987
+ continue;
988
+ }
989
+ // Extract clean explanation, filtering out any reasoning/deliberation
990
+ const rawExplanation = response.content?.trim() ?? '';
991
+ const explanation = this.extractCleanExplanation(rawExplanation);
992
+ if (explanation) {
993
+ this.callbacks.onEditExplanation?.({
994
+ explanation,
995
+ files,
996
+ toolName: result.call.name,
997
+ toolCallId: result.call.id,
998
+ });
999
+ }
1000
+ }
1001
+ catch (error) {
1002
+ logDebug(`[agent] Failed to generate edit explanation: ${safeErrorMessage(error)}`);
1003
+ }
1004
+ }
1005
+ }
1006
+ get providerTools() {
1007
+ return this.toolRuntime.listProviderTools();
1008
+ }
1009
+ /**
1010
+ * Whether to suppress tool narration in the content field.
1011
+ * Previously suppressed for OpenAI but now we show all thinking/narration.
1012
+ */
1013
+ shouldSuppressToolNarration() {
1014
+ return false; // Always show thinking/narration
1015
+ }
1016
+ emitAssistantMessage(content, metadata) {
1017
+ if (!content || !content.trim()) {
1018
+ return;
1019
+ }
1020
+ const elapsedMs = this.activeRun ? Date.now() - this.activeRun.startedAt : undefined;
1021
+ const payload = { ...metadata };
1022
+ if (typeof elapsedMs === 'number') {
1023
+ payload.elapsedMs = elapsedMs;
1024
+ }
1025
+ this.callbacks.onAssistantMessage?.(content, payload);
1026
+ }
1027
+ /**
1028
+ * Trigger verification for a final response if callback is registered
1029
+ * and response contains verifiable claims (implementation, build success, etc.)
1030
+ */
1031
+ /**
1032
+ * Always-on adversarial review of a finished answer (annotate-only). When
1033
+ * the adversarial flag is on and real work happened this turn, a critic
1034
+ * pass tries to refute the draft; findings are appended as a visible
1035
+ * caveat. Non-destructive (peeks tool history without draining the cursor
1036
+ * drainToolExecutions relies on) and fail-open — a critic error returns
1037
+ * the answer unchanged.
1038
+ */
1039
+ async maybeAdversarialReview(finalReply) {
1040
+ if (!isAdversarialEnabled() || this.cancellationRequested || !finalReply.trim()) {
1041
+ return finalReply;
1042
+ }
1043
+ try {
1044
+ if (typeof this.toolRuntime.getToolHistory !== 'function')
1045
+ return finalReply;
1046
+ const recent = this.toolRuntime.getToolHistory().slice(this.toolHistoryCursor);
1047
+ if (recent.length === 0)
1048
+ return finalReply; // no real work this turn — skip
1049
+ const actions = recent.map((e) => `${e.toolName}${e.success === false ? ' (failed)' : ''}`).join(', ');
1050
+ let request = '';
1051
+ for (let i = this.messages.length - 1; i >= 0; i--) {
1052
+ const m = this.messages[i];
1053
+ if (m && m.role === 'user') {
1054
+ request = typeof m.content === 'string' ? m.content : '';
1055
+ break;
1056
+ }
1057
+ }
1058
+ const review = await reviewDraft(this.provider, { request, actions, draft: finalReply });
1059
+ if (review.ok || !review.findings)
1060
+ return finalReply;
1061
+ return `${finalReply}\n\n---\n⚠ Adversarial review:\n${review.findings}`;
1062
+ }
1063
+ catch {
1064
+ return finalReply;
1065
+ }
1066
+ }
1067
+ triggerVerificationIfNeeded(response) {
1068
+ if (!this.callbacks.onVerificationNeeded) {
1069
+ return;
1070
+ }
1071
+ // Only trigger verification for responses that likely contain verifiable claims
1072
+ // These patterns indicate the model is claiming to have completed work
1073
+ const verifiablePatterns = [
1074
+ /\b(implemented|created|wrote|added|fixed|built|deployed|completed|refactored)\b/i,
1075
+ /\b(tests?\s+(are\s+)?pass(ing)?|build\s+succeed)/i,
1076
+ /\b(file|function|class|module|component)\s+(has been|is now|was)\s+(created|updated|modified)/i,
1077
+ /✅|✓|\[done\]|\[complete\]/i,
1078
+ /\bcommit(ted)?\b.*\b(success|done)\b/i,
1079
+ ];
1080
+ const hasVerifiableClaims = verifiablePatterns.some(pattern => pattern.test(response));
1081
+ if (!hasVerifiableClaims) {
1082
+ return;
1083
+ }
1084
+ // Build conversation history for context (last 5 user/assistant exchanges)
1085
+ const conversationHistory = [];
1086
+ const recentMessages = this.messages.slice(-10);
1087
+ for (const msg of recentMessages) {
1088
+ if (msg.role === 'user' || msg.role === 'assistant') {
1089
+ const content = typeof msg.content === 'string' ? msg.content : '';
1090
+ if (content.length > 0) {
1091
+ conversationHistory.push(`${msg.role}: ${content.slice(0, 500)}`);
1092
+ }
1093
+ }
1094
+ }
1095
+ // Trigger verification callback
1096
+ this.callbacks.onVerificationNeeded(response, {
1097
+ workingDirectory: this.workingDirectory,
1098
+ conversationHistory,
1099
+ provider: this.providerId,
1100
+ model: this.modelId,
1101
+ });
1102
+ }
1103
+ /**
1104
+ * Extract a "command hash" from tool arguments for behavioral loop detection.
1105
+ * For execute_bash, this is the actual command. For other tools, key identifying args.
1106
+ */
1107
+ extractCmdHash(name, args) {
1108
+ // For bash/execute commands, extract the command itself
1109
+ if (name === 'execute_bash' || name === 'Bash') {
1110
+ const cmd = args['command'];
1111
+ if (cmd) {
1112
+ // Normalize: trim, take first 100 chars, remove variable parts like timestamps
1113
+ return cmd.trim().slice(0, 100).replace(/\d{10,}/g, 'N');
1114
+ }
1115
+ }
1116
+ // For file operations, use the path
1117
+ if (name === 'read_file' || name === 'Read' || name === 'read_files') {
1118
+ const path = args['path'] || args['file_path'] || args['paths'];
1119
+ if (path)
1120
+ return `path:${JSON.stringify(path).slice(0, 100)}`;
1121
+ }
1122
+ if (name === 'list_files' || name === 'Glob') {
1123
+ const path = args['path'] || args['pattern'];
1124
+ if (path)
1125
+ return `path:${JSON.stringify(path).slice(0, 100)}`;
1126
+ }
1127
+ // For search, use the query/pattern
1128
+ if (name === 'Grep' || name === 'grep' || name === 'search') {
1129
+ const pattern = args['pattern'] || args['query'];
1130
+ if (pattern)
1131
+ return `search:${String(pattern).slice(0, 100)}`;
1132
+ }
1133
+ // Default: use first significant arg value
1134
+ const firstArg = Object.values(args)[0];
1135
+ if (firstArg) {
1136
+ return String(firstArg).slice(0, 100);
1137
+ }
1138
+ return 'no-args';
1139
+ }
1140
+ /**
1141
+ * Check for behavioral loops - model calling the same tool with similar args repeatedly.
1142
+ * Returns an error message if a loop is detected, null otherwise.
1143
+ *
1144
+ * FUNDAMENTAL PREVENTION: Cached calls are excluded from loop detection since they
1145
+ * don't actually execute (the cache provides the result). This means:
1146
+ * - First call: executes and caches result
1147
+ * - Second identical call: returns cached result, NOT counted toward loop
1148
+ * - Only genuinely NEW (non-cached) repetitive calls trigger loop detection
1149
+ *
1150
+ * Direct execution tools (bash/edit) are also exempt to avoid short-circuiting
1151
+ * legitimate repeated user commands.
1152
+ *
1153
+ * This catches patterns like:
1154
+ * - "git status -sb" called 3 times with DIFFERENT outputs (cache miss each time)
1155
+ * - Repeated file reads where file content changed
1156
+ * - Repeated searches with same pattern but new results
1157
+ */
1158
+ checkBehavioralLoop(toolCalls) {
1159
+ // Skip loop detection for direct execution tools (bash/edit) to avoid false positives
1160
+ const loopEligibleCalls = toolCalls.filter(call => !this.shouldSkipLoopDetection(call));
1161
+ if (loopEligibleCalls.length === 0) {
1162
+ return null;
1163
+ }
1164
+ // Filter out calls that will be served from cache - these don't count toward loops
1165
+ // since they're handled fundamentally by the caching mechanism
1166
+ const nonCachedCalls = loopEligibleCalls.filter(call => this.getCachedToolResult(call) === null);
1167
+ // If all calls are cached, no loop detection needed
1168
+ if (nonCachedCalls.length === 0) {
1169
+ return null;
1170
+ }
1171
+ // Count existing occurrences in recent history
1172
+ const existingCounts = new Map();
1173
+ for (const { name, cmdHash } of this.recentToolCalls) {
1174
+ const key = `${name}:${cmdHash}`;
1175
+ existingCounts.set(key, (existingCounts.get(key) ?? 0) + 1);
1176
+ }
1177
+ // Check if ANY incoming NON-CACHED call would exceed threshold
1178
+ for (const call of nonCachedCalls) {
1179
+ const cmdHash = this.extractCmdHash(call.name, call.arguments ?? {});
1180
+ const key = `${call.name}:${cmdHash}`;
1181
+ const currentCount = existingCounts.get(key) ?? 0;
1182
+ // If adding this call would reach or exceed threshold, block immediately
1183
+ if (currentCount + 1 >= AgentRuntime.BEHAVIORAL_LOOP_THRESHOLD) {
1184
+ // Reset history to prevent immediate re-trigger
1185
+ this.recentToolCalls = [];
1186
+ return `Behavioral loop detected: "${call.name}" called ${currentCount + 1} times with similar arguments. The task appears stuck. Please try a different approach or provide more specific instructions.`;
1187
+ }
1188
+ }
1189
+ // Track only non-cached tool calls (cached ones are handled by caching)
1190
+ for (const call of nonCachedCalls) {
1191
+ const cmdHash = this.extractCmdHash(call.name, call.arguments ?? {});
1192
+ this.recentToolCalls.push({ name: call.name, cmdHash });
1193
+ }
1194
+ // Keep only recent history
1195
+ while (this.recentToolCalls.length > AgentRuntime.TOOL_HISTORY_SIZE) {
1196
+ this.recentToolCalls.shift();
1197
+ }
1198
+ return null;
1199
+ }
1200
+ /**
1201
+ * Provide an acknowledgement before the first tool call when the model
1202
+ * hasn't narrated its plan. This keeps the UI responsive and lets the
1203
+ * user know work is happening even before tool output arrives.
1204
+ */
1205
+ maybeAckToolCalls(toolCalls, hasModelNarration, usage, contextStats) {
1206
+ if (!toolCalls?.length) {
1207
+ return;
1208
+ }
1209
+ const acknowledgement = this.callbacks.onBeforeFirstToolCall?.(toolCalls.map((call) => call.name), hasModelNarration);
1210
+ if (acknowledgement && acknowledgement.trim()) {
1211
+ this.emitAssistantMessage(acknowledgement, {
1212
+ isFinal: false,
1213
+ usage,
1214
+ contextStats,
1215
+ });
1216
+ }
1217
+ }
1218
+ /**
1219
+ * Reset behavioral loop tracking (called when user provides new input or task completes)
1220
+ */
1221
+ resetBehavioralLoopTracking() {
1222
+ this.recentToolCalls = [];
1223
+ this.lastToolCallSignature = null;
1224
+ this.repeatedToolCallCount = 0;
1225
+ this.consecutiveFailures = 0;
1226
+ // Note: we DON'T clear toolResultCache here for cacheable tools; stateful tools bypass caching
1227
+ }
1228
+ /**
1229
+ * Create a stable cache key for a tool call based on name and arguments
1230
+ */
1231
+ getToolCacheKey(call) {
1232
+ const args = call.arguments ?? {};
1233
+ // Sort keys for consistent ordering
1234
+ const sortedArgs = Object.keys(args).sort().reduce((acc, key) => {
1235
+ acc[key] = args[key];
1236
+ return acc;
1237
+ }, {});
1238
+ return `${call.name}:${JSON.stringify(sortedArgs)}`;
1239
+ }
1240
+ /**
1241
+ * Only cache tools that are safe to reuse; stateful commands must always execute.
1242
+ */
1243
+ isCacheableTool(call) {
1244
+ const nameLower = call.name.toLowerCase();
1245
+ return !AgentRuntime.NON_CACHEABLE_TOOL_NAMES.has(nameLower);
1246
+ }
1247
+ /**
1248
+ * Direct execution tools should not trigger behavioral loop short-circuiting.
1249
+ */
1250
+ shouldSkipLoopDetection(call) {
1251
+ const nameLower = call.name.toLowerCase();
1252
+ return AgentRuntime.LOOP_EXEMPT_TOOL_NAMES.has(nameLower);
1253
+ }
1254
+ /**
1255
+ * Check if a tool output indicates failure
1256
+ */
1257
+ isToolOutputFailure(output) {
1258
+ const failurePatterns = [
1259
+ /\bfailed\b/i,
1260
+ /\berror[:\s]/i,
1261
+ /\bexception\b/i,
1262
+ /\bcommand not found\b/i,
1263
+ /\bno such file\b/i,
1264
+ /\bpermission denied\b/i,
1265
+ /\bexit code [1-9]/i,
1266
+ /\btimeout\b/i,
1267
+ /\bcannot\b.*\bfind\b/i,
1268
+ /\bunable to\b/i,
1269
+ /\bsyntax error\b/i,
1270
+ ];
1271
+ return failurePatterns.some(pattern => pattern.test(output));
1272
+ }
1273
+ /**
1274
+ * Track tool execution result and check for consecutive failures.
1275
+ * Returns error message if too many consecutive failures, null otherwise.
1276
+ */
1277
+ trackToolResult(output) {
1278
+ if (this.isToolOutputFailure(output)) {
1279
+ this.consecutiveFailures++;
1280
+ if (this.consecutiveFailures >= AgentRuntime.MAX_CONSECUTIVE_FAILURES) {
1281
+ const msg = `Multiple consecutive command failures detected (${this.consecutiveFailures}). The task appears to be stuck. Please review the errors and try a different approach.`;
1282
+ this.consecutiveFailures = 0; // Reset to allow retry with new approach
1283
+ return msg;
1284
+ }
1285
+ }
1286
+ else {
1287
+ // Successful execution resets the counter
1288
+ this.consecutiveFailures = 0;
1289
+ }
1290
+ return null;
1291
+ }
1292
+ /**
1293
+ * Get cached result for a tool call, or null if not cached
1294
+ */
1295
+ getCachedToolResult(call) {
1296
+ if (!this.isCacheableTool(call)) {
1297
+ return null;
1298
+ }
1299
+ const key = this.getToolCacheKey(call);
1300
+ return this.toolResultCache.get(key) ?? null;
1301
+ }
1302
+ /**
1303
+ * Cache a tool result for future identical calls
1304
+ */
1305
+ cacheToolResult(call, result) {
1306
+ if (!this.isCacheableTool(call)) {
1307
+ return;
1308
+ }
1309
+ const key = this.getToolCacheKey(call);
1310
+ // Evict oldest entries if cache is full
1311
+ if (this.toolResultCache.size >= AgentRuntime.TOOL_CACHE_MAX_SIZE) {
1312
+ const firstKey = this.toolResultCache.keys().next().value;
1313
+ if (firstKey) {
1314
+ this.toolResultCache.delete(firstKey);
1315
+ }
1316
+ }
1317
+ this.toolResultCache.set(key, result);
1318
+ }
1319
+ /**
1320
+ * Drain the list of tools executed during the most recent send() call.
1321
+ * Used by higher-level orchestrators to reason about progress.
1322
+ */
1323
+ drainToolExecutions() {
1324
+ if (typeof this.toolRuntime.getToolHistory !== 'function') {
1325
+ return [];
1326
+ }
1327
+ const history = this.toolRuntime.getToolHistory();
1328
+ const newEntries = history.slice(this.toolHistoryCursor);
1329
+ this.toolHistoryCursor = history.length;
1330
+ return newEntries.map((entry) => ({
1331
+ name: entry.toolName,
1332
+ success: entry.success ?? true,
1333
+ hasOutput: entry.hasOutput ?? true,
1334
+ }));
1335
+ }
1336
+ getHistory() {
1337
+ return this.messages.map(cloneMessage);
1338
+ }
1339
+ loadHistory(history) {
1340
+ this.messages.length = 0;
1341
+ if (history.length === 0) {
1342
+ if (this.baseSystemPrompt) {
1343
+ this.messages.push({ role: 'system', content: this.baseSystemPrompt });
1344
+ }
1345
+ return;
1346
+ }
1347
+ for (const message of history) {
1348
+ this.messages.push(cloneMessage(message));
1349
+ }
1350
+ }
1351
+ clearHistory() {
1352
+ this.messages.length = 0;
1353
+ if (this.baseSystemPrompt) {
1354
+ this.messages.push({ role: 'system', content: this.baseSystemPrompt });
1355
+ }
1356
+ }
1357
+ /**
1358
+ * Prune messages if approaching context limit
1359
+ *
1360
+ * This runs BEFORE each generation to ensure we stay within budget.
1361
+ * If LLM summarization is available, it will create intelligent summaries
1362
+ * instead of just removing old messages.
1363
+ */
1364
+ async pruneMessagesIfNeeded() {
1365
+ if (!this.contextManager) {
1366
+ return;
1367
+ }
1368
+ if (this.contextManager.isApproachingLimit(this.messages)) {
1369
+ // Try LLM-based summarization first (preserves context better)
1370
+ const result = await this.contextManager.pruneMessagesWithSummary(this.messages);
1371
+ if (result.removed > 0) {
1372
+ // Replace messages with pruned/summarized version
1373
+ this.messages.length = 0;
1374
+ this.messages.push(...result.pruned);
1375
+ // Notify callback with enriched stats
1376
+ const stats = this.contextManager.getStats(this.messages);
1377
+ const enrichedStats = {
1378
+ ...stats,
1379
+ summarized: result.summarized,
1380
+ method: result.summarized ? 'llm-summary' : 'simple-prune',
1381
+ };
1382
+ this.callbacks.onContextPruned?.(result.removed, enrichedStats);
1383
+ if (process.env['DEBUG_CONTEXT']) {
1384
+ logDebug(`[Context Manager] ${result.summarized ? 'Summarized' : 'Pruned'} ${result.removed} messages. ` +
1385
+ `Tokens: ${stats.totalTokens} (${stats.percentage}%)`);
1386
+ }
1387
+ }
1388
+ }
1389
+ }
1390
+ /**
1391
+ * Get current context statistics
1392
+ */
1393
+ getContextStats() {
1394
+ if (!this.contextManager) {
1395
+ return null;
1396
+ }
1397
+ return this.contextManager.getStats(this.messages);
1398
+ }
1399
+ /**
1400
+ * Get context manager instance
1401
+ */
1402
+ getContextManager() {
1403
+ return this.contextManager;
1404
+ }
1405
+ /**
1406
+ * Fetch model info from the provider API.
1407
+ * Returns context window and token limits from the real API.
1408
+ * Results are cached for the lifetime of this agent instance.
1409
+ */
1410
+ async fetchModelInfo() {
1411
+ if (this.modelInfoFetched) {
1412
+ return this.modelInfo;
1413
+ }
1414
+ this.modelInfoFetched = true;
1415
+ if (typeof this.provider.getModelInfo === 'function') {
1416
+ try {
1417
+ this.modelInfo = await this.provider.getModelInfo();
1418
+ }
1419
+ catch {
1420
+ // Ignore errors - fall back to null
1421
+ this.modelInfo = null;
1422
+ }
1423
+ }
1424
+ return this.modelInfo;
1425
+ }
1426
+ /**
1427
+ * Get cached model info (must call fetchModelInfo first)
1428
+ */
1429
+ getModelInfo() {
1430
+ return this.modelInfo;
1431
+ }
1432
+ /**
1433
+ * Get the context window size from the provider API.
1434
+ * Returns null if the provider doesn't support this or the API call fails.
1435
+ */
1436
+ async getContextWindowFromProvider() {
1437
+ const info = await this.fetchModelInfo();
1438
+ return info?.contextWindow ?? null;
1439
+ }
1440
+ /**
1441
+ * Auto-recover from context overflow errors by aggressively pruning messages.
1442
+ *
1443
+ * This is called when an API call fails due to context length exceeding limits.
1444
+ * It performs increasingly aggressive pruning on each attempt:
1445
+ * - Attempt 1: Remove 30% of oldest messages + truncate tool outputs to 5k
1446
+ * - Attempt 2: Remove 50% of oldest messages + truncate tool outputs to 2k
1447
+ * - Attempt 3: Remove 70% of oldest messages + truncate tool outputs to 500 chars
1448
+ *
1449
+ * @returns true if recovery was successful (context was reduced)
1450
+ */
1451
+ async recoverFromContextOverflow(attempt) {
1452
+ // Calculate reduction percentage based on attempt
1453
+ const reductionPercentages = [0.3, 0.5, 0.7];
1454
+ const reductionPercent = reductionPercentages[attempt - 1] ?? 0.7;
1455
+ // Increasingly aggressive tool output truncation limits
1456
+ const toolOutputLimits = [5000, 2000, 500];
1457
+ const toolOutputLimit = toolOutputLimits[attempt - 1] ?? 500;
1458
+ // Notify UI about recovery attempt
1459
+ const message = `Context overflow detected. Auto-squishing context (attempt ${attempt}/${MAX_CONTEXT_RECOVERY_ATTEMPTS}, removing ${Math.round(reductionPercent * 100)}% of history)...`;
1460
+ this.callbacks.onContextRecovery?.(attempt, MAX_CONTEXT_RECOVERY_ATTEMPTS, message);
1461
+ this.callbacks.onContextSquishing?.(message);
1462
+ // Separate system messages from conversation
1463
+ const systemMessages = [];
1464
+ const conversationMessages = [];
1465
+ for (const msg of this.messages) {
1466
+ if (msg.role === 'system') {
1467
+ systemMessages.push(msg);
1468
+ }
1469
+ else {
1470
+ conversationMessages.push(msg);
1471
+ }
1472
+ }
1473
+ // Calculate how many messages to remove (target)
1474
+ const targetRemoveCount = Math.floor(conversationMessages.length * reductionPercent);
1475
+ if (targetRemoveCount === 0 || conversationMessages.length <= 2) {
1476
+ // Nothing to remove or too few messages - can't recover
1477
+ return false;
1478
+ }
1479
+ // Group messages into conversation "turns" to maintain tool call/result pairing
1480
+ // A turn is: [user] or [assistant + tool results] or [assistant without tools]
1481
+ const turns = [];
1482
+ let currentTurn = [];
1483
+ for (let i = 0; i < conversationMessages.length; i++) {
1484
+ const msg = conversationMessages[i];
1485
+ if (msg.role === 'user') {
1486
+ // User messages start a new turn
1487
+ if (currentTurn.length > 0) {
1488
+ turns.push(currentTurn);
1489
+ }
1490
+ currentTurn = [msg];
1491
+ }
1492
+ else if (msg.role === 'assistant') {
1493
+ // Assistant messages start a new turn (flush previous)
1494
+ if (currentTurn.length > 0) {
1495
+ turns.push(currentTurn);
1496
+ }
1497
+ currentTurn = [msg];
1498
+ }
1499
+ else if (msg.role === 'tool') {
1500
+ // Tool results belong to the current assistant turn
1501
+ currentTurn.push(msg);
1502
+ }
1503
+ }
1504
+ // Don't forget the last turn
1505
+ if (currentTurn.length > 0) {
1506
+ turns.push(currentTurn);
1507
+ }
1508
+ // Calculate how many turns to remove
1509
+ const targetTurnsToRemove = Math.floor(turns.length * reductionPercent);
1510
+ if (targetTurnsToRemove === 0 || turns.length <= 2) {
1511
+ return false;
1512
+ }
1513
+ // Keep recent turns (remove from the beginning)
1514
+ const keepTurns = turns.slice(targetTurnsToRemove);
1515
+ // IMPORTANT: Ensure we don't start with orphaned tool messages
1516
+ // The first kept turn must NOT be a tool-only turn
1517
+ let startIndex = 0;
1518
+ while (startIndex < keepTurns.length) {
1519
+ const firstTurn = keepTurns[startIndex];
1520
+ if (firstTurn && firstTurn.length > 0) {
1521
+ const firstMsg = firstTurn[0];
1522
+ // If first message is a tool result, skip this turn
1523
+ if (firstMsg?.role === 'tool') {
1524
+ startIndex++;
1525
+ continue;
1526
+ }
1527
+ // If first message is an assistant with tool calls but we're missing results,
1528
+ // check if all tool results are present
1529
+ if (firstMsg?.role === 'assistant' && firstMsg.toolCalls && firstMsg.toolCalls.length > 0) {
1530
+ // PERF: Pre-compute tool call IDs as array, use direct Set lookup
1531
+ const toolCallIds = firstMsg.toolCalls.map(tc => tc.id);
1532
+ const presentToolResultIds = new Set(firstTurn.filter(m => m.role === 'tool').map(m => m.toolCallId));
1533
+ // PERF: Direct has() calls with early exit instead of spread + every()
1534
+ let allPresent = true;
1535
+ for (const id of toolCallIds) {
1536
+ if (!presentToolResultIds.has(id)) {
1537
+ allPresent = false;
1538
+ break;
1539
+ }
1540
+ }
1541
+ if (allPresent) {
1542
+ break;
1543
+ }
1544
+ // Otherwise skip this turn
1545
+ startIndex++;
1546
+ continue;
1547
+ }
1548
+ }
1549
+ break;
1550
+ }
1551
+ const validTurns = keepTurns.slice(startIndex);
1552
+ if (validTurns.length === 0) {
1553
+ return false;
1554
+ }
1555
+ // Flatten valid turns back to messages
1556
+ const keepMessages = validTurns.flat();
1557
+ const actualRemoveCount = conversationMessages.length - keepMessages.length;
1558
+ // Aggressively truncate tool outputs in remaining messages
1559
+ let truncatedCount = 0;
1560
+ for (const msg of keepMessages) {
1561
+ if (msg.role === 'tool' && msg.content) {
1562
+ const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
1563
+ if (content.length > toolOutputLimit) {
1564
+ // Truncate with smart ending
1565
+ const truncated = content.slice(0, toolOutputLimit);
1566
+ const lastNewline = truncated.lastIndexOf('\n');
1567
+ const cutPoint = lastNewline > toolOutputLimit * 0.7 ? lastNewline : toolOutputLimit;
1568
+ msg.content = `${truncated.slice(0, cutPoint)}\n\n[... truncated ${content.length - cutPoint} chars for context recovery ...]`;
1569
+ truncatedCount++;
1570
+ }
1571
+ }
1572
+ // Also truncate very long assistant messages
1573
+ if (msg.role === 'assistant' && msg.content && msg.content.length > toolOutputLimit * 2) {
1574
+ const content = msg.content;
1575
+ const limit = toolOutputLimit * 2;
1576
+ const truncated = content.slice(0, limit);
1577
+ const lastNewline = truncated.lastIndexOf('\n');
1578
+ const cutPoint = lastNewline > limit * 0.8 ? lastNewline : limit;
1579
+ msg.content = `${truncated.slice(0, cutPoint)}\n\n[... truncated for context recovery ...]`;
1580
+ truncatedCount++;
1581
+ }
1582
+ }
1583
+ // Also truncate system messages if they're huge (except first system prompt)
1584
+ for (let i = 1; i < systemMessages.length; i++) {
1585
+ const sys = systemMessages[i];
1586
+ if (sys && sys.content && sys.content.length > toolOutputLimit) {
1587
+ sys.content = `${sys.content.slice(0, toolOutputLimit)}\n[... truncated ...]`;
1588
+ truncatedCount++;
1589
+ }
1590
+ }
1591
+ // Rebuild message array
1592
+ this.messages.length = 0;
1593
+ // Add system messages
1594
+ for (const sys of systemMessages) {
1595
+ this.messages.push(sys);
1596
+ }
1597
+ // Add summary notice
1598
+ this.messages.push({
1599
+ role: 'system',
1600
+ content: `[Auto Context Recovery] Removed ${actualRemoveCount} messages and truncated ${truncatedCount} large outputs to stay within token limits.`,
1601
+ });
1602
+ // Add remaining conversation (maintaining tool call/result pairing)
1603
+ for (const msg of keepMessages) {
1604
+ this.messages.push(msg);
1605
+ }
1606
+ // Notify about pruning
1607
+ const stats = this.contextManager?.getStats(this.messages) ?? {};
1608
+ this.callbacks.onContextPruned?.(actualRemoveCount, {
1609
+ ...stats,
1610
+ method: 'emergency-recovery',
1611
+ attempt,
1612
+ removedPercent: reductionPercent * 100,
1613
+ turnsRemoved: targetTurnsToRemove + startIndex,
1614
+ truncatedOutputs: truncatedCount,
1615
+ toolOutputLimit,
1616
+ });
1617
+ // Check if we're still over limit after all reductions
1618
+ const newStats = this.contextManager?.getStats(this.messages);
1619
+ if (newStats && newStats.percentage > 100) {
1620
+ // Still over limit - do one more aggressive pass
1621
+ // Truncate ALL tool outputs to absolute minimum
1622
+ const minLimit = 200;
1623
+ for (const msg of this.messages) {
1624
+ if (msg.role === 'tool' && msg.content) {
1625
+ const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
1626
+ if (content.length > minLimit) {
1627
+ msg.content = `${content.slice(0, minLimit)}\n[... severely truncated ...]`;
1628
+ }
1629
+ }
1630
+ }
1631
+ }
1632
+ return true;
1633
+ }
1634
+ }
1635
+ function cloneMessage(message) {
1636
+ switch (message.role) {
1637
+ case 'assistant': {
1638
+ const clone = {
1639
+ role: 'assistant',
1640
+ content: message.content,
1641
+ };
1642
+ if (message.toolCalls) {
1643
+ clone.toolCalls = message.toolCalls.map(cloneToolCall);
1644
+ }
1645
+ return clone;
1646
+ }
1647
+ case 'tool':
1648
+ return {
1649
+ role: 'tool',
1650
+ name: message.name,
1651
+ content: message.content,
1652
+ toolCallId: message.toolCallId,
1653
+ };
1654
+ case 'system':
1655
+ return { role: 'system', content: message.content };
1656
+ case 'user':
1657
+ default:
1658
+ return { role: 'user', content: message.content };
1659
+ }
1660
+ }
1661
+ function cloneToolCall(call) {
1662
+ return {
1663
+ id: call.id,
1664
+ name: call.name,
1665
+ arguments: { ...(call.arguments ?? {}) },
1666
+ };
1667
+ }
1668
+ //# sourceMappingURL=agent.js.map