@machina.ai/cell-cli-core 1.8.2-rc1 → 1.11.0-rc1

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 (381) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/package.json +5 -3
  5. package/dist/src/agents/codebase-investigator.d.ts +36 -1
  6. package/dist/src/agents/codebase-investigator.js +93 -34
  7. package/dist/src/agents/codebase-investigator.js.map +1 -1
  8. package/dist/src/agents/executor.d.ts +15 -11
  9. package/dist/src/agents/executor.js +271 -115
  10. package/dist/src/agents/executor.js.map +1 -1
  11. package/dist/src/agents/executor.test.js +508 -242
  12. package/dist/src/agents/executor.test.js.map +1 -1
  13. package/dist/src/agents/invocation.d.ts +5 -2
  14. package/dist/src/agents/invocation.js +4 -2
  15. package/dist/src/agents/invocation.js.map +1 -1
  16. package/dist/src/agents/invocation.test.js +9 -0
  17. package/dist/src/agents/invocation.test.js.map +1 -1
  18. package/dist/src/agents/registry.d.ts +2 -1
  19. package/dist/src/agents/registry.js +28 -4
  20. package/dist/src/agents/registry.js.map +1 -1
  21. package/dist/src/agents/subagent-tool-wrapper.d.ts +3 -1
  22. package/dist/src/agents/subagent-tool-wrapper.js +4 -3
  23. package/dist/src/agents/subagent-tool-wrapper.js.map +1 -1
  24. package/dist/src/agents/subagent-tool-wrapper.test.js +8 -1
  25. package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -1
  26. package/dist/src/agents/types.d.ts +35 -6
  27. package/dist/src/agents/types.js +1 -0
  28. package/dist/src/agents/types.js.map +1 -1
  29. package/dist/src/code_assist/converter.d.ts +1 -0
  30. package/dist/src/code_assist/converter.js +1 -0
  31. package/dist/src/code_assist/converter.js.map +1 -1
  32. package/dist/src/code_assist/converter.test.js +19 -0
  33. package/dist/src/code_assist/converter.test.js.map +1 -1
  34. package/dist/src/code_assist/oauth-credential-storage.js +1 -1
  35. package/dist/src/code_assist/oauth-credential-storage.js.map +1 -1
  36. package/dist/src/code_assist/oauth-credential-storage.test.js +1 -1
  37. package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -1
  38. package/dist/src/code_assist/oauth2.js +13 -12
  39. package/dist/src/code_assist/oauth2.js.map +1 -1
  40. package/dist/src/code_assist/oauth2.test.js +14 -13
  41. package/dist/src/code_assist/oauth2.test.js.map +1 -1
  42. package/dist/src/code_assist/setup.js +4 -2
  43. package/dist/src/code_assist/setup.js.map +1 -1
  44. package/dist/src/config/config.d.ts +58 -18
  45. package/dist/src/config/config.js +125 -36
  46. package/dist/src/config/config.js.map +1 -1
  47. package/dist/src/config/config.test.js +125 -18
  48. package/dist/src/config/config.test.js.map +1 -1
  49. package/dist/src/config/storage.d.ts +0 -1
  50. package/dist/src/config/storage.js +2 -2
  51. package/dist/src/config/storage.js.map +1 -1
  52. package/dist/src/config/storage.test.js +7 -6
  53. package/dist/src/config/storage.test.js.map +1 -1
  54. package/dist/src/confirmation-bus/message-bus.d.ts +2 -1
  55. package/dist/src/confirmation-bus/message-bus.js +7 -1
  56. package/dist/src/confirmation-bus/message-bus.js.map +1 -1
  57. package/dist/src/confirmation-bus/types.d.ts +12 -2
  58. package/dist/src/confirmation-bus/types.js +1 -0
  59. package/dist/src/confirmation-bus/types.js.map +1 -1
  60. package/dist/src/core/client.d.ts +3 -1
  61. package/dist/src/core/client.js +70 -19
  62. package/dist/src/core/client.js.map +1 -1
  63. package/dist/src/core/client.test.js +199 -25
  64. package/dist/src/core/client.test.js.map +1 -1
  65. package/dist/src/core/contentGenerator.js +3 -1
  66. package/dist/src/core/contentGenerator.js.map +1 -1
  67. package/dist/src/core/coreToolScheduler.d.ts +7 -0
  68. package/dist/src/core/coreToolScheduler.js +58 -22
  69. package/dist/src/core/coreToolScheduler.js.map +1 -1
  70. package/dist/src/core/coreToolScheduler.test.js +351 -3
  71. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  72. package/dist/src/core/geminiChat.d.ts +7 -11
  73. package/dist/src/core/geminiChat.js +33 -70
  74. package/dist/src/core/geminiChat.js.map +1 -1
  75. package/dist/src/core/geminiChat.test.js +93 -228
  76. package/dist/src/core/geminiChat.test.js.map +1 -1
  77. package/dist/src/core/logger.js +11 -10
  78. package/dist/src/core/logger.js.map +1 -1
  79. package/dist/src/core/logger.test.js +2 -2
  80. package/dist/src/core/logger.test.js.map +1 -1
  81. package/dist/src/core/nonInteractiveToolExecutor.d.ts +3 -2
  82. package/dist/src/core/nonInteractiveToolExecutor.js +2 -2
  83. package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
  84. package/dist/src/core/nonInteractiveToolExecutor.test.js +11 -8
  85. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  86. package/dist/src/core/prompts.d.ts +2 -1
  87. package/dist/src/core/prompts.js +58 -123
  88. package/dist/src/core/prompts.js.map +1 -1
  89. package/dist/src/core/prompts.test.js +83 -29
  90. package/dist/src/core/prompts.test.js.map +1 -1
  91. package/dist/src/core/turn.d.ts +16 -2
  92. package/dist/src/core/turn.js +15 -2
  93. package/dist/src/core/turn.js.map +1 -1
  94. package/dist/src/core/turn.test.js +62 -2
  95. package/dist/src/core/turn.test.js.map +1 -1
  96. package/dist/src/generated/git-commit.d.ts +2 -2
  97. package/dist/src/generated/git-commit.js +2 -2
  98. package/dist/src/generated/git-commit.js.map +1 -1
  99. package/dist/src/ide/detect-ide.test.js +11 -0
  100. package/dist/src/ide/detect-ide.test.js.map +1 -1
  101. package/dist/src/ide/ide-client.js +5 -4
  102. package/dist/src/ide/ide-client.js.map +1 -1
  103. package/dist/src/ide/ide-client.test.js +4 -4
  104. package/dist/src/ide/ide-installer.js +1 -1
  105. package/dist/src/ide/ide-installer.js.map +1 -1
  106. package/dist/src/ide/ide-installer.test.js +13 -1
  107. package/dist/src/ide/ide-installer.test.js.map +1 -1
  108. package/dist/src/ide/process-utils.js +85 -75
  109. package/dist/src/ide/process-utils.js.map +1 -1
  110. package/dist/src/ide/process-utils.test.js +83 -90
  111. package/dist/src/ide/process-utils.test.js.map +1 -1
  112. package/dist/src/index.d.ts +6 -0
  113. package/dist/src/index.js +6 -0
  114. package/dist/src/index.js.map +1 -1
  115. package/dist/src/mcp/oauth-provider.js +21 -20
  116. package/dist/src/mcp/oauth-provider.js.map +1 -1
  117. package/dist/src/mcp/oauth-utils.js +9 -8
  118. package/dist/src/mcp/oauth-utils.js.map +1 -1
  119. package/dist/src/mcp/oauth-utils.test.js +13 -2
  120. package/dist/src/mcp/oauth-utils.test.js.map +1 -1
  121. package/dist/src/mcp/token-storage/file-token-storage.js +2 -1
  122. package/dist/src/mcp/token-storage/file-token-storage.js.map +1 -1
  123. package/dist/src/mcp/token-storage/file-token-storage.test.js +4 -3
  124. package/dist/src/mcp/token-storage/file-token-storage.test.js.map +1 -1
  125. package/dist/src/output/stream-json-formatter.d.ts +32 -0
  126. package/dist/src/output/stream-json-formatter.js +52 -0
  127. package/dist/src/output/stream-json-formatter.js.map +1 -0
  128. package/dist/src/output/stream-json-formatter.test.js +479 -0
  129. package/dist/src/output/stream-json-formatter.test.js.map +1 -0
  130. package/dist/src/output/types.d.ts +63 -1
  131. package/dist/src/output/types.js +11 -0
  132. package/dist/src/output/types.js.map +1 -1
  133. package/dist/src/prompts/prompt-registry.js +2 -1
  134. package/dist/src/prompts/prompt-registry.js.map +1 -1
  135. package/dist/src/routing/strategies/classifierStrategy.js +3 -2
  136. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  137. package/dist/src/services/chatRecordingService.d.ts +2 -1
  138. package/dist/src/services/chatRecordingService.js +2 -1
  139. package/dist/src/services/chatRecordingService.js.map +1 -1
  140. package/dist/src/services/loopDetectionService.js +3 -2
  141. package/dist/src/services/loopDetectionService.js.map +1 -1
  142. package/dist/src/services/shellExecutionService.d.ts +1 -0
  143. package/dist/src/services/shellExecutionService.js +177 -91
  144. package/dist/src/services/shellExecutionService.js.map +1 -1
  145. package/dist/src/services/shellExecutionService.test.js +113 -12
  146. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  147. package/dist/src/telemetry/activity-monitor.d.ts +116 -0
  148. package/dist/src/telemetry/activity-monitor.js +209 -0
  149. package/dist/src/telemetry/activity-monitor.js.map +1 -0
  150. package/dist/src/telemetry/activity-monitor.test.d.ts +6 -0
  151. package/dist/src/telemetry/activity-monitor.test.js +248 -0
  152. package/dist/src/telemetry/activity-monitor.test.js.map +1 -0
  153. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +15 -2
  154. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +154 -31
  155. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  156. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +84 -15
  157. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
  158. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +16 -3
  159. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +38 -5
  160. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  161. package/dist/src/telemetry/constants.d.ts +0 -28
  162. package/dist/src/telemetry/constants.js +0 -29
  163. package/dist/src/telemetry/constants.js.map +1 -1
  164. package/dist/src/telemetry/index.d.ts +6 -3
  165. package/dist/src/telemetry/index.js +12 -4
  166. package/dist/src/telemetry/index.js.map +1 -1
  167. package/dist/src/telemetry/loggers.d.ts +8 -2
  168. package/dist/src/telemetry/loggers.js +165 -299
  169. package/dist/src/telemetry/loggers.js.map +1 -1
  170. package/dist/src/telemetry/loggers.test.js +213 -21
  171. package/dist/src/telemetry/loggers.test.js.map +1 -1
  172. package/dist/src/telemetry/memory-monitor.d.ts +149 -0
  173. package/dist/src/telemetry/memory-monitor.js +335 -0
  174. package/dist/src/telemetry/memory-monitor.js.map +1 -0
  175. package/dist/src/telemetry/memory-monitor.test.d.ts +6 -0
  176. package/dist/src/telemetry/memory-monitor.test.js +472 -0
  177. package/dist/src/telemetry/memory-monitor.test.js.map +1 -0
  178. package/dist/src/telemetry/metrics.d.ts +131 -4
  179. package/dist/src/telemetry/metrics.js +182 -6
  180. package/dist/src/telemetry/metrics.js.map +1 -1
  181. package/dist/src/telemetry/metrics.test.js +360 -1
  182. package/dist/src/telemetry/metrics.test.js.map +1 -1
  183. package/dist/src/telemetry/sdk.js +3 -2
  184. package/dist/src/telemetry/sdk.js.map +1 -1
  185. package/dist/src/telemetry/telemetryAttributes.d.ts +8 -0
  186. package/dist/src/telemetry/telemetryAttributes.js +18 -0
  187. package/dist/src/telemetry/telemetryAttributes.js.map +1 -0
  188. package/dist/src/telemetry/types.d.ts +163 -7
  189. package/dist/src/telemetry/types.js +691 -38
  190. package/dist/src/telemetry/types.js.map +1 -1
  191. package/dist/src/telemetry/uiTelemetry.d.ts +1 -1
  192. package/dist/src/telemetry/uiTelemetry.js +1 -1
  193. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  194. package/dist/src/telemetry/uiTelemetry.test.js +1 -1
  195. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
  196. package/dist/src/tools/edit.js +8 -7
  197. package/dist/src/tools/edit.js.map +1 -1
  198. package/dist/src/tools/edit.test.js +0 -1
  199. package/dist/src/tools/edit.test.js.map +1 -1
  200. package/dist/src/tools/glob.d.ts +3 -2
  201. package/dist/src/tools/glob.js +8 -7
  202. package/dist/src/tools/glob.js.map +1 -1
  203. package/dist/src/tools/grep.d.ts +3 -2
  204. package/dist/src/tools/grep.js +20 -14
  205. package/dist/src/tools/grep.js.map +1 -1
  206. package/dist/src/tools/ls.d.ts +3 -2
  207. package/dist/src/tools/ls.js +8 -7
  208. package/dist/src/tools/ls.js.map +1 -1
  209. package/dist/src/tools/mcp-client-manager.d.ts +2 -9
  210. package/dist/src/tools/mcp-client-manager.js +6 -14
  211. package/dist/src/tools/mcp-client-manager.js.map +1 -1
  212. package/dist/src/tools/mcp-client-manager.test.js +16 -6
  213. package/dist/src/tools/mcp-client-manager.test.js.map +1 -1
  214. package/dist/src/tools/mcp-client.d.ts +3 -2
  215. package/dist/src/tools/mcp-client.js +49 -52
  216. package/dist/src/tools/mcp-client.js.map +1 -1
  217. package/dist/src/tools/mcp-client.test.js +168 -5
  218. package/dist/src/tools/mcp-client.test.js.map +1 -1
  219. package/dist/src/tools/mcp-tool.d.ts +2 -1
  220. package/dist/src/tools/mcp-tool.js +7 -3
  221. package/dist/src/tools/mcp-tool.js.map +1 -1
  222. package/dist/src/tools/memoryTool.d.ts +2 -2
  223. package/dist/src/tools/memoryTool.js +4 -4
  224. package/dist/src/tools/memoryTool.js.map +1 -1
  225. package/dist/src/tools/memoryTool.test.js +9 -8
  226. package/dist/src/tools/memoryTool.test.js.map +1 -1
  227. package/dist/src/tools/message-bus-integration.test.js +14 -1
  228. package/dist/src/tools/message-bus-integration.test.js.map +1 -1
  229. package/dist/src/tools/read-file.d.ts +4 -3
  230. package/dist/src/tools/read-file.js +9 -8
  231. package/dist/src/tools/read-file.js.map +1 -1
  232. package/dist/src/tools/read-many-files.d.ts +4 -3
  233. package/dist/src/tools/read-many-files.js +11 -8
  234. package/dist/src/tools/read-many-files.js.map +1 -1
  235. package/dist/src/tools/read-many-files.test.js +0 -1
  236. package/dist/src/tools/read-many-files.test.js.map +1 -1
  237. package/dist/src/tools/ripGrep.d.ts +3 -2
  238. package/dist/src/tools/ripGrep.js +47 -17
  239. package/dist/src/tools/ripGrep.js.map +1 -1
  240. package/dist/src/tools/ripGrep.test.js +106 -60
  241. package/dist/src/tools/ripGrep.test.js.map +1 -1
  242. package/dist/src/tools/shell.d.ts +1 -1
  243. package/dist/src/tools/shell.js +31 -14
  244. package/dist/src/tools/shell.js.map +1 -1
  245. package/dist/src/tools/shell.test.js +63 -9
  246. package/dist/src/tools/shell.test.js.map +1 -1
  247. package/dist/src/tools/smart-edit.d.ts +1 -20
  248. package/dist/src/tools/smart-edit.js +64 -61
  249. package/dist/src/tools/smart-edit.js.map +1 -1
  250. package/dist/src/tools/smart-edit.test.js +70 -87
  251. package/dist/src/tools/smart-edit.test.js.map +1 -1
  252. package/dist/src/tools/tool-error.d.ts +21 -0
  253. package/dist/src/tools/tool-error.js +27 -0
  254. package/dist/src/tools/tool-error.js.map +1 -1
  255. package/dist/src/tools/tool-names.d.ts +17 -0
  256. package/dist/src/tools/tool-names.js +21 -0
  257. package/dist/src/tools/tool-names.js.map +1 -0
  258. package/dist/src/tools/tool-registry.js +4 -3
  259. package/dist/src/tools/tool-registry.js.map +1 -1
  260. package/dist/src/tools/tools.d.ts +17 -10
  261. package/dist/src/tools/tools.js +54 -39
  262. package/dist/src/tools/tools.js.map +1 -1
  263. package/dist/src/tools/web-fetch.d.ts +11 -3
  264. package/dist/src/tools/web-fetch.js +90 -32
  265. package/dist/src/tools/web-fetch.js.map +1 -1
  266. package/dist/src/tools/web-fetch.test.js +388 -8
  267. package/dist/src/tools/web-fetch.test.js.map +1 -1
  268. package/dist/src/tools/web-search.d.ts +4 -3
  269. package/dist/src/tools/web-search.js +10 -7
  270. package/dist/src/tools/web-search.js.map +1 -1
  271. package/dist/src/tools/write-file.d.ts +1 -1
  272. package/dist/src/tools/write-file.js +3 -2
  273. package/dist/src/tools/write-file.js.map +1 -1
  274. package/dist/src/tools/write-file.test.js +0 -1
  275. package/dist/src/tools/write-file.test.js.map +1 -1
  276. package/dist/src/tools/write-todos.d.ts +3 -8
  277. package/dist/src/tools/write-todos.js +14 -7
  278. package/dist/src/tools/write-todos.js.map +1 -1
  279. package/dist/src/tools/write-todos.test.js +2 -2
  280. package/dist/src/tools/write-todos.test.js.map +1 -1
  281. package/dist/src/utils/bfsFileSearch.js +3 -2
  282. package/dist/src/utils/bfsFileSearch.js.map +1 -1
  283. package/dist/src/utils/debugLogger.d.ts +25 -0
  284. package/dist/src/utils/debugLogger.js +33 -0
  285. package/dist/src/utils/debugLogger.js.map +1 -0
  286. package/dist/src/utils/debugLogger.test.d.ts +6 -0
  287. package/dist/src/utils/debugLogger.test.js +67 -0
  288. package/dist/src/utils/debugLogger.test.js.map +1 -0
  289. package/dist/src/utils/delay.d.ts +16 -0
  290. package/dist/src/utils/delay.js +43 -0
  291. package/dist/src/utils/delay.js.map +1 -0
  292. package/dist/src/utils/delay.test.d.ts +6 -0
  293. package/dist/src/utils/delay.test.js +88 -0
  294. package/dist/src/utils/delay.test.js.map +1 -0
  295. package/dist/src/utils/editCorrector.js +6 -10
  296. package/dist/src/utils/editCorrector.js.map +1 -1
  297. package/dist/src/utils/editCorrector.test.js +3 -5
  298. package/dist/src/utils/editCorrector.test.js.map +1 -1
  299. package/dist/src/utils/editor.js +33 -37
  300. package/dist/src/utils/editor.js.map +1 -1
  301. package/dist/src/utils/editor.test.js +1 -0
  302. package/dist/src/utils/editor.test.js.map +1 -1
  303. package/dist/src/utils/environmentContext.js +0 -33
  304. package/dist/src/utils/environmentContext.js.map +1 -1
  305. package/dist/src/utils/environmentContext.test.js +0 -34
  306. package/dist/src/utils/environmentContext.test.js.map +1 -1
  307. package/dist/src/utils/fetch.d.ts +1 -0
  308. package/dist/src/utils/fetch.js +9 -0
  309. package/dist/src/utils/fetch.js.map +1 -1
  310. package/dist/src/utils/fileUtils.d.ts +4 -0
  311. package/dist/src/utils/fileUtils.js +34 -2
  312. package/dist/src/utils/fileUtils.js.map +1 -1
  313. package/dist/src/utils/fileUtils.test.js +12 -1
  314. package/dist/src/utils/fileUtils.test.js.map +1 -1
  315. package/dist/src/utils/formatters.d.ts +1 -0
  316. package/dist/src/utils/formatters.js +2 -1
  317. package/dist/src/utils/formatters.js.map +1 -1
  318. package/dist/src/utils/formatters.test.d.ts +6 -0
  319. package/dist/src/utils/formatters.test.js +26 -0
  320. package/dist/src/utils/formatters.test.js.map +1 -0
  321. package/dist/src/utils/getFolderStructure.js +2 -1
  322. package/dist/src/utils/getFolderStructure.js.map +1 -1
  323. package/dist/src/utils/getFolderStructure.test.js +7 -6
  324. package/dist/src/utils/getFolderStructure.test.js.map +1 -1
  325. package/dist/src/utils/gitIgnoreParser.js +7 -6
  326. package/dist/src/utils/gitIgnoreParser.js.map +1 -1
  327. package/dist/src/utils/gitIgnoreParser.test.js +30 -0
  328. package/dist/src/utils/gitIgnoreParser.test.js.map +1 -1
  329. package/dist/src/utils/installationManager.test.js +2 -1
  330. package/dist/src/utils/installationManager.test.js.map +1 -1
  331. package/dist/src/utils/llm-edit-fixer.js +5 -4
  332. package/dist/src/utils/llm-edit-fixer.js.map +1 -1
  333. package/dist/src/utils/memoryDiscovery.d.ts +3 -1
  334. package/dist/src/utils/memoryDiscovery.js +14 -12
  335. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  336. package/dist/src/utils/memoryDiscovery.test.js +136 -36
  337. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  338. package/dist/src/utils/memoryImportProcessor.js +3 -2
  339. package/dist/src/utils/memoryImportProcessor.js.map +1 -1
  340. package/dist/src/utils/nextSpeakerChecker.js +2 -1
  341. package/dist/src/utils/nextSpeakerChecker.js.map +1 -1
  342. package/dist/src/utils/pathCorrector.d.ts +25 -0
  343. package/dist/src/utils/pathCorrector.js +33 -0
  344. package/dist/src/utils/pathCorrector.js.map +1 -0
  345. package/dist/src/utils/pathCorrector.test.d.ts +6 -0
  346. package/dist/src/utils/pathCorrector.test.js +83 -0
  347. package/dist/src/utils/pathCorrector.test.js.map +1 -0
  348. package/dist/src/utils/retry.d.ts +3 -1
  349. package/dist/src/utils/retry.js +44 -33
  350. package/dist/src/utils/retry.js.map +1 -1
  351. package/dist/src/utils/retry.test.js +102 -40
  352. package/dist/src/utils/retry.test.js.map +1 -1
  353. package/dist/src/utils/safeJsonStringify.d.ts +4 -4
  354. package/dist/src/utils/safeJsonStringify.js +31 -7
  355. package/dist/src/utils/safeJsonStringify.js.map +1 -1
  356. package/dist/src/utils/shell-utils.d.ts +15 -2
  357. package/dist/src/utils/shell-utils.js +354 -137
  358. package/dist/src/utils/shell-utils.js.map +1 -1
  359. package/dist/src/utils/shell-utils.test.js +154 -60
  360. package/dist/src/utils/shell-utils.test.js.map +1 -1
  361. package/dist/src/utils/systemEncoding.js +5 -4
  362. package/dist/src/utils/systemEncoding.js.map +1 -1
  363. package/dist/src/utils/tool-utils.d.ts +2 -2
  364. package/dist/src/utils/tool-utils.js +15 -6
  365. package/dist/src/utils/tool-utils.js.map +1 -1
  366. package/dist/src/utils/tool-utils.test.js +8 -0
  367. package/dist/src/utils/tool-utils.test.js.map +1 -1
  368. package/dist/src/utils/userAccountManager.js +5 -4
  369. package/dist/src/utils/userAccountManager.js.map +1 -1
  370. package/dist/src/utils/userAccountManager.test.js +2 -1
  371. package/dist/src/utils/userAccountManager.test.js.map +1 -1
  372. package/dist/src/utils/workspaceContext.js +2 -1
  373. package/dist/src/utils/workspaceContext.js.map +1 -1
  374. package/dist/tsconfig.tsbuildinfo +1 -1
  375. package/package.json +5 -3
  376. package/dist/src/core/subagent.d.ts +0 -236
  377. package/dist/src/core/subagent.js +0 -482
  378. package/dist/src/core/subagent.js.map +0 -1
  379. package/dist/src/core/subagent.test.js +0 -530
  380. package/dist/src/core/subagent.test.js.map +0 -1
  381. /package/dist/src/{core/subagent.test.d.ts → output/stream-json-formatter.test.d.ts} +0 -0
@@ -3,16 +3,21 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
7
  import { AgentExecutor } from './executor.js';
8
- import { AgentTerminateMode } from './types.js';
9
8
  import { makeFakeConfig } from '../test-utils/config.js';
10
9
  import { ToolRegistry } from '../tools/tool-registry.js';
11
10
  import { LSTool } from '../tools/ls.js';
12
- import { ReadFileTool } from '../tools/read-file.js';
11
+ import { LS_TOOL_NAME, READ_FILE_TOOL_NAME } from '../tools/tool-names.js';
13
12
  import { GeminiChat, StreamEventType, } from '../core/geminiChat.js';
13
+ import {} from '@google/genai';
14
14
  import { MockTool } from '../test-utils/mock-tool.js';
15
15
  import { getDirectoryContextString } from '../utils/environmentContext.js';
16
+ import { z } from 'zod';
17
+ import { promptIdContext } from '../utils/promptIdContext.js';
18
+ import { logAgentStart, logAgentFinish } from '../telemetry/loggers.js';
19
+ import { AgentStartEvent, AgentFinishEvent } from '../telemetry/types.js';
20
+ import { AgentTerminateMode } from './types.js';
16
21
  const { mockSendMessageStream, mockExecuteToolCall } = vi.hoisted(() => ({
17
22
  mockSendMessageStream: vi.fn(),
18
23
  mockExecuteToolCall: vi.fn(),
@@ -30,13 +35,40 @@ vi.mock('../core/nonInteractiveToolExecutor.js', () => ({
30
35
  executeToolCall: mockExecuteToolCall,
31
36
  }));
32
37
  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' });
38
+ vi.mock('../telemetry/loggers.js', () => ({
39
+ logAgentStart: vi.fn(),
40
+ logAgentFinish: vi.fn(),
41
+ }));
42
+ vi.mock('../utils/promptIdContext.js', async (importOriginal) => {
43
+ const actual = await importOriginal();
44
+ return {
45
+ ...actual,
46
+ promptIdContext: {
47
+ ...actual.promptIdContext,
48
+ getStore: vi.fn(),
49
+ run: vi.fn((_id, fn) => fn()),
50
+ },
51
+ };
52
+ });
53
+ const MockedGeminiChat = vi.mocked(GeminiChat);
54
+ const mockedGetDirectoryContextString = vi.mocked(getDirectoryContextString);
55
+ const mockedPromptIdContext = vi.mocked(promptIdContext);
56
+ const mockedLogAgentStart = vi.mocked(logAgentStart);
57
+ const mockedLogAgentFinish = vi.mocked(logAgentFinish);
58
+ // Constants for testing
59
+ const TASK_COMPLETE_TOOL_NAME = 'complete_task';
60
+ const MOCK_TOOL_NOT_ALLOWED = new MockTool({ name: 'write_file_interactive' });
61
+ /**
62
+ * Helper to create a mock API response chunk.
63
+ * Uses conditional spread to handle readonly functionCalls property safely.
64
+ */
36
65
  const createMockResponseChunk = (parts, functionCalls) => ({
37
66
  candidates: [{ index: 0, content: { role: 'model', parts } }],
38
- functionCalls,
67
+ ...(functionCalls && functionCalls.length > 0 ? { functionCalls } : {}),
39
68
  });
69
+ /**
70
+ * Helper to mock a single turn of model response in the stream.
71
+ */
40
72
  const mockModelResponse = (functionCalls, thought, text) => {
41
73
  const parts = [];
42
74
  if (thought) {
@@ -47,9 +79,7 @@ const mockModelResponse = (functionCalls, thought, text) => {
47
79
  }
48
80
  if (text)
49
81
  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);
82
+ const responseChunk = createMockResponseChunk(parts, functionCalls);
53
83
  mockSendMessageStream.mockImplementationOnce(async () => (async function* () {
54
84
  yield {
55
85
  type: StreamEventType.CHUNK,
@@ -57,38 +87,67 @@ const mockModelResponse = (functionCalls, thought, text) => {
57
87
  };
58
88
  })());
59
89
  };
90
+ /**
91
+ * Helper to extract the message parameters sent to sendMessageStream.
92
+ * Provides type safety for inspecting mock calls.
93
+ */
94
+ const getMockMessageParams = (callIndex) => {
95
+ const call = mockSendMessageStream.mock.calls[callIndex];
96
+ expect(call).toBeDefined();
97
+ // Arg 1 of sendMessageStream is the message parameters
98
+ return call[1];
99
+ };
60
100
  let mockConfig;
61
101
  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
- });
102
+ /**
103
+ * Type-safe helper to create agent definitions for tests.
104
+ */
105
+ const createTestDefinition = (tools = [LS_TOOL_NAME], runConfigOverrides = {}, outputConfigMode = 'default', schema = z.string()) => {
106
+ let outputConfig;
107
+ if (outputConfigMode === 'default') {
108
+ outputConfig = {
109
+ outputName: 'finalResult',
110
+ description: 'The final result.',
111
+ schema,
112
+ };
113
+ }
114
+ return {
115
+ name: 'TestAgent',
116
+ description: 'An agent for testing.',
117
+ inputConfig: {
118
+ inputs: { goal: { type: 'string', required: true, description: 'goal' } },
119
+ },
120
+ modelConfig: { model: 'gemini-test-model', temp: 0, top_p: 1 },
121
+ runConfig: { max_time_minutes: 5, max_turns: 5, ...runConfigOverrides },
122
+ promptConfig: { systemPrompt: 'Achieve the goal: ${goal}.' },
123
+ toolConfig: { tools },
124
+ outputConfig,
125
+ };
126
+ };
74
127
  describe('AgentExecutor', () => {
75
128
  let activities;
76
129
  let onActivity;
77
130
  let abortController;
78
131
  let signal;
79
132
  beforeEach(async () => {
80
- mockSendMessageStream.mockClear();
81
- mockExecuteToolCall.mockClear();
82
- vi.clearAllMocks();
83
- // Use fake timers for timeout and concurrency testing
133
+ vi.resetAllMocks();
134
+ mockSendMessageStream.mockReset();
135
+ mockExecuteToolCall.mockReset();
136
+ mockedLogAgentStart.mockReset();
137
+ mockedLogAgentFinish.mockReset();
138
+ mockedPromptIdContext.getStore.mockReset();
139
+ mockedPromptIdContext.run.mockImplementation((_id, fn) => fn());
140
+ MockedGeminiChat.mockImplementation(() => ({
141
+ sendMessageStream: mockSendMessageStream,
142
+ }));
84
143
  vi.useFakeTimers();
85
144
  mockConfig = makeFakeConfig();
86
145
  parentToolRegistry = new ToolRegistry(mockConfig);
87
146
  parentToolRegistry.registerTool(new LSTool(mockConfig));
88
- parentToolRegistry.registerTool(new ReadFileTool(mockConfig));
147
+ parentToolRegistry.registerTool(new MockTool({ name: READ_FILE_TOOL_NAME }));
89
148
  parentToolRegistry.registerTool(MOCK_TOOL_NOT_ALLOWED);
90
149
  vi.spyOn(mockConfig, 'getToolRegistry').mockResolvedValue(parentToolRegistry);
91
- vi.mocked(getDirectoryContextString).mockResolvedValue('Mocked Environment Context');
150
+ mockedGetDirectoryContextString.mockResolvedValue('Mocked Environment Context');
92
151
  activities = [];
93
152
  onActivity = (activity) => activities.push(activity);
94
153
  abortController = new AbortController();
@@ -99,319 +158,526 @@ describe('AgentExecutor', () => {
99
158
  });
100
159
  describe('create (Initialization and Validation)', () => {
101
160
  it('should create successfully with allowed tools', async () => {
102
- const definition = createTestDefinition([LSTool.Name]);
161
+ const definition = createTestDefinition([LS_TOOL_NAME]);
103
162
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
104
163
  expect(executor).toBeInstanceOf(AgentExecutor);
105
164
  });
106
165
  it('SECURITY: should throw if a tool is not on the non-interactive allowlist', async () => {
107
166
  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`);
167
+ await expect(AgentExecutor.create(definition, mockConfig, onActivity)).rejects.toThrow(/not on the allow-list for non-interactive execution/);
109
168
  });
110
169
  it('should create an isolated ToolRegistry for the agent', async () => {
111
- const definition = createTestDefinition([LSTool.Name, ReadFileTool.Name]);
170
+ const definition = createTestDefinition([
171
+ LS_TOOL_NAME,
172
+ READ_FILE_TOOL_NAME,
173
+ ]);
112
174
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
113
- // @ts-expect-error - accessing private property for test validation
114
- const agentRegistry = executor.toolRegistry;
175
+ const agentRegistry = executor['toolRegistry'];
115
176
  expect(agentRegistry).not.toBe(parentToolRegistry);
116
- expect(agentRegistry.getAllToolNames()).toEqual(expect.arrayContaining([LSTool.Name, ReadFileTool.Name]));
177
+ expect(agentRegistry.getAllToolNames()).toEqual(expect.arrayContaining([LS_TOOL_NAME, READ_FILE_TOOL_NAME]));
117
178
  expect(agentRegistry.getAllToolNames()).toHaveLength(2);
118
179
  expect(agentRegistry.getTool(MOCK_TOOL_NOT_ALLOWED.name)).toBeUndefined();
119
180
  });
181
+ it('should use parentPromptId from context to create agentId', async () => {
182
+ const parentId = 'parent-id';
183
+ mockedPromptIdContext.getStore.mockReturnValue(parentId);
184
+ const definition = createTestDefinition();
185
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
186
+ expect(executor['agentId']).toMatch(new RegExp(`^${parentId}-${definition.name}-`));
187
+ });
120
188
  });
121
189
  describe('run (Execution Loop and Logic)', () => {
122
- it('should execute a successful work and extraction phase (Happy Path) and emit activities', async () => {
190
+ it('should log AgentFinish with error if run throws', async () => {
191
+ const definition = createTestDefinition();
192
+ // Make the definition invalid to cause an error during run
193
+ definition.inputConfig.inputs = {
194
+ goal: { type: 'string', required: true, description: 'goal' },
195
+ };
196
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
197
+ // Run without inputs to trigger validation error
198
+ await expect(executor.run({}, signal)).rejects.toThrow(/Missing required input parameters/);
199
+ expect(mockedLogAgentStart).toHaveBeenCalledTimes(1);
200
+ expect(mockedLogAgentFinish).toHaveBeenCalledTimes(1);
201
+ expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
202
+ terminate_reason: AgentTerminateMode.ERROR,
203
+ }));
204
+ });
205
+ it('should execute successfully when model calls complete_task with output (Happy Path with Output)', async () => {
123
206
  const definition = createTestDefinition();
124
207
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
125
208
  const inputs = { goal: 'Find files' };
126
209
  // Turn 1: Model calls ls
127
- mockModelResponse([{ name: LSTool.Name, args: { path: '.' }, id: 'call1' }], 'T1: Listing');
210
+ mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' }], 'T1: Listing');
128
211
  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',
212
+ status: 'success',
213
+ request: {
214
+ callId: 'call1',
215
+ name: LS_TOOL_NAME,
216
+ args: { path: '.' },
217
+ isClientInitiated: false,
218
+ prompt_id: 'test-prompt',
219
+ },
220
+ tool: {},
221
+ invocation: {},
222
+ response: {
223
+ callId: 'call1',
224
+ resultDisplay: 'file1.txt',
225
+ responseParts: [
226
+ {
227
+ functionResponse: {
228
+ name: LS_TOOL_NAME,
229
+ response: { result: 'file1.txt' },
230
+ id: 'call1',
231
+ },
137
232
  },
138
- },
139
- ],
140
- error: undefined,
233
+ ],
234
+ error: undefined,
235
+ errorType: undefined,
236
+ contentLength: undefined,
237
+ },
141
238
  });
142
- // Turn 2: Model stops
143
- mockModelResponse([], 'T2: Done');
144
- // Extraction Phase
145
- mockModelResponse([], undefined, 'Result: file1.txt.');
239
+ // Turn 2: Model calls complete_task with required output
240
+ mockModelResponse([
241
+ {
242
+ name: TASK_COMPLETE_TOOL_NAME,
243
+ args: { finalResult: 'Found file1.txt' },
244
+ id: 'call2',
245
+ },
246
+ ], 'T2: Done');
146
247
  const output = await executor.run(inputs, signal);
147
- expect(mockSendMessageStream).toHaveBeenCalledTimes(3);
148
- expect(mockExecuteToolCall).toHaveBeenCalledTimes(1);
149
- // Verify System Prompt Templating
248
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
150
249
  const chatConstructorArgs = MockedGeminiChat.mock.calls[0];
151
250
  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.');
251
+ expect(chatConfig?.systemInstruction).toContain(`MUST call the \`${TASK_COMPLETE_TOOL_NAME}\` tool`);
252
+ const turn1Params = getMockMessageParams(0);
253
+ const firstToolGroup = turn1Params.config?.tools?.[0];
254
+ expect(firstToolGroup).toBeDefined();
255
+ if (!firstToolGroup || !('functionDeclarations' in firstToolGroup)) {
256
+ throw new Error('Test expectation failed: Config does not contain functionDeclarations.');
257
+ }
258
+ const sentTools = firstToolGroup.functionDeclarations;
259
+ expect(sentTools).toBeDefined();
260
+ expect(sentTools).toEqual(expect.arrayContaining([
261
+ expect.objectContaining({ name: LS_TOOL_NAME }),
262
+ expect.objectContaining({ name: TASK_COMPLETE_TOOL_NAME }),
263
+ ]));
264
+ const completeToolDef = sentTools.find((t) => t.name === TASK_COMPLETE_TOOL_NAME);
265
+ expect(completeToolDef?.parameters?.required).toContain('finalResult');
266
+ expect(output.result).toBe('Found file1.txt');
170
267
  expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
171
- // Verify Activity Stream (Observability)
268
+ // Telemetry checks
269
+ expect(mockedLogAgentStart).toHaveBeenCalledTimes(1);
270
+ expect(mockedLogAgentStart).toHaveBeenCalledWith(mockConfig, expect.any(AgentStartEvent));
271
+ expect(mockedLogAgentFinish).toHaveBeenCalledTimes(1);
272
+ expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.any(AgentFinishEvent));
273
+ const finishEvent = mockedLogAgentFinish.mock.calls[0][1];
274
+ expect(finishEvent.terminate_reason).toBe(AgentTerminateMode.GOAL);
275
+ // Context checks
276
+ expect(mockedPromptIdContext.run).toHaveBeenCalledTimes(2); // Two turns
277
+ const agentId = executor['agentId'];
278
+ expect(mockedPromptIdContext.run).toHaveBeenNthCalledWith(1, `${agentId}#0`, expect.any(Function));
279
+ expect(mockedPromptIdContext.run).toHaveBeenNthCalledWith(2, `${agentId}#1`, expect.any(Function));
172
280
  expect(activities).toEqual(expect.arrayContaining([
173
- // Thought subjects are extracted by the executor (parseThought)
174
281
  expect.objectContaining({
175
282
  type: 'THOUGHT_CHUNK',
176
283
  data: { text: 'T1: Listing' },
177
284
  }),
178
285
  expect.objectContaining({
179
- type: 'TOOL_CALL_START',
180
- data: { name: LSTool.Name, args: { path: '.' } },
286
+ type: 'TOOL_CALL_END',
287
+ data: { name: LS_TOOL_NAME, output: 'file1.txt' },
181
288
  }),
182
289
  expect.objectContaining({
183
- type: 'TOOL_CALL_END',
184
- data: { name: LSTool.Name, output: 'file1.txt' },
290
+ type: 'TOOL_CALL_START',
291
+ data: {
292
+ name: TASK_COMPLETE_TOOL_NAME,
293
+ args: { finalResult: 'Found file1.txt' },
294
+ },
185
295
  }),
186
296
  expect.objectContaining({
187
- type: 'THOUGHT_CHUNK',
188
- data: { text: 'T2: Done' },
297
+ type: 'TOOL_CALL_END',
298
+ data: {
299
+ name: TASK_COMPLETE_TOOL_NAME,
300
+ output: expect.stringContaining('Output submitted'),
301
+ },
189
302
  }),
190
303
  ]));
191
304
  });
192
- it('should execute parallel tool calls concurrently', async () => {
193
- const definition = createTestDefinition([LSTool.Name, ReadFileTool.Name]);
305
+ it('should execute successfully when model calls complete_task without output (Happy Path No Output)', async () => {
306
+ const definition = createTestDefinition([LS_TOOL_NAME], {}, 'none');
194
307
  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}`,
308
+ mockModelResponse([
309
+ { name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' },
310
+ ]);
311
+ mockExecuteToolCall.mockResolvedValueOnce({
312
+ status: 'success',
313
+ request: {
314
+ callId: 'call1',
315
+ name: LS_TOOL_NAME,
316
+ args: { path: '.' },
317
+ isClientInitiated: false,
318
+ prompt_id: 'test-prompt',
319
+ },
320
+ tool: {},
321
+ invocation: {},
322
+ response: {
323
+ callId: 'call1',
324
+ resultDisplay: 'ok',
220
325
  responseParts: [
221
326
  {
222
327
  functionResponse: {
223
- name: reqInfo.name,
328
+ name: LS_TOOL_NAME,
224
329
  response: {},
225
- id: reqInfo.callId,
330
+ id: 'call1',
226
331
  },
227
332
  },
228
333
  ],
229
334
  error: undefined,
230
- };
335
+ errorType: undefined,
336
+ contentLength: undefined,
337
+ },
231
338
  });
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
- }));
339
+ mockModelResponse([{ name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call2' }], 'Task finished.');
340
+ const output = await executor.run({ goal: 'Do work' }, signal);
341
+ const turn1Params = getMockMessageParams(0);
342
+ const firstToolGroup = turn1Params.config?.tools?.[0];
343
+ expect(firstToolGroup).toBeDefined();
344
+ if (!firstToolGroup || !('functionDeclarations' in firstToolGroup)) {
345
+ throw new Error('Test expectation failed: Config does not contain functionDeclarations.');
346
+ }
347
+ const sentTools = firstToolGroup.functionDeclarations;
348
+ expect(sentTools).toBeDefined();
349
+ const completeToolDef = sentTools.find((t) => t.name === TASK_COMPLETE_TOOL_NAME);
350
+ expect(completeToolDef?.parameters?.required).toEqual([]);
351
+ expect(completeToolDef?.description).toContain('signal that you have completed');
352
+ expect(output.result).toBe('Task completed successfully.');
353
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
254
354
  });
255
- it('should handle tool execution failure gracefully and report error', async () => {
256
- const definition = createTestDefinition([LSTool.Name]);
355
+ it('should error immediately if the model stops tools without calling complete_task (Protocol Violation)', async () => {
356
+ const definition = createTestDefinition();
257
357
  const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
258
- // Turn 1: Model calls ls, but it fails
259
358
  mockModelResponse([
260
- { name: LSTool.Name, args: { path: '/invalid' }, id: 'call1' },
359
+ { name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' },
261
360
  ]);
262
- const errorMessage = 'Internal failure.';
263
361
  mockExecuteToolCall.mockResolvedValueOnce({
264
- callId: 'call1',
265
- resultDisplay: `Error: ${errorMessage}`,
266
- responseParts: undefined, // Failed tools might return undefined parts
267
- error: { message: errorMessage },
362
+ status: 'success',
363
+ request: {
364
+ callId: 'call1',
365
+ name: LS_TOOL_NAME,
366
+ args: { path: '.' },
367
+ isClientInitiated: false,
368
+ prompt_id: 'test-prompt',
369
+ },
370
+ tool: {},
371
+ invocation: {},
372
+ response: {
373
+ callId: 'call1',
374
+ resultDisplay: 'ok',
375
+ responseParts: [
376
+ {
377
+ functionResponse: {
378
+ name: LS_TOOL_NAME,
379
+ response: {},
380
+ id: 'call1',
381
+ },
382
+ },
383
+ ],
384
+ error: undefined,
385
+ errorType: undefined,
386
+ contentLength: undefined,
387
+ },
268
388
  });
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
389
+ mockModelResponse([], 'I think I am done.');
390
+ const output = await executor.run({ goal: 'Strict test' }, signal);
391
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
392
+ const expectedError = `Agent stopped calling tools but did not call '${TASK_COMPLETE_TOOL_NAME}' to finalize the session.`;
393
+ expect(output.terminate_reason).toBe(AgentTerminateMode.ERROR);
394
+ expect(output.result).toBe(expectedError);
395
+ // Telemetry check for error
396
+ expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
397
+ terminate_reason: AgentTerminateMode.ERROR,
398
+ }));
399
+ expect(activities).toContainEqual(expect.objectContaining({
400
+ type: 'ERROR',
401
+ data: expect.objectContaining({
402
+ context: 'protocol_violation',
403
+ error: expectedError,
404
+ }),
405
+ }));
406
+ });
407
+ it('should report an error if complete_task is called with missing required arguments', async () => {
408
+ const definition = createTestDefinition();
409
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
410
+ // Turn 1: Missing arg
411
+ mockModelResponse([
412
+ {
413
+ name: TASK_COMPLETE_TOOL_NAME,
414
+ args: { wrongArg: 'oops' },
415
+ id: 'call1',
416
+ },
417
+ ]);
418
+ // Turn 2: Corrected
419
+ mockModelResponse([
420
+ {
421
+ name: TASK_COMPLETE_TOOL_NAME,
422
+ args: { finalResult: 'Corrected result' },
423
+ id: 'call2',
424
+ },
425
+ ]);
426
+ const output = await executor.run({ goal: 'Error test' }, signal);
427
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
428
+ const expectedError = "Missing required argument 'finalResult' for completion.";
274
429
  expect(activities).toContainEqual(expect.objectContaining({
275
430
  type: 'ERROR',
276
431
  data: {
277
- error: errorMessage,
278
432
  context: 'tool_call',
279
- name: LSTool.Name,
433
+ name: TASK_COMPLETE_TOOL_NAME,
434
+ error: expectedError,
280
435
  },
281
436
  }));
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([
437
+ const turn2Params = getMockMessageParams(1);
438
+ const turn2Parts = turn2Params.message;
439
+ expect(turn2Parts).toBeDefined();
440
+ expect(turn2Parts).toHaveLength(1);
441
+ expect(turn2Parts[0]).toEqual(expect.objectContaining({
442
+ functionResponse: expect.objectContaining({
443
+ name: TASK_COMPLETE_TOOL_NAME,
444
+ response: { error: expectedError },
445
+ id: 'call1',
446
+ }),
447
+ }));
448
+ expect(output.result).toBe('Corrected result');
449
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
450
+ });
451
+ it('should handle multiple calls to complete_task in the same turn (accept first, block rest)', async () => {
452
+ const definition = createTestDefinition([], {}, 'none');
453
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
454
+ // Turn 1: Duplicate calls
455
+ mockModelResponse([
456
+ { name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call1' },
457
+ { name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call2' },
458
+ ]);
459
+ const output = await executor.run({ goal: 'Dup test' }, signal);
460
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
461
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
462
+ const completions = activities.filter((a) => a.type === 'TOOL_CALL_END' &&
463
+ a.data['name'] === TASK_COMPLETE_TOOL_NAME);
464
+ const errors = activities.filter((a) => a.type === 'ERROR' && a.data['name'] === TASK_COMPLETE_TOOL_NAME);
465
+ expect(completions).toHaveLength(1);
466
+ expect(errors).toHaveLength(1);
467
+ expect(errors[0].data['error']).toContain('Task already marked complete in this turn');
468
+ });
469
+ it('should execute parallel tool calls and then complete', async () => {
470
+ const definition = createTestDefinition([LS_TOOL_NAME]);
471
+ const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
472
+ const call1 = {
473
+ name: LS_TOOL_NAME,
474
+ args: { path: '/a' },
475
+ id: 'c1',
476
+ };
477
+ const call2 = {
478
+ name: LS_TOOL_NAME,
479
+ args: { path: '/b' },
480
+ id: 'c2',
481
+ };
482
+ // Turn 1: Parallel calls
483
+ mockModelResponse([call1, call2]);
484
+ // Concurrency mock
485
+ let callsStarted = 0;
486
+ let resolveCalls;
487
+ const bothStarted = new Promise((r) => {
488
+ resolveCalls = r;
489
+ });
490
+ mockExecuteToolCall.mockImplementation(async (_ctx, reqInfo) => {
491
+ callsStarted++;
492
+ if (callsStarted === 2)
493
+ resolveCalls();
494
+ await vi.advanceTimersByTimeAsync(100);
495
+ return {
496
+ status: 'success',
497
+ request: reqInfo,
498
+ tool: {},
499
+ invocation: {},
500
+ response: {
501
+ callId: reqInfo.callId,
502
+ resultDisplay: 'ok',
503
+ responseParts: [
504
+ {
505
+ functionResponse: {
506
+ name: reqInfo.name,
507
+ response: {},
508
+ id: reqInfo.callId,
509
+ },
510
+ },
511
+ ],
512
+ error: undefined,
513
+ errorType: undefined,
514
+ contentLength: undefined,
515
+ },
516
+ };
517
+ });
518
+ // Turn 2: Completion
519
+ mockModelResponse([
286
520
  {
287
- text: 'All tool calls failed. Please analyze the errors and try an alternative approach.',
521
+ name: TASK_COMPLETE_TOOL_NAME,
522
+ args: { finalResult: 'done' },
523
+ id: 'c3',
288
524
  },
289
525
  ]);
526
+ const runPromise = executor.run({ goal: 'Parallel' }, signal);
527
+ await vi.advanceTimersByTimeAsync(1);
528
+ await bothStarted;
529
+ await vi.advanceTimersByTimeAsync(150);
530
+ await vi.advanceTimersByTimeAsync(1);
531
+ const output = await runPromise;
532
+ expect(mockExecuteToolCall).toHaveBeenCalledTimes(2);
533
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
534
+ // Safe access to message parts
535
+ const turn2Params = getMockMessageParams(1);
536
+ const parts = turn2Params.message;
537
+ expect(parts).toBeDefined();
538
+ expect(parts).toHaveLength(2);
539
+ expect(parts).toEqual(expect.arrayContaining([
540
+ expect.objectContaining({
541
+ functionResponse: expect.objectContaining({ id: 'c1' }),
542
+ }),
543
+ expect.objectContaining({
544
+ functionResponse: expect.objectContaining({ id: 'c2' }),
545
+ }),
546
+ ]));
290
547
  });
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]);
548
+ it('SECURITY: should block unauthorized tools and provide explicit failure to model', async () => {
549
+ const definition = createTestDefinition([LS_TOOL_NAME]);
294
550
  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)
551
+ // Turn 1: Model tries to use a tool not in its config
552
+ const badCallId = 'bad_call_1';
297
553
  mockModelResponse([
298
554
  {
299
- name: ReadFileTool.Name,
300
- args: { path: 'config.txt' },
301
- id: 'call_blocked',
555
+ name: READ_FILE_TOOL_NAME,
556
+ args: { path: 'secret.txt' },
557
+ id: badCallId,
558
+ },
559
+ ]);
560
+ // Turn 2: Model gives up and completes
561
+ mockModelResponse([
562
+ {
563
+ name: TASK_COMPLETE_TOOL_NAME,
564
+ args: { finalResult: 'Could not read file.' },
565
+ id: 'c2',
302
566
  },
303
567
  ]);
304
- // Turn 2: Model stops
305
- mockModelResponse([]);
306
- // Extraction
307
- mockModelResponse([], undefined, 'Done.');
308
568
  const consoleWarnSpy = vi
309
569
  .spyOn(console, 'warn')
310
570
  .mockImplementation(() => { });
311
- await executor.run({ goal: 'Security test' }, signal);
312
- // Verify executeToolCall was NEVER called because the tool was unauthorized
571
+ await executor.run({ goal: 'Sec test' }, signal);
572
+ // Verify external executor was not called (Security held)
313
573
  expect(mockExecuteToolCall).not.toHaveBeenCalled();
314
- expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining(`attempted to call unauthorized tool '${ReadFileTool.Name}'`));
574
+ // 2. Verify console warning
575
+ expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining(`[AgentExecutor] Blocked call:`));
315
576
  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'],
325
- });
326
- 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');
577
+ // Verify specific error was sent back to model
578
+ const turn2Params = getMockMessageParams(1);
579
+ const parts = turn2Params.message;
580
+ expect(parts).toBeDefined();
581
+ expect(parts[0]).toEqual(expect.objectContaining({
582
+ functionResponse: expect.objectContaining({
583
+ id: badCallId,
584
+ name: READ_FILE_TOOL_NAME,
585
+ response: {
586
+ error: expect.stringContaining('Unauthorized tool call'),
587
+ },
588
+ }),
589
+ }));
590
+ // Verify Activity Stream reported the error
591
+ expect(activities).toContainEqual(expect.objectContaining({
592
+ type: 'ERROR',
593
+ data: expect.objectContaining({
594
+ context: 'tool_call_unauthorized',
595
+ name: READ_FILE_TOOL_NAME,
596
+ }),
597
+ }));
340
598
  });
341
599
  });
342
600
  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,
601
+ const mockWorkResponse = (id) => {
602
+ mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
603
+ mockExecuteToolCall.mockResolvedValueOnce({
604
+ status: 'success',
605
+ request: {
606
+ callId: id,
607
+ name: LS_TOOL_NAME,
608
+ args: { path: '.' },
609
+ isClientInitiated: false,
610
+ prompt_id: 'test-prompt',
611
+ },
612
+ tool: {},
613
+ invocation: {},
614
+ response: {
615
+ callId: id,
616
+ resultDisplay: 'ok',
617
+ responseParts: [
618
+ { functionResponse: { name: LS_TOOL_NAME, response: {}, id } },
619
+ ],
620
+ error: undefined,
621
+ errorType: undefined,
622
+ contentLength: undefined,
623
+ },
352
624
  });
353
625
  };
354
626
  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,
627
+ const MAX = 2;
628
+ const definition = createTestDefinition([LS_TOOL_NAME], {
629
+ max_turns: MAX,
358
630
  });
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);
631
+ const executor = await AgentExecutor.create(definition, mockConfig);
632
+ mockWorkResponse('t1');
633
+ mockWorkResponse('t2');
634
+ const output = await executor.run({ goal: 'Turns test' }, signal);
365
635
  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'));
636
+ expect(mockSendMessageStream).toHaveBeenCalledTimes(MAX);
369
637
  });
370
638
  it('should terminate if timeout is reached', async () => {
371
- const definition = createTestDefinition([LSTool.Name], {
372
- max_time_minutes: 5,
373
- max_turns: 100,
639
+ const definition = createTestDefinition([LS_TOOL_NAME], {
640
+ max_time_minutes: 1,
374
641
  });
375
- 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);
642
+ const executor = await AgentExecutor.create(definition, mockConfig);
643
+ mockModelResponse([
644
+ { name: LS_TOOL_NAME, args: { path: '.' }, id: 't1' },
645
+ ]);
646
+ // Long running tool
647
+ mockExecuteToolCall.mockImplementationOnce(async (_ctx, reqInfo) => {
648
+ await vi.advanceTimersByTimeAsync(61 * 1000);
382
649
  return {
383
- callId: 'loop',
384
- resultDisplay: 'ok',
385
- responseParts: [
386
- {
387
- functionResponse: { name: LSTool.Name, response: {}, id: 'loop' },
388
- },
389
- ],
390
- error: undefined,
650
+ status: 'success',
651
+ request: reqInfo,
652
+ tool: {},
653
+ invocation: {},
654
+ response: {
655
+ callId: 't1',
656
+ resultDisplay: 'ok',
657
+ responseParts: [],
658
+ error: undefined,
659
+ errorType: undefined,
660
+ contentLength: undefined,
661
+ },
391
662
  };
392
663
  });
393
- const output = await executor.run({ goal: 'Termination test' }, signal);
664
+ const output = await executor.run({ goal: 'Timeout test' }, signal);
394
665
  expect(output.terminate_reason).toBe(AgentTerminateMode.TIMEOUT);
395
- // Should only have called the model once before the timeout check stopped it
396
666
  expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
397
667
  });
398
- it('should terminate when AbortSignal is triggered mid-stream', async () => {
668
+ it('should terminate when AbortSignal is triggered', async () => {
399
669
  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
670
+ const executor = await AgentExecutor.create(definition, mockConfig);
671
+ mockSendMessageStream.mockImplementationOnce(async () => (async function* () {
404
672
  yield {
405
673
  type: StreamEventType.CHUNK,
406
674
  value: createMockResponseChunk([
407
- { text: '**Thinking** Step 1', thought: true },
675
+ { text: 'Thinking...', thought: true },
408
676
  ]),
409
677
  };
410
- // Simulate abort happening mid-stream
411
678
  abortController.abort();
412
- // The loop in callModel should break immediately due to signal check.
413
679
  })());
414
- const output = await executor.run({ goal: 'Termination test' }, signal);
680
+ const output = await executor.run({ goal: 'Abort test' }, signal);
415
681
  expect(output.terminate_reason).toBe(AgentTerminateMode.ABORTED);
416
682
  });
417
683
  });