@office-ai/aioncli-core 0.8.1 → 0.18.5

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 (778) hide show
  1. package/dist/index.d.ts +8 -2
  2. package/dist/index.js +7 -2
  3. package/dist/index.js.map +1 -1
  4. package/dist/src/agents/codebase-investigator.d.ts +36 -1
  5. package/dist/src/agents/codebase-investigator.js +93 -34
  6. package/dist/src/agents/codebase-investigator.js.map +1 -1
  7. package/dist/src/agents/codebase-investigator.test.d.ts +6 -0
  8. package/dist/src/agents/codebase-investigator.test.js +35 -0
  9. package/dist/src/agents/codebase-investigator.test.js.map +1 -0
  10. package/dist/src/agents/executor.d.ts +37 -11
  11. package/dist/src/agents/executor.js +512 -150
  12. package/dist/src/agents/executor.js.map +1 -1
  13. package/dist/src/agents/executor.test.js +1188 -245
  14. package/dist/src/agents/executor.test.js.map +1 -1
  15. package/dist/src/agents/invocation.d.ts +5 -2
  16. package/dist/src/agents/invocation.js +4 -2
  17. package/dist/src/agents/invocation.js.map +1 -1
  18. package/dist/src/agents/invocation.test.js +9 -0
  19. package/dist/src/agents/invocation.test.js.map +1 -1
  20. package/dist/src/agents/registry.d.ts +6 -1
  21. package/dist/src/agents/registry.js +51 -4
  22. package/dist/src/agents/registry.js.map +1 -1
  23. package/dist/src/agents/registry.test.js +30 -16
  24. package/dist/src/agents/registry.test.js.map +1 -1
  25. package/dist/src/agents/subagent-tool-wrapper.d.ts +3 -1
  26. package/dist/src/agents/subagent-tool-wrapper.js +4 -3
  27. package/dist/src/agents/subagent-tool-wrapper.js.map +1 -1
  28. package/dist/src/agents/subagent-tool-wrapper.test.js +9 -4
  29. package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -1
  30. package/dist/src/agents/types.d.ts +37 -7
  31. package/dist/src/agents/types.js +2 -0
  32. package/dist/src/agents/types.js.map +1 -1
  33. package/dist/src/code_assist/codeAssist.js +1 -1
  34. package/dist/src/code_assist/codeAssist.test.d.ts +6 -0
  35. package/dist/src/code_assist/codeAssist.test.js +99 -0
  36. package/dist/src/code_assist/codeAssist.test.js.map +1 -0
  37. package/dist/src/code_assist/converter.d.ts +1 -0
  38. package/dist/src/code_assist/converter.js +1 -0
  39. package/dist/src/code_assist/converter.js.map +1 -1
  40. package/dist/src/code_assist/converter.test.js +19 -0
  41. package/dist/src/code_assist/converter.test.js.map +1 -1
  42. package/dist/src/code_assist/experiments/client_metadata.d.ts +12 -0
  43. package/dist/src/code_assist/experiments/client_metadata.js +50 -0
  44. package/dist/src/code_assist/experiments/client_metadata.js.map +1 -0
  45. package/dist/src/code_assist/experiments/client_metadata.test.d.ts +6 -0
  46. package/dist/src/code_assist/experiments/client_metadata.test.js +99 -0
  47. package/dist/src/code_assist/experiments/client_metadata.test.js.map +1 -0
  48. package/dist/src/code_assist/experiments/experiments.d.ts +17 -0
  49. package/dist/src/code_assist/experiments/experiments.js +36 -0
  50. package/dist/src/code_assist/experiments/experiments.js.map +1 -0
  51. package/dist/src/code_assist/experiments/experiments.test.d.ts +6 -0
  52. package/dist/src/code_assist/experiments/experiments.test.js +92 -0
  53. package/dist/src/code_assist/experiments/experiments.test.js.map +1 -0
  54. package/dist/src/code_assist/experiments/flagNames.d.ts +13 -0
  55. package/dist/src/code_assist/experiments/flagNames.js +13 -0
  56. package/dist/src/code_assist/experiments/flagNames.js.map +1 -0
  57. package/dist/src/code_assist/experiments/types.d.ts +35 -0
  58. package/dist/src/code_assist/experiments/types.js +7 -0
  59. package/dist/src/code_assist/experiments/types.js.map +1 -0
  60. package/dist/src/code_assist/oauth-credential-storage.js +6 -5
  61. package/dist/src/code_assist/oauth-credential-storage.js.map +1 -1
  62. package/dist/src/code_assist/oauth-credential-storage.test.js +65 -3
  63. package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -1
  64. package/dist/src/code_assist/oauth2.d.ts +2 -2
  65. package/dist/src/code_assist/oauth2.js +161 -93
  66. package/dist/src/code_assist/oauth2.js.map +1 -1
  67. package/dist/src/code_assist/oauth2.test.js +103 -57
  68. package/dist/src/code_assist/oauth2.test.js.map +1 -1
  69. package/dist/src/code_assist/server.d.ts +6 -4
  70. package/dist/src/code_assist/server.js +16 -8
  71. package/dist/src/code_assist/server.js.map +1 -1
  72. package/dist/src/code_assist/server.test.js +126 -28
  73. package/dist/src/code_assist/server.test.js.map +1 -1
  74. package/dist/src/code_assist/setup.d.ts +2 -2
  75. package/dist/src/code_assist/setup.js +4 -2
  76. package/dist/src/code_assist/setup.js.map +1 -1
  77. package/dist/src/code_assist/types.d.ts +1 -1
  78. package/dist/src/code_assist/types.js.map +1 -1
  79. package/dist/src/commands/extensions.d.ts +7 -0
  80. package/dist/src/commands/extensions.js +9 -0
  81. package/dist/src/commands/extensions.js.map +1 -0
  82. package/dist/src/commands/extensions.test.d.ts +6 -0
  83. package/dist/src/commands/extensions.test.js +19 -0
  84. package/dist/src/commands/extensions.test.js.map +1 -0
  85. package/dist/src/config/config.d.ts +169 -43
  86. package/dist/src/config/config.js +418 -79
  87. package/dist/src/config/config.js.map +1 -1
  88. package/dist/src/config/config.test.js +684 -49
  89. package/dist/src/config/config.test.js.map +1 -1
  90. package/dist/src/config/defaultModelConfigs.d.ts +7 -0
  91. package/dist/src/config/defaultModelConfigs.js +185 -0
  92. package/dist/src/config/defaultModelConfigs.js.map +1 -0
  93. package/dist/src/config/models.d.ts +23 -2
  94. package/dist/src/config/models.js +50 -7
  95. package/dist/src/config/models.js.map +1 -1
  96. package/dist/src/config/models.test.js +71 -10
  97. package/dist/src/config/models.test.js.map +1 -1
  98. package/dist/src/config/storage.d.ts +3 -1
  99. package/dist/src/config/storage.js +22 -2
  100. package/dist/src/config/storage.js.map +1 -1
  101. package/dist/src/config/storage.test.js +7 -6
  102. package/dist/src/config/storage.test.js.map +1 -1
  103. package/dist/src/confirmation-bus/message-bus.d.ts +3 -2
  104. package/dist/src/confirmation-bus/message-bus.js +9 -3
  105. package/dist/src/confirmation-bus/message-bus.js.map +1 -1
  106. package/dist/src/confirmation-bus/message-bus.test.js +30 -24
  107. package/dist/src/confirmation-bus/message-bus.test.js.map +1 -1
  108. package/dist/src/confirmation-bus/types.d.ts +13 -2
  109. package/dist/src/confirmation-bus/types.js +1 -0
  110. package/dist/src/confirmation-bus/types.js.map +1 -1
  111. package/dist/src/core/apiKeyCredentialStorage.d.ts +17 -0
  112. package/dist/src/core/apiKeyCredentialStorage.js +64 -0
  113. package/dist/src/core/apiKeyCredentialStorage.js.map +1 -0
  114. package/dist/src/core/apiKeyCredentialStorage.test.d.ts +6 -0
  115. package/dist/src/core/apiKeyCredentialStorage.test.js +71 -0
  116. package/dist/src/core/apiKeyCredentialStorage.test.js.map +1 -0
  117. package/dist/src/core/baseLlmClient.d.ts +4 -8
  118. package/dist/src/core/baseLlmClient.js +6 -11
  119. package/dist/src/core/baseLlmClient.js.map +1 -1
  120. package/dist/src/core/baseLlmClient.test.js +22 -27
  121. package/dist/src/core/baseLlmClient.test.js.map +1 -1
  122. package/dist/src/core/client.d.ts +12 -19
  123. package/dist/src/core/client.js +104 -206
  124. package/dist/src/core/client.js.map +1 -1
  125. package/dist/src/core/client.test.js +329 -452
  126. package/dist/src/core/client.test.js.map +1 -1
  127. package/dist/src/core/contentGenerator.d.ts +3 -2
  128. package/dist/src/core/contentGenerator.js +56 -41
  129. package/dist/src/core/contentGenerator.js.map +1 -1
  130. package/dist/src/core/contentGenerator.test.js +49 -1
  131. package/dist/src/core/contentGenerator.test.js.map +1 -1
  132. package/dist/src/core/coreToolScheduler.d.ts +8 -4
  133. package/dist/src/core/coreToolScheduler.js +348 -179
  134. package/dist/src/core/coreToolScheduler.js.map +1 -1
  135. package/dist/src/core/coreToolScheduler.test.js +575 -219
  136. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  137. package/dist/src/core/fakeContentGenerator.d.ts +33 -0
  138. package/dist/src/core/fakeContentGenerator.js +58 -0
  139. package/dist/src/core/fakeContentGenerator.js.map +1 -0
  140. package/dist/src/core/fakeContentGenerator.test.d.ts +6 -0
  141. package/dist/src/core/fakeContentGenerator.test.js +127 -0
  142. package/dist/src/core/fakeContentGenerator.test.js.map +1 -0
  143. package/dist/src/core/geminiChat.d.ts +23 -18
  144. package/dist/src/core/geminiChat.js +186 -108
  145. package/dist/src/core/geminiChat.js.map +1 -1
  146. package/dist/src/core/geminiChat.test.js +581 -270
  147. package/dist/src/core/geminiChat.test.js.map +1 -1
  148. package/dist/src/core/logger.d.ts +7 -2
  149. package/dist/src/core/logger.js +35 -27
  150. package/dist/src/core/logger.js.map +1 -1
  151. package/dist/src/core/logger.test.js +45 -29
  152. package/dist/src/core/logger.test.js.map +1 -1
  153. package/dist/src/core/loggingContentGenerator.d.ts +1 -0
  154. package/dist/src/core/loggingContentGenerator.js +113 -33
  155. package/dist/src/core/loggingContentGenerator.js.map +1 -1
  156. package/dist/src/core/loggingContentGenerator.test.d.ts +6 -0
  157. package/dist/src/core/loggingContentGenerator.test.js +180 -0
  158. package/dist/src/core/loggingContentGenerator.test.js.map +1 -0
  159. package/dist/src/core/nonInteractiveToolExecutor.d.ts +3 -2
  160. package/dist/src/core/nonInteractiveToolExecutor.js +12 -7
  161. package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
  162. package/dist/src/core/nonInteractiveToolExecutor.test.js +12 -8
  163. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  164. package/dist/src/core/openaiContentGenerator.d.ts +15 -1
  165. package/dist/src/core/openaiContentGenerator.js +139 -22
  166. package/dist/src/core/openaiContentGenerator.js.map +1 -1
  167. package/dist/src/core/prompts.d.ts +2 -1
  168. package/dist/src/core/prompts.js +135 -154
  169. package/dist/src/core/prompts.js.map +1 -1
  170. package/dist/src/core/prompts.test.js +128 -189
  171. package/dist/src/core/prompts.test.js.map +1 -1
  172. package/dist/src/core/recordingContentGenerator.d.ts +18 -0
  173. package/dist/src/core/recordingContentGenerator.js +77 -0
  174. package/dist/src/core/recordingContentGenerator.js.map +1 -0
  175. package/dist/src/core/recordingContentGenerator.test.d.ts +6 -0
  176. package/dist/src/core/recordingContentGenerator.test.js +101 -0
  177. package/dist/src/core/recordingContentGenerator.test.js.map +1 -0
  178. package/dist/src/core/tokenLimits.test.d.ts +6 -0
  179. package/dist/src/core/tokenLimits.test.js +26 -0
  180. package/dist/src/core/tokenLimits.test.js.map +1 -0
  181. package/dist/src/core/turn.d.ts +23 -3
  182. package/dist/src/core/turn.js +18 -9
  183. package/dist/src/core/turn.js.map +1 -1
  184. package/dist/src/core/turn.test.js +98 -104
  185. package/dist/src/core/turn.test.js.map +1 -1
  186. package/dist/src/fallback/handler.js +60 -8
  187. package/dist/src/fallback/handler.js.map +1 -1
  188. package/dist/src/fallback/handler.test.js +132 -17
  189. package/dist/src/fallback/handler.test.js.map +1 -1
  190. package/dist/src/fallback/types.d.ts +1 -1
  191. package/dist/src/generated/git-commit.d.ts +2 -2
  192. package/dist/src/generated/git-commit.js +2 -2
  193. package/dist/src/generated/git-commit.js.map +1 -1
  194. package/dist/src/hooks/hookAggregator.d.ts +68 -0
  195. package/dist/src/hooks/hookAggregator.js +262 -0
  196. package/dist/src/hooks/hookAggregator.js.map +1 -0
  197. package/dist/src/hooks/hookAggregator.test.d.ts +6 -0
  198. package/dist/src/hooks/hookAggregator.test.js +387 -0
  199. package/dist/src/hooks/hookAggregator.test.js.map +1 -0
  200. package/dist/src/hooks/hookPlanner.d.ts +46 -0
  201. package/dist/src/hooks/hookPlanner.js +108 -0
  202. package/dist/src/hooks/hookPlanner.js.map +1 -0
  203. package/dist/src/hooks/hookPlanner.test.d.ts +6 -0
  204. package/dist/src/hooks/hookPlanner.test.js +255 -0
  205. package/dist/src/hooks/hookPlanner.test.js.map +1 -0
  206. package/dist/src/hooks/hookRegistry.d.ts +87 -0
  207. package/dist/src/hooks/hookRegistry.js +198 -0
  208. package/dist/src/hooks/hookRegistry.js.map +1 -0
  209. package/dist/src/hooks/hookRegistry.test.d.ts +6 -0
  210. package/dist/src/hooks/hookRegistry.test.js +341 -0
  211. package/dist/src/hooks/hookRegistry.test.js.map +1 -0
  212. package/dist/src/hooks/hookRunner.d.ts +42 -0
  213. package/dist/src/hooks/hookRunner.js +272 -0
  214. package/dist/src/hooks/hookRunner.js.map +1 -0
  215. package/dist/src/hooks/hookRunner.test.d.ts +6 -0
  216. package/dist/src/hooks/hookRunner.test.js +468 -0
  217. package/dist/src/hooks/hookRunner.test.js.map +1 -0
  218. package/dist/src/hooks/hookTranslator.d.ts +113 -0
  219. package/dist/src/hooks/hookTranslator.js +232 -0
  220. package/dist/src/hooks/hookTranslator.js.map +1 -0
  221. package/dist/src/hooks/hookTranslator.test.d.ts +6 -0
  222. package/dist/src/hooks/hookTranslator.test.js +192 -0
  223. package/dist/src/hooks/hookTranslator.test.js.map +1 -0
  224. package/dist/src/hooks/types.d.ts +384 -0
  225. package/dist/src/hooks/types.js +284 -0
  226. package/dist/src/hooks/types.js.map +1 -0
  227. package/dist/src/hooks/types.test.d.ts +6 -0
  228. package/dist/src/hooks/types.test.js +313 -0
  229. package/dist/src/hooks/types.test.js.map +1 -0
  230. package/dist/src/ide/detect-ide.d.ts +4 -0
  231. package/dist/src/ide/detect-ide.js +6 -1
  232. package/dist/src/ide/detect-ide.js.map +1 -1
  233. package/dist/src/ide/detect-ide.test.js +16 -0
  234. package/dist/src/ide/detect-ide.test.js.map +1 -1
  235. package/dist/src/ide/ide-client.d.ts +3 -1
  236. package/dist/src/ide/ide-client.js +12 -10
  237. package/dist/src/ide/ide-client.js.map +1 -1
  238. package/dist/src/ide/ide-client.test.js +163 -4
  239. package/dist/src/ide/ide-client.test.js.map +1 -1
  240. package/dist/src/ide/ide-installer.js +66 -21
  241. package/dist/src/ide/ide-installer.js.map +1 -1
  242. package/dist/src/ide/ide-installer.test.js +54 -1
  243. package/dist/src/ide/ide-installer.test.js.map +1 -1
  244. package/dist/src/ide/process-utils.js +85 -75
  245. package/dist/src/ide/process-utils.js.map +1 -1
  246. package/dist/src/ide/process-utils.test.js +83 -90
  247. package/dist/src/ide/process-utils.test.js.map +1 -1
  248. package/dist/src/ide/types.d.ts +5 -5
  249. package/dist/src/ide/types.js +1 -1
  250. package/dist/src/index.d.ts +21 -0
  251. package/dist/src/index.js +24 -0
  252. package/dist/src/index.js.map +1 -1
  253. package/dist/src/mcp/google-auth-provider.d.ts +2 -0
  254. package/dist/src/mcp/google-auth-provider.js +21 -3
  255. package/dist/src/mcp/google-auth-provider.js.map +1 -1
  256. package/dist/src/mcp/google-auth-provider.test.js +42 -9
  257. package/dist/src/mcp/google-auth-provider.test.js.map +1 -1
  258. package/dist/src/mcp/oauth-provider.d.ts +8 -5
  259. package/dist/src/mcp/oauth-provider.js +140 -55
  260. package/dist/src/mcp/oauth-provider.js.map +1 -1
  261. package/dist/src/mcp/oauth-provider.test.js +369 -2
  262. package/dist/src/mcp/oauth-provider.test.js.map +1 -1
  263. package/dist/src/mcp/oauth-token-storage.js +5 -4
  264. package/dist/src/mcp/oauth-token-storage.js.map +1 -1
  265. package/dist/src/mcp/oauth-token-storage.test.js +17 -11
  266. package/dist/src/mcp/oauth-token-storage.test.js.map +1 -1
  267. package/dist/src/mcp/oauth-utils.d.ts +7 -0
  268. package/dist/src/mcp/oauth-utils.js +28 -8
  269. package/dist/src/mcp/oauth-utils.js.map +1 -1
  270. package/dist/src/mcp/oauth-utils.test.js +45 -2
  271. package/dist/src/mcp/oauth-utils.test.js.map +1 -1
  272. package/dist/src/mcp/sa-impersonation-provider.d.ts +0 -6
  273. package/dist/src/mcp/sa-impersonation-provider.js +6 -23
  274. package/dist/src/mcp/sa-impersonation-provider.js.map +1 -1
  275. package/dist/src/mcp/token-storage/base-token-storage.test.js +75 -84
  276. package/dist/src/mcp/token-storage/base-token-storage.test.js.map +1 -1
  277. package/dist/src/mcp/token-storage/file-token-storage.js +3 -2
  278. package/dist/src/mcp/token-storage/file-token-storage.js.map +1 -1
  279. package/dist/src/mcp/token-storage/file-token-storage.test.js +11 -8
  280. package/dist/src/mcp/token-storage/file-token-storage.test.js.map +1 -1
  281. package/dist/src/mcp/token-storage/keychain-token-storage.d.ts +6 -2
  282. package/dist/src/mcp/token-storage/keychain-token-storage.js +63 -7
  283. package/dist/src/mcp/token-storage/keychain-token-storage.js.map +1 -1
  284. package/dist/src/mcp/token-storage/keychain-token-storage.test.js +54 -3
  285. package/dist/src/mcp/token-storage/keychain-token-storage.test.js.map +1 -1
  286. package/dist/src/mcp/token-storage/types.d.ts +6 -0
  287. package/dist/src/mcp/token-storage/types.js.map +1 -1
  288. package/dist/src/output/stream-json-formatter.d.ts +32 -0
  289. package/dist/src/output/stream-json-formatter.js +52 -0
  290. package/dist/src/output/stream-json-formatter.js.map +1 -0
  291. package/dist/src/output/stream-json-formatter.test.d.ts +6 -0
  292. package/dist/src/output/stream-json-formatter.test.js +479 -0
  293. package/dist/src/output/stream-json-formatter.test.js.map +1 -0
  294. package/dist/src/output/types.d.ts +63 -1
  295. package/dist/src/output/types.js +11 -0
  296. package/dist/src/output/types.js.map +1 -1
  297. package/dist/src/policy/config.d.ts +31 -0
  298. package/dist/src/policy/config.js +199 -0
  299. package/dist/src/policy/config.js.map +1 -0
  300. package/dist/src/policy/config.test.d.ts +6 -0
  301. package/dist/src/policy/config.test.js +538 -0
  302. package/dist/src/policy/config.test.js.map +1 -0
  303. package/dist/src/policy/index.d.ts +2 -0
  304. package/dist/src/policy/index.js +2 -0
  305. package/dist/src/policy/index.js.map +1 -1
  306. package/dist/src/policy/policies/discovered.toml +8 -0
  307. package/dist/src/policy/policies/read-only.toml +56 -0
  308. package/dist/src/policy/policies/write.toml +73 -0
  309. package/dist/src/policy/policies/yolo.toml +31 -0
  310. package/dist/src/policy/policy-engine.d.ts +12 -3
  311. package/dist/src/policy/policy-engine.js +74 -8
  312. package/dist/src/policy/policy-engine.js.map +1 -1
  313. package/dist/src/policy/policy-engine.test.js +460 -76
  314. package/dist/src/policy/policy-engine.test.js.map +1 -1
  315. package/dist/src/policy/toml-loader.d.ts +47 -0
  316. package/dist/src/policy/toml-loader.js +411 -0
  317. package/dist/src/policy/toml-loader.js.map +1 -0
  318. package/dist/src/policy/toml-loader.test.d.ts +6 -0
  319. package/dist/src/policy/toml-loader.test.js +376 -0
  320. package/dist/src/policy/toml-loader.test.js.map +1 -0
  321. package/dist/src/policy/types.d.ts +83 -0
  322. package/dist/src/policy/types.js +10 -0
  323. package/dist/src/policy/types.js.map +1 -1
  324. package/dist/src/prompts/mcp-prompts.test.d.ts +6 -0
  325. package/dist/src/prompts/mcp-prompts.test.js +39 -0
  326. package/dist/src/prompts/mcp-prompts.test.js.map +1 -0
  327. package/dist/src/prompts/prompt-registry.js +2 -1
  328. package/dist/src/prompts/prompt-registry.js.map +1 -1
  329. package/dist/src/prompts/prompt-registry.test.d.ts +6 -0
  330. package/dist/src/prompts/prompt-registry.test.js +96 -0
  331. package/dist/src/prompts/prompt-registry.test.js.map +1 -0
  332. package/dist/src/routing/modelRouterService.js +15 -0
  333. package/dist/src/routing/modelRouterService.js.map +1 -1
  334. package/dist/src/routing/modelRouterService.test.js +62 -0
  335. package/dist/src/routing/modelRouterService.test.js.map +1 -1
  336. package/dist/src/routing/strategies/classifierStrategy.d.ts +1 -1
  337. package/dist/src/routing/strategies/classifierStrategy.js +9 -16
  338. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  339. package/dist/src/routing/strategies/classifierStrategy.test.js +17 -13
  340. package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
  341. package/dist/src/routing/strategies/fallbackStrategy.js +1 -1
  342. package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
  343. package/dist/src/routing/strategies/fallbackStrategy.test.js +4 -0
  344. package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -1
  345. package/dist/src/routing/strategies/overrideStrategy.js +2 -2
  346. package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
  347. package/dist/src/routing/strategies/overrideStrategy.test.js +3 -0
  348. package/dist/src/routing/strategies/overrideStrategy.test.js.map +1 -1
  349. package/dist/src/safety/built-in.d.ts +21 -0
  350. package/dist/src/safety/built-in.js +106 -0
  351. package/dist/src/safety/built-in.js.map +1 -0
  352. package/dist/src/safety/built-in.test.d.ts +6 -0
  353. package/dist/src/safety/built-in.test.js +199 -0
  354. package/dist/src/safety/built-in.test.js.map +1 -0
  355. package/dist/src/safety/checker-runner.d.ts +48 -0
  356. package/dist/src/safety/checker-runner.js +208 -0
  357. package/dist/src/safety/checker-runner.js.map +1 -0
  358. package/dist/src/safety/checker-runner.test.d.ts +6 -0
  359. package/dist/src/safety/checker-runner.test.js +238 -0
  360. package/dist/src/safety/checker-runner.test.js.map +1 -0
  361. package/dist/src/safety/context-builder.d.ts +23 -0
  362. package/dist/src/safety/context-builder.js +47 -0
  363. package/dist/src/safety/context-builder.js.map +1 -0
  364. package/dist/src/safety/context-builder.test.d.ts +6 -0
  365. package/dist/src/safety/context-builder.test.js +49 -0
  366. package/dist/src/safety/context-builder.test.js.map +1 -0
  367. package/dist/src/safety/protocol.d.ts +88 -0
  368. package/dist/src/safety/protocol.js +15 -0
  369. package/dist/src/safety/protocol.js.map +1 -0
  370. package/dist/src/safety/registry.d.ts +26 -0
  371. package/dist/src/safety/registry.js +65 -0
  372. package/dist/src/safety/registry.js.map +1 -0
  373. package/dist/src/safety/registry.test.d.ts +6 -0
  374. package/dist/src/safety/registry.test.js +31 -0
  375. package/dist/src/safety/registry.test.js.map +1 -0
  376. package/dist/src/services/chatCompressionService.d.ts +32 -0
  377. package/dist/src/services/chatCompressionService.js +162 -0
  378. package/dist/src/services/chatCompressionService.js.map +1 -0
  379. package/dist/src/services/chatCompressionService.test.d.ts +6 -0
  380. package/dist/src/services/chatCompressionService.test.js +210 -0
  381. package/dist/src/services/chatCompressionService.test.js.map +1 -0
  382. package/dist/src/services/chatRecordingService.d.ts +3 -2
  383. package/dist/src/services/chatRecordingService.js +11 -9
  384. package/dist/src/services/chatRecordingService.js.map +1 -1
  385. package/dist/src/services/fileDiscoveryService.d.ts +2 -14
  386. package/dist/src/services/fileDiscoveryService.js +19 -55
  387. package/dist/src/services/fileDiscoveryService.js.map +1 -1
  388. package/dist/src/services/fileDiscoveryService.test.js +91 -11
  389. package/dist/src/services/fileDiscoveryService.test.js.map +1 -1
  390. package/dist/src/services/loopDetectionService.d.ts +4 -1
  391. package/dist/src/services/loopDetectionService.js +95 -42
  392. package/dist/src/services/loopDetectionService.js.map +1 -1
  393. package/dist/src/services/loopDetectionService.test.js +220 -12
  394. package/dist/src/services/loopDetectionService.test.js.map +1 -1
  395. package/dist/src/services/modelConfig.golden.test.d.ts +6 -0
  396. package/dist/src/services/modelConfig.golden.test.js +42 -0
  397. package/dist/src/services/modelConfig.golden.test.js.map +1 -0
  398. package/dist/src/services/modelConfig.integration.test.d.ts +6 -0
  399. package/dist/src/services/modelConfig.integration.test.js +247 -0
  400. package/dist/src/services/modelConfig.integration.test.js.map +1 -0
  401. package/dist/src/services/modelConfigService.d.ts +48 -0
  402. package/dist/src/services/modelConfigService.js +151 -0
  403. package/dist/src/services/modelConfigService.js.map +1 -0
  404. package/dist/src/services/modelConfigService.test.d.ts +6 -0
  405. package/dist/src/services/modelConfigService.test.js +531 -0
  406. package/dist/src/services/modelConfigService.test.js.map +1 -0
  407. package/dist/src/services/shellExecutionService.d.ts +1 -0
  408. package/dist/src/services/shellExecutionService.js +195 -92
  409. package/dist/src/services/shellExecutionService.js.map +1 -1
  410. package/dist/src/services/shellExecutionService.test.js +137 -14
  411. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  412. package/dist/src/services/test-data/resolved-aliases.golden.json +202 -0
  413. package/dist/src/telemetry/activity-monitor.d.ts +116 -0
  414. package/dist/src/telemetry/activity-monitor.js +209 -0
  415. package/dist/src/telemetry/activity-monitor.js.map +1 -0
  416. package/dist/src/telemetry/activity-monitor.test.d.ts +6 -0
  417. package/dist/src/telemetry/activity-monitor.test.js +251 -0
  418. package/dist/src/telemetry/activity-monitor.test.js.map +1 -0
  419. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +25 -7
  420. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +294 -76
  421. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  422. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +1 -0
  423. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +192 -66
  424. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
  425. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +25 -3
  426. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +59 -5
  427. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  428. package/dist/src/telemetry/constants.d.ts +0 -28
  429. package/dist/src/telemetry/constants.js +0 -29
  430. package/dist/src/telemetry/constants.js.map +1 -1
  431. package/dist/src/telemetry/gcp-exporters.js +0 -1
  432. package/dist/src/telemetry/gcp-exporters.js.map +1 -1
  433. package/dist/src/telemetry/gcp-exporters.test.js +1 -1
  434. package/dist/src/telemetry/gcp-exporters.test.js.map +1 -1
  435. package/dist/src/telemetry/index.d.ts +7 -3
  436. package/dist/src/telemetry/index.js +13 -4
  437. package/dist/src/telemetry/index.js.map +1 -1
  438. package/dist/src/telemetry/loggers.d.ts +14 -7
  439. package/dist/src/telemetry/loggers.js +197 -320
  440. package/dist/src/telemetry/loggers.js.map +1 -1
  441. package/dist/src/telemetry/loggers.test.circular.js +0 -1
  442. package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
  443. package/dist/src/telemetry/loggers.test.js +460 -59
  444. package/dist/src/telemetry/loggers.test.js.map +1 -1
  445. package/dist/src/telemetry/memory-monitor.d.ts +149 -0
  446. package/dist/src/telemetry/memory-monitor.js +335 -0
  447. package/dist/src/telemetry/memory-monitor.js.map +1 -0
  448. package/dist/src/telemetry/memory-monitor.test.d.ts +6 -0
  449. package/dist/src/telemetry/memory-monitor.test.js +472 -0
  450. package/dist/src/telemetry/memory-monitor.test.js.map +1 -0
  451. package/dist/src/telemetry/metrics.d.ts +180 -4
  452. package/dist/src/telemetry/metrics.js +270 -6
  453. package/dist/src/telemetry/metrics.js.map +1 -1
  454. package/dist/src/telemetry/metrics.test.js +502 -184
  455. package/dist/src/telemetry/metrics.test.js.map +1 -1
  456. package/dist/src/telemetry/sdk.js +3 -2
  457. package/dist/src/telemetry/sdk.js.map +1 -1
  458. package/dist/src/telemetry/semantic.d.ts +82 -0
  459. package/dist/src/telemetry/semantic.js +269 -0
  460. package/dist/src/telemetry/semantic.js.map +1 -0
  461. package/dist/src/telemetry/semantic.test.d.ts +6 -0
  462. package/dist/src/telemetry/semantic.test.js +387 -0
  463. package/dist/src/telemetry/semantic.test.js.map +1 -0
  464. package/dist/src/telemetry/telemetry-utils.test.js +29 -28
  465. package/dist/src/telemetry/telemetry-utils.test.js.map +1 -1
  466. package/dist/src/telemetry/telemetryAttributes.d.ts +8 -0
  467. package/dist/src/telemetry/telemetryAttributes.js +19 -0
  468. package/dist/src/telemetry/telemetryAttributes.js.map +1 -0
  469. package/dist/src/telemetry/trace.d.ts +46 -0
  470. package/dist/src/telemetry/trace.js +121 -0
  471. package/dist/src/telemetry/trace.js.map +1 -0
  472. package/dist/src/telemetry/types.d.ts +227 -29
  473. package/dist/src/telemetry/types.js +858 -72
  474. package/dist/src/telemetry/types.js.map +1 -1
  475. package/dist/src/telemetry/uiTelemetry.d.ts +1 -1
  476. package/dist/src/telemetry/uiTelemetry.js +7 -7
  477. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  478. package/dist/src/telemetry/uiTelemetry.test.js +89 -67
  479. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
  480. package/dist/src/test-utils/config.d.ts +1 -1
  481. package/dist/src/test-utils/config.js +1 -1
  482. package/dist/src/tools/base-tool-invocation.test.d.ts +6 -0
  483. package/dist/src/tools/base-tool-invocation.test.js +85 -0
  484. package/dist/src/tools/base-tool-invocation.test.js.map +1 -0
  485. package/dist/src/tools/edit.d.ts +4 -3
  486. package/dist/src/tools/edit.js +50 -47
  487. package/dist/src/tools/edit.js.map +1 -1
  488. package/dist/src/tools/edit.test.js +306 -216
  489. package/dist/src/tools/edit.test.js.map +1 -1
  490. package/dist/src/tools/glob.d.ts +4 -3
  491. package/dist/src/tools/glob.js +24 -27
  492. package/dist/src/tools/glob.js.map +1 -1
  493. package/dist/src/tools/glob.test.js +212 -205
  494. package/dist/src/tools/glob.test.js.map +1 -1
  495. package/dist/src/tools/grep.d.ts +4 -3
  496. package/dist/src/tools/grep.js +31 -25
  497. package/dist/src/tools/grep.js.map +1 -1
  498. package/dist/src/tools/grep.test.js +15 -12
  499. package/dist/src/tools/grep.test.js.map +1 -1
  500. package/dist/src/tools/ls.d.ts +4 -3
  501. package/dist/src/tools/ls.js +29 -35
  502. package/dist/src/tools/ls.js.map +1 -1
  503. package/dist/src/tools/ls.test.js +34 -42
  504. package/dist/src/tools/ls.test.js.map +1 -1
  505. package/dist/src/tools/mcp-client-manager.d.ts +49 -11
  506. package/dist/src/tools/mcp-client-manager.js +191 -31
  507. package/dist/src/tools/mcp-client-manager.js.map +1 -1
  508. package/dist/src/tools/mcp-client-manager.test.js +132 -25
  509. package/dist/src/tools/mcp-client-manager.test.js.map +1 -1
  510. package/dist/src/tools/mcp-client.d.ts +12 -5
  511. package/dist/src/tools/mcp-client.js +287 -267
  512. package/dist/src/tools/mcp-client.js.map +1 -1
  513. package/dist/src/tools/mcp-client.test.js +352 -45
  514. package/dist/src/tools/mcp-client.test.js.map +1 -1
  515. package/dist/src/tools/mcp-tool.d.ts +6 -2
  516. package/dist/src/tools/mcp-tool.js +19 -8
  517. package/dist/src/tools/mcp-tool.js.map +1 -1
  518. package/dist/src/tools/mcp-tool.test.js +186 -273
  519. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  520. package/dist/src/tools/memoryTool.d.ts +7 -5
  521. package/dist/src/tools/memoryTool.js +14 -12
  522. package/dist/src/tools/memoryTool.js.map +1 -1
  523. package/dist/src/tools/memoryTool.test.js +10 -9
  524. package/dist/src/tools/memoryTool.test.js.map +1 -1
  525. package/dist/src/tools/message-bus-integration.test.js +14 -1
  526. package/dist/src/tools/message-bus-integration.test.js.map +1 -1
  527. package/dist/src/tools/modifiable-tool.d.ts +5 -1
  528. package/dist/src/tools/modifiable-tool.js +38 -16
  529. package/dist/src/tools/modifiable-tool.js.map +1 -1
  530. package/dist/src/tools/modifiable-tool.test.js +66 -31
  531. package/dist/src/tools/modifiable-tool.test.js.map +1 -1
  532. package/dist/src/tools/read-file.d.ts +6 -5
  533. package/dist/src/tools/read-file.js +26 -32
  534. package/dist/src/tools/read-file.js.map +1 -1
  535. package/dist/src/tools/read-file.test.js +68 -33
  536. package/dist/src/tools/read-file.test.js.map +1 -1
  537. package/dist/src/tools/read-many-files.d.ts +6 -12
  538. package/dist/src/tools/read-many-files.js +28 -57
  539. package/dist/src/tools/read-many-files.js.map +1 -1
  540. package/dist/src/tools/read-many-files.test.js +37 -36
  541. package/dist/src/tools/read-many-files.test.js.map +1 -1
  542. package/dist/src/tools/ripGrep.d.ts +28 -10
  543. package/dist/src/tools/ripGrep.js +195 -193
  544. package/dist/src/tools/ripGrep.js.map +1 -1
  545. package/dist/src/tools/ripGrep.test.js +533 -204
  546. package/dist/src/tools/ripGrep.test.js.map +1 -1
  547. package/dist/src/tools/shell.d.ts +8 -6
  548. package/dist/src/tools/shell.js +64 -38
  549. package/dist/src/tools/shell.js.map +1 -1
  550. package/dist/src/tools/shell.test.js +134 -43
  551. package/dist/src/tools/shell.test.js.map +1 -1
  552. package/dist/src/tools/smart-edit.d.ts +10 -23
  553. package/dist/src/tools/smart-edit.js +100 -85
  554. package/dist/src/tools/smart-edit.js.map +1 -1
  555. package/dist/src/tools/smart-edit.test.js +229 -179
  556. package/dist/src/tools/smart-edit.test.js.map +1 -1
  557. package/dist/src/tools/tool-error.d.ts +21 -0
  558. package/dist/src/tools/tool-error.js +27 -0
  559. package/dist/src/tools/tool-error.js.map +1 -1
  560. package/dist/src/tools/tool-names.d.ts +17 -0
  561. package/dist/src/tools/tool-names.js +21 -0
  562. package/dist/src/tools/tool-names.js.map +1 -0
  563. package/dist/src/tools/tool-registry.d.ts +32 -20
  564. package/dist/src/tools/tool-registry.js +122 -78
  565. package/dist/src/tools/tool-registry.js.map +1 -1
  566. package/dist/src/tools/tool-registry.test.js +167 -90
  567. package/dist/src/tools/tool-registry.test.js.map +1 -1
  568. package/dist/src/tools/tools.d.ts +23 -8
  569. package/dist/src/tools/tools.js +69 -37
  570. package/dist/src/tools/tools.js.map +1 -1
  571. package/dist/src/tools/web-fetch.d.ts +11 -3
  572. package/dist/src/tools/web-fetch.js +80 -38
  573. package/dist/src/tools/web-fetch.js.map +1 -1
  574. package/dist/src/tools/web-fetch.test.js +338 -9
  575. package/dist/src/tools/web-fetch.test.js.map +1 -1
  576. package/dist/src/tools/web-search.d.ts +4 -3
  577. package/dist/src/tools/web-search.js +11 -9
  578. package/dist/src/tools/web-search.js.map +1 -1
  579. package/dist/src/tools/web-search.test.js +6 -0
  580. package/dist/src/tools/web-search.test.js.map +1 -1
  581. package/dist/src/tools/write-file.d.ts +3 -2
  582. package/dist/src/tools/write-file.js +41 -40
  583. package/dist/src/tools/write-file.js.map +1 -1
  584. package/dist/src/tools/write-file.test.js +130 -123
  585. package/dist/src/tools/write-file.test.js.map +1 -1
  586. package/dist/src/tools/write-todos.d.ts +34 -9
  587. package/dist/src/tools/write-todos.js +54 -11
  588. package/dist/src/tools/write-todos.js.map +1 -1
  589. package/dist/src/tools/write-todos.test.js +2 -2
  590. package/dist/src/tools/write-todos.test.js.map +1 -1
  591. package/dist/src/utils/bfsFileSearch.js +3 -2
  592. package/dist/src/utils/bfsFileSearch.js.map +1 -1
  593. package/dist/src/utils/channel.d.ts +19 -0
  594. package/dist/src/utils/channel.js +49 -0
  595. package/dist/src/utils/channel.js.map +1 -0
  596. package/dist/src/utils/channel.test.d.ts +6 -0
  597. package/dist/src/utils/channel.test.js +170 -0
  598. package/dist/src/utils/channel.test.js.map +1 -0
  599. package/dist/src/utils/debugLogger.d.ts +25 -0
  600. package/dist/src/utils/debugLogger.js +33 -0
  601. package/dist/src/utils/debugLogger.js.map +1 -0
  602. package/dist/src/utils/debugLogger.test.d.ts +6 -0
  603. package/dist/src/utils/debugLogger.test.js +69 -0
  604. package/dist/src/utils/debugLogger.test.js.map +1 -0
  605. package/dist/src/utils/delay.d.ts +16 -0
  606. package/dist/src/utils/delay.js +43 -0
  607. package/dist/src/utils/delay.js.map +1 -0
  608. package/dist/src/utils/delay.test.d.ts +6 -0
  609. package/dist/src/utils/delay.test.js +88 -0
  610. package/dist/src/utils/delay.test.js.map +1 -0
  611. package/dist/src/utils/editCorrector.js +10 -25
  612. package/dist/src/utils/editCorrector.js.map +1 -1
  613. package/dist/src/utils/editCorrector.test.js +19 -5
  614. package/dist/src/utils/editCorrector.test.js.map +1 -1
  615. package/dist/src/utils/editor.d.ts +4 -2
  616. package/dist/src/utils/editor.js +53 -39
  617. package/dist/src/utils/editor.js.map +1 -1
  618. package/dist/src/utils/editor.test.js +18 -45
  619. package/dist/src/utils/editor.test.js.map +1 -1
  620. package/dist/src/utils/environmentContext.d.ts +2 -1
  621. package/dist/src/utils/environmentContext.js +20 -33
  622. package/dist/src/utils/environmentContext.js.map +1 -1
  623. package/dist/src/utils/environmentContext.test.js +6 -34
  624. package/dist/src/utils/environmentContext.test.js.map +1 -1
  625. package/dist/src/utils/errorParsing.d.ts +1 -1
  626. package/dist/src/utils/errorParsing.js +5 -33
  627. package/dist/src/utils/errorParsing.js.map +1 -1
  628. package/dist/src/utils/errorParsing.test.js +0 -88
  629. package/dist/src/utils/errorParsing.test.js.map +1 -1
  630. package/dist/src/utils/errors.d.ts +3 -0
  631. package/dist/src/utils/errors.js +6 -0
  632. package/dist/src/utils/errors.js.map +1 -1
  633. package/dist/src/utils/events.d.ts +121 -0
  634. package/dist/src/utils/events.js +84 -0
  635. package/dist/src/utils/events.js.map +1 -0
  636. package/dist/src/utils/events.test.d.ts +6 -0
  637. package/dist/src/utils/events.test.js +212 -0
  638. package/dist/src/utils/events.test.js.map +1 -0
  639. package/dist/src/utils/extensionLoader.d.ts +86 -0
  640. package/dist/src/utils/extensionLoader.js +208 -0
  641. package/dist/src/utils/extensionLoader.js.map +1 -0
  642. package/dist/src/utils/extensionLoader.test.d.ts +6 -0
  643. package/dist/src/utils/extensionLoader.test.js +154 -0
  644. package/dist/src/utils/extensionLoader.test.js.map +1 -0
  645. package/dist/src/utils/fetch.d.ts +1 -0
  646. package/dist/src/utils/fetch.js +4 -0
  647. package/dist/src/utils/fetch.js.map +1 -1
  648. package/dist/src/utils/fileUtils.d.ts +4 -0
  649. package/dist/src/utils/fileUtils.js +34 -2
  650. package/dist/src/utils/fileUtils.js.map +1 -1
  651. package/dist/src/utils/fileUtils.test.js +87 -61
  652. package/dist/src/utils/fileUtils.test.js.map +1 -1
  653. package/dist/src/utils/filesearch/fileSearch.js +1 -1
  654. package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
  655. package/dist/src/utils/flashFallback.test.js +28 -47
  656. package/dist/src/utils/flashFallback.test.js.map +1 -1
  657. package/dist/src/utils/formatters.d.ts +1 -0
  658. package/dist/src/utils/formatters.js +2 -1
  659. package/dist/src/utils/formatters.js.map +1 -1
  660. package/dist/src/utils/formatters.test.d.ts +6 -0
  661. package/dist/src/utils/formatters.test.js +26 -0
  662. package/dist/src/utils/formatters.test.js.map +1 -0
  663. package/dist/src/utils/getFolderStructure.js +9 -17
  664. package/dist/src/utils/getFolderStructure.js.map +1 -1
  665. package/dist/src/utils/getFolderStructure.test.js +7 -6
  666. package/dist/src/utils/getFolderStructure.test.js.map +1 -1
  667. package/dist/src/utils/gitIgnoreParser.d.ts +4 -1
  668. package/dist/src/utils/gitIgnoreParser.js +28 -10
  669. package/dist/src/utils/gitIgnoreParser.js.map +1 -1
  670. package/dist/src/utils/gitIgnoreParser.test.js +58 -0
  671. package/dist/src/utils/gitIgnoreParser.test.js.map +1 -1
  672. package/dist/src/utils/googleErrors.d.ts +104 -0
  673. package/dist/src/utils/googleErrors.js +152 -0
  674. package/dist/src/utils/googleErrors.js.map +1 -0
  675. package/dist/src/utils/googleErrors.test.d.ts +6 -0
  676. package/dist/src/utils/googleErrors.test.js +301 -0
  677. package/dist/src/utils/googleErrors.test.js.map +1 -0
  678. package/dist/src/utils/googleQuotaErrors.d.ts +37 -0
  679. package/dist/src/utils/googleQuotaErrors.js +157 -0
  680. package/dist/src/utils/googleQuotaErrors.js.map +1 -0
  681. package/dist/src/utils/googleQuotaErrors.test.d.ts +6 -0
  682. package/dist/src/utils/googleQuotaErrors.test.js +311 -0
  683. package/dist/src/utils/googleQuotaErrors.test.js.map +1 -0
  684. package/dist/src/utils/httpErrors.d.ts +18 -0
  685. package/dist/src/utils/httpErrors.js +36 -0
  686. package/dist/src/utils/httpErrors.js.map +1 -0
  687. package/dist/src/utils/ignorePatterns.test.js +26 -30
  688. package/dist/src/utils/ignorePatterns.test.js.map +1 -1
  689. package/dist/src/utils/installationManager.js +2 -1
  690. package/dist/src/utils/installationManager.js.map +1 -1
  691. package/dist/src/utils/installationManager.test.js +6 -4
  692. package/dist/src/utils/installationManager.test.js.map +1 -1
  693. package/dist/src/utils/llm-edit-fixer.d.ts +1 -1
  694. package/dist/src/utils/llm-edit-fixer.js +33 -9
  695. package/dist/src/utils/llm-edit-fixer.js.map +1 -1
  696. package/dist/src/utils/llm-edit-fixer.test.js +38 -1
  697. package/dist/src/utils/llm-edit-fixer.test.js.map +1 -1
  698. package/dist/src/utils/memoryDiscovery.d.ts +20 -1
  699. package/dist/src/utils/memoryDiscovery.js +176 -12
  700. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  701. package/dist/src/utils/memoryDiscovery.test.js +299 -40
  702. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  703. package/dist/src/utils/memoryImportProcessor.js +4 -3
  704. package/dist/src/utils/memoryImportProcessor.js.map +1 -1
  705. package/dist/src/utils/memoryImportProcessor.test.js +8 -14
  706. package/dist/src/utils/memoryImportProcessor.test.js.map +1 -1
  707. package/dist/src/utils/nextSpeakerChecker.js +3 -3
  708. package/dist/src/utils/nextSpeakerChecker.js.map +1 -1
  709. package/dist/src/utils/nextSpeakerChecker.test.js +13 -5
  710. package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
  711. package/dist/src/utils/package.d.ts +12 -0
  712. package/dist/src/utils/package.js +15 -0
  713. package/dist/src/utils/package.js.map +1 -0
  714. package/dist/src/utils/pathCorrector.d.ts +25 -0
  715. package/dist/src/utils/pathCorrector.js +33 -0
  716. package/dist/src/utils/pathCorrector.js.map +1 -0
  717. package/dist/src/utils/pathCorrector.test.d.ts +6 -0
  718. package/dist/src/utils/pathCorrector.test.js +83 -0
  719. package/dist/src/utils/pathCorrector.test.js.map +1 -0
  720. package/dist/src/utils/pathReader.js +4 -4
  721. package/dist/src/utils/pathReader.js.map +1 -1
  722. package/dist/src/utils/pathReader.test.js +44 -1
  723. package/dist/src/utils/pathReader.test.js.map +1 -1
  724. package/dist/src/utils/paths.d.ts +1 -1
  725. package/dist/src/utils/paths.js +131 -29
  726. package/dist/src/utils/paths.js.map +1 -1
  727. package/dist/src/utils/paths.test.js +200 -68
  728. package/dist/src/utils/paths.test.js.map +1 -1
  729. package/dist/src/utils/quotaErrorDetection.d.ts +0 -2
  730. package/dist/src/utils/quotaErrorDetection.js +0 -46
  731. package/dist/src/utils/quotaErrorDetection.js.map +1 -1
  732. package/dist/src/utils/retry.d.ts +3 -10
  733. package/dist/src/utils/retry.js +97 -195
  734. package/dist/src/utils/retry.js.map +1 -1
  735. package/dist/src/utils/retry.test.js +179 -145
  736. package/dist/src/utils/retry.test.js.map +1 -1
  737. package/dist/src/utils/safeJsonStringify.d.ts +4 -4
  738. package/dist/src/utils/safeJsonStringify.js +31 -7
  739. package/dist/src/utils/safeJsonStringify.js.map +1 -1
  740. package/dist/src/utils/shell-utils.d.ts +15 -2
  741. package/dist/src/utils/shell-utils.js +387 -140
  742. package/dist/src/utils/shell-utils.js.map +1 -1
  743. package/dist/src/utils/shell-utils.test.js +244 -62
  744. package/dist/src/utils/shell-utils.test.js.map +1 -1
  745. package/dist/src/utils/stdio.d.ts +32 -0
  746. package/dist/src/utils/stdio.js +85 -0
  747. package/dist/src/utils/stdio.js.map +1 -0
  748. package/dist/src/utils/stdio.test.d.ts +6 -0
  749. package/dist/src/utils/stdio.test.js +47 -0
  750. package/dist/src/utils/stdio.test.js.map +1 -0
  751. package/dist/src/utils/summarizer.d.ts +4 -2
  752. package/dist/src/utils/summarizer.js +8 -9
  753. package/dist/src/utils/summarizer.js.map +1 -1
  754. package/dist/src/utils/summarizer.test.js +32 -12
  755. package/dist/src/utils/summarizer.test.js.map +1 -1
  756. package/dist/src/utils/systemEncoding.js +5 -4
  757. package/dist/src/utils/systemEncoding.js.map +1 -1
  758. package/dist/src/utils/systemEncoding.test.js +2 -1
  759. package/dist/src/utils/systemEncoding.test.js.map +1 -1
  760. package/dist/src/utils/terminal.d.ts +14 -0
  761. package/dist/src/utils/terminal.js +38 -0
  762. package/dist/src/utils/terminal.js.map +1 -0
  763. package/dist/src/utils/tool-utils.d.ts +2 -2
  764. package/dist/src/utils/tool-utils.js +15 -6
  765. package/dist/src/utils/tool-utils.js.map +1 -1
  766. package/dist/src/utils/tool-utils.test.js +8 -0
  767. package/dist/src/utils/tool-utils.test.js.map +1 -1
  768. package/dist/src/utils/userAccountManager.js +5 -4
  769. package/dist/src/utils/userAccountManager.js.map +1 -1
  770. package/dist/src/utils/userAccountManager.test.js +9 -7
  771. package/dist/src/utils/userAccountManager.test.js.map +1 -1
  772. package/dist/src/utils/workspaceContext.d.ts +4 -3
  773. package/dist/src/utils/workspaceContext.js +13 -13
  774. package/dist/src/utils/workspaceContext.js.map +1 -1
  775. package/dist/src/utils/workspaceContext.test.js +8 -7
  776. package/dist/src/utils/workspaceContext.test.js.map +1 -1
  777. package/dist/tsconfig.tsbuildinfo +1 -1
  778. package/package.json +12 -7
@@ -4,18 +4,39 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
7
+ import { debugLogger } from '../utils/debugLogger.js';
7
8
  import { AgentExecutor } from './executor.js';
8
- import { AgentTerminateMode } from './types.js';
9
9
  import { makeFakeConfig } from '../test-utils/config.js';
10
10
  import { ToolRegistry } from '../tools/tool-registry.js';
11
11
  import { LSTool } from '../tools/ls.js';
12
- import { ReadFileTool } from '../tools/read-file.js';
12
+ import { LS_TOOL_NAME, READ_FILE_TOOL_NAME } from '../tools/tool-names.js';
13
13
  import { GeminiChat, StreamEventType, } from '../core/geminiChat.js';
14
+ import {} from '@google/genai';
14
15
  import { MockTool } from '../test-utils/mock-tool.js';
15
16
  import { getDirectoryContextString } from '../utils/environmentContext.js';
16
- const { mockSendMessageStream, mockExecuteToolCall } = vi.hoisted(() => ({
17
+ import { z } from 'zod';
18
+ import { promptIdContext } from '../utils/promptIdContext.js';
19
+ import { logAgentStart, logAgentFinish, logRecoveryAttempt, } from '../telemetry/loggers.js';
20
+ import { AgentStartEvent, AgentFinishEvent, RecoveryAttemptEvent, } from '../telemetry/types.js';
21
+ import { AgentTerminateMode } from './types.js';
22
+ import { CompressionStatus } from '../core/turn.js';
23
+ import { ChatCompressionService } from '../services/chatCompressionService.js';
24
+ import { getModelConfigAlias } from './registry.js';
25
+ const { mockSendMessageStream, mockExecuteToolCall, mockSetSystemInstruction, mockCompress, mockSetTools, } = vi.hoisted(() => ({
17
26
  mockSendMessageStream: vi.fn(),
18
27
  mockExecuteToolCall: vi.fn(),
28
+ mockSetSystemInstruction: vi.fn(),
29
+ mockCompress: vi.fn(),
30
+ mockSetTools: vi.fn(),
31
+ }));
32
+ let mockChatHistory = [];
33
+ const mockSetHistory = vi.fn((newHistory) => {
34
+ mockChatHistory = newHistory;
35
+ });
36
+ vi.mock('../services/chatCompressionService.js', () => ({
37
+ ChatCompressionService: vi.fn().mockImplementation(() => ({
38
+ compress: mockCompress,
39
+ })),
19
40
  }));
20
41
  vi.mock('../core/geminiChat.js', async (importOriginal) => {
21
42
  const actual = await importOriginal();
@@ -23,6 +44,10 @@ vi.mock('../core/geminiChat.js', async (importOriginal) => {
23
44
  ...actual,
24
45
  GeminiChat: vi.fn().mockImplementation(() => ({
25
46
  sendMessageStream: mockSendMessageStream,
47
+ getHistory: vi.fn((_curated) => [...mockChatHistory]),
48
+ setHistory: mockSetHistory,
49
+ setSystemInstruction: mockSetSystemInstruction,
50
+ setTools: mockSetTools,
26
51
  })),
27
52
  };
28
53
  });
@@ -30,13 +55,42 @@ vi.mock('../core/nonInteractiveToolExecutor.js', () => ({
30
55
  executeToolCall: mockExecuteToolCall,
31
56
  }));
32
57
  vi.mock('../utils/environmentContext.js');
33
- const MockedGeminiChat = GeminiChat;
34
- // A mock tool that is NOT on the NON_INTERACTIVE_TOOL_ALLOWLIST
35
- const MOCK_TOOL_NOT_ALLOWED = new MockTool({ name: 'write_file' });
58
+ vi.mock('../telemetry/loggers.js', () => ({
59
+ logAgentStart: vi.fn(),
60
+ logAgentFinish: vi.fn(),
61
+ logRecoveryAttempt: vi.fn(),
62
+ }));
63
+ vi.mock('../utils/promptIdContext.js', async (importOriginal) => {
64
+ const actual = await importOriginal();
65
+ return {
66
+ ...actual,
67
+ promptIdContext: {
68
+ ...actual.promptIdContext,
69
+ getStore: vi.fn(),
70
+ run: vi.fn((_id, fn) => fn()),
71
+ },
72
+ };
73
+ });
74
+ const MockedGeminiChat = vi.mocked(GeminiChat);
75
+ const mockedGetDirectoryContextString = vi.mocked(getDirectoryContextString);
76
+ const mockedPromptIdContext = vi.mocked(promptIdContext);
77
+ const mockedLogAgentStart = vi.mocked(logAgentStart);
78
+ const mockedLogAgentFinish = vi.mocked(logAgentFinish);
79
+ const mockedLogRecoveryAttempt = vi.mocked(logRecoveryAttempt);
80
+ // Constants for testing
81
+ const TASK_COMPLETE_TOOL_NAME = 'complete_task';
82
+ const MOCK_TOOL_NOT_ALLOWED = new MockTool({ name: 'write_file_interactive' });
83
+ /**
84
+ * Helper to create a mock API response chunk.
85
+ * Uses conditional spread to handle readonly functionCalls property safely.
86
+ */
36
87
  const createMockResponseChunk = (parts, functionCalls) => ({
37
88
  candidates: [{ index: 0, content: { role: 'model', parts } }],
38
- functionCalls,
89
+ ...(functionCalls && functionCalls.length > 0 ? { functionCalls } : {}),
39
90
  });
91
+ /**
92
+ * Helper to mock a single turn of model response in the stream.
93
+ */
40
94
  const mockModelResponse = (functionCalls, thought, text) => {
41
95
  const parts = [];
42
96
  if (thought) {
@@ -47,9 +101,7 @@ const mockModelResponse = (functionCalls, thought, text) => {
47
101
  }
48
102
  if (text)
49
103
  parts.push({ text });
50
- const responseChunk = createMockResponseChunk(parts,
51
- // Ensure functionCalls is undefined if the array is empty, matching API behavior
52
- functionCalls.length > 0 ? functionCalls : undefined);
104
+ const responseChunk = createMockResponseChunk(parts, functionCalls);
53
105
  mockSendMessageStream.mockImplementationOnce(async () => (async function* () {
54
106
  yield {
55
107
  type: StreamEventType.CHUNK,
@@ -57,38 +109,85 @@ const mockModelResponse = (functionCalls, thought, text) => {
57
109
  };
58
110
  })());
59
111
  };
112
+ /**
113
+ * Helper to extract the message parameters sent to sendMessageStream.
114
+ * Provides type safety for inspecting mock calls.
115
+ */
116
+ const getMockMessageParams = (callIndex) => {
117
+ const call = mockSendMessageStream.mock.calls[callIndex];
118
+ expect(call).toBeDefined();
119
+ return {
120
+ modelConfigKey: call[0],
121
+ message: call[1],
122
+ };
123
+ };
60
124
  let mockConfig;
61
125
  let parentToolRegistry;
62
- const createTestDefinition = (tools = [LSTool.Name], runConfigOverrides = {}, outputConfigOverrides = {}) => ({
63
- name: 'TestAgent',
64
- description: 'An agent for testing.',
65
- inputConfig: {
66
- inputs: { goal: { type: 'string', required: true, description: 'goal' } },
67
- },
68
- modelConfig: { model: 'gemini-test-model', temp: 0, top_p: 1 },
69
- runConfig: { max_time_minutes: 5, max_turns: 5, ...runConfigOverrides },
70
- promptConfig: { systemPrompt: 'Achieve the goal: ${goal}.' },
71
- toolConfig: { tools },
72
- outputConfig: { description: 'The final result.', ...outputConfigOverrides },
73
- });
126
+ /**
127
+ * Type-safe helper to create agent definitions for tests.
128
+ */
129
+ const createTestDefinition = (tools = [LS_TOOL_NAME], runConfigOverrides = {}, outputConfigMode = 'default', schema = z.string()) => {
130
+ let outputConfig;
131
+ if (outputConfigMode === 'default') {
132
+ outputConfig = {
133
+ outputName: 'finalResult',
134
+ description: 'The final result.',
135
+ schema,
136
+ };
137
+ }
138
+ return {
139
+ name: 'TestAgent',
140
+ description: 'An agent for testing.',
141
+ inputConfig: {
142
+ inputs: { goal: { type: 'string', required: true, description: 'goal' } },
143
+ },
144
+ modelConfig: { model: 'gemini-test-model', temp: 0, top_p: 1 },
145
+ runConfig: { max_time_minutes: 5, max_turns: 5, ...runConfigOverrides },
146
+ promptConfig: { systemPrompt: 'Achieve the goal: ${goal}.' },
147
+ toolConfig: { tools },
148
+ outputConfig,
149
+ };
150
+ };
74
151
  describe('AgentExecutor', () => {
75
152
  let activities;
76
153
  let onActivity;
77
154
  let abortController;
78
155
  let signal;
79
156
  beforeEach(async () => {
80
- mockSendMessageStream.mockClear();
81
- mockExecuteToolCall.mockClear();
82
- vi.clearAllMocks();
83
- // Use fake timers for timeout and concurrency testing
157
+ vi.resetAllMocks();
158
+ mockCompress.mockClear();
159
+ mockSetHistory.mockClear();
160
+ mockSendMessageStream.mockReset();
161
+ mockSetSystemInstruction.mockReset();
162
+ mockSetTools.mockReset();
163
+ mockExecuteToolCall.mockReset();
164
+ mockedLogAgentStart.mockReset();
165
+ mockedLogAgentFinish.mockReset();
166
+ mockedPromptIdContext.getStore.mockReset();
167
+ mockedPromptIdContext.run.mockImplementation((_id, fn) => fn());
168
+ ChatCompressionService.mockImplementation(() => ({
169
+ compress: mockCompress,
170
+ }));
171
+ mockCompress.mockResolvedValue({
172
+ newHistory: null,
173
+ info: { compressionStatus: CompressionStatus.NOOP },
174
+ });
175
+ MockedGeminiChat.mockImplementation(() => ({
176
+ sendMessageStream: mockSendMessageStream,
177
+ setSystemInstruction: mockSetSystemInstruction,
178
+ setTools: mockSetTools,
179
+ getHistory: vi.fn((_curated) => [...mockChatHistory]),
180
+ getLastPromptTokenCount: vi.fn(() => 100),
181
+ setHistory: mockSetHistory,
182
+ }));
84
183
  vi.useFakeTimers();
85
184
  mockConfig = makeFakeConfig();
86
185
  parentToolRegistry = new ToolRegistry(mockConfig);
87
186
  parentToolRegistry.registerTool(new LSTool(mockConfig));
88
- parentToolRegistry.registerTool(new ReadFileTool(mockConfig));
187
+ parentToolRegistry.registerTool(new MockTool({ name: READ_FILE_TOOL_NAME }));
89
188
  parentToolRegistry.registerTool(MOCK_TOOL_NOT_ALLOWED);
90
189
  vi.spyOn(mockConfig, 'getToolRegistry').mockResolvedValue(parentToolRegistry);
91
- vi.mocked(getDirectoryContextString).mockResolvedValue('Mocked Environment Context');
190
+ mockedGetDirectoryContextString.mockResolvedValue('Mocked Environment Context');
92
191
  activities = [];
93
192
  onActivity = (activity) => activities.push(activity);
94
193
  abortController = new AbortController();
@@ -99,321 +198,1165 @@ describe('AgentExecutor', () => {
99
198
  });
100
199
  describe('create (Initialization and Validation)', () => {
101
200
  it('should create successfully with allowed tools', async () => {
102
- const definition = createTestDefinition([LSTool.Name]);
201
+ const definition = createTestDefinition([LS_TOOL_NAME]);
103
202
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
104
203
  expect(executor).toBeInstanceOf(AgentExecutor);
105
204
  });
106
205
  it('SECURITY: should throw if a tool is not on the non-interactive allowlist', async () => {
107
206
  const definition = createTestDefinition([MOCK_TOOL_NOT_ALLOWED.name]);
108
- await expect(AgentExecutor.create(definition, mockConfig, onActivity)).rejects.toThrow(`Tool "${MOCK_TOOL_NOT_ALLOWED.name}" is not on the allow-list for non-interactive execution`);
207
+ await expect(AgentExecutor.create(definition, mockConfig, onActivity)).rejects.toThrow(/not on the allow-list for non-interactive execution/);
109
208
  });
110
209
  it('should create an isolated ToolRegistry for the agent', async () => {
111
- const definition = createTestDefinition([LSTool.Name, ReadFileTool.Name]);
210
+ const definition = createTestDefinition([
211
+ LS_TOOL_NAME,
212
+ READ_FILE_TOOL_NAME,
213
+ ]);
112
214
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
113
- // @ts-expect-error - accessing private property for test validation
114
- const agentRegistry = executor.toolRegistry;
215
+ const agentRegistry = executor['toolRegistry'];
115
216
  expect(agentRegistry).not.toBe(parentToolRegistry);
116
- expect(agentRegistry.getAllToolNames()).toEqual(expect.arrayContaining([LSTool.Name, ReadFileTool.Name]));
217
+ expect(agentRegistry.getAllToolNames()).toEqual(expect.arrayContaining([LS_TOOL_NAME, READ_FILE_TOOL_NAME]));
117
218
  expect(agentRegistry.getAllToolNames()).toHaveLength(2);
118
219
  expect(agentRegistry.getTool(MOCK_TOOL_NOT_ALLOWED.name)).toBeUndefined();
119
220
  });
221
+ it('should use parentPromptId from context to create agentId', async () => {
222
+ const parentId = 'parent-id';
223
+ mockedPromptIdContext.getStore.mockReturnValue(parentId);
224
+ const definition = createTestDefinition();
225
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
226
+ expect(executor['agentId']).toMatch(new RegExp(`^${parentId}-${definition.name}-`));
227
+ });
228
+ it('should correctly apply templates to initialMessages', async () => {
229
+ const definition = createTestDefinition();
230
+ // Override promptConfig to use initialMessages instead of systemPrompt
231
+ definition.promptConfig = {
232
+ initialMessages: [
233
+ { role: 'user', parts: [{ text: 'Goal: ${goal}' }] },
234
+ { role: 'model', parts: [{ text: 'OK, starting on ${goal}.' }] },
235
+ ],
236
+ };
237
+ const inputs = { goal: 'TestGoal' };
238
+ // Mock a response to prevent the loop from running forever
239
+ mockModelResponse([
240
+ {
241
+ name: TASK_COMPLETE_TOOL_NAME,
242
+ args: { finalResult: 'done' },
243
+ id: 'call1',
244
+ },
245
+ ]);
246
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
247
+ await executor.run(inputs, signal);
248
+ const chatConstructorArgs = MockedGeminiChat.mock.calls[0];
249
+ const startHistory = chatConstructorArgs[3]; // history is the 4th arg
250
+ expect(startHistory).toBeDefined();
251
+ expect(startHistory).toHaveLength(2);
252
+ // Perform checks on defined objects to satisfy TS
253
+ const firstPart = startHistory?.[0]?.parts?.[0];
254
+ expect(firstPart?.text).toBe('Goal: TestGoal');
255
+ const secondPart = startHistory?.[1]?.parts?.[0];
256
+ expect(secondPart?.text).toBe('OK, starting on TestGoal.');
257
+ });
120
258
  });
121
259
  describe('run (Execution Loop and Logic)', () => {
122
- it('should execute a successful work and extraction phase (Happy Path) and emit activities', async () => {
260
+ it('should log AgentFinish with error if run throws', async () => {
261
+ const definition = createTestDefinition();
262
+ // Make the definition invalid to cause an error during run
263
+ definition.inputConfig.inputs = {
264
+ goal: { type: 'string', required: true, description: 'goal' },
265
+ };
266
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
267
+ // Run without inputs to trigger validation error
268
+ await expect(executor.run({}, signal)).rejects.toThrow(/Missing required input parameters/);
269
+ expect(mockedLogAgentStart).toHaveBeenCalledTimes(1);
270
+ expect(mockedLogAgentFinish).toHaveBeenCalledTimes(1);
271
+ expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
272
+ terminate_reason: AgentTerminateMode.ERROR,
273
+ }));
274
+ });
275
+ it('should execute successfully when model calls complete_task with output (Happy Path with Output)', async () => {
123
276
  const definition = createTestDefinition();
124
277
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
125
278
  const inputs = { goal: 'Find files' };
126
279
  // Turn 1: Model calls ls
127
- mockModelResponse([{ name: LSTool.Name, args: { path: '.' }, id: 'call1' }], 'T1: Listing');
280
+ mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' }], 'T1: Listing');
128
281
  mockExecuteToolCall.mockResolvedValueOnce({
129
- callId: 'call1',
130
- resultDisplay: 'file1.txt',
131
- responseParts: [
132
- {
133
- functionResponse: {
134
- name: LSTool.Name,
135
- response: { result: 'file1.txt' },
136
- id: 'call1',
282
+ status: 'success',
283
+ request: {
284
+ callId: 'call1',
285
+ name: LS_TOOL_NAME,
286
+ args: { path: '.' },
287
+ isClientInitiated: false,
288
+ prompt_id: 'test-prompt',
289
+ },
290
+ tool: {},
291
+ invocation: {},
292
+ response: {
293
+ callId: 'call1',
294
+ resultDisplay: 'file1.txt',
295
+ responseParts: [
296
+ {
297
+ functionResponse: {
298
+ name: LS_TOOL_NAME,
299
+ response: { result: 'file1.txt' },
300
+ id: 'call1',
301
+ },
137
302
  },
138
- },
139
- ],
140
- error: undefined,
303
+ ],
304
+ error: undefined,
305
+ errorType: undefined,
306
+ contentLength: undefined,
307
+ },
141
308
  });
142
- // Turn 2: Model stops
143
- mockModelResponse([], 'T2: Done');
144
- // Extraction Phase
145
- mockModelResponse([], undefined, 'Result: file1.txt.');
309
+ // Turn 2: Model calls complete_task with required output
310
+ mockModelResponse([
311
+ {
312
+ name: TASK_COMPLETE_TOOL_NAME,
313
+ args: { finalResult: 'Found file1.txt' },
314
+ id: 'call2',
315
+ },
316
+ ], 'T2: Done');
146
317
  const output = await executor.run(inputs, signal);
147
- expect(mockSendMessageStream).toHaveBeenCalledTimes(3);
148
- expect(mockExecuteToolCall).toHaveBeenCalledTimes(1);
149
- // Verify System Prompt Templating
150
- const chatConstructorArgs = MockedGeminiChat.mock.calls[0];
151
- const chatConfig = chatConstructorArgs[1];
152
- expect(chatConfig?.systemInstruction).toContain('Achieve the goal: Find files.');
153
- // Verify environment context is appended
154
- expect(chatConfig?.systemInstruction).toContain('# Environment Context\nMocked Environment Context');
155
- // Verify standard rules are appended
156
- expect(chatConfig?.systemInstruction).toContain('You are running in a non-interactive mode.');
157
- // Verify absolute path rule is appended
158
- expect(chatConfig?.systemInstruction).toContain('Always use absolute paths for file operations.');
159
- // Verify Extraction Phase Call (Specific arguments)
160
- expect(mockSendMessageStream).toHaveBeenCalledWith('gemini-test-model', expect.objectContaining({
161
- // Extraction message should be based on outputConfig.description
162
- message: expect.arrayContaining([
163
- {
164
- text: expect.stringContaining('Based on your work so far, provide: The final result.'),
165
- },
166
- ]),
167
- config: expect.objectContaining({ tools: undefined }), // No tools in extraction
168
- }), expect.stringContaining('#extraction'));
169
- expect(output.result).toBe('Result: file1.txt.');
318
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
319
+ const systemInstruction = MockedGeminiChat.mock.calls[0][1];
320
+ expect(systemInstruction).toContain(`MUST call the \`${TASK_COMPLETE_TOOL_NAME}\` tool`);
321
+ expect(systemInstruction).toContain('Mocked Environment Context');
322
+ expect(systemInstruction).toContain('You are running in a non-interactive mode');
323
+ expect(systemInstruction).toContain('Always use absolute paths');
324
+ const { modelConfigKey } = getMockMessageParams(0);
325
+ expect(modelConfigKey.model).toBe(getModelConfigAlias(definition));
326
+ const call = mockSetTools.mock.calls[0];
327
+ const sentTools = call[0][0].functionDeclarations;
328
+ expect(sentTools).toBeDefined();
329
+ expect(sentTools).toEqual(expect.arrayContaining([
330
+ expect.objectContaining({ name: LS_TOOL_NAME }),
331
+ expect.objectContaining({ name: TASK_COMPLETE_TOOL_NAME }),
332
+ ]));
333
+ const completeToolDef = sentTools.find((t) => t.name === TASK_COMPLETE_TOOL_NAME);
334
+ expect(completeToolDef?.parameters?.required).toContain('finalResult');
335
+ expect(output.result).toBe('Found file1.txt');
170
336
  expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
171
- // Verify Activity Stream (Observability)
337
+ // Telemetry checks
338
+ expect(mockedLogAgentStart).toHaveBeenCalledTimes(1);
339
+ expect(mockedLogAgentStart).toHaveBeenCalledWith(mockConfig, expect.any(AgentStartEvent));
340
+ expect(mockedLogAgentFinish).toHaveBeenCalledTimes(1);
341
+ expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.any(AgentFinishEvent));
342
+ const finishEvent = mockedLogAgentFinish.mock.calls[0][1];
343
+ expect(finishEvent.terminate_reason).toBe(AgentTerminateMode.GOAL);
344
+ // Context checks
345
+ expect(mockedPromptIdContext.run).toHaveBeenCalledTimes(2); // Two turns
346
+ const agentId = executor['agentId'];
347
+ expect(mockedPromptIdContext.run).toHaveBeenNthCalledWith(1, `${agentId}#0`, expect.any(Function));
348
+ expect(mockedPromptIdContext.run).toHaveBeenNthCalledWith(2, `${agentId}#1`, expect.any(Function));
172
349
  expect(activities).toEqual(expect.arrayContaining([
173
- // Thought subjects are extracted by the executor (parseThought)
174
350
  expect.objectContaining({
175
351
  type: 'THOUGHT_CHUNK',
176
352
  data: { text: 'T1: Listing' },
177
353
  }),
178
354
  expect.objectContaining({
179
- type: 'TOOL_CALL_START',
180
- data: { name: LSTool.Name, args: { path: '.' } },
355
+ type: 'TOOL_CALL_END',
356
+ data: { name: LS_TOOL_NAME, output: 'file1.txt' },
181
357
  }),
182
358
  expect.objectContaining({
183
- type: 'TOOL_CALL_END',
184
- data: { name: LSTool.Name, output: 'file1.txt' },
359
+ type: 'TOOL_CALL_START',
360
+ data: {
361
+ name: TASK_COMPLETE_TOOL_NAME,
362
+ args: { finalResult: 'Found file1.txt' },
363
+ },
185
364
  }),
186
365
  expect.objectContaining({
187
- type: 'THOUGHT_CHUNK',
188
- data: { text: 'T2: Done' },
366
+ type: 'TOOL_CALL_END',
367
+ data: {
368
+ name: TASK_COMPLETE_TOOL_NAME,
369
+ output: expect.stringContaining('Output submitted'),
370
+ },
189
371
  }),
190
372
  ]));
191
373
  });
192
- it('should execute parallel tool calls concurrently', async () => {
193
- const definition = createTestDefinition([LSTool.Name, ReadFileTool.Name]);
374
+ it('should execute successfully when model calls complete_task without output (Happy Path No Output)', async () => {
375
+ const definition = createTestDefinition([LS_TOOL_NAME], {}, 'none');
194
376
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
195
- const call1 = {
196
- name: LSTool.Name,
197
- args: { path: '/dir1' },
198
- id: 'call1',
199
- };
200
- // Using LSTool twice for simplicity in mocking standardized responses.
201
- const call2 = {
202
- name: LSTool.Name,
203
- args: { path: '/dir2' },
204
- id: 'call2',
205
- };
206
- // Turn 1: Model calls two tools simultaneously
207
- mockModelResponse([call1, call2], 'T1: Listing both');
208
- // Use concurrency tracking to ensure parallelism
209
- let activeCalls = 0;
210
- let maxActiveCalls = 0;
211
- mockExecuteToolCall.mockImplementation(async (_ctx, reqInfo) => {
212
- activeCalls++;
213
- maxActiveCalls = Math.max(maxActiveCalls, activeCalls);
214
- // Simulate latency. We must advance the fake timers for this to resolve.
215
- await new Promise((resolve) => setTimeout(resolve, 100));
216
- activeCalls--;
217
- return {
218
- callId: reqInfo.callId,
219
- resultDisplay: `Result for ${reqInfo.name}`,
377
+ mockModelResponse([
378
+ { name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' },
379
+ ]);
380
+ mockExecuteToolCall.mockResolvedValueOnce({
381
+ status: 'success',
382
+ request: {
383
+ callId: 'call1',
384
+ name: LS_TOOL_NAME,
385
+ args: { path: '.' },
386
+ isClientInitiated: false,
387
+ prompt_id: 'test-prompt',
388
+ },
389
+ tool: {},
390
+ invocation: {},
391
+ response: {
392
+ callId: 'call1',
393
+ resultDisplay: 'ok',
220
394
  responseParts: [
221
395
  {
222
396
  functionResponse: {
223
- name: reqInfo.name,
397
+ name: LS_TOOL_NAME,
224
398
  response: {},
225
- id: reqInfo.callId,
399
+ id: 'call1',
226
400
  },
227
401
  },
228
402
  ],
229
403
  error: undefined,
230
- };
404
+ errorType: undefined,
405
+ contentLength: undefined,
406
+ },
231
407
  });
232
- // Turn 2: Model stops
233
- mockModelResponse([]);
234
- // Extraction
235
- mockModelResponse([], undefined, 'Done.');
236
- const runPromise = executor.run({ goal: 'Parallel test' }, signal);
237
- // Advance timers while the parallel calls (Promise.all + setTimeout) are running
238
- await vi.advanceTimersByTimeAsync(150);
239
- await runPromise;
240
- expect(mockExecuteToolCall).toHaveBeenCalledTimes(2);
241
- expect(maxActiveCalls).toBe(2);
242
- // Verify the input to the next model call (Turn 2) contains both responses
243
- // sendMessageStream calls: [0] Turn 1, [1] Turn 2, [2] Extraction
244
- const turn2Input = mockSendMessageStream.mock.calls[1][1];
245
- const turn2Parts = turn2Input.message;
246
- // Promise.all preserves the order of the input array.
247
- expect(turn2Parts.length).toBe(2);
248
- expect(turn2Parts[0]).toEqual(expect.objectContaining({
249
- functionResponse: expect.objectContaining({ id: 'call1' }),
250
- }));
251
- expect(turn2Parts[1]).toEqual(expect.objectContaining({
252
- functionResponse: expect.objectContaining({ id: 'call2' }),
253
- }));
408
+ mockModelResponse([{ name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call2' }], 'Task finished.');
409
+ const output = await executor.run({ goal: 'Do work' }, signal);
410
+ const { modelConfigKey } = getMockMessageParams(0);
411
+ expect(modelConfigKey.model).toBe(getModelConfigAlias(definition));
412
+ const call = mockSetTools.mock.calls[0];
413
+ const sentTools = call[0][0].functionDeclarations;
414
+ expect(sentTools).toBeDefined();
415
+ const completeToolDef = sentTools.find((t) => t.name === TASK_COMPLETE_TOOL_NAME);
416
+ expect(completeToolDef?.parameters?.required).toEqual([]);
417
+ expect(completeToolDef?.description).toContain('signal that you have completed');
418
+ expect(output.result).toBe('Task completed successfully.');
419
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
254
420
  });
255
- it('should handle tool execution failure gracefully and report error', async () => {
256
- const definition = createTestDefinition([LSTool.Name]);
421
+ it('should error immediately if the model stops tools without calling complete_task (Protocol Violation)', async () => {
422
+ const definition = createTestDefinition();
257
423
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
258
- // Turn 1: Model calls ls, but it fails
259
424
  mockModelResponse([
260
- { name: LSTool.Name, args: { path: '/invalid' }, id: 'call1' },
425
+ { name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' },
261
426
  ]);
262
- const errorMessage = 'Internal failure.';
263
427
  mockExecuteToolCall.mockResolvedValueOnce({
264
- callId: 'call1',
265
- resultDisplay: `Error: ${errorMessage}`,
266
- responseParts: undefined, // Failed tools might return undefined parts
267
- error: { message: errorMessage },
428
+ status: 'success',
429
+ request: {
430
+ callId: 'call1',
431
+ name: LS_TOOL_NAME,
432
+ args: { path: '.' },
433
+ isClientInitiated: false,
434
+ prompt_id: 'test-prompt',
435
+ },
436
+ tool: {},
437
+ invocation: {},
438
+ response: {
439
+ callId: 'call1',
440
+ resultDisplay: 'ok',
441
+ responseParts: [
442
+ {
443
+ functionResponse: {
444
+ name: LS_TOOL_NAME,
445
+ response: {},
446
+ id: 'call1',
447
+ },
448
+ },
449
+ ],
450
+ error: undefined,
451
+ errorType: undefined,
452
+ contentLength: undefined,
453
+ },
268
454
  });
269
- // Turn 2: Model stops
270
- mockModelResponse([]);
271
- mockModelResponse([], undefined, 'Failed.');
272
- await executor.run({ goal: 'Failure test' }, signal);
273
- // Verify that the error was reported in the activity stream
455
+ // Turn 2 (protocol violation)
456
+ mockModelResponse([], 'I think I am done.');
457
+ // Turn 3 (recovery turn - also fails)
458
+ mockModelResponse([], 'I still give up.');
459
+ const output = await executor.run({ goal: 'Strict test' }, signal);
460
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(3);
461
+ const expectedError = `Agent stopped calling tools but did not call '${TASK_COMPLETE_TOOL_NAME}'.`;
462
+ expect(output.terminate_reason).toBe(AgentTerminateMode.ERROR_NO_COMPLETE_TASK_CALL);
463
+ expect(output.result).toBe(expectedError);
464
+ // Telemetry check for error
465
+ expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
466
+ terminate_reason: AgentTerminateMode.ERROR_NO_COMPLETE_TASK_CALL,
467
+ }));
468
+ expect(activities).toContainEqual(expect.objectContaining({
469
+ type: 'ERROR',
470
+ data: expect.objectContaining({
471
+ context: 'protocol_violation',
472
+ error: expectedError,
473
+ }),
474
+ }));
475
+ });
476
+ it('should report an error if complete_task is called with missing required arguments', async () => {
477
+ const definition = createTestDefinition();
478
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
479
+ // Turn 1: Missing arg
480
+ mockModelResponse([
481
+ {
482
+ name: TASK_COMPLETE_TOOL_NAME,
483
+ args: { wrongArg: 'oops' },
484
+ id: 'call1',
485
+ },
486
+ ]);
487
+ // Turn 2: Corrected
488
+ mockModelResponse([
489
+ {
490
+ name: TASK_COMPLETE_TOOL_NAME,
491
+ args: { finalResult: 'Corrected result' },
492
+ id: 'call2',
493
+ },
494
+ ]);
495
+ const output = await executor.run({ goal: 'Error test' }, signal);
496
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
497
+ const expectedError = "Missing required argument 'finalResult' for completion.";
274
498
  expect(activities).toContainEqual(expect.objectContaining({
275
499
  type: 'ERROR',
276
500
  data: {
277
- error: errorMessage,
278
501
  context: 'tool_call',
279
- name: LSTool.Name,
502
+ name: TASK_COMPLETE_TOOL_NAME,
503
+ error: expectedError,
280
504
  },
281
505
  }));
282
- // Verify the input to the next model call (Turn 2) contains the fallback error message
283
- const turn2Input = mockSendMessageStream.mock.calls[1][1];
284
- const turn2Parts = turn2Input.message;
285
- expect(turn2Parts).toEqual([
506
+ const turn2Params = getMockMessageParams(1);
507
+ const turn2Parts = turn2Params.message;
508
+ expect(turn2Parts).toBeDefined();
509
+ expect(turn2Parts).toHaveLength(1);
510
+ expect(turn2Parts[0]).toEqual(expect.objectContaining({
511
+ functionResponse: expect.objectContaining({
512
+ name: TASK_COMPLETE_TOOL_NAME,
513
+ response: { error: expectedError },
514
+ id: 'call1',
515
+ }),
516
+ }));
517
+ expect(output.result).toBe('Corrected result');
518
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
519
+ });
520
+ it('should handle multiple calls to complete_task in the same turn (accept first, block rest)', async () => {
521
+ const definition = createTestDefinition([], {}, 'none');
522
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
523
+ // Turn 1: Duplicate calls
524
+ mockModelResponse([
525
+ { name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call1' },
526
+ { name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call2' },
527
+ ]);
528
+ const output = await executor.run({ goal: 'Dup test' }, signal);
529
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
530
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
531
+ const completions = activities.filter((a) => a.type === 'TOOL_CALL_END' &&
532
+ a.data['name'] === TASK_COMPLETE_TOOL_NAME);
533
+ const errors = activities.filter((a) => a.type === 'ERROR' && a.data['name'] === TASK_COMPLETE_TOOL_NAME);
534
+ expect(completions).toHaveLength(1);
535
+ expect(errors).toHaveLength(1);
536
+ expect(errors[0].data['error']).toContain('Task already marked complete in this turn');
537
+ });
538
+ it('should execute parallel tool calls and then complete', async () => {
539
+ const definition = createTestDefinition([LS_TOOL_NAME]);
540
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
541
+ const call1 = {
542
+ name: LS_TOOL_NAME,
543
+ args: { path: '/a' },
544
+ id: 'c1',
545
+ };
546
+ const call2 = {
547
+ name: LS_TOOL_NAME,
548
+ args: { path: '/b' },
549
+ id: 'c2',
550
+ };
551
+ // Turn 1: Parallel calls
552
+ mockModelResponse([call1, call2]);
553
+ // Concurrency mock
554
+ let callsStarted = 0;
555
+ let resolveCalls;
556
+ const bothStarted = new Promise((r) => {
557
+ resolveCalls = r;
558
+ });
559
+ mockExecuteToolCall.mockImplementation(async (_ctx, reqInfo) => {
560
+ callsStarted++;
561
+ if (callsStarted === 2)
562
+ resolveCalls();
563
+ await vi.advanceTimersByTimeAsync(100);
564
+ return {
565
+ status: 'success',
566
+ request: reqInfo,
567
+ tool: {},
568
+ invocation: {},
569
+ response: {
570
+ callId: reqInfo.callId,
571
+ resultDisplay: 'ok',
572
+ responseParts: [
573
+ {
574
+ functionResponse: {
575
+ name: reqInfo.name,
576
+ response: {},
577
+ id: reqInfo.callId,
578
+ },
579
+ },
580
+ ],
581
+ error: undefined,
582
+ errorType: undefined,
583
+ contentLength: undefined,
584
+ },
585
+ };
586
+ });
587
+ // Turn 2: Completion
588
+ mockModelResponse([
286
589
  {
287
- text: 'All tool calls failed. Please analyze the errors and try an alternative approach.',
590
+ name: TASK_COMPLETE_TOOL_NAME,
591
+ args: { finalResult: 'done' },
592
+ id: 'c3',
288
593
  },
289
594
  ]);
595
+ const runPromise = executor.run({ goal: 'Parallel' }, signal);
596
+ await vi.advanceTimersByTimeAsync(1);
597
+ await bothStarted;
598
+ await vi.advanceTimersByTimeAsync(150);
599
+ await vi.advanceTimersByTimeAsync(1);
600
+ const output = await runPromise;
601
+ expect(mockExecuteToolCall).toHaveBeenCalledTimes(2);
602
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
603
+ // Safe access to message parts
604
+ const turn2Params = getMockMessageParams(1);
605
+ const parts = turn2Params.message;
606
+ expect(parts).toBeDefined();
607
+ expect(parts).toHaveLength(2);
608
+ expect(parts).toEqual(expect.arrayContaining([
609
+ expect.objectContaining({
610
+ functionResponse: expect.objectContaining({ id: 'c1' }),
611
+ }),
612
+ expect.objectContaining({
613
+ functionResponse: expect.objectContaining({ id: 'c2' }),
614
+ }),
615
+ ]));
290
616
  });
291
- it('SECURITY: should block calls to tools not registered for the agent at runtime', async () => {
292
- // Agent definition only includes LSTool
293
- const definition = createTestDefinition([LSTool.Name]);
617
+ it('SECURITY: should block unauthorized tools and provide explicit failure to model', async () => {
618
+ const definition = createTestDefinition([LS_TOOL_NAME]);
294
619
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
295
- // Turn 1: Model hallucinates a call to ReadFileTool
296
- // (ReadFileTool exists in the parent registry but not the agent's isolated registry)
620
+ // Turn 1: Model tries to use a tool not in its config
621
+ const badCallId = 'bad_call_1';
297
622
  mockModelResponse([
298
623
  {
299
- name: ReadFileTool.Name,
300
- args: { path: 'config.txt' },
301
- id: 'call_blocked',
624
+ name: READ_FILE_TOOL_NAME,
625
+ args: { path: 'secret.txt' },
626
+ id: badCallId,
627
+ },
628
+ ]);
629
+ // Turn 2: Model gives up and completes
630
+ mockModelResponse([
631
+ {
632
+ name: TASK_COMPLETE_TOOL_NAME,
633
+ args: { finalResult: 'Could not read file.' },
634
+ id: 'c2',
302
635
  },
303
636
  ]);
304
- // Turn 2: Model stops
305
- mockModelResponse([]);
306
- // Extraction
307
- mockModelResponse([], undefined, 'Done.');
308
637
  const consoleWarnSpy = vi
309
- .spyOn(console, 'warn')
638
+ .spyOn(debugLogger, 'warn')
310
639
  .mockImplementation(() => { });
311
- await executor.run({ goal: 'Security test' }, signal);
312
- // Verify executeToolCall was NEVER called because the tool was unauthorized
640
+ await executor.run({ goal: 'Sec test' }, signal);
641
+ // Verify external executor was not called (Security held)
313
642
  expect(mockExecuteToolCall).not.toHaveBeenCalled();
314
- expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining(`attempted to call unauthorized tool '${ReadFileTool.Name}'`));
643
+ // 2. Verify console warning
644
+ expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining(`[AgentExecutor] Blocked call:`));
315
645
  consoleWarnSpy.mockRestore();
316
- // Verify the input to the next model call (Turn 2) indicates failure (as the only call was blocked)
317
- const turn2Input = mockSendMessageStream.mock.calls[1][1];
318
- const turn2Parts = turn2Input.message;
319
- expect(turn2Parts[0].text).toContain('All tool calls failed');
320
- });
321
- it('should use OutputConfig completion_criteria in the extraction message', async () => {
322
- const definition = createTestDefinition([LSTool.Name], {}, {
323
- description: 'A summary.',
324
- completion_criteria: ['Must include file names', 'Must be concise'],
646
+ // Verify specific error was sent back to model
647
+ const turn2Params = getMockMessageParams(1);
648
+ const parts = turn2Params.message;
649
+ expect(parts).toBeDefined();
650
+ expect(parts[0]).toEqual(expect.objectContaining({
651
+ functionResponse: expect.objectContaining({
652
+ id: badCallId,
653
+ name: READ_FILE_TOOL_NAME,
654
+ response: {
655
+ error: expect.stringContaining('Unauthorized tool call'),
656
+ },
657
+ }),
658
+ }));
659
+ // Verify Activity Stream reported the error
660
+ expect(activities).toContainEqual(expect.objectContaining({
661
+ type: 'ERROR',
662
+ data: expect.objectContaining({
663
+ context: 'tool_call_unauthorized',
664
+ name: READ_FILE_TOOL_NAME,
665
+ }),
666
+ }));
667
+ });
668
+ });
669
+ describe('Edge Cases and Error Handling', () => {
670
+ it('should report an error if complete_task output fails schema validation', async () => {
671
+ const definition = createTestDefinition([], {}, 'default', z.string().min(10));
672
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
673
+ // Turn 1: Invalid arg (too short)
674
+ mockModelResponse([
675
+ {
676
+ name: TASK_COMPLETE_TOOL_NAME,
677
+ args: { finalResult: 'short' },
678
+ id: 'call1',
679
+ },
680
+ ]);
681
+ // Turn 2: Corrected
682
+ mockModelResponse([
683
+ {
684
+ name: TASK_COMPLETE_TOOL_NAME,
685
+ args: { finalResult: 'This is a much longer and valid result' },
686
+ id: 'call2',
687
+ },
688
+ ]);
689
+ const output = await executor.run({ goal: 'Validation test' }, signal);
690
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
691
+ const expectedError = 'Output validation failed: {"formErrors":["String must contain at least 10 character(s)"],"fieldErrors":{}}';
692
+ // Check that the error was reported in the activity stream
693
+ expect(activities).toContainEqual(expect.objectContaining({
694
+ type: 'ERROR',
695
+ data: {
696
+ context: 'tool_call',
697
+ name: TASK_COMPLETE_TOOL_NAME,
698
+ error: expect.stringContaining('Output validation failed'),
699
+ },
700
+ }));
701
+ // Check that the error was sent back to the model for the next turn
702
+ const turn2Params = getMockMessageParams(1);
703
+ const turn2Parts = turn2Params.message;
704
+ expect(turn2Parts).toEqual([
705
+ expect.objectContaining({
706
+ functionResponse: expect.objectContaining({
707
+ name: TASK_COMPLETE_TOOL_NAME,
708
+ response: { error: expectedError },
709
+ id: 'call1',
710
+ }),
711
+ }),
712
+ ]);
713
+ // Check that the agent eventually succeeded
714
+ expect(output.result).toContain('This is a much longer and valid result');
715
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
716
+ });
717
+ it('should throw and log if GeminiChat creation fails', async () => {
718
+ const definition = createTestDefinition();
719
+ const initError = new Error('Chat creation failed');
720
+ MockedGeminiChat.mockImplementationOnce(() => {
721
+ throw initError;
325
722
  });
723
+ // We expect the error to be thrown during the run, not creation
724
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
725
+ await expect(executor.run({ goal: 'test' }, signal)).rejects.toThrow(`Failed to create chat object: ${initError}`);
726
+ // Ensure the error was reported via the activity callback
727
+ expect(activities).toContainEqual(expect.objectContaining({
728
+ type: 'ERROR',
729
+ data: expect.objectContaining({
730
+ error: `Error: Failed to create chat object: ${initError}`,
731
+ }),
732
+ }));
733
+ // Ensure the agent run was logged as a failure
734
+ expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
735
+ terminate_reason: AgentTerminateMode.ERROR,
736
+ }));
737
+ });
738
+ it('should handle a failed tool call and feed the error to the model', async () => {
739
+ const definition = createTestDefinition([LS_TOOL_NAME]);
326
740
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
327
- // Turn 1: Model stops immediately
328
- mockModelResponse([]);
329
- // Extraction Phase
330
- mockModelResponse([], undefined, 'Result: Done.');
331
- await executor.run({ goal: 'Extraction test' }, signal);
332
- // Verify the extraction call (the second call)
333
- const extractionCallArgs = mockSendMessageStream.mock.calls[1][1];
334
- const extractionMessageParts = extractionCallArgs.message;
335
- const extractionText = extractionMessageParts[0].text;
336
- expect(extractionText).toContain('Based on your work so far, provide: A summary.');
337
- expect(extractionText).toContain('Be sure you have addressed:');
338
- expect(extractionText).toContain('- Must include file names');
339
- expect(extractionText).toContain('- Must be concise');
741
+ const toolErrorMessage = 'Tool failed spectacularly';
742
+ // Turn 1: Model calls a tool that will fail
743
+ mockModelResponse([
744
+ { name: LS_TOOL_NAME, args: { path: '/fake' }, id: 'call1' },
745
+ ]);
746
+ mockExecuteToolCall.mockResolvedValueOnce({
747
+ status: 'error',
748
+ request: {
749
+ callId: 'call1',
750
+ name: LS_TOOL_NAME,
751
+ args: { path: '/fake' },
752
+ isClientInitiated: false,
753
+ prompt_id: 'test-prompt',
754
+ },
755
+ tool: {},
756
+ invocation: {},
757
+ response: {
758
+ callId: 'call1',
759
+ resultDisplay: '',
760
+ responseParts: [
761
+ {
762
+ functionResponse: {
763
+ name: LS_TOOL_NAME,
764
+ response: { error: toolErrorMessage },
765
+ id: 'call1',
766
+ },
767
+ },
768
+ ],
769
+ error: {
770
+ type: 'ToolError',
771
+ message: toolErrorMessage,
772
+ },
773
+ errorType: 'ToolError',
774
+ contentLength: 0,
775
+ },
776
+ });
777
+ // Turn 2: Model sees the error and completes
778
+ mockModelResponse([
779
+ {
780
+ name: TASK_COMPLETE_TOOL_NAME,
781
+ args: { finalResult: 'Aborted due to tool failure.' },
782
+ id: 'call2',
783
+ },
784
+ ]);
785
+ const output = await executor.run({ goal: 'Tool failure test' }, signal);
786
+ expect(mockExecuteToolCall).toHaveBeenCalledTimes(1);
787
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
788
+ // Verify the error was reported in the activity stream
789
+ expect(activities).toContainEqual(expect.objectContaining({
790
+ type: 'ERROR',
791
+ data: {
792
+ context: 'tool_call',
793
+ name: LS_TOOL_NAME,
794
+ error: toolErrorMessage,
795
+ },
796
+ }));
797
+ // Verify the error was sent back to the model
798
+ const turn2Params = getMockMessageParams(1);
799
+ const parts = turn2Params.message;
800
+ expect(parts).toEqual([
801
+ expect.objectContaining({
802
+ functionResponse: expect.objectContaining({
803
+ name: LS_TOOL_NAME,
804
+ id: 'call1',
805
+ response: {
806
+ error: toolErrorMessage,
807
+ },
808
+ }),
809
+ }),
810
+ ]);
811
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
812
+ expect(output.result).toBe('Aborted due to tool failure.');
340
813
  });
341
814
  });
342
815
  describe('run (Termination Conditions)', () => {
343
- const mockKeepAliveResponse = () => {
344
- mockModelResponse([{ name: LSTool.Name, args: { path: '.' }, id: 'loop' }], 'Looping');
345
- mockExecuteToolCall.mockResolvedValue({
346
- callId: 'loop',
347
- resultDisplay: 'ok',
348
- responseParts: [
349
- { functionResponse: { name: LSTool.Name, response: {}, id: 'loop' } },
350
- ],
351
- error: undefined,
816
+ const mockWorkResponse = (id) => {
817
+ mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
818
+ mockExecuteToolCall.mockResolvedValueOnce({
819
+ status: 'success',
820
+ request: {
821
+ callId: id,
822
+ name: LS_TOOL_NAME,
823
+ args: { path: '.' },
824
+ isClientInitiated: false,
825
+ prompt_id: 'test-prompt',
826
+ },
827
+ tool: {},
828
+ invocation: {},
829
+ response: {
830
+ callId: id,
831
+ resultDisplay: 'ok',
832
+ responseParts: [
833
+ { functionResponse: { name: LS_TOOL_NAME, response: {}, id } },
834
+ ],
835
+ error: undefined,
836
+ errorType: undefined,
837
+ contentLength: undefined,
838
+ },
352
839
  });
353
840
  };
354
841
  it('should terminate when max_turns is reached', async () => {
355
- const MAX_TURNS = 2;
356
- const definition = createTestDefinition([LSTool.Name], {
357
- max_turns: MAX_TURNS,
842
+ const MAX = 2;
843
+ const definition = createTestDefinition([LS_TOOL_NAME], {
844
+ max_turns: MAX,
358
845
  });
359
- const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
360
- // Turn 1
361
- mockKeepAliveResponse();
362
- // Turn 2
363
- mockKeepAliveResponse();
364
- const output = await executor.run({ goal: 'Termination test' }, signal);
846
+ const executor = await AgentExecutor.create(definition, mockConfig);
847
+ mockWorkResponse('t1');
848
+ mockWorkResponse('t2');
849
+ // Recovery turn
850
+ mockModelResponse([], 'I give up');
851
+ const output = await executor.run({ goal: 'Turns test' }, signal);
365
852
  expect(output.terminate_reason).toBe(AgentTerminateMode.MAX_TURNS);
366
- expect(mockSendMessageStream).toHaveBeenCalledTimes(MAX_TURNS);
367
- // Extraction phase should be skipped when termination is forced
368
- expect(mockSendMessageStream).not.toHaveBeenCalledWith(expect.any(String), expect.any(Object), expect.stringContaining('#extraction'));
369
- });
370
- it('should terminate if timeout is reached', async () => {
371
- const definition = createTestDefinition([LSTool.Name], {
372
- max_time_minutes: 5,
373
- max_turns: 100,
853
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(MAX + 1);
854
+ });
855
+ it('should terminate with TIMEOUT if a model call takes too long', async () => {
856
+ const definition = createTestDefinition([LS_TOOL_NAME], {
857
+ max_time_minutes: 0.5, // 30 seconds
374
858
  });
375
859
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
376
- // Turn 1 setup
377
- mockModelResponse([{ name: LSTool.Name, args: { path: '.' }, id: 'loop' }], 'Looping');
378
- // Mock a tool call that takes a long time, causing the overall timeout
379
- mockExecuteToolCall.mockImplementation(async () => {
380
- // Advance time past the 5-minute limit during the tool call execution
381
- await vi.advanceTimersByTimeAsync(5 * 60 * 1000 + 1);
860
+ // Mock a model call that is interruptible by an abort signal.
861
+ mockSendMessageStream.mockImplementationOnce(async (_key, _message, _promptId, signal) =>
862
+ // eslint-disable-next-line require-yield
863
+ (async function* () {
864
+ await new Promise((resolve) => {
865
+ // This promise resolves when aborted, ending the generator.
866
+ signal?.addEventListener('abort', () => {
867
+ resolve();
868
+ });
869
+ });
870
+ })());
871
+ // Recovery turn
872
+ mockModelResponse([], 'I give up');
873
+ const runPromise = executor.run({ goal: 'Timeout test' }, signal);
874
+ // Advance time past the timeout to trigger the abort.
875
+ await vi.advanceTimersByTimeAsync(31 * 1000);
876
+ const output = await runPromise;
877
+ expect(output.terminate_reason).toBe(AgentTerminateMode.TIMEOUT);
878
+ expect(output.result).toContain('Agent timed out after 0.5 minutes.');
879
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
880
+ // Verify activity stream reported the timeout
881
+ expect(activities).toContainEqual(expect.objectContaining({
882
+ type: 'ERROR',
883
+ data: expect.objectContaining({
884
+ context: 'timeout',
885
+ error: 'Agent timed out after 0.5 minutes.',
886
+ }),
887
+ }));
888
+ // Verify telemetry
889
+ expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
890
+ terminate_reason: AgentTerminateMode.TIMEOUT,
891
+ }));
892
+ });
893
+ it('should terminate with TIMEOUT if a tool call takes too long', async () => {
894
+ const definition = createTestDefinition([LS_TOOL_NAME], {
895
+ max_time_minutes: 1,
896
+ });
897
+ const executor = await AgentExecutor.create(definition, mockConfig);
898
+ mockModelResponse([
899
+ { name: LS_TOOL_NAME, args: { path: '.' }, id: 't1' },
900
+ ]);
901
+ // Long running tool
902
+ mockExecuteToolCall.mockImplementationOnce(async (_ctx, reqInfo) => {
903
+ await vi.advanceTimersByTimeAsync(61 * 1000);
382
904
  return {
383
- callId: 'loop',
384
- resultDisplay: 'ok',
385
- responseParts: [
386
- {
387
- functionResponse: { name: LSTool.Name, response: {}, id: 'loop' },
388
- },
389
- ],
390
- error: undefined,
905
+ status: 'success',
906
+ request: reqInfo,
907
+ tool: {},
908
+ invocation: {},
909
+ response: {
910
+ callId: 't1',
911
+ resultDisplay: 'ok',
912
+ responseParts: [],
913
+ error: undefined,
914
+ errorType: undefined,
915
+ contentLength: undefined,
916
+ },
391
917
  };
392
918
  });
393
- const output = await executor.run({ goal: 'Termination test' }, signal);
919
+ // Recovery turn
920
+ mockModelResponse([], 'I give up');
921
+ const output = await executor.run({ goal: 'Timeout test' }, signal);
394
922
  expect(output.terminate_reason).toBe(AgentTerminateMode.TIMEOUT);
395
- // Should only have called the model once before the timeout check stopped it
396
- expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
923
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
397
924
  });
398
- it('should terminate when AbortSignal is triggered mid-stream', async () => {
925
+ it('should terminate when AbortSignal is triggered', async () => {
399
926
  const definition = createTestDefinition();
400
- const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
401
- // Mock the model response stream
402
- mockSendMessageStream.mockImplementation(async () => (async function* () {
403
- // Yield the first chunk
927
+ const executor = await AgentExecutor.create(definition, mockConfig);
928
+ mockSendMessageStream.mockImplementationOnce(async () => (async function* () {
404
929
  yield {
405
930
  type: StreamEventType.CHUNK,
406
931
  value: createMockResponseChunk([
407
- { text: '**Thinking** Step 1', thought: true },
932
+ { text: 'Thinking...', thought: true },
408
933
  ]),
409
934
  };
410
- // Simulate abort happening mid-stream
411
935
  abortController.abort();
412
- // The loop in callModel should break immediately due to signal check.
413
936
  })());
414
- const output = await executor.run({ goal: 'Termination test' }, signal);
937
+ const output = await executor.run({ goal: 'Abort test' }, signal);
415
938
  expect(output.terminate_reason).toBe(AgentTerminateMode.ABORTED);
416
939
  });
417
940
  });
941
+ describe('run (Recovery Turns)', () => {
942
+ const mockWorkResponse = (id) => {
943
+ mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
944
+ mockExecuteToolCall.mockResolvedValueOnce({
945
+ status: 'success',
946
+ request: {
947
+ callId: id,
948
+ name: LS_TOOL_NAME,
949
+ args: { path: '.' },
950
+ isClientInitiated: false,
951
+ prompt_id: 'test-prompt',
952
+ },
953
+ tool: {},
954
+ invocation: {},
955
+ response: {
956
+ callId: id,
957
+ resultDisplay: 'ok',
958
+ responseParts: [
959
+ { functionResponse: { name: LS_TOOL_NAME, response: {}, id } },
960
+ ],
961
+ error: undefined,
962
+ errorType: undefined,
963
+ contentLength: undefined,
964
+ },
965
+ });
966
+ };
967
+ it('should recover successfully if complete_task is called during the grace turn after MAX_TURNS', async () => {
968
+ const MAX = 1;
969
+ const definition = createTestDefinition([LS_TOOL_NAME], {
970
+ max_turns: MAX,
971
+ });
972
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
973
+ // Turn 1 (hits max_turns)
974
+ mockWorkResponse('t1');
975
+ // Recovery Turn (succeeds)
976
+ mockModelResponse([
977
+ {
978
+ name: TASK_COMPLETE_TOOL_NAME,
979
+ args: { finalResult: 'Recovered!' },
980
+ id: 't2',
981
+ },
982
+ ], 'Recovering from max turns');
983
+ const output = await executor.run({ goal: 'Turns recovery' }, signal);
984
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
985
+ expect(output.result).toBe('Recovered!');
986
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(MAX + 1); // 1 regular + 1 recovery
987
+ expect(activities).toContainEqual(expect.objectContaining({
988
+ type: 'THOUGHT_CHUNK',
989
+ data: {
990
+ text: 'Execution limit reached (MAX_TURNS). Attempting one final recovery turn with a grace period.',
991
+ },
992
+ }));
993
+ expect(activities).toContainEqual(expect.objectContaining({
994
+ type: 'THOUGHT_CHUNK',
995
+ data: { text: 'Graceful recovery succeeded.' },
996
+ }));
997
+ });
998
+ it('should fail if complete_task is NOT called during the grace turn after MAX_TURNS', async () => {
999
+ const MAX = 1;
1000
+ const definition = createTestDefinition([LS_TOOL_NAME], {
1001
+ max_turns: MAX,
1002
+ });
1003
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
1004
+ // Turn 1 (hits max_turns)
1005
+ mockWorkResponse('t1');
1006
+ // Recovery Turn (fails by calling no tools)
1007
+ mockModelResponse([], 'I give up again.');
1008
+ const output = await executor.run({ goal: 'Turns recovery fail' }, signal);
1009
+ expect(output.terminate_reason).toBe(AgentTerminateMode.MAX_TURNS);
1010
+ expect(output.result).toContain('Agent reached max turns limit');
1011
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(MAX + 1);
1012
+ expect(activities).toContainEqual(expect.objectContaining({
1013
+ type: 'ERROR',
1014
+ data: expect.objectContaining({
1015
+ context: 'recovery_turn',
1016
+ error: 'Graceful recovery attempt failed. Reason: stop',
1017
+ }),
1018
+ }));
1019
+ });
1020
+ it('should recover successfully from a protocol violation (no complete_task)', async () => {
1021
+ const definition = createTestDefinition();
1022
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
1023
+ // Turn 1: Normal work
1024
+ mockWorkResponse('t1');
1025
+ // Turn 2: Protocol violation (no tool calls)
1026
+ mockModelResponse([], 'I think I am done, but I forgot the right tool.');
1027
+ // Turn 3: Recovery turn (succeeds)
1028
+ mockModelResponse([
1029
+ {
1030
+ name: TASK_COMPLETE_TOOL_NAME,
1031
+ args: { finalResult: 'Recovered from violation!' },
1032
+ id: 't3',
1033
+ },
1034
+ ], 'My mistake, here is the completion.');
1035
+ const output = await executor.run({ goal: 'Violation recovery' }, signal);
1036
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(3);
1037
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
1038
+ expect(output.result).toBe('Recovered from violation!');
1039
+ expect(activities).toContainEqual(expect.objectContaining({
1040
+ type: 'THOUGHT_CHUNK',
1041
+ data: {
1042
+ text: 'Execution limit reached (ERROR_NO_COMPLETE_TASK_CALL). Attempting one final recovery turn with a grace period.',
1043
+ },
1044
+ }));
1045
+ });
1046
+ it('should fail recovery from a protocol violation if it violates again', async () => {
1047
+ const definition = createTestDefinition();
1048
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
1049
+ // Turn 1: Normal work
1050
+ mockWorkResponse('t1');
1051
+ // Turn 2: Protocol violation (no tool calls)
1052
+ mockModelResponse([], 'I think I am done, but I forgot the right tool.');
1053
+ // Turn 3: Recovery turn (fails again)
1054
+ mockModelResponse([], 'I still dont know what to do.');
1055
+ const output = await executor.run({ goal: 'Violation recovery fail' }, signal);
1056
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(3);
1057
+ expect(output.terminate_reason).toBe(AgentTerminateMode.ERROR_NO_COMPLETE_TASK_CALL);
1058
+ expect(output.result).toContain(`Agent stopped calling tools but did not call '${TASK_COMPLETE_TOOL_NAME}'`);
1059
+ expect(activities).toContainEqual(expect.objectContaining({
1060
+ type: 'ERROR',
1061
+ data: expect.objectContaining({
1062
+ context: 'recovery_turn',
1063
+ error: 'Graceful recovery attempt failed. Reason: stop',
1064
+ }),
1065
+ }));
1066
+ });
1067
+ it('should recover successfully from a TIMEOUT', async () => {
1068
+ const definition = createTestDefinition([LS_TOOL_NAME], {
1069
+ max_time_minutes: 0.5, // 30 seconds
1070
+ });
1071
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
1072
+ // Mock a model call that gets interrupted by the timeout.
1073
+ mockSendMessageStream.mockImplementationOnce(async (_key, _message, _promptId, signal) =>
1074
+ // eslint-disable-next-line require-yield
1075
+ (async function* () {
1076
+ // This promise never resolves, it waits for abort.
1077
+ await new Promise((resolve) => {
1078
+ signal?.addEventListener('abort', () => resolve());
1079
+ });
1080
+ })());
1081
+ // Recovery turn (succeeds)
1082
+ mockModelResponse([
1083
+ {
1084
+ name: TASK_COMPLETE_TOOL_NAME,
1085
+ args: { finalResult: 'Recovered from timeout!' },
1086
+ id: 't2',
1087
+ },
1088
+ ], 'Apologies for the delay, finishing up.');
1089
+ const runPromise = executor.run({ goal: 'Timeout recovery' }, signal);
1090
+ // Advance time past the timeout to trigger the abort and recovery.
1091
+ await vi.advanceTimersByTimeAsync(31 * 1000);
1092
+ const output = await runPromise;
1093
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2); // 1 failed + 1 recovery
1094
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
1095
+ expect(output.result).toBe('Recovered from timeout!');
1096
+ expect(activities).toContainEqual(expect.objectContaining({
1097
+ type: 'THOUGHT_CHUNK',
1098
+ data: {
1099
+ text: 'Execution limit reached (TIMEOUT). Attempting one final recovery turn with a grace period.',
1100
+ },
1101
+ }));
1102
+ });
1103
+ it('should fail recovery from a TIMEOUT if the grace period also times out', async () => {
1104
+ const definition = createTestDefinition([LS_TOOL_NAME], {
1105
+ max_time_minutes: 0.5, // 30 seconds
1106
+ });
1107
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
1108
+ mockSendMessageStream.mockImplementationOnce(async (_key, _message, _promptId, signal) =>
1109
+ // eslint-disable-next-line require-yield
1110
+ (async function* () {
1111
+ await new Promise((resolve) => signal?.addEventListener('abort', () => resolve()));
1112
+ })());
1113
+ // Mock the recovery call to also be long-running
1114
+ mockSendMessageStream.mockImplementationOnce(async (_key, _message, _promptId, signal) =>
1115
+ // eslint-disable-next-line require-yield
1116
+ (async function* () {
1117
+ await new Promise((resolve) => signal?.addEventListener('abort', () => resolve()));
1118
+ })());
1119
+ const runPromise = executor.run({ goal: 'Timeout recovery fail' }, signal);
1120
+ // 1. Trigger the main timeout
1121
+ await vi.advanceTimersByTimeAsync(31 * 1000);
1122
+ // 2. Let microtasks run (start recovery turn)
1123
+ await vi.advanceTimersByTimeAsync(1);
1124
+ // 3. Trigger the grace period timeout (60s)
1125
+ await vi.advanceTimersByTimeAsync(61 * 1000);
1126
+ const output = await runPromise;
1127
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
1128
+ expect(output.terminate_reason).toBe(AgentTerminateMode.TIMEOUT);
1129
+ expect(output.result).toContain('Agent timed out after 0.5 minutes.');
1130
+ expect(activities).toContainEqual(expect.objectContaining({
1131
+ type: 'ERROR',
1132
+ data: expect.objectContaining({
1133
+ context: 'recovery_turn',
1134
+ error: 'Graceful recovery attempt failed. Reason: stop',
1135
+ }),
1136
+ }));
1137
+ });
1138
+ });
1139
+ describe('Telemetry and Logging', () => {
1140
+ const mockWorkResponse = (id) => {
1141
+ mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
1142
+ mockExecuteToolCall.mockResolvedValueOnce({
1143
+ status: 'success',
1144
+ request: {
1145
+ callId: id,
1146
+ name: LS_TOOL_NAME,
1147
+ args: { path: '.' },
1148
+ isClientInitiated: false,
1149
+ prompt_id: 'test-prompt',
1150
+ },
1151
+ tool: {},
1152
+ invocation: {},
1153
+ response: {
1154
+ callId: id,
1155
+ resultDisplay: 'ok',
1156
+ responseParts: [
1157
+ { functionResponse: { name: LS_TOOL_NAME, response: {}, id } },
1158
+ ],
1159
+ error: undefined,
1160
+ errorType: undefined,
1161
+ contentLength: undefined,
1162
+ },
1163
+ });
1164
+ };
1165
+ beforeEach(() => {
1166
+ mockedLogRecoveryAttempt.mockClear();
1167
+ });
1168
+ it('should log a RecoveryAttemptEvent when a recoverable error occurs and recovery fails', async () => {
1169
+ const MAX = 1;
1170
+ const definition = createTestDefinition([LS_TOOL_NAME], {
1171
+ max_turns: MAX,
1172
+ });
1173
+ const executor = await AgentExecutor.create(definition, mockConfig);
1174
+ // Turn 1 (hits max_turns)
1175
+ mockWorkResponse('t1');
1176
+ // Recovery Turn (fails by calling no tools)
1177
+ mockModelResponse([], 'I give up again.');
1178
+ await executor.run({ goal: 'Turns recovery fail' }, signal);
1179
+ expect(mockedLogRecoveryAttempt).toHaveBeenCalledTimes(1);
1180
+ const recoveryEvent = mockedLogRecoveryAttempt.mock.calls[0][1];
1181
+ expect(recoveryEvent).toBeInstanceOf(RecoveryAttemptEvent);
1182
+ expect(recoveryEvent.agent_name).toBe(definition.name);
1183
+ expect(recoveryEvent.reason).toBe(AgentTerminateMode.MAX_TURNS);
1184
+ expect(recoveryEvent.success).toBe(false);
1185
+ expect(recoveryEvent.turn_count).toBe(1);
1186
+ expect(recoveryEvent.duration_ms).toBeGreaterThanOrEqual(0);
1187
+ });
1188
+ it('should log a successful RecoveryAttemptEvent when recovery succeeds', async () => {
1189
+ const MAX = 1;
1190
+ const definition = createTestDefinition([LS_TOOL_NAME], {
1191
+ max_turns: MAX,
1192
+ });
1193
+ const executor = await AgentExecutor.create(definition, mockConfig);
1194
+ // Turn 1 (hits max_turns)
1195
+ mockWorkResponse('t1');
1196
+ // Recovery Turn (succeeds)
1197
+ mockModelResponse([
1198
+ {
1199
+ name: TASK_COMPLETE_TOOL_NAME,
1200
+ args: { finalResult: 'Recovered!' },
1201
+ id: 't2',
1202
+ },
1203
+ ], 'Recovering from max turns');
1204
+ await executor.run({ goal: 'Turns recovery success' }, signal);
1205
+ expect(mockedLogRecoveryAttempt).toHaveBeenCalledTimes(1);
1206
+ const recoveryEvent = mockedLogRecoveryAttempt.mock.calls[0][1];
1207
+ expect(recoveryEvent).toBeInstanceOf(RecoveryAttemptEvent);
1208
+ expect(recoveryEvent.success).toBe(true);
1209
+ expect(recoveryEvent.reason).toBe(AgentTerminateMode.MAX_TURNS);
1210
+ });
1211
+ });
1212
+ describe('Chat Compression', () => {
1213
+ const mockWorkResponse = (id) => {
1214
+ mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
1215
+ mockExecuteToolCall.mockResolvedValueOnce({
1216
+ status: 'success',
1217
+ request: {
1218
+ callId: id,
1219
+ name: LS_TOOL_NAME,
1220
+ args: { path: '.' },
1221
+ isClientInitiated: false,
1222
+ prompt_id: 'test-prompt',
1223
+ },
1224
+ tool: {},
1225
+ invocation: {},
1226
+ response: {
1227
+ callId: id,
1228
+ resultDisplay: 'ok',
1229
+ responseParts: [
1230
+ { functionResponse: { name: LS_TOOL_NAME, response: {}, id } },
1231
+ ],
1232
+ error: undefined,
1233
+ errorType: undefined,
1234
+ contentLength: undefined,
1235
+ },
1236
+ });
1237
+ };
1238
+ it('should attempt to compress chat history on each turn', async () => {
1239
+ const definition = createTestDefinition();
1240
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
1241
+ // Mock compression to do nothing
1242
+ mockCompress.mockResolvedValue({
1243
+ newHistory: null,
1244
+ info: { compressionStatus: CompressionStatus.NOOP },
1245
+ });
1246
+ // Turn 1
1247
+ mockWorkResponse('t1');
1248
+ // Turn 2: Complete
1249
+ mockModelResponse([
1250
+ {
1251
+ name: TASK_COMPLETE_TOOL_NAME,
1252
+ args: { finalResult: 'Done' },
1253
+ id: 'call2',
1254
+ },
1255
+ ], 'T2');
1256
+ await executor.run({ goal: 'Compress test' }, signal);
1257
+ expect(mockCompress).toHaveBeenCalledTimes(2);
1258
+ });
1259
+ it('should update chat history when compression is successful', async () => {
1260
+ const definition = createTestDefinition();
1261
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
1262
+ const compressedHistory = [
1263
+ { role: 'user', parts: [{ text: 'compressed' }] },
1264
+ ];
1265
+ mockCompress.mockResolvedValue({
1266
+ newHistory: compressedHistory,
1267
+ info: { compressionStatus: CompressionStatus.COMPRESSED },
1268
+ });
1269
+ // Turn 1: Complete
1270
+ mockModelResponse([
1271
+ {
1272
+ name: TASK_COMPLETE_TOOL_NAME,
1273
+ args: { finalResult: 'Done' },
1274
+ id: 'call1',
1275
+ },
1276
+ ], 'T1');
1277
+ await executor.run({ goal: 'Compress success' }, signal);
1278
+ expect(mockCompress).toHaveBeenCalledTimes(1);
1279
+ expect(mockSetHistory).toHaveBeenCalledTimes(1);
1280
+ expect(mockSetHistory).toHaveBeenCalledWith(compressedHistory);
1281
+ });
1282
+ it('should pass hasFailedCompressionAttempt=true to compression after a failure', async () => {
1283
+ const definition = createTestDefinition();
1284
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
1285
+ // First call fails
1286
+ mockCompress.mockResolvedValueOnce({
1287
+ newHistory: null,
1288
+ info: {
1289
+ compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
1290
+ },
1291
+ });
1292
+ // Second call is neutral
1293
+ mockCompress.mockResolvedValueOnce({
1294
+ newHistory: null,
1295
+ info: { compressionStatus: CompressionStatus.NOOP },
1296
+ });
1297
+ // Turn 1
1298
+ mockWorkResponse('t1');
1299
+ // Turn 2: Complete
1300
+ mockModelResponse([
1301
+ {
1302
+ name: TASK_COMPLETE_TOOL_NAME,
1303
+ args: { finalResult: 'Done' },
1304
+ id: 't2',
1305
+ },
1306
+ ], 'T2');
1307
+ await executor.run({ goal: 'Compress fail' }, signal);
1308
+ expect(mockCompress).toHaveBeenCalledTimes(2);
1309
+ // First call, hasFailedCompressionAttempt is false
1310
+ expect(mockCompress.mock.calls[0][5]).toBe(false);
1311
+ // Second call, hasFailedCompressionAttempt is true
1312
+ expect(mockCompress.mock.calls[1][5]).toBe(true);
1313
+ });
1314
+ it('should reset hasFailedCompressionAttempt flag after a successful compression', async () => {
1315
+ const definition = createTestDefinition();
1316
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
1317
+ const compressedHistory = [
1318
+ { role: 'user', parts: [{ text: 'compressed' }] },
1319
+ ];
1320
+ // Turn 1: Fails
1321
+ mockCompress.mockResolvedValueOnce({
1322
+ newHistory: null,
1323
+ info: {
1324
+ compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
1325
+ },
1326
+ });
1327
+ // Turn 2: Succeeds
1328
+ mockCompress.mockResolvedValueOnce({
1329
+ newHistory: compressedHistory,
1330
+ info: { compressionStatus: CompressionStatus.COMPRESSED },
1331
+ });
1332
+ // Turn 3: Neutral
1333
+ mockCompress.mockResolvedValueOnce({
1334
+ newHistory: null,
1335
+ info: { compressionStatus: CompressionStatus.NOOP },
1336
+ });
1337
+ // Turn 1
1338
+ mockWorkResponse('t1');
1339
+ // Turn 2
1340
+ mockWorkResponse('t2');
1341
+ // Turn 3: Complete
1342
+ mockModelResponse([
1343
+ {
1344
+ name: TASK_COMPLETE_TOOL_NAME,
1345
+ args: { finalResult: 'Done' },
1346
+ id: 't3',
1347
+ },
1348
+ ], 'T3');
1349
+ await executor.run({ goal: 'Compress reset' }, signal);
1350
+ expect(mockCompress).toHaveBeenCalledTimes(3);
1351
+ // Call 1: hasFailed... is false
1352
+ expect(mockCompress.mock.calls[0][5]).toBe(false);
1353
+ // Call 2: hasFailed... is true
1354
+ expect(mockCompress.mock.calls[1][5]).toBe(true);
1355
+ // Call 3: hasFailed... is false again
1356
+ expect(mockCompress.mock.calls[2][5]).toBe(false);
1357
+ expect(mockSetHistory).toHaveBeenCalledTimes(1);
1358
+ expect(mockSetHistory).toHaveBeenCalledWith(compressedHistory);
1359
+ });
1360
+ });
418
1361
  });
419
1362
  //# sourceMappingURL=executor.test.js.map