@machina.ai/cell-cli 1.41.1-rc2 → 1.45.1-rc2

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