@machina.ai/cell-cli 1.41.1-rc1 → 1.45.1-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 (562) hide show
  1. package/dist/index.js +17 -17
  2. package/dist/index.js.map +1 -1
  3. package/dist/package.json +5 -5
  4. package/dist/src/acp/README.md +81 -0
  5. package/dist/src/acp/{commandHandler.d.ts → acpCommandHandler.d.ts} +1 -1
  6. package/dist/src/acp/{commandHandler.js → acpCommandHandler.js} +2 -2
  7. package/dist/src/acp/acpCommandHandler.js.map +1 -0
  8. package/dist/src/acp/{commandHandler.test.js → acpCommandHandler.test.js} +4 -5
  9. package/dist/src/acp/acpCommandHandler.test.js.map +1 -0
  10. package/dist/src/acp/acpErrors.d.ts +1 -1
  11. package/dist/src/acp/acpErrors.js +1 -1
  12. package/dist/src/acp/acpErrors.test.d.ts +1 -1
  13. package/dist/src/acp/acpErrors.test.js +1 -1
  14. package/dist/src/acp/{fileSystemService.d.ts → acpFileSystemService.d.ts} +1 -1
  15. package/dist/src/acp/{fileSystemService.js → acpFileSystemService.js} +7 -4
  16. package/dist/src/acp/acpFileSystemService.js.map +1 -0
  17. package/dist/src/acp/{fileSystemService.test.js → acpFileSystemService.test.js} +3 -3
  18. package/dist/src/acp/acpFileSystemService.test.js.map +1 -0
  19. package/dist/src/acp/acpResume.test.d.ts +1 -1
  20. package/dist/src/acp/acpResume.test.js +12 -7
  21. package/dist/src/acp/acpResume.test.js.map +1 -1
  22. package/dist/src/acp/acpRpcDispatcher.d.ts +28 -0
  23. package/dist/src/acp/acpRpcDispatcher.js +177 -0
  24. package/dist/src/acp/acpRpcDispatcher.js.map +1 -0
  25. package/dist/src/acp/acpRpcDispatcher.test.d.ts +6 -0
  26. package/dist/src/acp/acpRpcDispatcher.test.js +238 -0
  27. package/dist/src/acp/acpRpcDispatcher.test.js.map +1 -0
  28. package/dist/src/acp/acpSession.d.ts +36 -0
  29. package/dist/src/acp/{acpClient.js → acpSession.js} +333 -774
  30. package/dist/src/acp/acpSession.js.map +1 -0
  31. package/dist/src/acp/acpSession.test.d.ts +6 -0
  32. package/dist/src/acp/acpSession.test.js +739 -0
  33. package/dist/src/acp/acpSession.test.js.map +1 -0
  34. package/dist/src/acp/acpSessionManager.d.ts +30 -0
  35. package/dist/src/acp/acpSessionManager.js +206 -0
  36. package/dist/src/acp/acpSessionManager.js.map +1 -0
  37. package/dist/src/acp/acpSessionManager.test.d.ts +6 -0
  38. package/dist/src/acp/acpSessionManager.test.js +283 -0
  39. package/dist/src/acp/acpSessionManager.test.js.map +1 -0
  40. package/dist/src/acp/acpStdioTransport.d.ts +9 -0
  41. package/dist/src/acp/acpStdioTransport.js +23 -0
  42. package/dist/src/acp/acpStdioTransport.js.map +1 -0
  43. package/dist/src/acp/acpUtils.d.ts +56 -0
  44. package/dist/src/acp/acpUtils.js +288 -0
  45. package/dist/src/acp/acpUtils.js.map +1 -0
  46. package/dist/src/acp/commands/commandRegistry.d.ts +1 -1
  47. package/dist/src/acp/commands/commandRegistry.js +1 -1
  48. package/dist/src/acp/commands/extensions.d.ts +1 -1
  49. package/dist/src/acp/commands/extensions.js +1 -1
  50. package/dist/src/acp/commands/extensions.test.d.ts +6 -0
  51. package/dist/src/acp/commands/extensions.test.js +70 -0
  52. package/dist/src/acp/commands/extensions.test.js.map +1 -0
  53. package/dist/src/acp/commands/init.d.ts +1 -1
  54. package/dist/src/acp/commands/init.js +1 -1
  55. package/dist/src/acp/commands/memory.d.ts +3 -8
  56. package/dist/src/acp/commands/memory.js +18 -47
  57. package/dist/src/acp/commands/memory.js.map +1 -1
  58. package/dist/src/acp/commands/restore.d.ts +1 -1
  59. package/dist/src/acp/commands/restore.js +1 -1
  60. package/dist/src/acp/commands/restore.test.js +7 -13
  61. package/dist/src/acp/commands/restore.test.js.map +1 -1
  62. package/dist/src/acp/commands/types.d.ts +1 -1
  63. package/dist/src/acp/commands/types.js +1 -1
  64. package/dist/src/commands/extensions/configure.test.js +6 -2
  65. package/dist/src/commands/extensions/configure.test.js.map +1 -1
  66. package/dist/src/commands/extensions/utils.d.ts +1 -1
  67. package/dist/src/commands/extensions/utils.js +1 -2
  68. package/dist/src/commands/extensions/utils.js.map +1 -1
  69. package/dist/src/commands/mcp/list.js +19 -5
  70. package/dist/src/commands/mcp/list.js.map +1 -1
  71. package/dist/src/commands/mcp/list.test.js +285 -100
  72. package/dist/src/commands/mcp/list.test.js.map +1 -1
  73. package/dist/src/config/auth.d.ts +1 -1
  74. package/dist/src/config/auth.js +4 -3
  75. package/dist/src/config/auth.js.map +1 -1
  76. package/dist/src/config/auth.test.js +11 -4
  77. package/dist/src/config/auth.test.js.map +1 -1
  78. package/dist/src/config/config.d.ts +4 -0
  79. package/dist/src/config/config.js +65 -47
  80. package/dist/src/config/config.js.map +1 -1
  81. package/dist/src/config/config.test.js +25 -106
  82. package/dist/src/config/config.test.js.map +1 -1
  83. package/dist/src/config/extension-manager-agents.test.js +2 -0
  84. package/dist/src/config/extension-manager-agents.test.js.map +1 -1
  85. package/dist/src/config/extension-manager-hydration.test.js +2 -0
  86. package/dist/src/config/extension-manager-hydration.test.js.map +1 -1
  87. package/dist/src/config/extension-manager-scope.test.js +4 -2
  88. package/dist/src/config/extension-manager-scope.test.js.map +1 -1
  89. package/dist/src/config/extension-manager-themes.spec.js +2 -0
  90. package/dist/src/config/extension-manager-themes.spec.js.map +1 -1
  91. package/dist/src/config/extension-manager.d.ts +2 -2
  92. package/dist/src/config/extension-manager.js +2 -1
  93. package/dist/src/config/extension-manager.js.map +1 -1
  94. package/dist/src/config/extensionRegistryClient.js +0 -1
  95. package/dist/src/config/extensionRegistryClient.js.map +1 -1
  96. package/dist/src/config/extensions/consent.d.ts +1 -1
  97. package/dist/src/config/extensions/consent.js +5 -4
  98. package/dist/src/config/extensions/consent.js.map +1 -1
  99. package/dist/src/config/extensions/consent.test.js +22 -0
  100. package/dist/src/config/extensions/consent.test.js.map +1 -1
  101. package/dist/src/config/extensions/extensionEnablement.js +4 -2
  102. package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
  103. package/dist/src/config/extensions/extensionSettings.d.ts +3 -3
  104. package/dist/src/config/extensions/extensionSettings.js +7 -3
  105. package/dist/src/config/extensions/extensionSettings.js.map +1 -1
  106. package/dist/src/config/extensions/variables.js +1 -3
  107. package/dist/src/config/extensions/variables.js.map +1 -1
  108. package/dist/src/config/footerItems.d.ts +4 -0
  109. package/dist/src/config/footerItems.js +6 -0
  110. package/dist/src/config/footerItems.js.map +1 -1
  111. package/dist/src/config/footerItems.test.js +1 -0
  112. package/dist/src/config/footerItems.test.js.map +1 -1
  113. package/dist/src/config/mcp/mcpServerEnablement.js +1 -1
  114. package/dist/src/config/mcp/mcpServerEnablement.js.map +1 -1
  115. package/dist/src/config/mutual-exclusivity.test.js +33 -0
  116. package/dist/src/config/mutual-exclusivity.test.js.map +1 -0
  117. package/dist/src/config/settings-env-isolation.test.d.ts +6 -0
  118. package/dist/src/config/settings-env-isolation.test.js +188 -0
  119. package/dist/src/config/settings-env-isolation.test.js.map +1 -0
  120. package/dist/src/config/settings.d.ts +15 -1
  121. package/dist/src/config/settings.js +89 -17
  122. package/dist/src/config/settings.js.map +1 -1
  123. package/dist/src/config/settings.test.js +167 -0
  124. package/dist/src/config/settings.test.js.map +1 -1
  125. package/dist/src/config/settingsSchema.d.ts +56 -25
  126. package/dist/src/config/settingsSchema.js +66 -27
  127. package/dist/src/config/settingsSchema.js.map +1 -1
  128. package/dist/src/config/settingsSchema.test.js +8 -0
  129. package/dist/src/config/settingsSchema.test.js.map +1 -1
  130. package/dist/src/config/skipExtensions.test.d.ts +6 -0
  131. package/dist/src/config/skipExtensions.test.js +49 -0
  132. package/dist/src/config/skipExtensions.test.js.map +1 -0
  133. package/dist/src/config/workspace-policy-cli.test.js +0 -5
  134. package/dist/src/config/workspace-policy-cli.test.js.map +1 -1
  135. package/dist/src/gemini.d.ts +2 -2
  136. package/dist/src/gemini.js +120 -31
  137. package/dist/src/gemini.js.map +1 -1
  138. package/dist/src/gemini.test.js +147 -16
  139. package/dist/src/gemini.test.js.map +1 -1
  140. package/dist/src/gemini_cleanup.test.js +1 -1
  141. package/dist/src/gemini_cleanup.test.js.map +1 -1
  142. package/dist/src/generated/git-commit.d.ts +2 -2
  143. package/dist/src/generated/git-commit.js +2 -2
  144. package/dist/src/interactiveCli.js +1 -1
  145. package/dist/src/interactiveCli.js.map +1 -1
  146. package/dist/src/nonInteractiveCli.d.ts +7 -0
  147. package/dist/src/nonInteractiveCli.js +56 -6
  148. package/dist/src/nonInteractiveCli.js.map +1 -1
  149. package/dist/src/nonInteractiveCli.test.js +253 -18
  150. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  151. package/dist/src/nonInteractiveCliAgentSession.d.ts +7 -0
  152. package/dist/src/nonInteractiveCliAgentSession.js +22 -3
  153. package/dist/src/nonInteractiveCliAgentSession.js.map +1 -1
  154. package/dist/src/nonInteractiveCliAgentSession.test.js +200 -20
  155. package/dist/src/nonInteractiveCliAgentSession.test.js.map +1 -1
  156. package/dist/src/output-redirection.test.d.ts +6 -0
  157. package/dist/src/output-redirection.test.js +77 -0
  158. package/dist/src/output-redirection.test.js.map +1 -0
  159. package/dist/src/patches/http-proxy-agent.d.ts +6 -0
  160. package/dist/src/patches/http-proxy-agent.js +8 -0
  161. package/dist/src/patches/http-proxy-agent.js.map +1 -0
  162. package/dist/src/patches/https-proxy-agent.d.ts +6 -0
  163. package/dist/src/patches/https-proxy-agent.js +8 -0
  164. package/dist/src/patches/https-proxy-agent.js.map +1 -0
  165. package/dist/src/services/BuiltinCommandLoader.js +5 -1
  166. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  167. package/dist/src/services/BuiltinCommandLoader.test.js +6 -1
  168. package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -1
  169. package/dist/src/services/FileCommandLoader.d.ts +21 -0
  170. package/dist/src/services/FileCommandLoader.js +58 -6
  171. package/dist/src/services/FileCommandLoader.js.map +1 -1
  172. package/dist/src/services/FileCommandLoader.test.js +27 -1
  173. package/dist/src/services/FileCommandLoader.test.js.map +1 -1
  174. package/dist/src/test-utils/mockCommandContext.js +6 -1
  175. package/dist/src/test-utils/mockCommandContext.js.map +1 -1
  176. package/dist/src/test-utils/mockConfig.js +0 -3
  177. package/dist/src/test-utils/mockConfig.js.map +1 -1
  178. package/dist/src/test-utils/render.js +1 -0
  179. package/dist/src/test-utils/render.js.map +1 -1
  180. package/dist/src/test-utils/settings.d.ts +1 -0
  181. package/dist/src/test-utils/settings.js.map +1 -1
  182. package/dist/src/ui/AppContainer.js +60 -73
  183. package/dist/src/ui/AppContainer.js.map +1 -1
  184. package/dist/src/ui/AppContainer.test.js +101 -1
  185. package/dist/src/ui/AppContainer.test.js.map +1 -1
  186. package/dist/src/ui/auth/AuthDialog.js +6 -3
  187. package/dist/src/ui/auth/AuthDialog.js.map +1 -1
  188. package/dist/src/ui/auth/AuthDialog.test.js +33 -10
  189. package/dist/src/ui/auth/AuthDialog.test.js.map +1 -1
  190. package/dist/src/ui/auth/LoginRestartDialog.d.ts +13 -0
  191. package/dist/src/ui/auth/{LoginWithGoogleRestartDialog.js → LoginRestartDialog.js} +7 -6
  192. package/dist/src/ui/auth/LoginRestartDialog.js.map +1 -0
  193. package/dist/src/ui/auth/LoginRestartDialog.test.d.ts +6 -0
  194. package/dist/src/ui/auth/{LoginWithGoogleRestartDialog.test.js → LoginRestartDialog.test.js} +13 -8
  195. package/dist/src/ui/auth/LoginRestartDialog.test.js.map +1 -0
  196. package/dist/src/ui/auth/useAuth.d.ts +1 -1
  197. package/dist/src/ui/auth/useAuth.js +2 -2
  198. package/dist/src/ui/auth/useAuth.js.map +1 -1
  199. package/dist/src/ui/auth/useAuth.test.js +10 -10
  200. package/dist/src/ui/auth/useAuth.test.js.map +1 -1
  201. package/dist/src/ui/commands/agentsCommand.js +19 -2
  202. package/dist/src/ui/commands/agentsCommand.js.map +1 -1
  203. package/dist/src/ui/commands/agentsCommand.test.js +34 -3
  204. package/dist/src/ui/commands/agentsCommand.test.js.map +1 -1
  205. package/dist/src/ui/commands/bugCommand.js +36 -0
  206. package/dist/src/ui/commands/bugCommand.js.map +1 -1
  207. package/dist/src/ui/commands/bugCommand.test.js +106 -1
  208. package/dist/src/ui/commands/bugCommand.test.js.map +1 -1
  209. package/dist/src/ui/commands/bugMemoryCommand.d.ts +7 -0
  210. package/dist/src/ui/commands/bugMemoryCommand.js +62 -0
  211. package/dist/src/ui/commands/bugMemoryCommand.js.map +1 -0
  212. package/dist/src/ui/commands/bugMemoryCommand.test.js +100 -0
  213. package/dist/src/ui/commands/bugMemoryCommand.test.js.map +1 -0
  214. package/dist/src/ui/commands/commandsCommand.js +52 -4
  215. package/dist/src/ui/commands/commandsCommand.js.map +1 -1
  216. package/dist/src/ui/commands/commandsCommand.test.js +75 -2
  217. package/dist/src/ui/commands/commandsCommand.test.js.map +1 -1
  218. package/dist/src/ui/commands/compressCommand.js +28 -26
  219. package/dist/src/ui/commands/compressCommand.js.map +1 -1
  220. package/dist/src/ui/commands/compressCommand.test.js +5 -0
  221. package/dist/src/ui/commands/compressCommand.test.js.map +1 -1
  222. package/dist/src/ui/commands/directoryCommand.js +2 -2
  223. package/dist/src/ui/commands/directoryCommand.js.map +1 -1
  224. package/dist/src/ui/commands/directoryCommand.test.js +1 -0
  225. package/dist/src/ui/commands/directoryCommand.test.js.map +1 -1
  226. package/dist/src/ui/commands/exportSessionCommand.d.ts +7 -0
  227. package/dist/src/ui/commands/exportSessionCommand.js +74 -0
  228. package/dist/src/ui/commands/exportSessionCommand.js.map +1 -0
  229. package/dist/src/ui/commands/exportSessionCommand.test.js +100 -0
  230. package/dist/src/ui/commands/exportSessionCommand.test.js.map +1 -0
  231. package/dist/src/ui/commands/extensionsCommand.js +1 -0
  232. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  233. package/dist/src/ui/commands/extensionsCommand.test.js +4 -0
  234. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  235. package/dist/src/ui/commands/marketplaceCommand.js +13 -1
  236. package/dist/src/ui/commands/marketplaceCommand.js.map +1 -1
  237. package/dist/src/ui/commands/memoryCommand.d.ts +2 -1
  238. package/dist/src/ui/commands/memoryCommand.js +110 -117
  239. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  240. package/dist/src/ui/commands/memoryCommand.test.js +18 -71
  241. package/dist/src/ui/commands/memoryCommand.test.js.map +1 -1
  242. package/dist/src/ui/commands/quitCommand.js +3 -1
  243. package/dist/src/ui/commands/quitCommand.js.map +1 -1
  244. package/dist/src/ui/commands/quitCommand.test.js +46 -1
  245. package/dist/src/ui/commands/quitCommand.test.js.map +1 -1
  246. package/dist/src/ui/commands/rewindCommand.js.map +1 -1
  247. package/dist/src/ui/commands/skillsCommand.js +1 -1
  248. package/dist/src/ui/commands/skillsCommand.js.map +1 -1
  249. package/dist/src/ui/commands/skillsCommand.test.js +19 -0
  250. package/dist/src/ui/commands/skillsCommand.test.js.map +1 -1
  251. package/dist/src/ui/commands/types.d.ts +3 -1
  252. package/dist/src/ui/commands/types.js.map +1 -1
  253. package/dist/src/ui/components/AsciiArt.d.ts +6 -6
  254. package/dist/src/ui/components/AsciiArt.js +6 -6
  255. package/dist/src/ui/components/AskUserDialog.js +3 -1
  256. package/dist/src/ui/components/AskUserDialog.js.map +1 -1
  257. package/dist/src/ui/components/AskUserDialog.test.js +43 -0
  258. package/dist/src/ui/components/AskUserDialog.test.js.map +1 -1
  259. package/dist/src/ui/components/Composer.js +1 -1
  260. package/dist/src/ui/components/Composer.js.map +1 -1
  261. package/dist/src/ui/components/DialogManager.js +4 -0
  262. package/dist/src/ui/components/DialogManager.js.map +1 -1
  263. package/dist/src/ui/components/EditorSettingsDialog.js +3 -6
  264. package/dist/src/ui/components/EditorSettingsDialog.js.map +1 -1
  265. package/dist/src/ui/components/Footer.js +6 -0
  266. package/dist/src/ui/components/Footer.js.map +1 -1
  267. package/dist/src/ui/components/FooterConfigDialog.js +1 -0
  268. package/dist/src/ui/components/FooterConfigDialog.js.map +1 -1
  269. package/dist/src/ui/components/FooterConfigDialog.test.js +1 -1
  270. package/dist/src/ui/components/FooterConfigDialog.test.js.map +1 -1
  271. package/dist/src/ui/components/HistoryItemDisplay.js +3 -1
  272. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  273. package/dist/src/ui/components/HistoryItemDisplay.test.js +15 -0
  274. package/dist/src/ui/components/HistoryItemDisplay.test.js.map +1 -1
  275. package/dist/src/ui/components/{SkillInboxDialog.d.ts → InboxDialog.d.ts} +3 -2
  276. package/dist/src/ui/components/InboxDialog.js +756 -0
  277. package/dist/src/ui/components/InboxDialog.js.map +1 -0
  278. package/dist/src/ui/components/InboxDialog.test.d.ts +6 -0
  279. package/dist/src/ui/components/InboxDialog.test.js +824 -0
  280. package/dist/src/ui/components/InboxDialog.test.js.map +1 -0
  281. package/dist/src/ui/components/InputPrompt.d.ts +3 -0
  282. package/dist/src/ui/components/InputPrompt.js +35 -17
  283. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  284. package/dist/src/ui/components/InputPrompt.test.js +95 -46
  285. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  286. package/dist/src/ui/components/ListeningIndicator.d.ts +10 -0
  287. package/dist/src/ui/components/ListeningIndicator.js +30 -0
  288. package/dist/src/ui/components/ListeningIndicator.js.map +1 -0
  289. package/dist/src/ui/components/MainContent.test.js +23 -3
  290. package/dist/src/ui/components/MainContent.test.js.map +1 -1
  291. package/dist/src/ui/components/ModelDialog.js +40 -34
  292. package/dist/src/ui/components/ModelDialog.js.map +1 -1
  293. package/dist/src/ui/components/ModelDialog.test.js +30 -27
  294. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  295. package/dist/src/ui/components/ModelStatsDisplay.js +1 -1
  296. package/dist/src/ui/components/ModelStatsDisplay.js.map +1 -1
  297. package/dist/src/ui/components/ModelStatsDisplay.test.js +41 -0
  298. package/dist/src/ui/components/ModelStatsDisplay.test.js.map +1 -1
  299. package/dist/src/ui/components/SessionBrowser.js +7 -0
  300. package/dist/src/ui/components/SessionBrowser.js.map +1 -1
  301. package/dist/src/ui/components/SessionSummaryDisplay.js +9 -4
  302. package/dist/src/ui/components/SessionSummaryDisplay.js.map +1 -1
  303. package/dist/src/ui/components/SessionSummaryDisplay.test.js +12 -23
  304. package/dist/src/ui/components/SessionSummaryDisplay.test.js.map +1 -1
  305. package/dist/src/ui/components/SettingsDialog.js +37 -17
  306. package/dist/src/ui/components/SettingsDialog.js.map +1 -1
  307. package/dist/src/ui/components/SettingsDialog.test.js +83 -1
  308. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  309. package/dist/src/ui/components/StatsDisplay.js +2 -2
  310. package/dist/src/ui/components/StatsDisplay.js.map +1 -1
  311. package/dist/src/ui/components/StatsDisplay.test.js +24 -0
  312. package/dist/src/ui/components/StatsDisplay.test.js.map +1 -1
  313. package/dist/src/ui/components/ThemeDialog.constants.d.ts +26 -0
  314. package/dist/src/ui/components/ThemeDialog.constants.js +27 -0
  315. package/dist/src/ui/components/ThemeDialog.constants.js.map +1 -0
  316. package/dist/src/ui/components/ThemeDialog.js +5 -15
  317. package/dist/src/ui/components/ThemeDialog.js.map +1 -1
  318. package/dist/src/ui/components/ToolConfirmationQueue.test.js +1 -1
  319. package/dist/src/ui/components/ToolConfirmationQueue.test.js.map +1 -1
  320. package/dist/src/ui/components/VoiceModelDialog.js +7 -2
  321. package/dist/src/ui/components/VoiceModelDialog.js.map +1 -1
  322. package/dist/src/ui/components/VoiceModelDialog.test.d.ts +6 -0
  323. package/dist/src/ui/components/VoiceModelDialog.test.js +68 -0
  324. package/dist/src/ui/components/VoiceModelDialog.test.js.map +1 -0
  325. package/dist/src/ui/components/messages/ExportSessionMessage.d.ts +11 -0
  326. package/dist/src/ui/components/messages/ExportSessionMessage.js +15 -0
  327. package/dist/src/ui/components/messages/ExportSessionMessage.js.map +1 -0
  328. package/dist/src/ui/components/messages/ExportSessionMessage.test.d.ts +6 -0
  329. package/dist/src/ui/components/messages/ExportSessionMessage.test.js +31 -0
  330. package/dist/src/ui/components/messages/ExportSessionMessage.test.js.map +1 -0
  331. package/dist/src/ui/components/messages/ShellToolMessage.js +6 -2
  332. package/dist/src/ui/components/messages/ShellToolMessage.js.map +1 -1
  333. package/dist/src/ui/components/messages/ShellToolMessage.test.js +33 -0
  334. package/dist/src/ui/components/messages/ShellToolMessage.test.js.map +1 -1
  335. package/dist/src/ui/components/messages/SubagentGroupDisplay.js +13 -13
  336. package/dist/src/ui/components/messages/SubagentGroupDisplay.js.map +1 -1
  337. package/dist/src/ui/components/messages/SubagentGroupDisplay.test.js +5 -5
  338. package/dist/src/ui/components/messages/SubagentGroupDisplay.test.js.map +1 -1
  339. package/dist/src/ui/components/messages/SubagentHistoryMessage.test.js +4 -3
  340. package/dist/src/ui/components/messages/SubagentHistoryMessage.test.js.map +1 -1
  341. package/dist/src/ui/components/messages/SubagentProgressDisplay.d.ts +1 -1
  342. package/dist/src/ui/components/messages/SubagentProgressDisplay.js +7 -6
  343. package/dist/src/ui/components/messages/SubagentProgressDisplay.js.map +1 -1
  344. package/dist/src/ui/components/messages/SubagentProgressDisplay.test.js +10 -9
  345. package/dist/src/ui/components/messages/SubagentProgressDisplay.test.js.map +1 -1
  346. package/dist/src/ui/components/messages/ToolConfirmationMessage.js +3 -9
  347. package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
  348. package/dist/src/ui/components/messages/ToolConfirmationMessage.test.js +60 -0
  349. package/dist/src/ui/components/messages/ToolConfirmationMessage.test.js.map +1 -1
  350. package/dist/src/ui/components/messages/ToolGroupDisplay.d.ts +13 -0
  351. package/dist/src/ui/components/messages/ToolGroupDisplay.js +78 -0
  352. package/dist/src/ui/components/messages/ToolGroupDisplay.js.map +1 -0
  353. package/dist/src/ui/components/messages/ToolGroupDisplay.test.d.ts +6 -0
  354. package/dist/src/ui/components/messages/ToolGroupDisplay.test.js +210 -0
  355. package/dist/src/ui/components/messages/ToolGroupDisplay.test.js.map +1 -0
  356. package/dist/src/ui/components/messages/ToolGroupMessage.js +4 -2
  357. package/dist/src/ui/components/messages/ToolGroupMessage.js.map +1 -1
  358. package/dist/src/ui/components/messages/ToolGroupMessage.test.js +28 -0
  359. package/dist/src/ui/components/messages/ToolGroupMessage.test.js.map +1 -1
  360. package/dist/src/ui/components/messages/ToolGroupMessageRegression.test.js +3 -3
  361. package/dist/src/ui/components/messages/ToolGroupMessageRegression.test.js.map +1 -1
  362. package/dist/src/ui/components/messages/ToolMessage.js +6 -2
  363. package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
  364. package/dist/src/ui/components/messages/ToolShared.d.ts +1 -0
  365. package/dist/src/ui/components/messages/ToolShared.js +5 -3
  366. package/dist/src/ui/components/messages/ToolShared.js.map +1 -1
  367. package/dist/src/ui/components/messages/ToolShared.test.js +18 -1
  368. package/dist/src/ui/components/messages/ToolShared.test.js.map +1 -1
  369. package/dist/src/ui/components/shared/BaseSettingsDialog.d.ts +6 -1
  370. package/dist/src/ui/components/shared/BaseSettingsDialog.js +8 -8
  371. package/dist/src/ui/components/shared/BaseSettingsDialog.js.map +1 -1
  372. package/dist/src/ui/components/shared/performance.test.js +9 -0
  373. package/dist/src/ui/components/shared/performance.test.js.map +1 -1
  374. package/dist/src/ui/components/shared/text-buffer.js +22 -5
  375. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  376. package/dist/src/ui/components/shared/text-buffer.test.js +211 -0
  377. package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
  378. package/dist/src/ui/constants/tips.js +0 -1
  379. package/dist/src/ui/constants/tips.js.map +1 -1
  380. package/dist/src/ui/contexts/UIActionsContext.d.ts +1 -0
  381. package/dist/src/ui/contexts/UIActionsContext.js.map +1 -1
  382. package/dist/src/ui/contexts/UIStateContext.d.ts +2 -0
  383. package/dist/src/ui/contexts/UIStateContext.js.map +1 -1
  384. package/dist/src/ui/hooks/atCommandProcessor.js +83 -73
  385. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  386. package/dist/src/ui/hooks/atCommandProcessor.test.js +134 -42
  387. package/dist/src/ui/hooks/atCommandProcessor.test.js.map +1 -1
  388. package/dist/src/ui/hooks/slashCommandProcessor.js +13 -0
  389. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  390. package/dist/src/ui/hooks/slashCommandProcessor.test.js +85 -0
  391. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
  392. package/dist/src/ui/hooks/useAgentStream.d.ts +2 -2
  393. package/dist/src/ui/hooks/useAgentStream.js +63 -30
  394. package/dist/src/ui/hooks/useAgentStream.js.map +1 -1
  395. package/dist/src/ui/hooks/useAgentStream.test.js +1 -1
  396. package/dist/src/ui/hooks/useAgentStream.test.js.map +1 -1
  397. package/dist/src/ui/hooks/useAtCompletion.js +0 -2
  398. package/dist/src/ui/hooks/useAtCompletion.js.map +1 -1
  399. package/dist/src/ui/hooks/useGeminiStream.d.ts +2 -2
  400. package/dist/src/ui/hooks/useGeminiStream.js +48 -29
  401. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  402. package/dist/src/ui/hooks/useGeminiStream.test.js +44 -82
  403. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  404. package/dist/src/ui/hooks/useGitBranchName.js +29 -16
  405. package/dist/src/ui/hooks/useGitBranchName.js.map +1 -1
  406. package/dist/src/ui/hooks/useGitBranchName.test.js +102 -51
  407. package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
  408. package/dist/src/ui/hooks/useIncludeDirsTrust.js +2 -2
  409. package/dist/src/ui/hooks/useIncludeDirsTrust.js.map +1 -1
  410. package/dist/src/ui/hooks/useIncludeDirsTrust.test.js +2 -0
  411. package/dist/src/ui/hooks/useIncludeDirsTrust.test.js.map +1 -1
  412. package/dist/src/ui/hooks/useMessageQueue.d.ts +2 -1
  413. package/dist/src/ui/hooks/useMessageQueue.js +3 -1
  414. package/dist/src/ui/hooks/useMessageQueue.js.map +1 -1
  415. package/dist/src/ui/hooks/useMessageQueue.test.js +38 -0
  416. package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
  417. package/dist/src/ui/hooks/useSessionBrowser.d.ts +3 -3
  418. package/dist/src/ui/hooks/useSessionBrowser.js.map +1 -1
  419. package/dist/src/ui/hooks/useSessionBrowser.test.js +44 -38
  420. package/dist/src/ui/hooks/useSessionBrowser.test.js.map +1 -1
  421. package/dist/src/ui/hooks/useSessionResume.d.ts +3 -3
  422. package/dist/src/ui/hooks/useSessionResume.js.map +1 -1
  423. package/dist/src/ui/hooks/useSessionResume.test.js +6 -4
  424. package/dist/src/ui/hooks/useSessionResume.test.js.map +1 -1
  425. package/dist/src/ui/hooks/useSuspend.d.ts +1 -3
  426. package/dist/src/ui/hooks/useSuspend.js +3 -17
  427. package/dist/src/ui/hooks/useSuspend.js.map +1 -1
  428. package/dist/src/ui/hooks/useSuspend.test.js +0 -14
  429. package/dist/src/ui/hooks/useSuspend.test.js.map +1 -1
  430. package/dist/src/ui/hooks/useToolScheduler.test.js +6 -6
  431. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  432. package/dist/src/ui/hooks/useVoiceMode.js +25 -19
  433. package/dist/src/ui/hooks/useVoiceMode.js.map +1 -1
  434. package/dist/src/ui/hooks/vim-passthrough.test.js +10 -0
  435. package/dist/src/ui/hooks/vim-passthrough.test.js.map +1 -1
  436. package/dist/src/ui/hooks/vim.js +8 -0
  437. package/dist/src/ui/hooks/vim.js.map +1 -1
  438. package/dist/src/ui/hooks/vim.test.js +61 -0
  439. package/dist/src/ui/hooks/vim.test.js.map +1 -1
  440. package/dist/src/ui/key/keyBindings.d.ts +2 -0
  441. package/dist/src/ui/key/keyBindings.js +26 -9
  442. package/dist/src/ui/key/keyBindings.js.map +1 -1
  443. package/dist/src/ui/key/keyBindings.test.js +24 -0
  444. package/dist/src/ui/key/keyBindings.test.js.map +1 -1
  445. package/dist/src/ui/key/keyMatchers.test.js +26 -5
  446. package/dist/src/ui/key/keyMatchers.test.js.map +1 -1
  447. package/dist/src/ui/themes/theme-manager.js +0 -2
  448. package/dist/src/ui/themes/theme-manager.js.map +1 -1
  449. package/dist/src/ui/types.d.ts +23 -2
  450. package/dist/src/ui/types.js +3 -2
  451. package/dist/src/ui/types.js.map +1 -1
  452. package/dist/src/ui/utils/TableRenderer.js +6 -6
  453. package/dist/src/ui/utils/TableRenderer.js.map +1 -1
  454. package/dist/src/ui/utils/TableRenderer.test.js +10 -0
  455. package/dist/src/ui/utils/TableRenderer.test.js.map +1 -1
  456. package/dist/src/ui/utils/directoryUtils.test.js +0 -5
  457. package/dist/src/ui/utils/directoryUtils.test.js.map +1 -1
  458. package/dist/src/ui/utils/editorUtils.d.ts +2 -1
  459. package/dist/src/ui/utils/editorUtils.js +75 -28
  460. package/dist/src/ui/utils/editorUtils.js.map +1 -1
  461. package/dist/src/ui/utils/latexToUnicode.d.ts +21 -0
  462. package/dist/src/ui/utils/latexToUnicode.js +538 -0
  463. package/dist/src/ui/utils/latexToUnicode.js.map +1 -0
  464. package/dist/src/ui/utils/latexToUnicode.test.d.ts +6 -0
  465. package/dist/src/ui/utils/latexToUnicode.test.js +222 -0
  466. package/dist/src/ui/utils/latexToUnicode.test.js.map +1 -0
  467. package/dist/src/ui/utils/markdownParsingUtils.d.ts +1 -5
  468. package/dist/src/ui/utils/markdownParsingUtils.js +36 -1
  469. package/dist/src/ui/utils/markdownParsingUtils.js.map +1 -1
  470. package/dist/src/ui/utils/markdownParsingUtils.test.js +35 -0
  471. package/dist/src/ui/utils/markdownParsingUtils.test.js.map +1 -1
  472. package/dist/src/ui/utils/memorySnapshot.d.ts +19 -0
  473. package/dist/src/ui/utils/memorySnapshot.js +28 -0
  474. package/dist/src/ui/utils/memorySnapshot.js.map +1 -0
  475. package/dist/src/ui/utils/memorySnapshot.test.d.ts +6 -0
  476. package/dist/src/ui/utils/memorySnapshot.test.js +62 -0
  477. package/dist/src/ui/utils/memorySnapshot.test.js.map +1 -0
  478. package/dist/src/ui/utils/updateCheck.js +11 -2
  479. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  480. package/dist/src/ui/utils/updateCheck.test.js +73 -0
  481. package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
  482. package/dist/src/utils/commands.d.ts +1 -1
  483. package/dist/src/utils/commands.js +1 -1
  484. package/dist/src/utils/commands.test.js +14 -14
  485. package/dist/src/utils/commands.test.js.map +1 -1
  486. package/dist/src/utils/envVarResolver.js +10 -7
  487. package/dist/src/utils/envVarResolver.js.map +1 -1
  488. package/dist/src/utils/gitUtils.js +1 -2
  489. package/dist/src/utils/gitUtils.js.map +1 -1
  490. package/dist/src/utils/handleAutoUpdate.d.ts +1 -1
  491. package/dist/src/utils/handleAutoUpdate.js +15 -3
  492. package/dist/src/utils/handleAutoUpdate.js.map +1 -1
  493. package/dist/src/utils/handleAutoUpdate.test.js +45 -16
  494. package/dist/src/utils/handleAutoUpdate.test.js.map +1 -1
  495. package/dist/src/utils/installationInfo.d.ts +1 -0
  496. package/dist/src/utils/installationInfo.js +16 -1
  497. package/dist/src/utils/installationInfo.js.map +1 -1
  498. package/dist/src/utils/installationInfo.test.js +16 -0
  499. package/dist/src/utils/installationInfo.test.js.map +1 -1
  500. package/dist/src/utils/jsonoutput.js +0 -2
  501. package/dist/src/utils/jsonoutput.js.map +1 -1
  502. package/dist/src/utils/processUtils.d.ts +28 -0
  503. package/dist/src/utils/processUtils.js +71 -0
  504. package/dist/src/utils/processUtils.js.map +1 -1
  505. package/dist/src/utils/processUtils.test.js +122 -1
  506. package/dist/src/utils/processUtils.test.js.map +1 -1
  507. package/dist/src/utils/readStdin.js +22 -4
  508. package/dist/src/utils/readStdin.js.map +1 -1
  509. package/dist/src/utils/readStdin.test.js +32 -0
  510. package/dist/src/utils/readStdin.test.js.map +1 -1
  511. package/dist/src/utils/relaunch.js +6 -13
  512. package/dist/src/utils/relaunch.js.map +1 -1
  513. package/dist/src/utils/relaunch.test.js +82 -86
  514. package/dist/src/utils/relaunch.test.js.map +1 -1
  515. package/dist/src/utils/sandbox.js +34 -24
  516. package/dist/src/utils/sandbox.js.map +1 -1
  517. package/dist/src/utils/sandbox.test.js +108 -9
  518. package/dist/src/utils/sandbox.test.js.map +1 -1
  519. package/dist/src/utils/sandboxUtils.js +12 -7
  520. package/dist/src/utils/sandboxUtils.js.map +1 -1
  521. package/dist/src/utils/sandboxUtils.test.js +68 -0
  522. package/dist/src/utils/sandboxUtils.test.js.map +1 -1
  523. package/dist/src/utils/sessionCleanup.js +49 -14
  524. package/dist/src/utils/sessionCleanup.js.map +1 -1
  525. package/dist/src/utils/sessionCleanup.test.js +63 -0
  526. package/dist/src/utils/sessionCleanup.test.js.map +1 -1
  527. package/dist/src/utils/sessionUtils.js +22 -7
  528. package/dist/src/utils/sessionUtils.js.map +1 -1
  529. package/dist/src/utils/sessionUtils.test.js +99 -0
  530. package/dist/src/utils/sessionUtils.test.js.map +1 -1
  531. package/dist/src/utils/sessions.js +2 -4
  532. package/dist/src/utils/sessions.js.map +1 -1
  533. package/dist/src/utils/sessions.test.js +9 -12
  534. package/dist/src/utils/sessions.test.js.map +1 -1
  535. package/dist/src/utils/userStartupWarnings.js +4 -3
  536. package/dist/src/utils/userStartupWarnings.js.map +1 -1
  537. package/dist/src/utils/userStartupWarnings.test.js +40 -3
  538. package/dist/src/utils/userStartupWarnings.test.js.map +1 -1
  539. package/dist/src/validateNonInterActiveAuth.js +1 -1
  540. package/dist/src/validateNonInterActiveAuth.js.map +1 -1
  541. package/dist/tsconfig.tsbuildinfo +1 -1
  542. package/package.json +5 -5
  543. package/dist/src/acp/acpClient.d.ts +0 -56
  544. package/dist/src/acp/acpClient.js.map +0 -1
  545. package/dist/src/acp/acpClient.test.js +0 -1814
  546. package/dist/src/acp/acpClient.test.js.map +0 -1
  547. package/dist/src/acp/commandHandler.js.map +0 -1
  548. package/dist/src/acp/commandHandler.test.js.map +0 -1
  549. package/dist/src/acp/fileSystemService.js.map +0 -1
  550. package/dist/src/acp/fileSystemService.test.js.map +0 -1
  551. package/dist/src/ui/auth/LoginWithGoogleRestartDialog.d.ts +0 -12
  552. package/dist/src/ui/auth/LoginWithGoogleRestartDialog.js.map +0 -1
  553. package/dist/src/ui/auth/LoginWithGoogleRestartDialog.test.js.map +0 -1
  554. package/dist/src/ui/components/SkillInboxDialog.js +0 -420
  555. package/dist/src/ui/components/SkillInboxDialog.js.map +0 -1
  556. package/dist/src/ui/components/SkillInboxDialog.test.js +0 -467
  557. package/dist/src/ui/components/SkillInboxDialog.test.js.map +0 -1
  558. /package/dist/src/acp/{commandHandler.test.d.ts → acpCommandHandler.test.d.ts} +0 -0
  559. /package/dist/src/{ui/components/SkillInboxDialog.test.d.ts → acp/acpFileSystemService.test.d.ts} +0 -0
  560. /package/dist/src/{acp/acpClient.test.d.ts → config/mutual-exclusivity.test.d.ts} +0 -0
  561. /package/dist/src/{acp/fileSystemService.test.d.ts → ui/commands/bugMemoryCommand.test.d.ts} +0 -0
  562. /package/dist/src/ui/{auth/LoginWithGoogleRestartDialog.test.d.ts → commands/exportSessionCommand.test.d.ts} +0 -0
@@ -1,1814 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
7
- import { GeminiAgent, Session } from './acpClient.js';
8
- import * as acp from '@agentclientprotocol/sdk';
9
- import { AuthType, ToolConfirmationOutcome, StreamEventType, ReadManyFilesTool, LlmRole, processSingleFileContent, InvalidStreamError, } from '@google/gemini-cli-core';
10
- import { SettingScope, loadSettings, } from '../config/settings.js';
11
- import { loadCliConfig } from '../config/config.js';
12
- import * as fs from 'node:fs/promises';
13
- import * as path from 'node:path';
14
- import { ApprovalMode } from '@google/gemini-cli-core/src/policy/types.js';
15
- const startMemoryServiceMock = vi.hoisted(() => vi.fn());
16
- vi.mock('../config/config.js', () => ({
17
- loadCliConfig: vi.fn(),
18
- }));
19
- vi.mock('../config/settings.js', async (importOriginal) => {
20
- const actual = await importOriginal();
21
- return {
22
- ...actual,
23
- loadSettings: vi.fn(),
24
- };
25
- });
26
- vi.mock('node:crypto', () => ({
27
- randomUUID: () => 'test-session-id',
28
- }));
29
- vi.mock('node:fs/promises');
30
- vi.mock('node:path', async (importOriginal) => {
31
- const actual = await importOriginal();
32
- return {
33
- ...actual,
34
- resolve: vi.fn(),
35
- };
36
- });
37
- vi.mock('../ui/commands/memoryCommand.js', () => ({
38
- memoryCommand: {
39
- name: 'memory',
40
- action: vi.fn(),
41
- },
42
- }));
43
- vi.mock('../ui/commands/extensionsCommand.js', () => ({
44
- extensionsCommand: vi.fn().mockReturnValue({
45
- name: 'extensions',
46
- action: vi.fn(),
47
- }),
48
- }));
49
- vi.mock('../ui/commands/restoreCommand.js', () => ({
50
- restoreCommand: vi.fn().mockReturnValue({
51
- name: 'restore',
52
- action: vi.fn(),
53
- }),
54
- }));
55
- vi.mock('../ui/commands/initCommand.js', () => ({
56
- initCommand: {
57
- name: 'init',
58
- action: vi.fn(),
59
- },
60
- }));
61
- vi.mock('@google/gemini-cli-core', async (importOriginal) => {
62
- const actual = await importOriginal();
63
- return {
64
- ...actual,
65
- startMemoryService: startMemoryServiceMock,
66
- updatePolicy: vi.fn(),
67
- createPolicyUpdater: vi.fn(),
68
- ReadManyFilesTool: vi.fn(),
69
- logToolCall: vi.fn(),
70
- LlmRole: {
71
- MAIN: 'main',
72
- SUBAGENT: 'subagent',
73
- UTILITY_TOOL: 'utility_tool',
74
- UTILITY_COMPRESSOR: 'utility_compressor',
75
- UTILITY_SUMMARIZER: 'utility_summarizer',
76
- UTILITY_ROUTER: 'utility_router',
77
- UTILITY_LOOP_DETECTOR: 'utility_loop_detector',
78
- UTILITY_NEXT_SPEAKER: 'utility_next_speaker',
79
- UTILITY_EDIT_CORRECTOR: 'utility_edit_corrector',
80
- UTILITY_AUTOCOMPLETE: 'utility_autocomplete',
81
- UTILITY_FAST_ACK_HELPER: 'utility_fast_ack_helper',
82
- },
83
- CoreToolCallStatus: {
84
- Validating: 'validating',
85
- Scheduled: 'scheduled',
86
- Error: 'error',
87
- Success: 'success',
88
- Executing: 'executing',
89
- Cancelled: 'cancelled',
90
- AwaitingApproval: 'awaiting_approval',
91
- },
92
- processSingleFileContent: vi.fn(),
93
- };
94
- });
95
- // Helper to create mock streams
96
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
- async function* createMockStream(items) {
98
- for (const item of items) {
99
- yield item;
100
- }
101
- }
102
- describe('GeminiAgent', () => {
103
- let mockConfig;
104
- let mockSettings;
105
- let mockArgv;
106
- let mockConnection;
107
- let agent;
108
- beforeEach(() => {
109
- vi.clearAllMocks();
110
- startMemoryServiceMock.mockResolvedValue(undefined);
111
- mockConfig = {
112
- refreshAuth: vi.fn(),
113
- initialize: vi.fn(),
114
- waitForMcpInit: vi.fn(),
115
- getFileSystemService: vi.fn(),
116
- setFileSystemService: vi.fn(),
117
- getContentGeneratorConfig: vi.fn(),
118
- isAutoMemoryEnabled: vi.fn().mockReturnValue(false),
119
- getActiveModel: vi.fn().mockReturnValue('gemini-pro'),
120
- getModel: vi.fn().mockReturnValue('gemini-pro'),
121
- getGeminiClient: vi.fn().mockReturnValue({
122
- startChat: vi.fn().mockResolvedValue({}),
123
- }),
124
- getMessageBus: vi.fn().mockReturnValue({
125
- publish: vi.fn(),
126
- subscribe: vi.fn(),
127
- unsubscribe: vi.fn(),
128
- }),
129
- getApprovalMode: vi.fn().mockReturnValue('default'),
130
- isPlanEnabled: vi.fn().mockReturnValue(true),
131
- getGemini31LaunchedSync: vi.fn().mockReturnValue(false),
132
- getHasAccessToPreviewModel: vi.fn().mockReturnValue(false),
133
- getCheckpointingEnabled: vi.fn().mockReturnValue(false),
134
- getDisableAlwaysAllow: vi.fn().mockReturnValue(false),
135
- validatePathAccess: vi.fn().mockReturnValue(null),
136
- getWorkspaceContext: vi.fn().mockReturnValue({
137
- addReadOnlyPath: vi.fn(),
138
- }),
139
- getPolicyEngine: vi.fn().mockReturnValue({
140
- addRule: vi.fn(),
141
- }),
142
- messageBus: {
143
- publish: vi.fn(),
144
- subscribe: vi.fn(),
145
- unsubscribe: vi.fn(),
146
- },
147
- storage: {
148
- getWorkspaceAutoSavedPolicyPath: vi.fn(),
149
- getAutoSavedPolicyPath: vi.fn(),
150
- setClientName: vi.fn(),
151
- },
152
- setClientName: vi.fn(),
153
- get config() {
154
- return this;
155
- },
156
- };
157
- mockSettings = {
158
- merged: {
159
- security: { auth: { selectedType: 'login_with_google' } },
160
- mcpServers: {},
161
- },
162
- setValue: vi.fn(),
163
- };
164
- mockArgv = {};
165
- mockConnection = {
166
- sessionUpdate: vi.fn(),
167
- requestPermission: vi.fn(),
168
- };
169
- loadCliConfig.mockResolvedValue(mockConfig);
170
- loadSettings.mockImplementation(() => ({
171
- merged: {
172
- security: {
173
- auth: { selectedType: AuthType.LOGIN_WITH_GOOGLE },
174
- enablePermanentToolApproval: true,
175
- },
176
- mcpServers: {},
177
- },
178
- setValue: vi.fn(),
179
- }));
180
- agent = new GeminiAgent(mockConfig, mockSettings, mockArgv, mockConnection);
181
- });
182
- it('should initialize correctly', async () => {
183
- const response = await agent.initialize({
184
- clientCapabilities: { fs: { readTextFile: true, writeTextFile: true } },
185
- protocolVersion: 1,
186
- });
187
- expect(response.protocolVersion).toBe(acp.PROTOCOL_VERSION);
188
- expect(response.authMethods).toHaveLength(4);
189
- const gatewayAuth = response.authMethods?.find((m) => m.id === AuthType.GATEWAY);
190
- expect(gatewayAuth?._meta).toEqual({
191
- gateway: {
192
- protocol: 'google',
193
- restartRequired: 'false',
194
- },
195
- });
196
- const geminiAuth = response.authMethods?.find((m) => m.id === AuthType.USE_GEMINI);
197
- expect(geminiAuth?._meta).toEqual({
198
- 'api-key': {
199
- provider: 'google',
200
- },
201
- });
202
- expect(response.agentCapabilities?.loadSession).toBe(true);
203
- });
204
- it('should authenticate correctly', async () => {
205
- await agent.authenticate({
206
- methodId: AuthType.LOGIN_WITH_GOOGLE,
207
- });
208
- expect(mockConfig.refreshAuth).toHaveBeenCalledWith(AuthType.LOGIN_WITH_GOOGLE, undefined, undefined, undefined, undefined);
209
- expect(mockSettings.setValue).toHaveBeenCalledWith(SettingScope.User, 'security.auth.selectedType', AuthType.LOGIN_WITH_GOOGLE);
210
- });
211
- it('should authenticate correctly with api-key in _meta', async () => {
212
- await agent.authenticate({
213
- methodId: AuthType.USE_GEMINI,
214
- _meta: {
215
- 'api-key': 'test-api-key',
216
- },
217
- });
218
- expect(mockConfig.refreshAuth).toHaveBeenCalledWith(AuthType.USE_GEMINI, undefined, 'test-api-key', undefined, undefined);
219
- expect(mockSettings.setValue).toHaveBeenCalledWith(SettingScope.User, 'security.auth.selectedType', AuthType.USE_GEMINI);
220
- });
221
- it('should authenticate correctly with gateway method', async () => {
222
- await agent.authenticate({
223
- methodId: AuthType.GATEWAY,
224
- _meta: {
225
- gateway: {
226
- baseUrl: 'https://example.com',
227
- headers: { Authorization: 'Bearer token' },
228
- },
229
- },
230
- });
231
- expect(mockConfig.refreshAuth).toHaveBeenCalledWith(AuthType.GATEWAY, undefined, undefined, 'https://example.com', { Authorization: 'Bearer token' });
232
- expect(mockSettings.setValue).toHaveBeenCalledWith(SettingScope.User, 'security.auth.selectedType', AuthType.GATEWAY);
233
- });
234
- it('should throw acp.RequestError when gateway payload is malformed', async () => {
235
- await expect(agent.authenticate({
236
- methodId: AuthType.GATEWAY,
237
- _meta: {
238
- gateway: {
239
- // Invalid baseUrl
240
- baseUrl: 123,
241
- headers: { Authorization: 'Bearer token' },
242
- },
243
- },
244
- })).rejects.toThrow(/Malformed gateway payload/);
245
- });
246
- it('should create a new session', async () => {
247
- vi.useFakeTimers();
248
- mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({
249
- apiKey: 'test-key',
250
- });
251
- const response = await agent.newSession({
252
- cwd: '/tmp',
253
- mcpServers: [],
254
- });
255
- expect(response.sessionId).toBe('test-session-id');
256
- expect(loadCliConfig).toHaveBeenCalled();
257
- expect(mockConfig.initialize).toHaveBeenCalled();
258
- expect(mockConfig.getGeminiClient).toHaveBeenCalled();
259
- // Verify deferred call
260
- await vi.runAllTimersAsync();
261
- expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(expect.objectContaining({
262
- update: expect.objectContaining({
263
- sessionUpdate: 'available_commands_update',
264
- }),
265
- }));
266
- vi.useRealTimers();
267
- });
268
- it('should start auto memory for new ACP sessions when enabled', async () => {
269
- mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({
270
- apiKey: 'test-key',
271
- });
272
- mockConfig.isAutoMemoryEnabled = vi.fn().mockReturnValue(true);
273
- await agent.newSession({
274
- cwd: '/tmp',
275
- mcpServers: [],
276
- });
277
- expect(startMemoryServiceMock).toHaveBeenCalledWith(mockConfig);
278
- });
279
- it('should not start auto memory for new ACP sessions when disabled', async () => {
280
- mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({
281
- apiKey: 'test-key',
282
- });
283
- mockConfig.isAutoMemoryEnabled = vi.fn().mockReturnValue(false);
284
- await agent.newSession({
285
- cwd: '/tmp',
286
- mcpServers: [],
287
- });
288
- expect(startMemoryServiceMock).not.toHaveBeenCalled();
289
- });
290
- it('should return modes without plan mode when plan is disabled', async () => {
291
- mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({
292
- apiKey: 'test-key',
293
- });
294
- mockConfig.isPlanEnabled = vi.fn().mockReturnValue(false);
295
- mockConfig.getApprovalMode = vi.fn().mockReturnValue('default');
296
- const response = await agent.newSession({
297
- cwd: '/tmp',
298
- mcpServers: [],
299
- });
300
- expect(response.modes).toEqual({
301
- availableModes: [
302
- { id: 'default', name: 'Default', description: 'Prompts for approval' },
303
- {
304
- id: 'autoEdit',
305
- name: 'Auto Edit',
306
- description: 'Auto-approves edit tools',
307
- },
308
- { id: 'yolo', name: 'YOLO', description: 'Auto-approves all tools' },
309
- ],
310
- currentModeId: 'default',
311
- });
312
- expect(response.models).toEqual({
313
- availableModels: expect.arrayContaining([
314
- expect.objectContaining({
315
- modelId: 'auto-gemini-2.5',
316
- name: 'Auto (Gemini 2.5)',
317
- }),
318
- ]),
319
- currentModelId: 'gemini-pro',
320
- });
321
- });
322
- it('should include preview models when user has access', async () => {
323
- mockConfig.getHasAccessToPreviewModel = vi.fn().mockReturnValue(true);
324
- mockConfig.getGemini31LaunchedSync = vi.fn().mockReturnValue(true);
325
- const response = await agent.newSession({
326
- cwd: '/tmp',
327
- mcpServers: [],
328
- });
329
- expect(response.models?.availableModels).toEqual(expect.arrayContaining([
330
- expect.objectContaining({
331
- modelId: 'auto-gemini-3',
332
- name: expect.stringContaining('Auto'),
333
- }),
334
- expect.objectContaining({
335
- modelId: 'gemini-3.1-pro-preview',
336
- name: 'gemini-3.1-pro-preview',
337
- }),
338
- ]));
339
- });
340
- it('should include gemini-3.1-flash-lite when useGemini31FlashLite is true', async () => {
341
- mockConfig.getHasAccessToPreviewModel = vi.fn().mockReturnValue(true);
342
- mockConfig.getGemini31LaunchedSync = vi.fn().mockReturnValue(true);
343
- mockConfig.getGemini31FlashLiteLaunchedSync = vi.fn().mockReturnValue(true);
344
- const response = await agent.newSession({
345
- cwd: '/tmp',
346
- mcpServers: [],
347
- });
348
- expect(response.models?.availableModels).toEqual(expect.arrayContaining([
349
- expect.objectContaining({
350
- modelId: 'gemini-3.1-flash-lite-preview',
351
- name: 'gemini-3.1-flash-lite-preview',
352
- }),
353
- ]));
354
- });
355
- it('should return modes with plan mode when plan is enabled', async () => {
356
- mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({
357
- apiKey: 'test-key',
358
- });
359
- mockConfig.isPlanEnabled = vi.fn().mockReturnValue(true);
360
- mockConfig.getApprovalMode = vi.fn().mockReturnValue('plan');
361
- const response = await agent.newSession({
362
- cwd: '/tmp',
363
- mcpServers: [],
364
- });
365
- expect(response.modes).toEqual({
366
- availableModes: [
367
- { id: 'default', name: 'Default', description: 'Prompts for approval' },
368
- {
369
- id: 'autoEdit',
370
- name: 'Auto Edit',
371
- description: 'Auto-approves edit tools',
372
- },
373
- { id: 'yolo', name: 'YOLO', description: 'Auto-approves all tools' },
374
- { id: 'plan', name: 'Plan', description: 'Read-only mode' },
375
- ],
376
- currentModeId: 'plan',
377
- });
378
- expect(response.models).toEqual({
379
- availableModels: expect.arrayContaining([
380
- expect.objectContaining({
381
- modelId: 'auto-gemini-2.5',
382
- name: 'Auto (Gemini 2.5)',
383
- }),
384
- ]),
385
- currentModelId: 'gemini-pro',
386
- });
387
- });
388
- it('should fail session creation if Gemini API key is missing', async () => {
389
- loadSettings.mockImplementation(() => ({
390
- merged: {
391
- security: { auth: { selectedType: AuthType.USE_GEMINI } },
392
- mcpServers: {},
393
- },
394
- setValue: vi.fn(),
395
- }));
396
- mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({
397
- apiKey: undefined,
398
- });
399
- await expect(agent.newSession({
400
- cwd: '/tmp',
401
- mcpServers: [],
402
- })).rejects.toMatchObject({
403
- message: 'Gemini API key is missing or not configured.',
404
- });
405
- });
406
- it('should create a new session with mcp servers', async () => {
407
- const mcpServers = [
408
- {
409
- name: 'test-server',
410
- command: 'node',
411
- args: ['server.js'],
412
- env: [{ name: 'KEY', value: 'VALUE' }],
413
- },
414
- ];
415
- await agent.newSession({
416
- cwd: '/tmp',
417
- mcpServers,
418
- });
419
- expect(loadCliConfig).toHaveBeenCalledWith(expect.objectContaining({
420
- mcpServers: expect.objectContaining({
421
- 'test-server': expect.objectContaining({
422
- command: 'node',
423
- args: ['server.js'],
424
- env: { KEY: 'VALUE' },
425
- }),
426
- }),
427
- }), 'test-session-id', mockArgv, { cwd: '/tmp' });
428
- });
429
- it('should handle authentication failure gracefully', async () => {
430
- mockConfig.refreshAuth.mockRejectedValue(new Error('Auth failed'));
431
- const debugSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
432
- // Should throw RequestError with custom message
433
- await expect(agent.newSession({
434
- cwd: '/tmp',
435
- mcpServers: [],
436
- })).rejects.toMatchObject({
437
- message: 'Auth failed',
438
- });
439
- debugSpy.mockRestore();
440
- });
441
- it('should initialize file system service if client supports it', async () => {
442
- agent = new GeminiAgent(mockConfig, mockSettings, mockArgv, mockConnection);
443
- await agent.initialize({
444
- clientCapabilities: { fs: { readTextFile: true, writeTextFile: true } },
445
- protocolVersion: 1,
446
- });
447
- await agent.newSession({
448
- cwd: '/tmp',
449
- mcpServers: [],
450
- });
451
- expect(mockConfig.setFileSystemService).toHaveBeenCalled();
452
- });
453
- it('should cancel a session', async () => {
454
- await agent.newSession({ cwd: '/tmp', mcpServers: [] });
455
- // Mock the session's cancelPendingPrompt
456
- const session = agent.sessions.get('test-session-id');
457
- if (!session)
458
- throw new Error('Session not found');
459
- session.cancelPendingPrompt = vi.fn();
460
- await agent.cancel({ sessionId: 'test-session-id' });
461
- expect(session.cancelPendingPrompt).toHaveBeenCalled();
462
- });
463
- it('should throw error when cancelling non-existent session', async () => {
464
- await expect(agent.cancel({ sessionId: 'unknown' })).rejects.toThrow('Session not found');
465
- });
466
- it('should delegate prompt to session', async () => {
467
- await agent.newSession({ cwd: '/tmp', mcpServers: [] });
468
- const session = agent.sessions.get('test-session-id');
469
- if (!session)
470
- throw new Error('Session not found');
471
- session.prompt = vi.fn().mockResolvedValue({ stopReason: 'end_turn' });
472
- const result = await agent.prompt({
473
- sessionId: 'test-session-id',
474
- prompt: [],
475
- });
476
- expect(session.prompt).toHaveBeenCalled();
477
- expect(result).toMatchObject({ stopReason: 'end_turn' });
478
- });
479
- it('should delegate setMode to session', async () => {
480
- await agent.newSession({ cwd: '/tmp', mcpServers: [] });
481
- const session = agent.sessions.get('test-session-id');
482
- if (!session)
483
- throw new Error('Session not found');
484
- session.setMode = vi.fn().mockReturnValue({});
485
- const result = await agent.setSessionMode({
486
- sessionId: 'test-session-id',
487
- modeId: 'plan',
488
- });
489
- expect(session.setMode).toHaveBeenCalledWith('plan');
490
- expect(result).toEqual({});
491
- });
492
- it('should throw error when setting mode on non-existent session', async () => {
493
- await expect(agent.setSessionMode({
494
- sessionId: 'unknown',
495
- modeId: 'plan',
496
- })).rejects.toThrow('Session not found: unknown');
497
- });
498
- it('should delegate setModel to session (unstable)', async () => {
499
- await agent.newSession({ cwd: '/tmp', mcpServers: [] });
500
- const session = agent.sessions.get('test-session-id');
501
- if (!session)
502
- throw new Error('Session not found');
503
- session.setModel = vi.fn().mockReturnValue({});
504
- const result = await agent.unstable_setSessionModel({
505
- sessionId: 'test-session-id',
506
- modelId: 'gemini-2.0-pro-exp',
507
- });
508
- expect(session.setModel).toHaveBeenCalledWith('gemini-2.0-pro-exp');
509
- expect(result).toEqual({});
510
- });
511
- it('should throw error when setting model on non-existent session (unstable)', async () => {
512
- await expect(agent.unstable_setSessionModel({
513
- sessionId: 'unknown',
514
- modelId: 'gemini-2.0-pro-exp',
515
- })).rejects.toThrow('Session not found: unknown');
516
- });
517
- });
518
- describe('Session', () => {
519
- let mockChat;
520
- let mockConfig;
521
- let mockConnection;
522
- let session;
523
- let mockToolRegistry;
524
- let mockTool;
525
- let mockMessageBus;
526
- beforeEach(() => {
527
- mockChat = {
528
- sendMessageStream: vi.fn(),
529
- addHistory: vi.fn(),
530
- recordCompletedToolCalls: vi.fn(),
531
- getHistory: vi.fn().mockReturnValue([]),
532
- };
533
- mockTool = {
534
- kind: 'read',
535
- build: vi.fn().mockReturnValue({
536
- getDescription: () => 'Test Tool',
537
- toolLocations: () => [],
538
- shouldConfirmExecute: vi.fn().mockResolvedValue(null),
539
- execute: vi.fn().mockResolvedValue({ llmContent: 'Tool Result' }),
540
- }),
541
- };
542
- mockToolRegistry = {
543
- getTool: vi.fn().mockReturnValue(mockTool),
544
- };
545
- mockMessageBus = {
546
- publish: vi.fn(),
547
- subscribe: vi.fn(),
548
- unsubscribe: vi.fn(),
549
- };
550
- mockConfig = {
551
- getModel: vi.fn().mockReturnValue('gemini-pro'),
552
- getActiveModel: vi.fn().mockReturnValue('gemini-pro'),
553
- getModelRouterService: vi.fn().mockReturnValue({
554
- route: vi.fn().mockResolvedValue({ model: 'resolved-model' }),
555
- }),
556
- getToolRegistry: vi.fn().mockReturnValue(mockToolRegistry),
557
- getMcpServers: vi.fn(),
558
- getFileService: vi.fn().mockReturnValue({
559
- shouldIgnoreFile: vi.fn().mockReturnValue(false),
560
- }),
561
- getFileFilteringOptions: vi.fn().mockReturnValue({}),
562
- getFileSystemService: vi.fn().mockReturnValue({}),
563
- getTargetDir: vi.fn().mockReturnValue('/tmp'),
564
- getEnableRecursiveFileSearch: vi.fn().mockReturnValue(false),
565
- getDebugMode: vi.fn().mockReturnValue(false),
566
- getMessageBus: vi.fn().mockReturnValue(mockMessageBus),
567
- setApprovalMode: vi.fn(),
568
- setModel: vi.fn(),
569
- isPlanEnabled: vi.fn().mockReturnValue(true),
570
- getCheckpointingEnabled: vi.fn().mockReturnValue(false),
571
- getGitService: vi.fn().mockResolvedValue({}),
572
- validatePathAccess: vi.fn().mockReturnValue(null),
573
- getWorkspaceContext: vi.fn().mockReturnValue({
574
- addReadOnlyPath: vi.fn(),
575
- }),
576
- waitForMcpInit: vi.fn(),
577
- getDisableAlwaysAllow: vi.fn().mockReturnValue(false),
578
- get config() {
579
- return this;
580
- },
581
- get toolRegistry() {
582
- return mockToolRegistry;
583
- },
584
- };
585
- mockConnection = {
586
- sessionUpdate: vi.fn(),
587
- requestPermission: vi.fn(),
588
- sendNotification: vi.fn(),
589
- };
590
- session = new Session('session-1', mockChat, mockConfig, mockConnection, {
591
- system: { settings: {} },
592
- systemDefaults: { settings: {} },
593
- user: { settings: {} },
594
- workspace: { settings: {} },
595
- merged: {
596
- security: { enablePermanentToolApproval: true },
597
- mcpServers: {},
598
- },
599
- errors: [],
600
- });
601
- ReadManyFilesTool.mockImplementation(() => ({
602
- name: 'read_many_files',
603
- kind: 'read',
604
- build: vi.fn().mockReturnValue({
605
- getDescription: () => 'Read files',
606
- toolLocations: () => [],
607
- execute: vi.fn().mockResolvedValue({
608
- llmContent: ['--- file.txt ---\n\nFile content\n\n'],
609
- }),
610
- }),
611
- }));
612
- });
613
- afterEach(() => {
614
- vi.restoreAllMocks();
615
- });
616
- it('should send available commands', async () => {
617
- await session.sendAvailableCommands();
618
- expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(expect.objectContaining({
619
- update: expect.objectContaining({
620
- sessionUpdate: 'available_commands_update',
621
- availableCommands: expect.arrayContaining([
622
- expect.objectContaining({ name: 'memory' }),
623
- expect.objectContaining({ name: 'extensions' }),
624
- expect.objectContaining({ name: 'restore' }),
625
- expect.objectContaining({ name: 'init' }),
626
- ]),
627
- }),
628
- }));
629
- });
630
- it('should await MCP initialization before processing a prompt', async () => {
631
- const stream = createMockStream([
632
- {
633
- type: StreamEventType.CHUNK,
634
- value: { candidates: [{ content: { parts: [{ text: 'Hi' }] } }] },
635
- },
636
- ]);
637
- mockChat.sendMessageStream.mockResolvedValue(stream);
638
- await session.prompt({
639
- sessionId: 'session-1',
640
- prompt: [{ type: 'text', text: 'test' }],
641
- });
642
- expect(mockConfig.waitForMcpInit).toHaveBeenCalledOnce();
643
- const waitOrder = mockConfig.waitForMcpInit.mock
644
- .invocationCallOrder[0];
645
- const sendOrder = mockChat.sendMessageStream.mock
646
- .invocationCallOrder[0];
647
- expect(waitOrder).toBeLessThan(sendOrder);
648
- });
649
- it('should handle prompt with text response', async () => {
650
- const stream = createMockStream([
651
- {
652
- type: StreamEventType.CHUNK,
653
- value: {
654
- candidates: [{ content: { parts: [{ text: 'Hello' }] } }],
655
- },
656
- },
657
- ]);
658
- mockChat.sendMessageStream.mockResolvedValue(stream);
659
- const result = await session.prompt({
660
- sessionId: 'session-1',
661
- prompt: [{ type: 'text', text: 'Hi' }],
662
- });
663
- expect(mockChat.sendMessageStream).toHaveBeenCalled();
664
- expect(mockConnection.sessionUpdate).toHaveBeenCalledWith({
665
- sessionId: 'session-1',
666
- update: {
667
- sessionUpdate: 'agent_message_chunk',
668
- content: { type: 'text', text: 'Hello' },
669
- },
670
- });
671
- expect(result).toMatchObject({ stopReason: 'end_turn' });
672
- });
673
- it('should use model router to determine model', async () => {
674
- const mockRouter = {
675
- route: vi.fn().mockResolvedValue({ model: 'routed-model' }),
676
- };
677
- mockConfig.getModelRouterService.mockReturnValue(mockRouter);
678
- const stream = createMockStream([
679
- {
680
- type: StreamEventType.CHUNK,
681
- value: {
682
- candidates: [{ content: { parts: [{ text: 'Hello' }] } }],
683
- },
684
- },
685
- ]);
686
- mockChat.sendMessageStream.mockResolvedValue(stream);
687
- await session.prompt({
688
- sessionId: 'session-1',
689
- prompt: [{ type: 'text', text: 'Hi' }],
690
- });
691
- expect(mockRouter.route).toHaveBeenCalledWith(expect.objectContaining({
692
- requestedModel: 'gemini-pro',
693
- request: [{ text: 'Hi' }],
694
- }));
695
- expect(mockChat.sendMessageStream).toHaveBeenCalledWith(expect.objectContaining({ model: 'routed-model' }), expect.any(Array), expect.any(String), expect.any(Object), expect.any(String));
696
- });
697
- it('should handle prompt with empty response (InvalidStreamError)', async () => {
698
- mockChat.sendMessageStream.mockRejectedValue(new InvalidStreamError('Empty response', 'NO_RESPONSE_TEXT'));
699
- const result = await session.prompt({
700
- sessionId: 'session-1',
701
- prompt: [{ type: 'text', text: 'Hi' }],
702
- });
703
- expect(mockChat.sendMessageStream).toHaveBeenCalled();
704
- expect(result).toMatchObject({ stopReason: 'end_turn' });
705
- });
706
- it('should handle prompt with empty response (NO_RESPONSE_TEXT anomaly)', async () => {
707
- mockChat.sendMessageStream.mockRejectedValue({ type: 'NO_RESPONSE_TEXT' });
708
- const result = await session.prompt({
709
- sessionId: 'session-1',
710
- prompt: [{ type: 'text', text: 'Hi' }],
711
- });
712
- expect(mockChat.sendMessageStream).toHaveBeenCalled();
713
- expect(result).toMatchObject({ stopReason: 'end_turn' });
714
- });
715
- it('should handle prompt with no finish reason (InvalidStreamError)', async () => {
716
- mockChat.sendMessageStream.mockRejectedValue(new InvalidStreamError('No finish reason', 'NO_FINISH_REASON'));
717
- const result = await session.prompt({
718
- sessionId: 'session-1',
719
- prompt: [{ type: 'text', text: 'Hi' }],
720
- });
721
- expect(mockChat.sendMessageStream).toHaveBeenCalled();
722
- expect(result).toMatchObject({ stopReason: 'end_turn' });
723
- });
724
- it('should handle prompt with no finish reason (NO_FINISH_REASON anomaly)', async () => {
725
- mockChat.sendMessageStream.mockRejectedValue({ type: 'NO_FINISH_REASON' });
726
- const result = await session.prompt({
727
- sessionId: 'session-1',
728
- prompt: [{ type: 'text', text: 'Hi' }],
729
- });
730
- expect(mockChat.sendMessageStream).toHaveBeenCalled();
731
- expect(result).toMatchObject({ stopReason: 'end_turn' });
732
- });
733
- it('should handle /memory command', async () => {
734
- const handleCommandSpy = vi
735
- .spyOn(session
736
- .commandHandler, 'handleCommand')
737
- .mockResolvedValue(true);
738
- const result = await session.prompt({
739
- sessionId: 'session-1',
740
- prompt: [{ type: 'text', text: '/memory view' }],
741
- });
742
- expect(result).toMatchObject({ stopReason: 'end_turn' });
743
- expect(handleCommandSpy).toHaveBeenCalledWith('/memory view', expect.any(Object));
744
- expect(mockChat.sendMessageStream).not.toHaveBeenCalled();
745
- });
746
- it('should handle /extensions command', async () => {
747
- const handleCommandSpy = vi
748
- .spyOn(session
749
- .commandHandler, 'handleCommand')
750
- .mockResolvedValue(true);
751
- const result = await session.prompt({
752
- sessionId: 'session-1',
753
- prompt: [{ type: 'text', text: '/extensions list' }],
754
- });
755
- expect(result).toMatchObject({ stopReason: 'end_turn' });
756
- expect(handleCommandSpy).toHaveBeenCalledWith('/extensions list', expect.any(Object));
757
- expect(mockChat.sendMessageStream).not.toHaveBeenCalled();
758
- });
759
- it('should handle /extensions explore command', async () => {
760
- const handleCommandSpy = vi
761
- .spyOn(session
762
- .commandHandler, 'handleCommand')
763
- .mockResolvedValue(true);
764
- const result = await session.prompt({
765
- sessionId: 'session-1',
766
- prompt: [{ type: 'text', text: '/extensions explore' }],
767
- });
768
- expect(result).toMatchObject({ stopReason: 'end_turn' });
769
- expect(handleCommandSpy).toHaveBeenCalledWith('/extensions explore', expect.any(Object));
770
- expect(mockChat.sendMessageStream).not.toHaveBeenCalled();
771
- });
772
- it('should handle /restore command', async () => {
773
- const handleCommandSpy = vi
774
- .spyOn(session
775
- .commandHandler, 'handleCommand')
776
- .mockResolvedValue(true);
777
- const result = await session.prompt({
778
- sessionId: 'session-1',
779
- prompt: [{ type: 'text', text: '/restore' }],
780
- });
781
- expect(result).toMatchObject({ stopReason: 'end_turn' });
782
- expect(handleCommandSpy).toHaveBeenCalledWith('/restore', expect.any(Object));
783
- expect(mockChat.sendMessageStream).not.toHaveBeenCalled();
784
- });
785
- it('should handle /init command', async () => {
786
- const handleCommandSpy = vi
787
- .spyOn(session
788
- .commandHandler, 'handleCommand')
789
- .mockResolvedValue(true);
790
- const result = await session.prompt({
791
- sessionId: 'session-1',
792
- prompt: [{ type: 'text', text: '/init' }],
793
- });
794
- expect(result).toMatchObject({ stopReason: 'end_turn' });
795
- expect(handleCommandSpy).toHaveBeenCalledWith('/init', expect.any(Object));
796
- expect(mockChat.sendMessageStream).not.toHaveBeenCalled();
797
- });
798
- it('should handle tool calls', async () => {
799
- const stream1 = createMockStream([
800
- {
801
- type: StreamEventType.CHUNK,
802
- value: {
803
- functionCalls: [{ name: 'test_tool', args: { foo: 'bar' } }],
804
- },
805
- },
806
- ]);
807
- const stream2 = createMockStream([
808
- {
809
- type: StreamEventType.CHUNK,
810
- value: {
811
- candidates: [{ content: { parts: [{ text: 'Result' }] } }],
812
- },
813
- },
814
- ]);
815
- mockChat.sendMessageStream
816
- .mockResolvedValueOnce(stream1)
817
- .mockResolvedValueOnce(stream2);
818
- const result = await session.prompt({
819
- sessionId: 'session-1',
820
- prompt: [{ type: 'text', text: 'Call tool' }],
821
- });
822
- expect(mockToolRegistry.getTool).toHaveBeenCalledWith('test_tool');
823
- expect(mockTool.build).toHaveBeenCalledWith({ foo: 'bar' });
824
- expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(expect.objectContaining({
825
- update: expect.objectContaining({
826
- sessionUpdate: 'tool_call',
827
- status: 'in_progress',
828
- kind: 'read',
829
- }),
830
- }));
831
- expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(expect.objectContaining({
832
- update: expect.objectContaining({
833
- sessionUpdate: 'tool_call_update',
834
- status: 'completed',
835
- title: 'Test Tool',
836
- locations: [],
837
- kind: 'read',
838
- }),
839
- }));
840
- expect(result).toMatchObject({ stopReason: 'end_turn' });
841
- });
842
- it('should handle tool call permission request', async () => {
843
- const confirmationDetails = {
844
- type: 'info',
845
- onConfirm: vi.fn(),
846
- };
847
- mockTool.build.mockReturnValue({
848
- getDescription: () => 'Test Tool',
849
- toolLocations: () => [],
850
- shouldConfirmExecute: vi.fn().mockResolvedValue(confirmationDetails),
851
- execute: vi.fn().mockResolvedValue({ llmContent: 'Tool Result' }),
852
- });
853
- mockConnection.requestPermission.mockResolvedValue({
854
- outcome: {
855
- outcome: 'selected',
856
- optionId: ToolConfirmationOutcome.ProceedOnce,
857
- },
858
- });
859
- const stream1 = createMockStream([
860
- {
861
- type: StreamEventType.CHUNK,
862
- value: {
863
- functionCalls: [{ name: 'test_tool', args: {} }],
864
- },
865
- },
866
- ]);
867
- const stream2 = createMockStream([
868
- {
869
- type: StreamEventType.CHUNK,
870
- value: { candidates: [] },
871
- },
872
- ]);
873
- mockChat.sendMessageStream
874
- .mockResolvedValueOnce(stream1)
875
- .mockResolvedValueOnce(stream2);
876
- await session.prompt({
877
- sessionId: 'session-1',
878
- prompt: [{ type: 'text', text: 'Call tool' }],
879
- });
880
- expect(mockConnection.requestPermission).toHaveBeenCalled();
881
- expect(confirmationDetails.onConfirm).toHaveBeenCalledWith(ToolConfirmationOutcome.ProceedOnce);
882
- });
883
- it('should exclude always allow options when disableAlwaysAllow is true', async () => {
884
- mockConfig.getDisableAlwaysAllow = vi.fn().mockReturnValue(true);
885
- const confirmationDetails = {
886
- type: 'info',
887
- onConfirm: vi.fn(),
888
- };
889
- mockTool.build.mockReturnValue({
890
- getDescription: () => 'Test Tool',
891
- toolLocations: () => [],
892
- shouldConfirmExecute: vi.fn().mockResolvedValue(confirmationDetails),
893
- execute: vi.fn().mockResolvedValue({ llmContent: 'Tool Result' }),
894
- });
895
- mockConnection.requestPermission.mockResolvedValue({
896
- outcome: {
897
- outcome: 'selected',
898
- optionId: ToolConfirmationOutcome.ProceedOnce,
899
- },
900
- });
901
- const stream1 = createMockStream([
902
- {
903
- type: StreamEventType.CHUNK,
904
- value: {
905
- functionCalls: [{ name: 'test_tool', args: {} }],
906
- },
907
- },
908
- ]);
909
- const stream2 = createMockStream([
910
- {
911
- type: StreamEventType.CHUNK,
912
- value: { candidates: [] },
913
- },
914
- ]);
915
- mockChat.sendMessageStream
916
- .mockResolvedValueOnce(stream1)
917
- .mockResolvedValueOnce(stream2);
918
- await session.prompt({
919
- sessionId: 'session-1',
920
- prompt: [{ type: 'text', text: 'Call tool' }],
921
- });
922
- expect(mockConnection.requestPermission).toHaveBeenCalledWith(expect.objectContaining({
923
- options: expect.not.arrayContaining([
924
- expect.objectContaining({
925
- optionId: ToolConfirmationOutcome.ProceedAlways,
926
- }),
927
- ]),
928
- }));
929
- });
930
- it('should exclude always allow and save permanent option when enablePermanentToolApproval is false', async () => {
931
- mockConfig.getDisableAlwaysAllow = vi.fn().mockReturnValue(false);
932
- const confirmationDetails = {
933
- type: 'edit',
934
- onConfirm: vi.fn(),
935
- };
936
- mockTool.build.mockReturnValue({
937
- getDescription: () => 'Test Tool',
938
- toolLocations: () => [],
939
- shouldConfirmExecute: vi.fn().mockResolvedValue(confirmationDetails),
940
- execute: vi.fn().mockResolvedValue({ llmContent: 'Tool Result' }),
941
- });
942
- const customSettings = {
943
- system: { settings: {} },
944
- systemDefaults: { settings: {} },
945
- user: { settings: {} },
946
- workspace: { settings: {} },
947
- merged: {
948
- security: { enablePermanentToolApproval: false },
949
- mcpServers: {},
950
- },
951
- errors: [],
952
- };
953
- const localSession = new Session('session-2', mockChat, mockConfig, mockConnection, customSettings);
954
- mockConnection.requestPermission.mockResolvedValueOnce({
955
- outcome: {
956
- outcome: 'selected',
957
- optionId: ToolConfirmationOutcome.ProceedOnce,
958
- },
959
- });
960
- const stream1 = createMockStream([
961
- {
962
- type: StreamEventType.CHUNK,
963
- value: {
964
- functionCalls: [{ name: 'test_tool', args: {} }],
965
- },
966
- },
967
- ]);
968
- const stream2 = createMockStream([
969
- {
970
- type: StreamEventType.CHUNK,
971
- value: { candidates: [] },
972
- },
973
- ]);
974
- mockChat.sendMessageStream
975
- .mockResolvedValueOnce(stream1)
976
- .mockResolvedValueOnce(stream2);
977
- await localSession.prompt({
978
- sessionId: 'session-2',
979
- prompt: [{ type: 'text', text: 'Call tool' }],
980
- });
981
- expect(mockConnection.requestPermission).toHaveBeenCalledWith(expect.objectContaining({
982
- options: expect.not.arrayContaining([
983
- expect.objectContaining({
984
- optionId: ToolConfirmationOutcome.ProceedAlwaysAndSave,
985
- }),
986
- ]),
987
- }));
988
- expect(mockConnection.requestPermission).toHaveBeenCalledWith(expect.objectContaining({
989
- options: expect.arrayContaining([
990
- expect.objectContaining({
991
- optionId: ToolConfirmationOutcome.ProceedAlways,
992
- }),
993
- ]),
994
- }));
995
- });
996
- it('should include always allow and save permanent option when enablePermanentToolApproval is true', async () => {
997
- mockConfig.getDisableAlwaysAllow = vi.fn().mockReturnValue(false);
998
- const confirmationDetails = {
999
- type: 'edit',
1000
- onConfirm: vi.fn(),
1001
- };
1002
- mockTool.build.mockReturnValue({
1003
- getDescription: () => 'Test Tool',
1004
- toolLocations: () => [],
1005
- shouldConfirmExecute: vi.fn().mockResolvedValue(confirmationDetails),
1006
- execute: vi.fn().mockResolvedValue({ llmContent: 'Tool Result' }),
1007
- });
1008
- const customSettings = {
1009
- system: { settings: {} },
1010
- systemDefaults: { settings: {} },
1011
- user: { settings: {} },
1012
- workspace: { settings: {} },
1013
- merged: {
1014
- security: { enablePermanentToolApproval: true },
1015
- mcpServers: {},
1016
- },
1017
- errors: [],
1018
- };
1019
- const localSession = new Session('session-2', mockChat, mockConfig, mockConnection, customSettings);
1020
- mockConnection.requestPermission.mockResolvedValueOnce({
1021
- outcome: {
1022
- outcome: 'selected',
1023
- optionId: ToolConfirmationOutcome.ProceedOnce,
1024
- },
1025
- });
1026
- const stream1 = createMockStream([
1027
- {
1028
- type: StreamEventType.CHUNK,
1029
- value: {
1030
- functionCalls: [{ name: 'test_tool', args: {} }],
1031
- },
1032
- },
1033
- ]);
1034
- const stream2 = createMockStream([
1035
- {
1036
- type: StreamEventType.CHUNK,
1037
- value: { candidates: [] },
1038
- },
1039
- ]);
1040
- mockChat.sendMessageStream
1041
- .mockResolvedValueOnce(stream1)
1042
- .mockResolvedValueOnce(stream2);
1043
- await localSession.prompt({
1044
- sessionId: 'session-2',
1045
- prompt: [{ type: 'text', text: 'Call tool' }],
1046
- });
1047
- expect(mockConnection.requestPermission).toHaveBeenCalledWith(expect.objectContaining({
1048
- options: expect.arrayContaining([
1049
- expect.objectContaining({
1050
- optionId: ToolConfirmationOutcome.ProceedAlwaysAndSave,
1051
- name: 'Allow for this file in all future sessions',
1052
- }),
1053
- ]),
1054
- }));
1055
- });
1056
- it('should use filePath for ACP diff content in permission request', async () => {
1057
- const confirmationDetails = {
1058
- type: 'edit',
1059
- title: 'Confirm Write: test.txt',
1060
- fileName: 'test.txt',
1061
- filePath: '/tmp/test.txt',
1062
- originalContent: 'old',
1063
- newContent: 'new',
1064
- onConfirm: vi.fn(),
1065
- };
1066
- mockTool.build.mockReturnValue({
1067
- getDescription: () => 'Test Tool',
1068
- toolLocations: () => [],
1069
- shouldConfirmExecute: vi.fn().mockResolvedValue(confirmationDetails),
1070
- execute: vi.fn().mockResolvedValue({ llmContent: 'Tool Result' }),
1071
- });
1072
- mockConnection.requestPermission.mockResolvedValue({
1073
- outcome: {
1074
- outcome: 'selected',
1075
- optionId: ToolConfirmationOutcome.ProceedOnce,
1076
- },
1077
- });
1078
- const stream1 = createMockStream([
1079
- {
1080
- type: StreamEventType.CHUNK,
1081
- value: {
1082
- functionCalls: [{ name: 'test_tool', args: {} }],
1083
- },
1084
- },
1085
- ]);
1086
- const stream2 = createMockStream([
1087
- {
1088
- type: StreamEventType.CHUNK,
1089
- value: { candidates: [] },
1090
- },
1091
- ]);
1092
- mockChat.sendMessageStream
1093
- .mockResolvedValueOnce(stream1)
1094
- .mockResolvedValueOnce(stream2);
1095
- await session.prompt({
1096
- sessionId: 'session-1',
1097
- prompt: [{ type: 'text', text: 'Call tool' }],
1098
- });
1099
- expect(mockConnection.requestPermission).toHaveBeenCalledWith(expect.objectContaining({
1100
- toolCall: expect.objectContaining({
1101
- content: expect.arrayContaining([
1102
- expect.objectContaining({
1103
- type: 'diff',
1104
- path: '/tmp/test.txt',
1105
- oldText: 'old',
1106
- newText: 'new',
1107
- }),
1108
- ]),
1109
- }),
1110
- }));
1111
- });
1112
- it('should split getDisplayTitle and getExplanation for title and content in permission request', async () => {
1113
- const confirmationDetails = {
1114
- type: 'info',
1115
- onConfirm: vi.fn(),
1116
- };
1117
- mockTool.build.mockReturnValue({
1118
- getDescription: () => 'Original Description',
1119
- getDisplayTitle: () => 'Display Title Only',
1120
- getExplanation: () => 'A detailed explanation text',
1121
- toolLocations: () => [],
1122
- shouldConfirmExecute: vi.fn().mockResolvedValue(confirmationDetails),
1123
- execute: vi.fn().mockResolvedValue({ llmContent: 'Tool Result' }),
1124
- });
1125
- mockConnection.requestPermission.mockResolvedValue({
1126
- outcome: {
1127
- outcome: 'selected',
1128
- optionId: ToolConfirmationOutcome.ProceedOnce,
1129
- },
1130
- });
1131
- const stream1 = createMockStream([
1132
- {
1133
- type: StreamEventType.CHUNK,
1134
- value: {
1135
- functionCalls: [{ name: 'test_tool', args: {} }],
1136
- },
1137
- },
1138
- ]);
1139
- const stream2 = createMockStream([
1140
- {
1141
- type: StreamEventType.CHUNK,
1142
- value: { candidates: [] },
1143
- },
1144
- ]);
1145
- mockChat.sendMessageStream
1146
- .mockResolvedValueOnce(stream1)
1147
- .mockResolvedValueOnce(stream2);
1148
- await session.prompt({
1149
- sessionId: 'session-1',
1150
- prompt: [{ type: 'text', text: 'Call tool' }],
1151
- });
1152
- expect(mockConnection.requestPermission).toHaveBeenCalledWith(expect.objectContaining({
1153
- toolCall: expect.objectContaining({
1154
- title: 'Display Title Only',
1155
- content: [],
1156
- }),
1157
- }));
1158
- expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(expect.objectContaining({
1159
- update: expect.objectContaining({
1160
- sessionUpdate: 'agent_thought_chunk',
1161
- content: { type: 'text', text: 'A detailed explanation text' },
1162
- }),
1163
- }));
1164
- });
1165
- it('should call updatePolicy when tool permission triggers always allow', async () => {
1166
- const confirmationDetails = {
1167
- type: 'info',
1168
- onConfirm: vi.fn(),
1169
- };
1170
- mockTool.build.mockReturnValue({
1171
- getDescription: () => 'Test Tool',
1172
- toolLocations: () => [],
1173
- shouldConfirmExecute: vi.fn().mockResolvedValue(confirmationDetails),
1174
- execute: vi.fn().mockResolvedValue({ llmContent: 'Tool Result' }),
1175
- });
1176
- mockConnection.requestPermission.mockResolvedValue({
1177
- outcome: {
1178
- outcome: 'selected',
1179
- optionId: ToolConfirmationOutcome.ProceedAlways,
1180
- },
1181
- });
1182
- const stream1 = createMockStream([
1183
- {
1184
- type: StreamEventType.CHUNK,
1185
- value: {
1186
- functionCalls: [{ name: 'test_tool', args: {} }],
1187
- },
1188
- },
1189
- ]);
1190
- const stream2 = createMockStream([
1191
- {
1192
- type: StreamEventType.CHUNK,
1193
- value: { candidates: [] },
1194
- },
1195
- ]);
1196
- mockChat.sendMessageStream
1197
- .mockResolvedValueOnce(stream1)
1198
- .mockResolvedValueOnce(stream2);
1199
- const { updatePolicy } = await import('@google/gemini-cli-core');
1200
- await session.prompt({
1201
- sessionId: 'session-1',
1202
- prompt: [{ type: 'text', text: 'Call tool' }],
1203
- });
1204
- expect(confirmationDetails.onConfirm).toHaveBeenCalled();
1205
- expect(updatePolicy).toHaveBeenCalled();
1206
- });
1207
- it('should use filePath for ACP diff content in tool result', async () => {
1208
- mockTool.build.mockReturnValue({
1209
- getDescription: () => 'Test Tool',
1210
- toolLocations: () => [],
1211
- shouldConfirmExecute: vi.fn().mockResolvedValue(null),
1212
- execute: vi.fn().mockResolvedValue({
1213
- llmContent: 'Tool Result',
1214
- returnDisplay: {
1215
- fileName: 'test.txt',
1216
- filePath: '/tmp/test.txt',
1217
- originalContent: 'old',
1218
- newContent: 'new',
1219
- },
1220
- }),
1221
- });
1222
- const stream1 = createMockStream([
1223
- {
1224
- type: StreamEventType.CHUNK,
1225
- value: {
1226
- functionCalls: [{ name: 'test_tool', args: {} }],
1227
- },
1228
- },
1229
- ]);
1230
- const stream2 = createMockStream([
1231
- {
1232
- type: StreamEventType.CHUNK,
1233
- value: { candidates: [] },
1234
- },
1235
- ]);
1236
- mockChat.sendMessageStream
1237
- .mockResolvedValueOnce(stream1)
1238
- .mockResolvedValueOnce(stream2);
1239
- await session.prompt({
1240
- sessionId: 'session-1',
1241
- prompt: [{ type: 'text', text: 'Call tool' }],
1242
- });
1243
- const updateCalls = mockConnection.sessionUpdate.mock.calls.map((call) => call[0]);
1244
- const toolCallUpdate = updateCalls.find((call) => call.update?.sessionUpdate === 'tool_call_update');
1245
- expect(toolCallUpdate).toEqual(expect.objectContaining({
1246
- update: expect.objectContaining({
1247
- content: expect.arrayContaining([
1248
- expect.objectContaining({
1249
- type: 'diff',
1250
- path: '/tmp/test.txt',
1251
- oldText: 'old',
1252
- newText: 'new',
1253
- }),
1254
- ]),
1255
- }),
1256
- }));
1257
- });
1258
- it('should handle tool call cancellation by user', async () => {
1259
- const confirmationDetails = {
1260
- type: 'info',
1261
- onConfirm: vi.fn(),
1262
- };
1263
- mockTool.build.mockReturnValue({
1264
- getDescription: () => 'Test Tool',
1265
- toolLocations: () => [],
1266
- shouldConfirmExecute: vi.fn().mockResolvedValue(confirmationDetails),
1267
- execute: vi.fn().mockResolvedValue({ llmContent: 'Tool Result' }),
1268
- });
1269
- mockConnection.requestPermission.mockResolvedValue({
1270
- outcome: { outcome: 'cancelled' },
1271
- });
1272
- const stream1 = createMockStream([
1273
- {
1274
- type: StreamEventType.CHUNK,
1275
- value: {
1276
- functionCalls: [{ name: 'test_tool', args: {} }],
1277
- },
1278
- },
1279
- ]);
1280
- const stream2 = createMockStream([
1281
- {
1282
- type: StreamEventType.CHUNK,
1283
- value: { candidates: [] },
1284
- },
1285
- ]);
1286
- mockChat.sendMessageStream
1287
- .mockResolvedValueOnce(stream1)
1288
- .mockResolvedValueOnce(stream2);
1289
- await session.prompt({
1290
- sessionId: 'session-1',
1291
- prompt: [{ type: 'text', text: 'Call tool' }],
1292
- });
1293
- // When cancelled, it sends an error response to the model
1294
- // We can verify that the second call to sendMessageStream contains the error
1295
- expect(mockChat.sendMessageStream).toHaveBeenCalledTimes(2);
1296
- const secondCallArgs = mockChat.sendMessageStream.mock.calls[1];
1297
- const parts = secondCallArgs[1]; // parts
1298
- expect(parts).toEqual(expect.arrayContaining([
1299
- expect.objectContaining({
1300
- functionResponse: expect.objectContaining({
1301
- response: {
1302
- error: expect.stringContaining('canceled by the user'),
1303
- },
1304
- }),
1305
- }),
1306
- ]));
1307
- });
1308
- it('should include _meta.kind in diff tool calls', async () => {
1309
- // Test 'add' (no original content)
1310
- const addConfirmation = {
1311
- type: 'edit',
1312
- fileName: 'new.txt',
1313
- originalContent: null,
1314
- newContent: 'New content',
1315
- onConfirm: vi.fn(),
1316
- };
1317
- // Test 'modify' (original and new content)
1318
- const modifyConfirmation = {
1319
- type: 'edit',
1320
- fileName: 'existing.txt',
1321
- originalContent: 'Old content',
1322
- newContent: 'New content',
1323
- onConfirm: vi.fn(),
1324
- };
1325
- // Test 'delete' (original content, no new content)
1326
- const deleteConfirmation = {
1327
- type: 'edit',
1328
- fileName: 'deleted.txt',
1329
- originalContent: 'Old content',
1330
- newContent: '',
1331
- onConfirm: vi.fn(),
1332
- };
1333
- const mockBuild = vi.fn();
1334
- mockTool.build = mockBuild;
1335
- // Helper to simulate tool call and check permission request
1336
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1337
- const checkDiffKind = async (confirmation, expectedKind) => {
1338
- mockBuild.mockReturnValueOnce({
1339
- getDescription: () => 'Test Tool',
1340
- toolLocations: () => [],
1341
- shouldConfirmExecute: vi.fn().mockResolvedValue(confirmation),
1342
- execute: vi.fn().mockResolvedValue({ llmContent: 'Result' }),
1343
- });
1344
- mockConnection.requestPermission.mockResolvedValueOnce({
1345
- outcome: {
1346
- outcome: 'selected',
1347
- optionId: ToolConfirmationOutcome.ProceedOnce,
1348
- },
1349
- });
1350
- const stream = createMockStream([
1351
- {
1352
- type: StreamEventType.CHUNK,
1353
- value: {
1354
- functionCalls: [{ name: 'test_tool', args: {} }],
1355
- },
1356
- },
1357
- ]);
1358
- const emptyStream = createMockStream([]);
1359
- mockChat.sendMessageStream
1360
- .mockResolvedValueOnce(stream)
1361
- .mockResolvedValueOnce(emptyStream);
1362
- await session.prompt({
1363
- sessionId: 'session-1',
1364
- prompt: [{ type: 'text', text: 'Call tool' }],
1365
- });
1366
- expect(mockConnection.requestPermission).toHaveBeenCalledWith(expect.objectContaining({
1367
- toolCall: expect.objectContaining({
1368
- content: expect.arrayContaining([
1369
- expect.objectContaining({
1370
- type: 'diff',
1371
- _meta: { kind: expectedKind },
1372
- }),
1373
- ]),
1374
- }),
1375
- }));
1376
- };
1377
- await checkDiffKind(addConfirmation, 'add');
1378
- await checkDiffKind(modifyConfirmation, 'modify');
1379
- await checkDiffKind(deleteConfirmation, 'delete');
1380
- });
1381
- it('should handle @path resolution', async () => {
1382
- path.resolve.mockReturnValue('/tmp/file.txt');
1383
- fs.stat.mockResolvedValue({
1384
- isDirectory: () => false,
1385
- });
1386
- const stream = createMockStream([
1387
- {
1388
- type: StreamEventType.CHUNK,
1389
- value: { candidates: [] },
1390
- },
1391
- ]);
1392
- mockChat.sendMessageStream.mockResolvedValue(stream);
1393
- await session.prompt({
1394
- sessionId: 'session-1',
1395
- prompt: [
1396
- { type: 'text', text: 'Read' },
1397
- {
1398
- type: 'resource_link',
1399
- uri: 'file://file.txt',
1400
- mimeType: 'text/plain',
1401
- name: 'file.txt',
1402
- },
1403
- ],
1404
- });
1405
- expect(path.resolve).toHaveBeenCalled();
1406
- expect(fs.stat).toHaveBeenCalled();
1407
- expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(expect.objectContaining({
1408
- update: expect.objectContaining({
1409
- sessionUpdate: 'tool_call_update',
1410
- status: 'completed',
1411
- title: 'Read files',
1412
- locations: [],
1413
- kind: 'read',
1414
- }),
1415
- }));
1416
- // Verify ReadManyFilesTool was used (implicitly by checking if sendMessageStream was called with resolved content)
1417
- // Since we mocked ReadManyFilesTool to return specific content, we can check the args passed to sendMessageStream
1418
- expect(mockChat.sendMessageStream).toHaveBeenCalledWith(expect.anything(), expect.arrayContaining([
1419
- expect.objectContaining({
1420
- text: expect.stringContaining('Content from @file.txt'),
1421
- }),
1422
- ]), expect.anything(), expect.any(AbortSignal), LlmRole.MAIN);
1423
- });
1424
- it('should handle @path resolution error', async () => {
1425
- path.resolve.mockReturnValue('/tmp/error.txt');
1426
- fs.stat.mockResolvedValue({
1427
- isDirectory: () => false,
1428
- });
1429
- const MockReadManyFilesTool = ReadManyFilesTool;
1430
- MockReadManyFilesTool.mockImplementationOnce(() => ({
1431
- name: 'read_many_files',
1432
- kind: 'read',
1433
- build: vi.fn().mockReturnValue({
1434
- getDescription: () => 'Read files',
1435
- toolLocations: () => [],
1436
- execute: vi.fn().mockRejectedValue(new Error('File read failed')),
1437
- }),
1438
- }));
1439
- const stream = createMockStream([
1440
- {
1441
- type: StreamEventType.CHUNK,
1442
- value: { candidates: [] },
1443
- },
1444
- ]);
1445
- mockChat.sendMessageStream.mockResolvedValue(stream);
1446
- await expect(session.prompt({
1447
- sessionId: 'session-1',
1448
- prompt: [
1449
- { type: 'text', text: 'Read' },
1450
- {
1451
- type: 'resource_link',
1452
- uri: 'file://error.txt',
1453
- mimeType: 'text/plain',
1454
- name: 'error.txt',
1455
- },
1456
- ],
1457
- })).rejects.toThrow('File read failed');
1458
- expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(expect.objectContaining({
1459
- update: expect.objectContaining({
1460
- sessionUpdate: 'tool_call_update',
1461
- status: 'failed',
1462
- content: expect.arrayContaining([
1463
- expect.objectContaining({
1464
- content: expect.objectContaining({
1465
- text: expect.stringMatching(/File read failed/),
1466
- }),
1467
- }),
1468
- ]),
1469
- kind: 'read',
1470
- }),
1471
- }));
1472
- });
1473
- it('should handle @path validation error and bubble it to user', async () => {
1474
- mockConfig.getTargetDir.mockReturnValue('/workspace');
1475
- path.resolve.mockReturnValue('/tmp/disallowed.txt');
1476
- mockConfig.validatePathAccess.mockReturnValue('Path is outside workspace');
1477
- // Force fs.stat to fail to skip direct reading and triggers the warning
1478
- fs.stat.mockRejectedValue(new Error('File not found'));
1479
- const stream = createMockStream([
1480
- {
1481
- type: StreamEventType.CHUNK,
1482
- value: { candidates: [] },
1483
- },
1484
- ]);
1485
- mockChat.sendMessageStream.mockResolvedValue(stream);
1486
- await session.prompt({
1487
- sessionId: 'session-1',
1488
- prompt: [
1489
- {
1490
- type: 'resource_link',
1491
- uri: 'file://disallowed.txt',
1492
- mimeType: 'text/plain',
1493
- name: 'disallowed.txt',
1494
- },
1495
- ],
1496
- });
1497
- // Verify warning sent via sendUpdate
1498
- expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(expect.objectContaining({
1499
- update: expect.objectContaining({
1500
- sessionUpdate: 'agent_thought_chunk',
1501
- content: expect.objectContaining({
1502
- text: expect.stringContaining('Warning: skipping access to `disallowed.txt`. Reason: Path is outside workspace'),
1503
- }),
1504
- }),
1505
- }));
1506
- });
1507
- it('should read absolute file directly if outside workspace', async () => {
1508
- mockConfig.getTargetDir.mockReturnValue('/workspace');
1509
- const testFilePath = '/tmp/custom.txt';
1510
- path.resolve.mockReturnValue(testFilePath);
1511
- mockConfig.validatePathAccess.mockReturnValue('Path is outside workspace');
1512
- mockConnection.requestPermission.mockResolvedValue({
1513
- outcome: {
1514
- outcome: 'selected',
1515
- optionId: ToolConfirmationOutcome.ProceedOnce,
1516
- },
1517
- });
1518
- const mockStats = {
1519
- isFile: () => true,
1520
- isDirectory: () => false,
1521
- };
1522
- fs.stat.mockResolvedValue(mockStats);
1523
- processSingleFileContent.mockResolvedValue({
1524
- llmContent: 'Absolute File Content',
1525
- });
1526
- const stream = createMockStream([
1527
- {
1528
- type: StreamEventType.CHUNK,
1529
- value: { candidates: [] },
1530
- },
1531
- ]);
1532
- mockChat.sendMessageStream.mockResolvedValue(stream);
1533
- await session.prompt({
1534
- sessionId: 'session-1',
1535
- prompt: [
1536
- {
1537
- type: 'resource_link',
1538
- uri: `file://${testFilePath}`,
1539
- mimeType: 'text/plain',
1540
- name: 'custom.txt',
1541
- },
1542
- ],
1543
- });
1544
- expect(processSingleFileContent).toHaveBeenCalledWith(testFilePath, expect.anything(), expect.anything());
1545
- // Verify content appended to sendMessageStream parts
1546
- expect(mockChat.sendMessageStream).toHaveBeenCalledWith(expect.anything(), expect.arrayContaining([
1547
- expect.objectContaining({
1548
- text: 'Absolute File Content',
1549
- }),
1550
- ]), expect.anything(), expect.any(AbortSignal), expect.anything());
1551
- });
1552
- it('should read escaping relative file directly if outside workspace', async () => {
1553
- mockConfig.getTargetDir.mockReturnValue('/workspace');
1554
- const testFilePath = '../../custom.txt';
1555
- path.resolve.mockReturnValue('/custom.txt');
1556
- mockConfig.validatePathAccess.mockReturnValue('Path is outside workspace');
1557
- mockConnection.requestPermission.mockResolvedValue({
1558
- outcome: {
1559
- outcome: 'selected',
1560
- optionId: ToolConfirmationOutcome.ProceedOnce,
1561
- },
1562
- });
1563
- const mockStats = {
1564
- isFile: () => true,
1565
- isDirectory: () => false,
1566
- };
1567
- fs.stat.mockResolvedValue(mockStats);
1568
- processSingleFileContent.mockResolvedValue({
1569
- llmContent: 'Escaping Relative File Content',
1570
- });
1571
- const stream = createMockStream([
1572
- {
1573
- type: StreamEventType.CHUNK,
1574
- value: { candidates: [] },
1575
- },
1576
- ]);
1577
- mockChat.sendMessageStream.mockResolvedValue(stream);
1578
- await session.prompt({
1579
- sessionId: 'session-1',
1580
- prompt: [
1581
- {
1582
- type: 'resource_link',
1583
- uri: `file://${testFilePath}`,
1584
- mimeType: 'text/plain',
1585
- name: 'custom.txt',
1586
- },
1587
- ],
1588
- });
1589
- expect(processSingleFileContent).toHaveBeenCalledWith('/custom.txt', expect.any(String), expect.anything());
1590
- expect(mockChat.sendMessageStream).toHaveBeenCalledWith(expect.anything(), expect.arrayContaining([
1591
- expect.objectContaining({
1592
- text: 'Escaping Relative File Content',
1593
- }),
1594
- ]), expect.anything(), expect.any(AbortSignal), expect.anything());
1595
- });
1596
- it('should handle cancellation during prompt', async () => {
1597
- let streamController;
1598
- const stream = new ReadableStream({
1599
- start(controller) {
1600
- streamController = controller;
1601
- },
1602
- });
1603
- let streamStarted;
1604
- const streamStartedPromise = new Promise((resolve) => {
1605
- streamStarted = resolve;
1606
- });
1607
- // Adapt web stream to async iterable
1608
- async function* asyncStream() {
1609
- process.stdout.write('TEST: asyncStream started\n');
1610
- streamStarted(true);
1611
- const reader = stream.getReader();
1612
- try {
1613
- while (true) {
1614
- process.stdout.write('TEST: waiting for read\n');
1615
- const { done, value } = await reader.read();
1616
- process.stdout.write(`TEST: read returned done=${done}\n`);
1617
- if (done)
1618
- break;
1619
- yield value;
1620
- }
1621
- }
1622
- finally {
1623
- process.stdout.write('TEST: releasing lock\n');
1624
- reader.releaseLock();
1625
- }
1626
- }
1627
- mockChat.sendMessageStream.mockResolvedValue(asyncStream());
1628
- process.stdout.write('TEST: calling prompt\n');
1629
- const promptPromise = session.prompt({
1630
- sessionId: 'session-1',
1631
- prompt: [{ type: 'text', text: 'Hi' }],
1632
- });
1633
- process.stdout.write('TEST: waiting for streamStarted\n');
1634
- await streamStartedPromise;
1635
- process.stdout.write('TEST: streamStarted\n');
1636
- await session.cancelPendingPrompt();
1637
- process.stdout.write('TEST: cancelled\n');
1638
- // Close the stream to allow prompt loop to continue and check aborted signal
1639
- streamController.close();
1640
- process.stdout.write('TEST: stream closed\n');
1641
- const result = await promptPromise;
1642
- process.stdout.write(`TEST: result received ${JSON.stringify(result)}\n`);
1643
- expect(result).toEqual({ stopReason: 'cancelled' });
1644
- });
1645
- it('should handle rate limit error', async () => {
1646
- const error = new Error('Rate limit');
1647
- error.status = 429;
1648
- mockChat.sendMessageStream.mockRejectedValue(error);
1649
- await expect(session.prompt({
1650
- sessionId: 'session-1',
1651
- prompt: [{ type: 'text', text: 'Hi' }],
1652
- })).rejects.toMatchObject({
1653
- code: 429,
1654
- message: 'Rate limit exceeded. Try again later.',
1655
- });
1656
- });
1657
- it('should handle tool execution error', async () => {
1658
- mockTool.build.mockReturnValue({
1659
- getDescription: () => 'Test Tool',
1660
- toolLocations: () => [],
1661
- shouldConfirmExecute: vi.fn().mockResolvedValue(null),
1662
- execute: vi.fn().mockRejectedValue(new Error('Tool failed')),
1663
- });
1664
- const stream1 = createMockStream([
1665
- {
1666
- type: StreamEventType.CHUNK,
1667
- value: {
1668
- functionCalls: [{ name: 'test_tool', args: {} }],
1669
- },
1670
- },
1671
- ]);
1672
- const stream2 = createMockStream([
1673
- {
1674
- type: StreamEventType.CHUNK,
1675
- value: { candidates: [] },
1676
- },
1677
- ]);
1678
- mockChat.sendMessageStream
1679
- .mockResolvedValueOnce(stream1)
1680
- .mockResolvedValueOnce(stream2);
1681
- await session.prompt({
1682
- sessionId: 'session-1',
1683
- prompt: [{ type: 'text', text: 'Call tool' }],
1684
- });
1685
- expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(expect.objectContaining({
1686
- update: expect.objectContaining({
1687
- sessionUpdate: 'tool_call_update',
1688
- status: 'failed',
1689
- content: expect.arrayContaining([
1690
- expect.objectContaining({
1691
- content: expect.objectContaining({ text: 'Tool failed' }),
1692
- }),
1693
- ]),
1694
- kind: 'read',
1695
- }),
1696
- }));
1697
- });
1698
- it('should handle missing tool', async () => {
1699
- mockToolRegistry.getTool.mockReturnValue(undefined);
1700
- const stream1 = createMockStream([
1701
- {
1702
- type: StreamEventType.CHUNK,
1703
- value: {
1704
- functionCalls: [{ name: 'unknown_tool', args: {} }],
1705
- },
1706
- },
1707
- ]);
1708
- const stream2 = createMockStream([
1709
- {
1710
- type: StreamEventType.CHUNK,
1711
- value: { candidates: [] },
1712
- },
1713
- ]);
1714
- mockChat.sendMessageStream
1715
- .mockResolvedValueOnce(stream1)
1716
- .mockResolvedValueOnce(stream2);
1717
- await session.prompt({
1718
- sessionId: 'session-1',
1719
- prompt: [{ type: 'text', text: 'Call tool' }],
1720
- });
1721
- // Should send error response to model
1722
- expect(mockChat.sendMessageStream).toHaveBeenCalledTimes(2);
1723
- const secondCallArgs = mockChat.sendMessageStream.mock.calls[1];
1724
- const parts = secondCallArgs[1];
1725
- expect(parts).toEqual(expect.arrayContaining([
1726
- expect.objectContaining({
1727
- functionResponse: expect.objectContaining({
1728
- response: {
1729
- error: expect.stringContaining('not found in registry'),
1730
- },
1731
- }),
1732
- }),
1733
- ]));
1734
- });
1735
- it('should ignore files based on configuration', async () => {
1736
- mockConfig.getFileService().shouldIgnoreFile.mockReturnValue(true);
1737
- const stream = createMockStream([
1738
- {
1739
- type: StreamEventType.CHUNK,
1740
- value: { candidates: [] },
1741
- },
1742
- ]);
1743
- mockChat.sendMessageStream.mockResolvedValue(stream);
1744
- await session.prompt({
1745
- sessionId: 'session-1',
1746
- prompt: [
1747
- {
1748
- type: 'resource_link',
1749
- uri: 'file://ignored.txt',
1750
- mimeType: 'text/plain',
1751
- name: 'ignored.txt',
1752
- },
1753
- ],
1754
- });
1755
- // Should not read file
1756
- expect(mockToolRegistry.getTool).not.toHaveBeenCalledWith('read_many_files');
1757
- });
1758
- it('should handle directory resolution with glob', async () => {
1759
- path.resolve.mockReturnValue('/tmp/dir');
1760
- fs.stat.mockResolvedValue({
1761
- isDirectory: () => true,
1762
- });
1763
- const stream = createMockStream([
1764
- {
1765
- type: StreamEventType.CHUNK,
1766
- value: { candidates: [] },
1767
- },
1768
- ]);
1769
- mockChat.sendMessageStream.mockResolvedValue(stream);
1770
- await session.prompt({
1771
- sessionId: 'session-1',
1772
- prompt: [
1773
- {
1774
- type: 'resource_link',
1775
- uri: 'file://dir',
1776
- mimeType: 'text/plain',
1777
- name: 'dir',
1778
- },
1779
- ],
1780
- });
1781
- // Should use glob
1782
- // ReadManyFilesTool is instantiated directly, so we check if the mock instance's build method was called
1783
- const MockReadManyFilesTool = ReadManyFilesTool;
1784
- const mockInstance = MockReadManyFilesTool.mock.results[MockReadManyFilesTool.mock.results.length - 1].value;
1785
- expect(mockInstance.build).toHaveBeenCalled();
1786
- });
1787
- it('should set mode on config', () => {
1788
- session.setMode(ApprovalMode.AUTO_EDIT);
1789
- expect(mockConfig.setApprovalMode).toHaveBeenCalledWith(ApprovalMode.AUTO_EDIT);
1790
- });
1791
- it('should throw error for invalid mode', () => {
1792
- expect(() => session.setMode('invalid-mode')).toThrow('Invalid or unavailable mode: invalid-mode');
1793
- });
1794
- it('should set model on config', () => {
1795
- session.setModel('gemini-2.0-flash-exp');
1796
- expect(mockConfig.setModel).toHaveBeenCalledWith('gemini-2.0-flash-exp');
1797
- });
1798
- it('should handle unquoted commands from autocomplete (with empty leading parts)', async () => {
1799
- // Mock handleCommand to verify it gets called
1800
- const handleCommandSpy = vi
1801
- .spyOn(session
1802
- .commandHandler, 'handleCommand')
1803
- .mockResolvedValue(true);
1804
- await session.prompt({
1805
- sessionId: 'session-1',
1806
- prompt: [
1807
- { type: 'text', text: '' },
1808
- { type: 'text', text: '/memory' },
1809
- ],
1810
- });
1811
- expect(handleCommandSpy).toHaveBeenCalledWith('/memory', expect.anything());
1812
- });
1813
- });
1814
- //# sourceMappingURL=acpClient.test.js.map