@office-ai/aioncli-core 0.18.7 → 0.24.0

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 (890) hide show
  1. package/dist/docs/CONTRIBUTING.md +546 -0
  2. package/dist/docs/architecture.md +80 -0
  3. package/dist/docs/assets/connected_devtools.png +0 -0
  4. package/dist/docs/assets/gemini-screenshot.png +0 -0
  5. package/dist/docs/assets/release_patch.png +0 -0
  6. package/dist/docs/assets/theme-ansi-light.png +0 -0
  7. package/dist/docs/assets/theme-ansi.png +0 -0
  8. package/dist/docs/assets/theme-atom-one.png +0 -0
  9. package/dist/docs/assets/theme-ayu-light.png +0 -0
  10. package/dist/docs/assets/theme-ayu.png +0 -0
  11. package/dist/docs/assets/theme-custom.png +0 -0
  12. package/dist/docs/assets/theme-default-light.png +0 -0
  13. package/dist/docs/assets/theme-default.png +0 -0
  14. package/dist/docs/assets/theme-dracula.png +0 -0
  15. package/dist/docs/assets/theme-github-light.png +0 -0
  16. package/dist/docs/assets/theme-github.png +0 -0
  17. package/dist/docs/assets/theme-google-light.png +0 -0
  18. package/dist/docs/assets/theme-xcode-light.png +0 -0
  19. package/dist/docs/changelogs/index.md +592 -0
  20. package/dist/docs/changelogs/latest.md +225 -0
  21. package/dist/docs/changelogs/preview.md +129 -0
  22. package/dist/docs/changelogs/releases.md +896 -0
  23. package/dist/docs/cli/authentication.md +3 -0
  24. package/dist/docs/cli/checkpointing.md +94 -0
  25. package/dist/docs/cli/commands.md +354 -0
  26. package/dist/docs/cli/configuration.md +780 -0
  27. package/dist/docs/cli/custom-commands.md +315 -0
  28. package/dist/docs/cli/enterprise.md +565 -0
  29. package/dist/docs/cli/gemini-ignore.md +71 -0
  30. package/dist/docs/cli/gemini-md.md +108 -0
  31. package/dist/docs/cli/generation-settings.md +210 -0
  32. package/dist/docs/cli/headless.md +388 -0
  33. package/dist/docs/cli/index.md +63 -0
  34. package/dist/docs/cli/keyboard-shortcuts.md +143 -0
  35. package/dist/docs/cli/model-routing.md +37 -0
  36. package/dist/docs/cli/model.md +62 -0
  37. package/dist/docs/cli/sandbox.md +171 -0
  38. package/dist/docs/cli/session-management.md +158 -0
  39. package/dist/docs/cli/settings.md +114 -0
  40. package/dist/docs/cli/skills.md +156 -0
  41. package/dist/docs/cli/system-prompt.md +93 -0
  42. package/dist/docs/cli/telemetry.md +791 -0
  43. package/dist/docs/cli/themes.md +237 -0
  44. package/dist/docs/cli/token-caching.md +20 -0
  45. package/dist/docs/cli/trusted-folders.md +95 -0
  46. package/dist/docs/cli/tutorials/skills-getting-started.md +124 -0
  47. package/dist/docs/cli/tutorials.md +87 -0
  48. package/dist/docs/cli/uninstall.md +47 -0
  49. package/dist/docs/core/index.md +101 -0
  50. package/dist/docs/core/memport.md +244 -0
  51. package/dist/docs/core/policy-engine.md +267 -0
  52. package/dist/docs/core/tools-api.md +131 -0
  53. package/dist/docs/examples/proxy-script.md +83 -0
  54. package/dist/docs/extension.md +160 -0
  55. package/dist/docs/extensions/extension-releasing.md +183 -0
  56. package/dist/docs/extensions/getting-started-extensions.md +245 -0
  57. package/dist/docs/extensions/index.md +293 -0
  58. package/dist/docs/faq.md +154 -0
  59. package/dist/docs/get-started/authentication.md +321 -0
  60. package/dist/docs/get-started/configuration-v1.md +888 -0
  61. package/dist/docs/get-started/configuration.md +1536 -0
  62. package/dist/docs/get-started/deployment.md +143 -0
  63. package/dist/docs/get-started/examples.md +219 -0
  64. package/dist/docs/get-started/gemini-3.md +116 -0
  65. package/dist/docs/get-started/index.md +71 -0
  66. package/dist/docs/get-started/installation.md +141 -0
  67. package/dist/docs/hooks/best-practices.md +856 -0
  68. package/dist/docs/hooks/index.md +687 -0
  69. package/dist/docs/hooks/reference.md +168 -0
  70. package/dist/docs/hooks/writing-hooks.md +1026 -0
  71. package/dist/docs/ide-integration/ide-companion-spec.md +267 -0
  72. package/dist/docs/ide-integration/index.md +202 -0
  73. package/dist/docs/index.md +147 -0
  74. package/dist/docs/integration-tests.md +211 -0
  75. package/dist/docs/issue-and-pr-automation.md +134 -0
  76. package/dist/docs/local-development.md +128 -0
  77. package/dist/docs/mermaid/context.mmd +103 -0
  78. package/dist/docs/mermaid/render-path.mmd +64 -0
  79. package/dist/docs/npm.md +62 -0
  80. package/dist/docs/quota-and-pricing.md +158 -0
  81. package/dist/docs/release-confidence.md +164 -0
  82. package/dist/docs/releases.md +540 -0
  83. package/dist/docs/sidebar.json +301 -0
  84. package/dist/docs/tools/file-system.md +217 -0
  85. package/dist/docs/tools/index.md +95 -0
  86. package/dist/docs/tools/mcp-server.md +1044 -0
  87. package/dist/docs/tools/memory.md +54 -0
  88. package/dist/docs/tools/shell.md +260 -0
  89. package/dist/docs/tools/todos.md +57 -0
  90. package/dist/docs/tools/web-fetch.md +59 -0
  91. package/dist/docs/tools/web-search.md +42 -0
  92. package/dist/docs/tos-privacy.md +96 -0
  93. package/dist/docs/troubleshooting.md +158 -0
  94. package/dist/index.d.ts +1 -1
  95. package/dist/index.js +1 -1
  96. package/dist/index.js.map +1 -1
  97. package/dist/src/agents/a2a-client-manager.d.ts +78 -0
  98. package/dist/src/agents/a2a-client-manager.js +295 -0
  99. package/dist/src/agents/a2a-client-manager.js.map +1 -0
  100. package/dist/src/agents/a2a-client-manager.test.d.ts +6 -0
  101. package/dist/src/agents/a2a-client-manager.test.js +237 -0
  102. package/dist/src/agents/a2a-client-manager.test.js.map +1 -0
  103. package/dist/src/agents/a2aUtils.d.ts +28 -0
  104. package/dist/src/agents/a2aUtils.js +111 -0
  105. package/dist/src/agents/a2aUtils.js.map +1 -0
  106. package/dist/src/agents/a2aUtils.test.d.ts +6 -0
  107. package/dist/src/agents/a2aUtils.test.js +147 -0
  108. package/dist/src/agents/a2aUtils.test.js.map +1 -0
  109. package/dist/src/agents/codebase-investigator.d.ts +2 -2
  110. package/dist/src/agents/codebase-investigator.js +5 -4
  111. package/dist/src/agents/codebase-investigator.js.map +1 -1
  112. package/dist/src/agents/delegate-to-agent-tool.d.ts +19 -0
  113. package/dist/src/agents/delegate-to-agent-tool.js +115 -0
  114. package/dist/src/agents/delegate-to-agent-tool.js.map +1 -0
  115. package/dist/src/agents/delegate-to-agent-tool.test.d.ts +6 -0
  116. package/dist/src/agents/delegate-to-agent-tool.test.js +165 -0
  117. package/dist/src/agents/delegate-to-agent-tool.test.js.map +1 -0
  118. package/dist/src/agents/introspection-agent.d.ts +23 -0
  119. package/dist/src/agents/introspection-agent.js +72 -0
  120. package/dist/src/agents/introspection-agent.js.map +1 -0
  121. package/dist/src/agents/introspection-agent.test.d.ts +6 -0
  122. package/dist/src/agents/introspection-agent.test.js +47 -0
  123. package/dist/src/agents/introspection-agent.test.js.map +1 -0
  124. package/dist/src/agents/local-executor.d.ts +108 -0
  125. package/dist/src/agents/local-executor.js +801 -0
  126. package/dist/src/agents/local-executor.js.map +1 -0
  127. package/dist/src/agents/local-executor.test.d.ts +6 -0
  128. package/dist/src/agents/local-executor.test.js +1380 -0
  129. package/dist/src/agents/local-executor.test.js.map +1 -0
  130. package/dist/src/agents/local-invocation.d.ts +45 -0
  131. package/dist/src/agents/local-invocation.js +101 -0
  132. package/dist/src/agents/local-invocation.js.map +1 -0
  133. package/dist/src/agents/local-invocation.test.d.ts +6 -0
  134. package/dist/src/agents/local-invocation.test.js +218 -0
  135. package/dist/src/agents/local-invocation.test.js.map +1 -0
  136. package/dist/src/agents/registry.d.ts +25 -1
  137. package/dist/src/agents/registry.js +149 -5
  138. package/dist/src/agents/registry.js.map +1 -1
  139. package/dist/src/agents/registry.test.js +247 -25
  140. package/dist/src/agents/registry.test.js.map +1 -1
  141. package/dist/src/agents/remote-invocation.d.ts +35 -0
  142. package/dist/src/agents/remote-invocation.js +129 -0
  143. package/dist/src/agents/remote-invocation.js.map +1 -0
  144. package/dist/src/agents/remote-invocation.test.d.ts +6 -0
  145. package/dist/src/agents/remote-invocation.test.js +201 -0
  146. package/dist/src/agents/remote-invocation.test.js.map +1 -0
  147. package/dist/src/agents/subagent-tool-wrapper.d.ts +2 -2
  148. package/dist/src/agents/subagent-tool-wrapper.js +11 -6
  149. package/dist/src/agents/subagent-tool-wrapper.js.map +1 -1
  150. package/dist/src/agents/subagent-tool-wrapper.test.js +25 -17
  151. package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -1
  152. package/dist/src/agents/toml-loader.d.ts +74 -0
  153. package/dist/src/agents/toml-loader.js +248 -0
  154. package/dist/src/agents/toml-loader.js.map +1 -0
  155. package/dist/src/agents/toml-loader.test.d.ts +6 -0
  156. package/dist/src/agents/toml-loader.test.js +309 -0
  157. package/dist/src/agents/toml-loader.test.js.map +1 -0
  158. package/dist/src/agents/types.d.ts +18 -4
  159. package/dist/src/availability/errorClassification.d.ts +7 -0
  160. package/dist/src/availability/errorClassification.js +20 -0
  161. package/dist/src/availability/errorClassification.js.map +1 -0
  162. package/dist/src/availability/modelAvailabilityService.d.ts +36 -0
  163. package/dist/src/availability/modelAvailabilityService.js +87 -0
  164. package/dist/src/availability/modelAvailabilityService.js.map +1 -0
  165. package/dist/src/availability/modelAvailabilityService.test.d.ts +6 -0
  166. package/dist/src/availability/modelAvailabilityService.test.js +140 -0
  167. package/dist/src/availability/modelAvailabilityService.test.js.map +1 -0
  168. package/dist/src/availability/modelPolicy.d.ts +49 -0
  169. package/dist/src/availability/modelPolicy.js +7 -0
  170. package/dist/src/availability/modelPolicy.js.map +1 -0
  171. package/dist/src/availability/policyCatalog.d.ts +23 -0
  172. package/dist/src/availability/policyCatalog.js +82 -0
  173. package/dist/src/availability/policyCatalog.js.map +1 -0
  174. package/dist/src/availability/policyCatalog.test.d.ts +6 -0
  175. package/dist/src/availability/policyCatalog.test.js +70 -0
  176. package/dist/src/availability/policyCatalog.test.js.map +1 -0
  177. package/dist/src/availability/policyHelpers.d.ts +52 -0
  178. package/dist/src/availability/policyHelpers.js +136 -0
  179. package/dist/src/availability/policyHelpers.js.map +1 -0
  180. package/dist/src/availability/policyHelpers.test.d.ts +6 -0
  181. package/dist/src/availability/policyHelpers.test.js +182 -0
  182. package/dist/src/availability/policyHelpers.test.js.map +1 -0
  183. package/dist/src/availability/testUtils.d.ts +10 -0
  184. package/dist/src/availability/testUtils.js +22 -0
  185. package/dist/src/availability/testUtils.js.map +1 -0
  186. package/dist/src/code_assist/experiments/client_metadata.js +3 -2
  187. package/dist/src/code_assist/experiments/client_metadata.js.map +1 -1
  188. package/dist/src/code_assist/experiments/client_metadata.test.js +7 -10
  189. package/dist/src/code_assist/experiments/client_metadata.test.js.map +1 -1
  190. package/dist/src/code_assist/experiments/experiments.js +2 -2
  191. package/dist/src/code_assist/experiments/experiments.js.map +1 -1
  192. package/dist/src/code_assist/oauth2.d.ts +2 -0
  193. package/dist/src/code_assist/oauth2.js +73 -17
  194. package/dist/src/code_assist/oauth2.js.map +1 -1
  195. package/dist/src/code_assist/oauth2.test.js +195 -18
  196. package/dist/src/code_assist/oauth2.test.js.map +1 -1
  197. package/dist/src/code_assist/server.d.ts +10 -1
  198. package/dist/src/code_assist/server.js +81 -15
  199. package/dist/src/code_assist/server.js.map +1 -1
  200. package/dist/src/code_assist/server.test.js +221 -25
  201. package/dist/src/code_assist/server.test.js.map +1 -1
  202. package/dist/src/code_assist/setup.js +6 -4
  203. package/dist/src/code_assist/setup.js.map +1 -1
  204. package/dist/src/code_assist/setup.test.js +63 -0
  205. package/dist/src/code_assist/setup.test.js.map +1 -1
  206. package/dist/src/code_assist/telemetry.d.ts +14 -0
  207. package/dist/src/code_assist/telemetry.js +156 -0
  208. package/dist/src/code_assist/telemetry.js.map +1 -0
  209. package/dist/src/code_assist/telemetry.test.d.ts +6 -0
  210. package/dist/src/code_assist/telemetry.test.js +300 -0
  211. package/dist/src/code_assist/telemetry.test.js.map +1 -0
  212. package/dist/src/code_assist/types.d.ts +84 -1
  213. package/dist/src/code_assist/types.js +21 -0
  214. package/dist/src/code_assist/types.js.map +1 -1
  215. package/dist/src/commands/init.d.ts +7 -0
  216. package/dist/src/commands/init.js +53 -0
  217. package/dist/src/commands/init.js.map +1 -0
  218. package/dist/src/commands/init.test.d.ts +6 -0
  219. package/dist/src/commands/init.test.js +25 -0
  220. package/dist/src/commands/init.test.js.map +1 -0
  221. package/dist/src/commands/restore.d.ts +9 -0
  222. package/dist/src/commands/restore.js +46 -0
  223. package/dist/src/commands/restore.js.map +1 -0
  224. package/dist/src/commands/restore.test.d.ts +6 -0
  225. package/dist/src/commands/restore.test.js +137 -0
  226. package/dist/src/commands/restore.test.js.map +1 -0
  227. package/dist/src/commands/types.d.ts +41 -0
  228. package/dist/src/commands/types.js +7 -0
  229. package/dist/src/commands/types.js.map +1 -0
  230. package/dist/src/config/config.d.ts +120 -23
  231. package/dist/src/config/config.js +336 -116
  232. package/dist/src/config/config.js.map +1 -1
  233. package/dist/src/config/config.test.js +373 -95
  234. package/dist/src/config/config.test.js.map +1 -1
  235. package/dist/src/config/defaultModelConfigs.js +46 -0
  236. package/dist/src/config/defaultModelConfigs.js.map +1 -1
  237. package/dist/src/config/flashFallback.test.js +11 -35
  238. package/dist/src/config/flashFallback.test.js.map +1 -1
  239. package/dist/src/config/models.d.ts +29 -15
  240. package/dist/src/config/models.js +78 -28
  241. package/dist/src/config/models.js.map +1 -1
  242. package/dist/src/config/models.test.js +91 -77
  243. package/dist/src/config/models.test.js.map +1 -1
  244. package/dist/src/config/storage.d.ts +4 -0
  245. package/dist/src/config/storage.js +12 -0
  246. package/dist/src/config/storage.js.map +1 -1
  247. package/dist/src/config/storage.test.js +16 -0
  248. package/dist/src/config/storage.test.js.map +1 -1
  249. package/dist/src/confirmation-bus/message-bus.d.ts +6 -0
  250. package/dist/src/confirmation-bus/message-bus.js +66 -3
  251. package/dist/src/confirmation-bus/message-bus.js.map +1 -1
  252. package/dist/src/confirmation-bus/types.d.ts +29 -2
  253. package/dist/src/confirmation-bus/types.js +3 -0
  254. package/dist/src/confirmation-bus/types.js.map +1 -1
  255. package/dist/src/core/baseLlmClient.d.ts +30 -2
  256. package/dist/src/core/baseLlmClient.js +107 -49
  257. package/dist/src/core/baseLlmClient.js.map +1 -1
  258. package/dist/src/core/baseLlmClient.test.js +271 -13
  259. package/dist/src/core/baseLlmClient.test.js.map +1 -1
  260. package/dist/src/core/client.d.ts +5 -1
  261. package/dist/src/core/client.js +241 -63
  262. package/dist/src/core/client.js.map +1 -1
  263. package/dist/src/core/client.test.js +462 -72
  264. package/dist/src/core/client.test.js.map +1 -1
  265. package/dist/src/core/clientHookTriggers.d.ts +36 -0
  266. package/dist/src/core/clientHookTriggers.js +76 -0
  267. package/dist/src/core/clientHookTriggers.js.map +1 -0
  268. package/dist/src/core/contentGenerator.js +17 -4
  269. package/dist/src/core/contentGenerator.js.map +1 -1
  270. package/dist/src/core/contentGenerator.test.js +132 -3
  271. package/dist/src/core/contentGenerator.test.js.map +1 -1
  272. package/dist/src/core/coreToolHookTriggers.d.ts +55 -0
  273. package/dist/src/core/coreToolHookTriggers.js +304 -0
  274. package/dist/src/core/coreToolHookTriggers.js.map +1 -0
  275. package/dist/src/core/coreToolHookTriggers.test.d.ts +6 -0
  276. package/dist/src/core/coreToolHookTriggers.test.js +191 -0
  277. package/dist/src/core/coreToolHookTriggers.test.js.map +1 -0
  278. package/dist/src/core/coreToolScheduler.d.ts +6 -85
  279. package/dist/src/core/coreToolScheduler.js +91 -274
  280. package/dist/src/core/coreToolScheduler.js.map +1 -1
  281. package/dist/src/core/coreToolScheduler.test.js +161 -346
  282. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  283. package/dist/src/core/geminiChat.js +132 -76
  284. package/dist/src/core/geminiChat.js.map +1 -1
  285. package/dist/src/core/geminiChat.test.js +240 -257
  286. package/dist/src/core/geminiChat.test.js.map +1 -1
  287. package/dist/src/core/geminiChatHookTriggers.d.ts +64 -0
  288. package/dist/src/core/geminiChatHookTriggers.js +136 -0
  289. package/dist/src/core/geminiChatHookTriggers.js.map +1 -0
  290. package/dist/src/core/geminiChat_network_retry.test.d.ts +6 -0
  291. package/dist/src/core/geminiChat_network_retry.test.js +196 -0
  292. package/dist/src/core/geminiChat_network_retry.test.js.map +1 -0
  293. package/dist/src/core/logger.js.map +1 -1
  294. package/dist/src/core/loggingContentGenerator.js +23 -6
  295. package/dist/src/core/loggingContentGenerator.js.map +1 -1
  296. package/dist/src/core/nonInteractiveToolExecutor.test.js +13 -8
  297. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  298. package/dist/src/core/prompts.js +82 -26
  299. package/dist/src/core/prompts.js.map +1 -1
  300. package/dist/src/core/prompts.test.js +102 -3
  301. package/dist/src/core/prompts.test.js.map +1 -1
  302. package/dist/src/core/sessionHookTriggers.d.ts +29 -0
  303. package/dist/src/core/sessionHookTriggers.js +75 -0
  304. package/dist/src/core/sessionHookTriggers.js.map +1 -0
  305. package/dist/src/core/turn.d.ts +34 -21
  306. package/dist/src/core/turn.js +33 -13
  307. package/dist/src/core/turn.js.map +1 -1
  308. package/dist/src/core/turn.test.js +0 -5
  309. package/dist/src/core/turn.test.js.map +1 -1
  310. package/dist/src/fallback/handler.js +101 -93
  311. package/dist/src/fallback/handler.js.map +1 -1
  312. package/dist/src/fallback/handler.test.js +186 -173
  313. package/dist/src/fallback/handler.test.js.map +1 -1
  314. package/dist/src/fallback/types.d.ts +8 -0
  315. package/dist/src/generated/git-commit.d.ts +3 -3
  316. package/dist/src/generated/git-commit.js +3 -3
  317. package/dist/src/generated/git-commit.js.map +1 -1
  318. package/dist/src/hooks/hookAggregator.js +7 -0
  319. package/dist/src/hooks/hookAggregator.js.map +1 -1
  320. package/dist/src/hooks/hookEventHandler.d.ts +113 -0
  321. package/dist/src/hooks/hookEventHandler.js +571 -0
  322. package/dist/src/hooks/hookEventHandler.js.map +1 -0
  323. package/dist/src/hooks/hookEventHandler.test.d.ts +6 -0
  324. package/dist/src/hooks/hookEventHandler.test.js +461 -0
  325. package/dist/src/hooks/hookEventHandler.test.js.map +1 -0
  326. package/dist/src/hooks/hookPlanner.d.ts +1 -5
  327. package/dist/src/hooks/hookPlanner.js +2 -7
  328. package/dist/src/hooks/hookPlanner.js.map +1 -1
  329. package/dist/src/hooks/hookPlanner.test.js +62 -2
  330. package/dist/src/hooks/hookPlanner.test.js.map +1 -1
  331. package/dist/src/hooks/hookRegistry.d.ts +6 -18
  332. package/dist/src/hooks/hookRegistry.js +49 -35
  333. package/dist/src/hooks/hookRegistry.js.map +1 -1
  334. package/dist/src/hooks/hookRegistry.test.js +167 -8
  335. package/dist/src/hooks/hookRegistry.test.js.map +1 -1
  336. package/dist/src/hooks/hookRunner.d.ts +5 -3
  337. package/dist/src/hooks/hookRunner.js +74 -18
  338. package/dist/src/hooks/hookRunner.js.map +1 -1
  339. package/dist/src/hooks/hookRunner.test.js +174 -36
  340. package/dist/src/hooks/hookRunner.test.js.map +1 -1
  341. package/dist/src/hooks/hookSystem.d.ts +40 -0
  342. package/dist/src/hooks/hookSystem.js +65 -0
  343. package/dist/src/hooks/hookSystem.js.map +1 -0
  344. package/dist/src/hooks/hookSystem.test.d.ts +6 -0
  345. package/dist/src/hooks/hookSystem.test.js +319 -0
  346. package/dist/src/hooks/hookSystem.test.js.map +1 -0
  347. package/dist/src/hooks/index.d.ts +17 -0
  348. package/dist/src/hooks/index.js +18 -0
  349. package/dist/src/hooks/index.js.map +1 -0
  350. package/dist/src/hooks/trustedHooks.d.ts +28 -0
  351. package/dist/src/hooks/trustedHooks.js +90 -0
  352. package/dist/src/hooks/trustedHooks.js.map +1 -0
  353. package/dist/src/hooks/trustedHooks.test.d.ts +6 -0
  354. package/dist/src/hooks/trustedHooks.test.js +154 -0
  355. package/dist/src/hooks/trustedHooks.test.js.map +1 -0
  356. package/dist/src/hooks/types.d.ts +21 -11
  357. package/dist/src/hooks/types.js +31 -27
  358. package/dist/src/hooks/types.js.map +1 -1
  359. package/dist/src/hooks/types.test.js +5 -24
  360. package/dist/src/hooks/types.test.js.map +1 -1
  361. package/dist/src/ide/detect-ide.test.js +32 -1
  362. package/dist/src/ide/detect-ide.test.js.map +1 -1
  363. package/dist/src/ide/ide-client.js +9 -4
  364. package/dist/src/ide/ide-client.js.map +1 -1
  365. package/dist/src/ide/ide-client.test.js +17 -0
  366. package/dist/src/ide/ide-client.test.js.map +1 -1
  367. package/dist/src/ide/ide-installer.test.js +1 -1
  368. package/dist/src/ide/ide-installer.test.js.map +1 -1
  369. package/dist/src/ide/types.d.ts +4 -4
  370. package/dist/src/index.d.ts +17 -1
  371. package/dist/src/index.js +18 -2
  372. package/dist/src/index.js.map +1 -1
  373. package/dist/src/mcp/auth-provider.d.ts +16 -0
  374. package/dist/src/mcp/auth-provider.js +7 -0
  375. package/dist/src/mcp/auth-provider.js.map +1 -0
  376. package/dist/src/mcp/google-auth-provider.d.ts +10 -2
  377. package/dist/src/mcp/google-auth-provider.js +28 -0
  378. package/dist/src/mcp/google-auth-provider.js.map +1 -1
  379. package/dist/src/mcp/google-auth-provider.test.js +45 -0
  380. package/dist/src/mcp/google-auth-provider.test.js.map +1 -1
  381. package/dist/src/mcp/oauth-provider.js +6 -2
  382. package/dist/src/mcp/oauth-provider.js.map +1 -1
  383. package/dist/src/mcp/oauth-provider.test.js +4 -1
  384. package/dist/src/mcp/oauth-provider.test.js.map +1 -1
  385. package/dist/src/mcp/oauth-utils.d.ts +8 -1
  386. package/dist/src/mcp/oauth-utils.js +31 -2
  387. package/dist/src/mcp/oauth-utils.js.map +1 -1
  388. package/dist/src/mcp/oauth-utils.test.js +42 -0
  389. package/dist/src/mcp/oauth-utils.test.js.map +1 -1
  390. package/dist/src/mcp/sa-impersonation-provider.d.ts +2 -2
  391. package/dist/src/mcp/sa-impersonation-provider.js.map +1 -1
  392. package/dist/src/mcp/token-storage/hybrid-token-storage.js +1 -1
  393. package/dist/src/mcp/token-storage/hybrid-token-storage.js.map +1 -1
  394. package/dist/src/mcp/token-storage/keychain-token-storage.js +1 -1
  395. package/dist/src/mcp/token-storage/keychain-token-storage.js.map +1 -1
  396. package/dist/src/output/json-formatter.d.ts +2 -2
  397. package/dist/src/output/json-formatter.js +6 -3
  398. package/dist/src/output/json-formatter.js.map +1 -1
  399. package/dist/src/output/json-formatter.test.js +37 -9
  400. package/dist/src/output/json-formatter.test.js.map +1 -1
  401. package/dist/src/output/stream-json-formatter.js +6 -0
  402. package/dist/src/output/stream-json-formatter.js.map +1 -1
  403. package/dist/src/output/stream-json-formatter.test.js +98 -100
  404. package/dist/src/output/stream-json-formatter.test.js.map +1 -1
  405. package/dist/src/output/types.d.ts +3 -0
  406. package/dist/src/output/types.js.map +1 -1
  407. package/dist/src/policy/config.js +140 -15
  408. package/dist/src/policy/config.js.map +1 -1
  409. package/dist/src/policy/config.test.js +21 -0
  410. package/dist/src/policy/config.test.js.map +1 -1
  411. package/dist/src/policy/persistence.test.d.ts +6 -0
  412. package/dist/src/policy/persistence.test.js +154 -0
  413. package/dist/src/policy/persistence.test.js.map +1 -0
  414. package/dist/src/policy/policies/agent.toml +31 -0
  415. package/dist/src/policy/policies/read-only.toml +5 -0
  416. package/dist/src/policy/policies/write.toml +5 -0
  417. package/dist/src/policy/policies/yolo.toml +1 -0
  418. package/dist/src/policy/policy-engine.d.ts +30 -1
  419. package/dist/src/policy/policy-engine.js +192 -5
  420. package/dist/src/policy/policy-engine.js.map +1 -1
  421. package/dist/src/policy/policy-engine.test.js +520 -3
  422. package/dist/src/policy/policy-engine.test.js.map +1 -1
  423. package/dist/src/policy/policy-updater.test.d.ts +6 -0
  424. package/dist/src/policy/policy-updater.test.js +116 -0
  425. package/dist/src/policy/policy-updater.test.js.map +1 -0
  426. package/dist/src/policy/shell-safety.test.d.ts +6 -0
  427. package/dist/src/policy/shell-safety.test.js +75 -0
  428. package/dist/src/policy/shell-safety.test.js.map +1 -0
  429. package/dist/src/policy/toml-loader.d.ts +3 -5
  430. package/dist/src/policy/toml-loader.js +12 -60
  431. package/dist/src/policy/toml-loader.js.map +1 -1
  432. package/dist/src/policy/toml-loader.test.js +38 -7
  433. package/dist/src/policy/toml-loader.test.js.map +1 -1
  434. package/dist/src/policy/types.d.ts +72 -1
  435. package/dist/src/policy/types.js +21 -0
  436. package/dist/src/policy/types.js.map +1 -1
  437. package/dist/src/policy/utils.d.ts +21 -0
  438. package/dist/src/policy/utils.js +42 -0
  439. package/dist/src/policy/utils.js.map +1 -0
  440. package/dist/src/policy/utils.test.d.ts +6 -0
  441. package/dist/src/policy/utils.test.js +64 -0
  442. package/dist/src/policy/utils.test.js.map +1 -0
  443. package/dist/src/resources/resource-registry.d.ts +30 -0
  444. package/dist/src/resources/resource-registry.js +57 -0
  445. package/dist/src/resources/resource-registry.js.map +1 -0
  446. package/dist/src/resources/resource-registry.test.d.ts +6 -0
  447. package/dist/src/resources/resource-registry.test.js +54 -0
  448. package/dist/src/resources/resource-registry.test.js.map +1 -0
  449. package/dist/src/routing/modelRouterService.js +0 -15
  450. package/dist/src/routing/modelRouterService.js.map +1 -1
  451. package/dist/src/routing/modelRouterService.test.js +0 -62
  452. package/dist/src/routing/modelRouterService.test.js.map +1 -1
  453. package/dist/src/routing/strategies/classifierStrategy.js +10 -21
  454. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  455. package/dist/src/routing/strategies/classifierStrategy.test.js +2 -1
  456. package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
  457. package/dist/src/routing/strategies/compositeStrategy.js +4 -2
  458. package/dist/src/routing/strategies/compositeStrategy.js.map +1 -1
  459. package/dist/src/routing/strategies/compositeStrategy.test.js +11 -10
  460. package/dist/src/routing/strategies/compositeStrategy.test.js.map +1 -1
  461. package/dist/src/routing/strategies/fallbackStrategy.js +20 -12
  462. package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
  463. package/dist/src/routing/strategies/fallbackStrategy.test.js +63 -39
  464. package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -1
  465. package/dist/src/routing/strategies/overrideStrategy.js +3 -2
  466. package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
  467. package/dist/src/safety/checker-runner.js +17 -6
  468. package/dist/src/safety/checker-runner.js.map +1 -1
  469. package/dist/src/scheduler/tool-executor.d.ts +22 -0
  470. package/dist/src/scheduler/tool-executor.js +198 -0
  471. package/dist/src/scheduler/tool-executor.js.map +1 -0
  472. package/dist/src/scheduler/tool-executor.test.d.ts +6 -0
  473. package/dist/src/scheduler/tool-executor.test.js +231 -0
  474. package/dist/src/scheduler/tool-executor.test.js.map +1 -0
  475. package/dist/src/scheduler/types.d.ts +95 -0
  476. package/dist/src/scheduler/types.js +7 -0
  477. package/dist/src/scheduler/types.js.map +1 -0
  478. package/dist/src/services/chatCompressionService.d.ts +1 -0
  479. package/dist/src/services/chatCompressionService.js +38 -8
  480. package/dist/src/services/chatCompressionService.js.map +1 -1
  481. package/dist/src/services/chatCompressionService.test.js +35 -31
  482. package/dist/src/services/chatCompressionService.test.js.map +1 -1
  483. package/dist/src/services/chatRecordingService.d.ts +14 -0
  484. package/dist/src/services/chatRecordingService.js +37 -0
  485. package/dist/src/services/chatRecordingService.js.map +1 -1
  486. package/dist/src/services/contextManager.d.ts +29 -0
  487. package/dist/src/services/contextManager.js +71 -0
  488. package/dist/src/services/contextManager.js.map +1 -0
  489. package/dist/src/services/contextManager.test.d.ts +6 -0
  490. package/dist/src/services/contextManager.test.js +104 -0
  491. package/dist/src/services/contextManager.test.js.map +1 -0
  492. package/dist/src/services/environmentSanitization.d.ts +15 -0
  493. package/dist/src/services/environmentSanitization.js +141 -0
  494. package/dist/src/services/environmentSanitization.js.map +1 -0
  495. package/dist/src/services/environmentSanitization.test.d.ts +6 -0
  496. package/dist/src/services/environmentSanitization.test.js +284 -0
  497. package/dist/src/services/environmentSanitization.test.js.map +1 -0
  498. package/dist/src/services/fileSystemService.d.ts +0 -9
  499. package/dist/src/services/fileSystemService.js +0 -11
  500. package/dist/src/services/fileSystemService.js.map +1 -1
  501. package/dist/src/services/gitService.js +18 -2
  502. package/dist/src/services/gitService.js.map +1 -1
  503. package/dist/src/services/gitService.test.js +56 -0
  504. package/dist/src/services/gitService.test.js.map +1 -1
  505. package/dist/src/services/loopDetectionService.js +5 -4
  506. package/dist/src/services/loopDetectionService.js.map +1 -1
  507. package/dist/src/services/loopDetectionService.test.js +14 -8
  508. package/dist/src/services/loopDetectionService.test.js.map +1 -1
  509. package/dist/src/services/modelConfig.golden.test.js +32 -0
  510. package/dist/src/services/modelConfig.golden.test.js.map +1 -1
  511. package/dist/src/services/modelConfig.integration.test.js +1 -1
  512. package/dist/src/services/modelConfig.integration.test.js.map +1 -1
  513. package/dist/src/services/modelConfigService.d.ts +4 -0
  514. package/dist/src/services/modelConfigService.js +8 -3
  515. package/dist/src/services/modelConfigService.js.map +1 -1
  516. package/dist/src/services/modelConfigService.test.js +221 -0
  517. package/dist/src/services/modelConfigService.test.js.map +1 -1
  518. package/dist/src/services/modelConfigServiceTestUtils.d.ts +10 -0
  519. package/dist/src/services/modelConfigServiceTestUtils.js +17 -0
  520. package/dist/src/services/modelConfigServiceTestUtils.js.map +1 -0
  521. package/dist/src/services/sessionSummaryService.d.ts +28 -0
  522. package/dist/src/services/sessionSummaryService.js +131 -0
  523. package/dist/src/services/sessionSummaryService.js.map +1 -0
  524. package/dist/src/services/sessionSummaryService.test.d.ts +6 -0
  525. package/dist/src/services/sessionSummaryService.test.js +785 -0
  526. package/dist/src/services/sessionSummaryService.test.js.map +1 -0
  527. package/dist/src/services/sessionSummaryUtils.d.ts +16 -0
  528. package/dist/src/services/sessionSummaryUtils.js +129 -0
  529. package/dist/src/services/sessionSummaryUtils.js.map +1 -0
  530. package/dist/src/services/sessionSummaryUtils.test.d.ts +6 -0
  531. package/dist/src/services/sessionSummaryUtils.test.js +137 -0
  532. package/dist/src/services/sessionSummaryUtils.test.js.map +1 -0
  533. package/dist/src/services/shellExecutionService.d.ts +4 -0
  534. package/dist/src/services/shellExecutionService.js +45 -27
  535. package/dist/src/services/shellExecutionService.js.map +1 -1
  536. package/dist/src/services/shellExecutionService.test.js +240 -8
  537. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  538. package/dist/src/services/test-data/resolved-aliases-retry.golden.json +238 -0
  539. package/dist/src/services/test-data/resolved-aliases.golden.json +36 -0
  540. package/dist/src/skills/skillLoader.d.ts +28 -0
  541. package/dist/src/skills/skillLoader.js +77 -0
  542. package/dist/src/skills/skillLoader.js.map +1 -0
  543. package/dist/src/skills/skillLoader.test.d.ts +1 -0
  544. package/dist/src/skills/skillLoader.test.js +2 -0
  545. package/dist/src/skills/skillLoader.test.js.map +1 -0
  546. package/dist/src/skills/skillManager.d.ts +51 -0
  547. package/dist/src/skills/skillManager.js +89 -0
  548. package/dist/src/skills/skillManager.js.map +1 -0
  549. package/dist/src/skills/skillManager.test.d.ts +6 -0
  550. package/dist/src/skills/skillManager.test.js +128 -0
  551. package/dist/src/skills/skillManager.test.js.map +1 -0
  552. package/dist/src/telemetry/activity-detector.test.js.map +1 -1
  553. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +11 -7
  554. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +127 -47
  555. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  556. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +105 -18
  557. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
  558. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +9 -3
  559. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +20 -5
  560. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  561. package/dist/src/telemetry/config.js +2 -0
  562. package/dist/src/telemetry/config.js.map +1 -1
  563. package/dist/src/telemetry/config.test.js +25 -0
  564. package/dist/src/telemetry/config.test.js.map +1 -1
  565. package/dist/src/telemetry/gcp-exporters.d.ts +4 -3
  566. package/dist/src/telemetry/gcp-exporters.js +8 -4
  567. package/dist/src/telemetry/gcp-exporters.js.map +1 -1
  568. package/dist/src/telemetry/index.d.ts +2 -1
  569. package/dist/src/telemetry/index.js +2 -1
  570. package/dist/src/telemetry/index.js.map +1 -1
  571. package/dist/src/telemetry/loggers.d.ts +5 -3
  572. package/dist/src/telemetry/loggers.js +353 -334
  573. package/dist/src/telemetry/loggers.js.map +1 -1
  574. package/dist/src/telemetry/loggers.test.circular.js +1 -0
  575. package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
  576. package/dist/src/telemetry/loggers.test.js +238 -31
  577. package/dist/src/telemetry/loggers.test.js.map +1 -1
  578. package/dist/src/telemetry/metrics.d.ts +22 -0
  579. package/dist/src/telemetry/metrics.js +32 -0
  580. package/dist/src/telemetry/metrics.js.map +1 -1
  581. package/dist/src/telemetry/metrics.test.js +64 -0
  582. package/dist/src/telemetry/metrics.test.js.map +1 -1
  583. package/dist/src/telemetry/sanitize.d.ts +25 -0
  584. package/dist/src/telemetry/sanitize.js +48 -0
  585. package/dist/src/telemetry/sanitize.js.map +1 -0
  586. package/dist/src/telemetry/sanitize.test.d.ts +6 -0
  587. package/dist/src/telemetry/sanitize.test.js +279 -0
  588. package/dist/src/telemetry/sanitize.test.js.map +1 -0
  589. package/dist/src/telemetry/sdk.d.ts +9 -2
  590. package/dist/src/telemetry/sdk.js +142 -17
  591. package/dist/src/telemetry/sdk.js.map +1 -1
  592. package/dist/src/telemetry/sdk.test.js +130 -28
  593. package/dist/src/telemetry/sdk.test.js.map +1 -1
  594. package/dist/src/telemetry/semantic.js +1 -1
  595. package/dist/src/telemetry/semantic.js.map +1 -1
  596. package/dist/src/telemetry/startupProfiler.d.ts +51 -0
  597. package/dist/src/telemetry/startupProfiler.js +170 -0
  598. package/dist/src/telemetry/startupProfiler.js.map +1 -0
  599. package/dist/src/telemetry/startupProfiler.test.d.ts +6 -0
  600. package/dist/src/telemetry/startupProfiler.test.js +285 -0
  601. package/dist/src/telemetry/startupProfiler.test.js.map +1 -0
  602. package/dist/src/telemetry/telemetry.test.js +10 -3
  603. package/dist/src/telemetry/telemetry.test.js.map +1 -1
  604. package/dist/src/telemetry/trace.js +2 -2
  605. package/dist/src/telemetry/trace.js.map +1 -1
  606. package/dist/src/telemetry/types.d.ts +62 -16
  607. package/dist/src/telemetry/types.js +157 -27
  608. package/dist/src/telemetry/types.js.map +1 -1
  609. package/dist/src/telemetry/uiTelemetry.d.ts +1 -0
  610. package/dist/src/telemetry/uiTelemetry.js +2 -0
  611. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  612. package/dist/src/telemetry/uiTelemetry.test.js +4 -0
  613. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
  614. package/dist/src/test-utils/mock-message-bus.d.ts +61 -0
  615. package/dist/src/test-utils/mock-message-bus.js +160 -0
  616. package/dist/src/test-utils/mock-message-bus.js.map +1 -0
  617. package/dist/src/test-utils/mock-tool.d.ts +5 -3
  618. package/dist/src/test-utils/mock-tool.js +12 -11
  619. package/dist/src/test-utils/mock-tool.js.map +1 -1
  620. package/dist/src/tools/activate-skill.d.ts +27 -0
  621. package/dist/src/tools/activate-skill.js +120 -0
  622. package/dist/src/tools/activate-skill.js.map +1 -0
  623. package/dist/src/tools/activate-skill.test.d.ts +6 -0
  624. package/dist/src/tools/activate-skill.test.js +95 -0
  625. package/dist/src/tools/activate-skill.test.js.map +1 -0
  626. package/dist/src/tools/confirmation-policy.test.d.ts +6 -0
  627. package/dist/src/tools/confirmation-policy.test.js +142 -0
  628. package/dist/src/tools/confirmation-policy.test.js.map +1 -0
  629. package/dist/src/tools/edit.d.ts +27 -5
  630. package/dist/src/tools/edit.js +449 -137
  631. package/dist/src/tools/edit.js.map +1 -1
  632. package/dist/src/tools/edit.test.js +258 -526
  633. package/dist/src/tools/edit.test.js.map +1 -1
  634. package/dist/src/tools/get-internal-docs.d.ts +27 -0
  635. package/dist/src/tools/get-internal-docs.js +129 -0
  636. package/dist/src/tools/get-internal-docs.js.map +1 -0
  637. package/dist/src/tools/get-internal-docs.test.d.ts +6 -0
  638. package/dist/src/tools/get-internal-docs.test.js +57 -0
  639. package/dist/src/tools/get-internal-docs.test.js.map +1 -0
  640. package/dist/src/tools/glob.d.ts +2 -2
  641. package/dist/src/tools/glob.js +1 -1
  642. package/dist/src/tools/glob.js.map +1 -1
  643. package/dist/src/tools/glob.test.js +2 -1
  644. package/dist/src/tools/glob.test.js.map +1 -1
  645. package/dist/src/tools/grep.d.ts +2 -2
  646. package/dist/src/tools/grep.js +1 -1
  647. package/dist/src/tools/grep.js.map +1 -1
  648. package/dist/src/tools/grep.test.js +5 -4
  649. package/dist/src/tools/grep.test.js.map +1 -1
  650. package/dist/src/tools/ls.d.ts +2 -2
  651. package/dist/src/tools/ls.js +2 -2
  652. package/dist/src/tools/ls.js.map +1 -1
  653. package/dist/src/tools/ls.test.js +2 -1
  654. package/dist/src/tools/ls.test.js.map +1 -1
  655. package/dist/src/tools/mcp-client-manager.d.ts +3 -1
  656. package/dist/src/tools/mcp-client-manager.js +42 -9
  657. package/dist/src/tools/mcp-client-manager.js.map +1 -1
  658. package/dist/src/tools/mcp-client-manager.test.js +66 -10
  659. package/dist/src/tools/mcp-client-manager.test.js.map +1 -1
  660. package/dist/src/tools/mcp-client.d.ts +44 -6
  661. package/dist/src/tools/mcp-client.js +476 -176
  662. package/dist/src/tools/mcp-client.js.map +1 -1
  663. package/dist/src/tools/mcp-client.test.js +633 -36
  664. package/dist/src/tools/mcp-client.test.js.map +1 -1
  665. package/dist/src/tools/mcp-tool.d.ts +2 -2
  666. package/dist/src/tools/mcp-tool.js +20 -7
  667. package/dist/src/tools/mcp-tool.js.map +1 -1
  668. package/dist/src/tools/mcp-tool.test.js +35 -5
  669. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  670. package/dist/src/tools/memoryTool.d.ts +3 -3
  671. package/dist/src/tools/memoryTool.js +3 -4
  672. package/dist/src/tools/memoryTool.js.map +1 -1
  673. package/dist/src/tools/memoryTool.test.js +5 -2
  674. package/dist/src/tools/memoryTool.test.js.map +1 -1
  675. package/dist/src/tools/message-bus-integration.test.js +10 -37
  676. package/dist/src/tools/message-bus-integration.test.js.map +1 -1
  677. package/dist/src/tools/modifiable-tool.js.map +1 -1
  678. package/dist/src/tools/modifiable-tool.test.js +22 -13
  679. package/dist/src/tools/modifiable-tool.test.js.map +1 -1
  680. package/dist/src/tools/read-file.d.ts +2 -2
  681. package/dist/src/tools/read-file.js +2 -2
  682. package/dist/src/tools/read-file.js.map +1 -1
  683. package/dist/src/tools/read-file.test.js +3 -2
  684. package/dist/src/tools/read-file.test.js.map +1 -1
  685. package/dist/src/tools/read-many-files.d.ts +2 -2
  686. package/dist/src/tools/read-many-files.js +7 -6
  687. package/dist/src/tools/read-many-files.js.map +1 -1
  688. package/dist/src/tools/read-many-files.test.js +4 -3
  689. package/dist/src/tools/read-many-files.test.js.map +1 -1
  690. package/dist/src/tools/ripGrep.d.ts +3 -2
  691. package/dist/src/tools/ripGrep.js +18 -7
  692. package/dist/src/tools/ripGrep.js.map +1 -1
  693. package/dist/src/tools/ripGrep.test.js +60 -4
  694. package/dist/src/tools/ripGrep.test.js.map +1 -1
  695. package/dist/src/tools/shell.d.ts +5 -7
  696. package/dist/src/tools/shell.js +77 -51
  697. package/dist/src/tools/shell.js.map +1 -1
  698. package/dist/src/tools/shell.test.js +59 -63
  699. package/dist/src/tools/shell.test.js.map +1 -1
  700. package/dist/src/tools/tool-error.d.ts +2 -1
  701. package/dist/src/tools/tool-error.js +2 -0
  702. package/dist/src/tools/tool-error.js.map +1 -1
  703. package/dist/src/tools/tool-names.d.ts +17 -0
  704. package/dist/src/tools/tool-names.js +59 -0
  705. package/dist/src/tools/tool-names.js.map +1 -1
  706. package/dist/src/tools/tool-names.test.d.ts +6 -0
  707. package/dist/src/tools/tool-names.test.js +43 -0
  708. package/dist/src/tools/tool-names.test.js.map +1 -0
  709. package/dist/src/tools/tool-registry.d.ts +11 -7
  710. package/dist/src/tools/tool-registry.js +15 -10
  711. package/dist/src/tools/tool-registry.js.map +1 -1
  712. package/dist/src/tools/tool-registry.test.js +16 -11
  713. package/dist/src/tools/tool-registry.test.js.map +1 -1
  714. package/dist/src/tools/tools.d.ts +25 -6
  715. package/dist/src/tools/tools.js +44 -25
  716. package/dist/src/tools/tools.js.map +1 -1
  717. package/dist/src/tools/tools.test.js +3 -1
  718. package/dist/src/tools/tools.test.js.map +1 -1
  719. package/dist/src/tools/web-fetch.d.ts +2 -2
  720. package/dist/src/tools/web-fetch.js +22 -9
  721. package/dist/src/tools/web-fetch.js.map +1 -1
  722. package/dist/src/tools/web-fetch.test.js +18 -19
  723. package/dist/src/tools/web-fetch.test.js.map +1 -1
  724. package/dist/src/tools/web-search.d.ts +2 -2
  725. package/dist/src/tools/web-search.js +5 -5
  726. package/dist/src/tools/web-search.js.map +1 -1
  727. package/dist/src/tools/web-search.test.js +2 -1
  728. package/dist/src/tools/web-search.test.js.map +1 -1
  729. package/dist/src/tools/write-file.d.ts +2 -2
  730. package/dist/src/tools/write-file.js +10 -4
  731. package/dist/src/tools/write-file.js.map +1 -1
  732. package/dist/src/tools/write-file.test.js +4 -1
  733. package/dist/src/tools/write-file.test.js.map +1 -1
  734. package/dist/src/tools/write-todos.d.ts +2 -2
  735. package/dist/src/tools/write-todos.js +5 -4
  736. package/dist/src/tools/write-todos.js.map +1 -1
  737. package/dist/src/tools/write-todos.test.js +2 -1
  738. package/dist/src/tools/write-todos.test.js.map +1 -1
  739. package/dist/src/utils/bfsFileSearch.d.ts +8 -0
  740. package/dist/src/utils/bfsFileSearch.js +63 -23
  741. package/dist/src/utils/bfsFileSearch.js.map +1 -1
  742. package/dist/src/utils/bfsFileSearch.test.js +65 -1
  743. package/dist/src/utils/bfsFileSearch.test.js.map +1 -1
  744. package/dist/src/utils/checkpointUtils.d.ts +82 -0
  745. package/dist/src/utils/checkpointUtils.js +117 -0
  746. package/dist/src/utils/checkpointUtils.js.map +1 -0
  747. package/dist/src/utils/checkpointUtils.test.d.ts +6 -0
  748. package/dist/src/utils/checkpointUtils.test.js +229 -0
  749. package/dist/src/utils/checkpointUtils.test.js.map +1 -0
  750. package/dist/src/utils/customHeaderUtils.d.ts +9 -0
  751. package/dist/src/utils/customHeaderUtils.js +34 -0
  752. package/dist/src/utils/customHeaderUtils.js.map +1 -0
  753. package/dist/src/utils/customHeaderUtils.test.d.ts +6 -0
  754. package/dist/src/utils/customHeaderUtils.test.js +77 -0
  755. package/dist/src/utils/customHeaderUtils.test.js.map +1 -0
  756. package/dist/src/utils/debugLogger.d.ts +3 -0
  757. package/dist/src/utils/debugLogger.js +28 -0
  758. package/dist/src/utils/debugLogger.js.map +1 -1
  759. package/dist/src/utils/editCorrector.js +6 -5
  760. package/dist/src/utils/editCorrector.js.map +1 -1
  761. package/dist/src/utils/editCorrector.test.js +7 -3
  762. package/dist/src/utils/editCorrector.test.js.map +1 -1
  763. package/dist/src/utils/editor.d.ts +9 -1
  764. package/dist/src/utils/editor.js +23 -14
  765. package/dist/src/utils/editor.js.map +1 -1
  766. package/dist/src/utils/environmentContext.d.ts +1 -0
  767. package/dist/src/utils/environmentContext.js +4 -0
  768. package/dist/src/utils/environmentContext.js.map +1 -1
  769. package/dist/src/utils/environmentContext.test.js +2 -0
  770. package/dist/src/utils/environmentContext.test.js.map +1 -1
  771. package/dist/src/utils/errorReporting.d.ts +1 -1
  772. package/dist/src/utils/errorReporting.js +13 -12
  773. package/dist/src/utils/errorReporting.js.map +1 -1
  774. package/dist/src/utils/errorReporting.test.js +17 -14
  775. package/dist/src/utils/errorReporting.test.js.map +1 -1
  776. package/dist/src/utils/errors.d.ts +8 -0
  777. package/dist/src/utils/errors.js +39 -2
  778. package/dist/src/utils/errors.js.map +1 -1
  779. package/dist/src/utils/errors.test.d.ts +6 -0
  780. package/dist/src/utils/errors.test.js +155 -0
  781. package/dist/src/utils/errors.test.js.map +1 -0
  782. package/dist/src/utils/events.d.ts +49 -19
  783. package/dist/src/utils/events.js +21 -9
  784. package/dist/src/utils/events.js.map +1 -1
  785. package/dist/src/utils/events.test.js +25 -0
  786. package/dist/src/utils/events.test.js.map +1 -1
  787. package/dist/src/utils/exitCodes.d.ts +12 -0
  788. package/dist/src/utils/exitCodes.js +13 -0
  789. package/dist/src/utils/exitCodes.js.map +1 -0
  790. package/dist/src/utils/extensionLoader.d.ts +2 -2
  791. package/dist/src/utils/extensionLoader.js +5 -6
  792. package/dist/src/utils/extensionLoader.js.map +1 -1
  793. package/dist/src/utils/extensionLoader.test.js +11 -0
  794. package/dist/src/utils/extensionLoader.test.js.map +1 -1
  795. package/dist/src/utils/fetch.d.ts +1 -1
  796. package/dist/src/utils/fetch.js +3 -3
  797. package/dist/src/utils/fetch.js.map +1 -1
  798. package/dist/src/utils/fileUtils.d.ts +4 -0
  799. package/dist/src/utils/fileUtils.js +53 -0
  800. package/dist/src/utils/fileUtils.js.map +1 -1
  801. package/dist/src/utils/fileUtils.test.js +127 -1
  802. package/dist/src/utils/fileUtils.test.js.map +1 -1
  803. package/dist/src/utils/filesearch/crawlCache.js.map +1 -1
  804. package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
  805. package/dist/src/utils/flashFallback.test.js +1 -1
  806. package/dist/src/utils/flashFallback.test.js.map +1 -1
  807. package/dist/src/utils/geminiIgnoreParser.d.ts +11 -0
  808. package/dist/src/utils/geminiIgnoreParser.js +20 -0
  809. package/dist/src/utils/geminiIgnoreParser.js.map +1 -1
  810. package/dist/src/utils/geminiIgnoreParser.test.js +48 -0
  811. package/dist/src/utils/geminiIgnoreParser.test.js.map +1 -1
  812. package/dist/src/utils/generateContentResponseUtilities.d.ts +3 -1
  813. package/dist/src/utils/generateContentResponseUtilities.js +106 -0
  814. package/dist/src/utils/generateContentResponseUtilities.js.map +1 -1
  815. package/dist/src/utils/generateContentResponseUtilities.test.js +279 -2
  816. package/dist/src/utils/generateContentResponseUtilities.test.js.map +1 -1
  817. package/dist/src/utils/getFolderStructure.js +7 -2
  818. package/dist/src/utils/getFolderStructure.js.map +1 -1
  819. package/dist/src/utils/googleErrors.js +31 -18
  820. package/dist/src/utils/googleErrors.js.map +1 -1
  821. package/dist/src/utils/googleErrors.test.js +10 -2
  822. package/dist/src/utils/googleErrors.test.js.map +1 -1
  823. package/dist/src/utils/googleQuotaErrors.d.ts +3 -3
  824. package/dist/src/utils/googleQuotaErrors.js +32 -6
  825. package/dist/src/utils/googleQuotaErrors.js.map +1 -1
  826. package/dist/src/utils/googleQuotaErrors.test.js +94 -2
  827. package/dist/src/utils/googleQuotaErrors.test.js.map +1 -1
  828. package/dist/src/utils/memoryDiscovery.d.ts +5 -0
  829. package/dist/src/utils/memoryDiscovery.js +9 -5
  830. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  831. package/dist/src/utils/memoryDiscovery.test.js +31 -1
  832. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  833. package/dist/src/utils/nextSpeakerChecker.test.js +4 -0
  834. package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
  835. package/dist/src/utils/package.d.ts +14 -0
  836. package/dist/src/utils/package.js +15 -2
  837. package/dist/src/utils/package.js.map +1 -1
  838. package/dist/src/utils/pathCorrector.js +12 -2
  839. package/dist/src/utils/pathCorrector.js.map +1 -1
  840. package/dist/src/utils/pathCorrector.test.js +6 -2
  841. package/dist/src/utils/pathCorrector.test.js.map +1 -1
  842. package/dist/src/utils/retry.d.ts +11 -0
  843. package/dist/src/utils/retry.js +62 -21
  844. package/dist/src/utils/retry.js.map +1 -1
  845. package/dist/src/utils/retry.test.js +170 -10
  846. package/dist/src/utils/retry.test.js.map +1 -1
  847. package/dist/src/utils/schemaValidator.d.ts +1 -1
  848. package/dist/src/utils/schemaValidator.js +1 -1
  849. package/dist/src/utils/shell-permissions.d.ts +52 -0
  850. package/dist/src/utils/shell-permissions.js +188 -0
  851. package/dist/src/utils/shell-permissions.js.map +1 -0
  852. package/dist/src/utils/shell-permissions.test.d.ts +6 -0
  853. package/dist/src/utils/shell-permissions.test.js +369 -0
  854. package/dist/src/utils/shell-permissions.test.js.map +1 -0
  855. package/dist/src/utils/shell-utils.d.ts +16 -47
  856. package/dist/src/utils/shell-utils.js +99 -195
  857. package/dist/src/utils/shell-utils.js.map +1 -1
  858. package/dist/src/utils/shell-utils.test.js +99 -288
  859. package/dist/src/utils/shell-utils.test.js.map +1 -1
  860. package/dist/src/utils/stdio.d.ts +2 -2
  861. package/dist/src/utils/stdio.js +2 -2
  862. package/dist/src/utils/stdio.js.map +1 -1
  863. package/dist/src/utils/stdio.test.js +5 -5
  864. package/dist/src/utils/stdio.test.js.map +1 -1
  865. package/dist/src/utils/summarizer.test.js +3 -2
  866. package/dist/src/utils/summarizer.test.js.map +1 -1
  867. package/dist/src/utils/terminal.d.ts +4 -0
  868. package/dist/src/utils/terminal.js +12 -0
  869. package/dist/src/utils/terminal.js.map +1 -1
  870. package/dist/src/utils/terminalSerializer.test.js +17 -0
  871. package/dist/src/utils/terminalSerializer.test.js.map +1 -1
  872. package/dist/src/utils/tokenCalculation.d.ts +19 -0
  873. package/dist/src/utils/tokenCalculation.js +85 -0
  874. package/dist/src/utils/tokenCalculation.js.map +1 -0
  875. package/dist/src/utils/tokenCalculation.test.d.ts +6 -0
  876. package/dist/src/utils/tokenCalculation.test.js +87 -0
  877. package/dist/src/utils/tokenCalculation.test.js.map +1 -0
  878. package/dist/src/utils/tool-utils.d.ts +9 -0
  879. package/dist/src/utils/tool-utils.js +29 -0
  880. package/dist/src/utils/tool-utils.js.map +1 -1
  881. package/dist/src/utils/tool-utils.test.js +17 -2
  882. package/dist/src/utils/tool-utils.test.js.map +1 -1
  883. package/dist/src/utils/version.d.ts +6 -0
  884. package/dist/src/utils/version.js +15 -0
  885. package/dist/src/utils/version.js.map +1 -0
  886. package/dist/src/utils/version.test.d.ts +6 -0
  887. package/dist/src/utils/version.test.js +39 -0
  888. package/dist/src/utils/version.test.js.map +1 -0
  889. package/dist/tsconfig.tsbuildinfo +1 -1
  890. package/package.json +8 -7
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  /* eslint-disable @typescript-eslint/no-explicit-any */
7
- const mockEnsureCorrectEdit = vi.hoisted(() => vi.fn());
7
+ const mockFixLLMEditWithInstruction = vi.hoisted(() => vi.fn());
8
8
  const mockGenerateJson = vi.hoisted(() => vi.fn());
9
9
  const mockOpenDiff = vi.hoisted(() => vi.fn());
10
10
  import { IdeClient } from '../ide/ide-client.js';
@@ -13,36 +13,38 @@ vi.mock('../ide/ide-client.js', () => ({
13
13
  getInstance: vi.fn(),
14
14
  },
15
15
  }));
16
- vi.mock('../utils/editCorrector.js', () => ({
17
- ensureCorrectEdit: mockEnsureCorrectEdit,
16
+ vi.mock('../utils/llm-edit-fixer.js', () => ({
17
+ FixLLMEditWithInstruction: mockFixLLMEditWithInstruction,
18
18
  }));
19
19
  vi.mock('../core/client.js', () => ({
20
20
  GeminiClient: vi.fn().mockImplementation(() => ({
21
21
  generateJson: mockGenerateJson,
22
+ getHistory: vi.fn().mockResolvedValue([]),
22
23
  })),
23
24
  }));
24
25
  vi.mock('../utils/editor.js', () => ({
25
26
  openDiff: mockOpenDiff,
26
27
  }));
27
- vi.mock('../telemetry/loggers.js', () => ({
28
- logFileOperation: vi.fn(),
29
- }));
30
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
31
- import { applyReplacement, EditTool } from './edit.js';
28
+ import { describe, it, expect, beforeEach, afterEach, vi, } from 'vitest';
29
+ import { EditTool, applyReplacement, calculateReplacement, } from './edit.js';
32
30
  import { ToolConfirmationOutcome } from './tools.js';
33
31
  import { ToolErrorType } from './tool-error.js';
32
+ import { createMockMessageBus, getMockMessageBusInstance, } from '../test-utils/mock-message-bus.js';
34
33
  import path from 'node:path';
35
34
  import fs from 'node:fs';
36
35
  import os from 'node:os';
37
36
  import { ApprovalMode } from '../policy/types.js';
37
+ import {} from '../config/config.js';
38
+ import {} from '@google/genai';
39
+ import { createMockWorkspaceContext } from '../test-utils/mockWorkspaceContext.js';
38
40
  import { StandardFileSystemService } from '../services/fileSystemService.js';
39
- import { WorkspaceContext } from '../utils/workspaceContext.js';
40
41
  describe('EditTool', () => {
41
42
  let tool;
42
43
  let tempDir;
43
44
  let rootDir;
44
45
  let mockConfig;
45
46
  let geminiClient;
47
+ let fileSystemService;
46
48
  let baseLlmClient;
47
49
  beforeEach(() => {
48
50
  vi.restoreAllMocks();
@@ -50,23 +52,26 @@ describe('EditTool', () => {
50
52
  rootDir = path.join(tempDir, 'root');
51
53
  fs.mkdirSync(rootDir);
52
54
  geminiClient = {
53
- generateJson: mockGenerateJson, // mockGenerateJson is already defined and hoisted
55
+ generateJson: mockGenerateJson,
56
+ getHistory: vi.fn().mockResolvedValue([]),
54
57
  };
55
58
  baseLlmClient = {
56
- generateJson: vi.fn(),
59
+ generateJson: mockGenerateJson,
57
60
  };
61
+ fileSystemService = new StandardFileSystemService();
58
62
  mockConfig = {
63
+ getUsageStatisticsEnabled: vi.fn(() => true),
64
+ getSessionId: vi.fn(() => 'mock-session-id'),
65
+ getContentGeneratorConfig: vi.fn(() => ({ authType: 'mock' })),
66
+ getProxy: vi.fn(() => undefined),
59
67
  getGeminiClient: vi.fn().mockReturnValue(geminiClient),
60
68
  getBaseLlmClient: vi.fn().mockReturnValue(baseLlmClient),
61
69
  getTargetDir: () => rootDir,
62
70
  getApprovalMode: vi.fn(),
63
71
  setApprovalMode: vi.fn(),
64
- getWorkspaceContext: () => new WorkspaceContext(rootDir),
65
- getFileSystemService: () => new StandardFileSystemService(),
72
+ getWorkspaceContext: () => createMockWorkspaceContext(rootDir),
73
+ getFileSystemService: () => fileSystemService,
66
74
  getIdeMode: () => false,
67
- // getGeminiConfig: () => ({ apiKey: 'test-api-key' }), // This was not a real Config method
68
- // Add other properties/methods of Config if EditTool uses them
69
- // Minimal other methods to satisfy Config type if needed by EditTool constructor or other direct uses:
70
75
  getApiKey: () => 'test-api-key',
71
76
  getModel: () => 'test-model',
72
77
  getSandbox: () => false,
@@ -81,34 +86,21 @@ describe('EditTool', () => {
81
86
  setUserMemory: vi.fn(),
82
87
  getGeminiMdFileCount: () => 0,
83
88
  setGeminiMdFileCount: vi.fn(),
84
- getToolRegistry: () => ({}), // Minimal mock for ToolRegistry
89
+ getToolRegistry: () => ({}),
85
90
  isInteractive: () => false,
91
+ getExperiments: () => { },
86
92
  };
87
- // Reset mocks before each test
88
93
  mockConfig.getApprovalMode.mockClear();
89
- // Default to not skipping confirmation
90
94
  mockConfig.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
91
- // Reset mocks and set default implementation for ensureCorrectEdit
92
- mockEnsureCorrectEdit.mockReset();
93
- mockEnsureCorrectEdit.mockImplementation(async (_, currentContent, params) => {
94
- let occurrences = 0;
95
- if (params.old_string && currentContent) {
96
- // Simple string counting for the mock
97
- let index = currentContent.indexOf(params.old_string);
98
- while (index !== -1) {
99
- occurrences++;
100
- index = currentContent.indexOf(params.old_string, index + 1);
101
- }
102
- }
103
- else if (params.old_string === '') {
104
- occurrences = 0; // Creating a new file
105
- }
106
- return Promise.resolve({ params, occurrences });
95
+ mockFixLLMEditWithInstruction.mockReset();
96
+ mockFixLLMEditWithInstruction.mockResolvedValue({
97
+ noChangesRequired: false,
98
+ search: '',
99
+ replace: '',
100
+ explanation: 'LLM fix failed',
107
101
  });
108
- // Default mock for generateJson to return the snippet unchanged
109
102
  mockGenerateJson.mockReset();
110
103
  mockGenerateJson.mockImplementation(async (contents, schema) => {
111
- // The problematic_snippet is the last part of the user's content
112
104
  const userContent = contents.find((c) => c.role === 'user');
113
105
  let promptText = '';
114
106
  if (userContent && userContent.parts) {
@@ -125,17 +117,17 @@ describe('EditTool', () => {
125
117
  });
126
118
  }
127
119
  if (schema.properties?.corrected_new_string) {
128
- // For new_string correction, we might need more sophisticated logic,
129
- // but for now, returning original is a safe default if not specified by a test.
130
120
  const originalNewStringMatch = promptText.match(/original_new_string \(what was intended to replace original_old_string\):\n```\n([\s\S]*?)\n```/);
131
121
  const originalNewString = originalNewStringMatch && originalNewStringMatch[1]
132
122
  ? originalNewStringMatch[1]
133
123
  : '';
134
124
  return Promise.resolve({ corrected_new_string: originalNewString });
135
125
  }
136
- return Promise.resolve({}); // Default empty object if schema doesn't match
126
+ return Promise.resolve({});
137
127
  });
138
- tool = new EditTool(mockConfig);
128
+ const bus = createMockMessageBus();
129
+ getMockMessageBusInstance(bus).defaultToolDecision = 'ask_user';
130
+ tool = new EditTool(mockConfig, bus);
139
131
  });
140
132
  afterEach(() => {
141
133
  fs.rmSync(tempDir, { recursive: true, force: true });
@@ -233,180 +225,85 @@ describe('EditTool', () => {
233
225
  expect(result).toBe(expected);
234
226
  });
235
227
  });
228
+ describe('calculateReplacement', () => {
229
+ const abortSignal = new AbortController().signal;
230
+ it.each([
231
+ {
232
+ name: 'perform an exact replacement',
233
+ content: 'hello world',
234
+ old_string: 'world',
235
+ new_string: 'moon',
236
+ expected: 'hello moon',
237
+ occurrences: 1,
238
+ },
239
+ {
240
+ name: 'perform a flexible, whitespace-insensitive replacement',
241
+ content: ' hello\n world\n',
242
+ old_string: 'hello\nworld',
243
+ new_string: 'goodbye\nmoon',
244
+ expected: ' goodbye\n moon\n',
245
+ occurrences: 1,
246
+ },
247
+ {
248
+ name: 'return 0 occurrences if no match is found',
249
+ content: 'hello world',
250
+ old_string: 'nomatch',
251
+ new_string: 'moon',
252
+ expected: 'hello world',
253
+ occurrences: 0,
254
+ },
255
+ ])('should $name', async ({ content, old_string, new_string, expected, occurrences }) => {
256
+ const result = await calculateReplacement(mockConfig, {
257
+ params: {
258
+ file_path: 'test.txt',
259
+ instruction: 'test',
260
+ old_string,
261
+ new_string,
262
+ },
263
+ currentContent: content,
264
+ abortSignal,
265
+ });
266
+ expect(result.newContent).toBe(expected);
267
+ expect(result.occurrences).toBe(occurrences);
268
+ });
269
+ it('should perform a regex-based replacement for flexible intra-line whitespace', async () => {
270
+ // This case would fail with the previous exact and line-trimming flexible logic
271
+ // because the whitespace *within* the line is different.
272
+ const content = ' function myFunc( a, b ) {\n return a + b;\n }';
273
+ const result = await calculateReplacement(mockConfig, {
274
+ params: {
275
+ file_path: 'test.js',
276
+ instruction: 'test',
277
+ old_string: 'function myFunc(a, b) {', // Note the normalized whitespace
278
+ new_string: 'const yourFunc = (a, b) => {',
279
+ },
280
+ currentContent: content,
281
+ abortSignal,
282
+ });
283
+ // The indentation from the original line should be preserved and applied to the new string.
284
+ const expectedContent = ' const yourFunc = (a, b) => {\n return a + b;\n }';
285
+ expect(result.newContent).toBe(expectedContent);
286
+ expect(result.occurrences).toBe(1);
287
+ });
288
+ });
236
289
  describe('validateToolParams', () => {
237
290
  it('should return null for valid params', () => {
238
291
  const params = {
239
292
  file_path: path.join(rootDir, 'test.txt'),
293
+ instruction: 'An instruction',
240
294
  old_string: 'old',
241
295
  new_string: 'new',
242
296
  };
243
297
  expect(tool.validateToolParams(params)).toBeNull();
244
298
  });
245
- it('should return error for path outside root', () => {
246
- const params = {
247
- file_path: path.join(tempDir, 'outside-root.txt'),
248
- old_string: 'old',
249
- new_string: 'new',
250
- };
251
- const error = tool.validateToolParams(params);
252
- expect(error).toContain('File path must be within one of the workspace directories');
253
- });
254
- });
255
- describe('shouldConfirmExecute', () => {
256
- const testFile = 'edit_me.txt';
257
- let filePath;
258
- beforeEach(() => {
259
- filePath = path.join(rootDir, testFile);
260
- });
261
- it('should resolve relative path and request confirmation', async () => {
262
- fs.writeFileSync(filePath, 'some old content here');
263
- const params = {
264
- file_path: testFile, // relative path
265
- old_string: 'old',
266
- new_string: 'new',
267
- };
268
- // ensureCorrectEdit will be called by shouldConfirmExecute
269
- mockEnsureCorrectEdit.mockResolvedValueOnce({
270
- params: { ...params, file_path: filePath },
271
- occurrences: 1,
272
- });
273
- const invocation = tool.build(params);
274
- const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
275
- expect(confirmation).toEqual(expect.objectContaining({
276
- title: `Confirm Edit: ${testFile}`,
277
- fileName: testFile,
278
- fileDiff: expect.any(String),
279
- }));
280
- });
281
- it('should request confirmation for valid edit', async () => {
282
- fs.writeFileSync(filePath, 'some old content here');
283
- const params = {
284
- file_path: filePath,
285
- old_string: 'old',
286
- new_string: 'new',
287
- };
288
- // ensureCorrectEdit will be called by shouldConfirmExecute
289
- mockEnsureCorrectEdit.mockResolvedValueOnce({ params, occurrences: 1 });
290
- const invocation = tool.build(params);
291
- const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
292
- expect(confirmation).toEqual(expect.objectContaining({
293
- title: `Confirm Edit: ${testFile}`,
294
- fileName: testFile,
295
- fileDiff: expect.any(String),
296
- }));
297
- });
298
- it('should return false if old_string is not found (ensureCorrectEdit returns 0)', async () => {
299
- fs.writeFileSync(filePath, 'some content here');
300
- const params = {
301
- file_path: filePath,
302
- old_string: 'not_found',
303
- new_string: 'new',
304
- };
305
- mockEnsureCorrectEdit.mockResolvedValueOnce({ params, occurrences: 0 });
306
- const invocation = tool.build(params);
307
- const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
308
- expect(confirmation).toBe(false);
309
- });
310
- it('should return false if multiple occurrences of old_string are found (ensureCorrectEdit returns > 1)', async () => {
311
- fs.writeFileSync(filePath, 'old old content here');
312
- const params = {
313
- file_path: filePath,
314
- old_string: 'old',
315
- new_string: 'new',
316
- };
317
- mockEnsureCorrectEdit.mockResolvedValueOnce({ params, occurrences: 2 });
318
- const invocation = tool.build(params);
319
- const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
320
- expect(confirmation).toBe(false);
321
- });
322
- it('should request confirmation for creating a new file (empty old_string)', async () => {
323
- const newFileName = 'new_file.txt';
324
- const newFilePath = path.join(rootDir, newFileName);
325
- const params = {
326
- file_path: newFilePath,
327
- old_string: '',
328
- new_string: 'new file content',
329
- };
330
- // ensureCorrectEdit might not be called if old_string is empty,
331
- // as shouldConfirmExecute handles this for diff generation.
332
- // If it is called, it should return 0 occurrences for a new file.
333
- mockEnsureCorrectEdit.mockResolvedValueOnce({ params, occurrences: 0 });
334
- const invocation = tool.build(params);
335
- const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
336
- expect(confirmation).toEqual(expect.objectContaining({
337
- title: `Confirm Edit: ${newFileName}`,
338
- fileName: newFileName,
339
- fileDiff: expect.any(String),
340
- }));
341
- });
342
- it('should use corrected params from ensureCorrectEdit for diff generation', async () => {
343
- const originalContent = 'This is the original string to be replaced.';
344
- const originalOldString = 'original string';
345
- const originalNewString = 'new string';
346
- const correctedOldString = 'original string to be replaced'; // More specific
347
- const correctedNewString = 'completely new string'; // Different replacement
348
- const expectedFinalContent = 'This is the completely new string.';
349
- fs.writeFileSync(filePath, originalContent);
350
- const params = {
351
- file_path: filePath,
352
- old_string: originalOldString,
353
- new_string: originalNewString,
354
- };
355
- // The main beforeEach already calls mockEnsureCorrectEdit.mockReset()
356
- // Set a specific mock for this test case
357
- let mockCalled = false;
358
- mockEnsureCorrectEdit.mockImplementationOnce(async (_, content, p, client, baseClient) => {
359
- mockCalled = true;
360
- expect(content).toBe(originalContent);
361
- expect(p).toBe(params);
362
- expect(client).toBe(geminiClient);
363
- expect(baseClient).toBe(baseLlmClient);
364
- return {
365
- params: {
366
- file_path: filePath,
367
- old_string: correctedOldString,
368
- new_string: correctedNewString,
369
- },
370
- occurrences: 1,
371
- };
372
- });
373
- const invocation = tool.build(params);
374
- const confirmation = (await invocation.shouldConfirmExecute(new AbortController().signal));
375
- expect(mockCalled).toBe(true); // Check if the mock implementation was run
376
- // expect(mockEnsureCorrectEdit).toHaveBeenCalledWith(originalContent, params, expect.anything()); // Keep this commented for now
377
- expect(confirmation).toEqual(expect.objectContaining({
378
- title: `Confirm Edit: ${testFile}`,
379
- fileName: testFile,
380
- }));
381
- // Check that the diff is based on the corrected strings leading to the new state
382
- expect(confirmation.fileDiff).toContain(`-${originalContent}`);
383
- expect(confirmation.fileDiff).toContain(`+${expectedFinalContent}`);
384
- // Verify that applying the correctedOldString and correctedNewString to originalContent
385
- // indeed produces the expectedFinalContent, which is what the diff should reflect.
386
- const patchedContent = originalContent.replace(correctedOldString, // This was the string identified by ensureCorrectEdit for replacement
387
- correctedNewString);
388
- expect(patchedContent).toBe(expectedFinalContent);
389
- });
390
- it('should rethrow calculateEdit errors when the abort signal is triggered', async () => {
391
- const filePath = path.join(rootDir, 'abort-confirmation.txt');
299
+ it('should return an error if path is outside the workspace', () => {
392
300
  const params = {
393
- file_path: filePath,
301
+ file_path: path.join(os.tmpdir(), 'outside.txt'),
302
+ instruction: 'An instruction',
394
303
  old_string: 'old',
395
304
  new_string: 'new',
396
305
  };
397
- const invocation = tool.build(params);
398
- const abortController = new AbortController();
399
- const abortError = new Error('Abort requested');
400
- const calculateSpy = vi
401
- .spyOn(invocation, 'calculateEdit')
402
- .mockImplementation(async () => {
403
- if (!abortController.signal.aborted) {
404
- abortController.abort();
405
- }
406
- throw abortError;
407
- });
408
- await expect(invocation.shouldConfirmExecute(abortController.signal)).rejects.toBe(abortError);
409
- calculateSpy.mockRestore();
306
+ expect(tool.validateToolParams(params)).toMatch(/must be within one of the workspace directories/);
410
307
  });
411
308
  });
412
309
  describe('execute', () => {
@@ -414,53 +311,17 @@ describe('EditTool', () => {
414
311
  let filePath;
415
312
  beforeEach(() => {
416
313
  filePath = path.join(rootDir, testFile);
417
- // Default for execute tests, can be overridden
418
- mockEnsureCorrectEdit.mockImplementation(async (_, content, params) => {
419
- let occurrences = 0;
420
- if (params.old_string && content) {
421
- let index = content.indexOf(params.old_string);
422
- while (index !== -1) {
423
- occurrences++;
424
- index = content.indexOf(params.old_string, index + 1);
425
- }
426
- }
427
- else if (params.old_string === '') {
428
- occurrences = 0;
429
- }
430
- return { params, occurrences };
431
- });
432
- });
433
- it('should resolve relative path and execute successfully', async () => {
434
- const initialContent = 'This is some old text.';
435
- const newContent = 'This is some new text.';
436
- fs.writeFileSync(filePath, initialContent, 'utf8');
437
- const params = {
438
- file_path: testFile, // relative path
439
- old_string: 'old',
440
- new_string: 'new',
441
- };
442
- const invocation = tool.build(params);
443
- const result = await invocation.execute(new AbortController().signal);
444
- expect(result.llmContent).toMatch(/Successfully modified file/);
445
- expect(fs.readFileSync(filePath, 'utf8')).toBe(newContent);
446
- });
447
- it('should throw error if file path is empty', async () => {
448
- const params = {
449
- file_path: '',
450
- old_string: 'old',
451
- new_string: 'new',
452
- };
453
- expect(() => tool.build(params)).toThrow(/The 'file_path' parameter must be non-empty./);
454
314
  });
455
315
  it('should reject when calculateEdit fails after an abort signal', async () => {
456
316
  const params = {
457
317
  file_path: path.join(rootDir, 'abort-execute.txt'),
318
+ instruction: 'Abort during execute',
458
319
  old_string: 'old',
459
320
  new_string: 'new',
460
321
  };
461
322
  const invocation = tool.build(params);
462
323
  const abortController = new AbortController();
463
- const abortError = new Error('Abort requested during execute');
324
+ const abortError = new Error('Abort requested during edit execution');
464
325
  const calculateSpy = vi
465
326
  .spyOn(invocation, 'calculateEdit')
466
327
  .mockImplementation(async () => {
@@ -474,16 +335,14 @@ describe('EditTool', () => {
474
335
  });
475
336
  it('should edit an existing file and return diff with fileName', async () => {
476
337
  const initialContent = 'This is some old text.';
477
- const newContent = 'This is some new text.'; // old -> new
338
+ const newContent = 'This is some new text.';
478
339
  fs.writeFileSync(filePath, initialContent, 'utf8');
479
340
  const params = {
480
341
  file_path: filePath,
342
+ instruction: 'Replace old with new',
481
343
  old_string: 'old',
482
344
  new_string: 'new',
483
345
  };
484
- // Specific mock for this test's execution path in calculateEdit
485
- // ensureCorrectEdit is NOT called by calculateEdit, only by shouldConfirmExecute
486
- // So, the default mockEnsureCorrectEdit should correctly return 1 occurrence for 'old' in initialContent
487
346
  const invocation = tool.build(params);
488
347
  const result = await invocation.execute(new AbortController().signal);
489
348
  expect(result.llmContent).toMatch(/Successfully modified file/);
@@ -493,196 +352,124 @@ describe('EditTool', () => {
493
352
  expect(display.fileDiff).toMatch(newContent);
494
353
  expect(display.fileName).toBe(testFile);
495
354
  });
496
- it('should create a new file if old_string is empty and file does not exist, and return created message', async () => {
497
- const newFileName = 'brand_new_file.txt';
498
- const newFilePath = path.join(rootDir, newFileName);
499
- const fileContent = 'Content for the new file.';
500
- const params = {
501
- file_path: newFilePath,
502
- old_string: '',
503
- new_string: fileContent,
504
- };
505
- mockConfig.getApprovalMode.mockReturnValueOnce(ApprovalMode.AUTO_EDIT);
506
- const invocation = tool.build(params);
507
- const result = await invocation.execute(new AbortController().signal);
508
- expect(result.llmContent).toMatch(/Created new file/);
509
- expect(fs.existsSync(newFilePath)).toBe(true);
510
- expect(fs.readFileSync(newFilePath, 'utf8')).toBe(fileContent);
511
- const display = result.returnDisplay;
512
- expect(display.fileDiff).toMatch(/\+Content for the new file\./);
513
- expect(display.fileName).toBe(newFileName);
514
- expect(result.returnDisplay.diffStat).toStrictEqual({
515
- model_added_lines: 1,
516
- model_removed_lines: 0,
517
- model_added_chars: 25,
518
- model_removed_chars: 0,
519
- user_added_lines: 0,
520
- user_removed_lines: 0,
521
- user_added_chars: 0,
522
- user_removed_chars: 0,
523
- });
524
- });
525
355
  it('should return error if old_string is not found in file', async () => {
526
356
  fs.writeFileSync(filePath, 'Some content.', 'utf8');
527
357
  const params = {
528
358
  file_path: filePath,
359
+ instruction: 'Replace non-existent text',
529
360
  old_string: 'nonexistent',
530
361
  new_string: 'replacement',
531
362
  };
532
- // The default mockEnsureCorrectEdit will return 0 occurrences for 'nonexistent'
533
363
  const invocation = tool.build(params);
534
364
  const result = await invocation.execute(new AbortController().signal);
535
- expect(result.llmContent).toMatch(/0 occurrences found for old_string in/);
365
+ expect(result.llmContent).toMatch(/0 occurrences found for old_string/);
536
366
  expect(result.returnDisplay).toMatch(/Failed to edit, could not find the string to replace./);
367
+ expect(mockFixLLMEditWithInstruction).toHaveBeenCalled();
537
368
  });
538
- it('should return error if multiple occurrences of old_string are found', async () => {
539
- fs.writeFileSync(filePath, 'multiple old old strings', 'utf8');
540
- const params = {
541
- file_path: filePath,
542
- old_string: 'old',
543
- new_string: 'new',
544
- };
545
- // The default mockEnsureCorrectEdit will return 2 occurrences for 'old'
546
- const invocation = tool.build(params);
547
- const result = await invocation.execute(new AbortController().signal);
548
- expect(result.llmContent).toMatch(/Expected 1 occurrence but found 2 for old_string in file/);
549
- expect(result.returnDisplay).toMatch(/Failed to edit, expected 1 occurrence but found 2/);
550
- });
551
- it('should successfully replace multiple occurrences when expected_replacements specified', async () => {
552
- fs.writeFileSync(filePath, 'old text\nold text\nold text', 'utf8');
369
+ it('should succeed if FixLLMEditWithInstruction corrects the params', async () => {
370
+ const initialContent = 'This is some original text.';
371
+ const finalContent = 'This is some brand new text.';
372
+ fs.writeFileSync(filePath, initialContent, 'utf8');
553
373
  const params = {
554
374
  file_path: filePath,
555
- old_string: 'old',
556
- new_string: 'new',
557
- expected_replacements: 3,
375
+ instruction: 'Replace original with brand new',
376
+ old_string: 'wrong text', // This will fail first
377
+ new_string: 'brand new text',
558
378
  };
379
+ mockFixLLMEditWithInstruction.mockResolvedValueOnce({
380
+ noChangesRequired: false,
381
+ search: 'original text', // The corrected search string
382
+ replace: 'brand new text',
383
+ explanation: 'Corrected the search string to match the file content.',
384
+ });
559
385
  const invocation = tool.build(params);
560
386
  const result = await invocation.execute(new AbortController().signal);
387
+ expect(result.error).toBeUndefined();
561
388
  expect(result.llmContent).toMatch(/Successfully modified file/);
562
- expect(fs.readFileSync(filePath, 'utf8')).toBe('new text\nnew text\nnew text');
563
- const display = result.returnDisplay;
564
- expect(display.fileDiff).toMatch(/-old text\n-old text\n-old text/);
565
- expect(display.fileDiff).toMatch(/\+new text\n\+new text\n\+new text/);
566
- expect(display.fileName).toBe(testFile);
567
- expect(result.returnDisplay.diffStat).toStrictEqual({
568
- model_added_lines: 3,
569
- model_removed_lines: 3,
570
- model_added_chars: 24,
571
- model_removed_chars: 24,
572
- user_added_lines: 0,
573
- user_removed_lines: 0,
574
- user_added_chars: 0,
575
- user_removed_chars: 0,
576
- });
389
+ expect(fs.readFileSync(filePath, 'utf8')).toBe(finalContent);
390
+ expect(mockFixLLMEditWithInstruction).toHaveBeenCalledTimes(1);
577
391
  });
578
- it('should return error if expected_replacements does not match actual occurrences', async () => {
579
- fs.writeFileSync(filePath, 'old text old text', 'utf8');
392
+ it('should preserve CRLF line endings when editing a file', async () => {
393
+ const initialContent = 'line one\r\nline two\r\n';
394
+ const newContent = 'line one\r\nline three\r\n';
395
+ fs.writeFileSync(filePath, initialContent, 'utf8');
580
396
  const params = {
581
397
  file_path: filePath,
582
- old_string: 'old',
583
- new_string: 'new',
584
- expected_replacements: 3, // Expecting 3 but only 2 exist
398
+ instruction: 'Replace two with three',
399
+ old_string: 'line two',
400
+ new_string: 'line three',
585
401
  };
586
402
  const invocation = tool.build(params);
587
- const result = await invocation.execute(new AbortController().signal);
588
- expect(result.llmContent).toMatch(/Expected 3 occurrences but found 2 for old_string in file/);
589
- expect(result.returnDisplay).toMatch(/Failed to edit, expected 3 occurrences but found 2/);
403
+ await invocation.execute(new AbortController().signal);
404
+ const finalContent = fs.readFileSync(filePath, 'utf8');
405
+ expect(finalContent).toBe(newContent);
590
406
  });
591
- it('should return error if trying to create a file that already exists (empty old_string)', async () => {
592
- fs.writeFileSync(filePath, 'Existing content', 'utf8');
407
+ it('should create a new file with CRLF line endings if new_string has them', async () => {
408
+ const newContentWithCRLF = 'new line one\r\nnew line two\r\n';
593
409
  const params = {
594
410
  file_path: filePath,
411
+ instruction: 'Create a new file',
595
412
  old_string: '',
596
- new_string: 'new content',
413
+ new_string: newContentWithCRLF,
597
414
  };
598
415
  const invocation = tool.build(params);
599
- const result = await invocation.execute(new AbortController().signal);
600
- expect(result.llmContent).toMatch(/File already exists, cannot create/);
601
- expect(result.returnDisplay).toMatch(/Attempted to create a file that already exists/);
416
+ await invocation.execute(new AbortController().signal);
417
+ const finalContent = fs.readFileSync(filePath, 'utf8');
418
+ expect(finalContent).toBe(newContentWithCRLF);
602
419
  });
603
- it('should include modification message when proposed content is modified', async () => {
604
- const initialContent = 'Line 1\nold line\nLine 3\nLine 4\nLine 5\n';
420
+ it('should return NO_CHANGE if FixLLMEditWithInstruction determines no changes are needed', async () => {
421
+ const initialContent = 'The price is $100.';
605
422
  fs.writeFileSync(filePath, initialContent, 'utf8');
606
423
  const params = {
607
424
  file_path: filePath,
608
- old_string: 'old',
609
- new_string: 'new',
610
- modified_by_user: true,
611
- ai_proposed_content: 'Line 1\nAI line\nLine 3\nLine 4\nLine 5\n',
425
+ instruction: 'Ensure the price is $100',
426
+ old_string: 'price is $50', // Incorrect old string
427
+ new_string: 'price is $100',
612
428
  };
613
- mockConfig.getApprovalMode.mockReturnValueOnce(ApprovalMode.AUTO_EDIT);
614
- const invocation = tool.build(params);
615
- const result = await invocation.execute(new AbortController().signal);
616
- expect(result.llmContent).toMatch(/User modified the `new_string` content/);
617
- expect(result.returnDisplay.diffStat).toStrictEqual({
618
- model_added_lines: 1,
619
- model_removed_lines: 1,
620
- model_added_chars: 7,
621
- model_removed_chars: 8,
622
- user_added_lines: 1,
623
- user_removed_lines: 1,
624
- user_added_chars: 8,
625
- user_removed_chars: 7,
429
+ mockFixLLMEditWithInstruction.mockResolvedValueOnce({
430
+ noChangesRequired: true,
431
+ search: '',
432
+ replace: '',
433
+ explanation: 'The price is already correctly set to $100.',
626
434
  });
627
- });
628
- it.each([
629
- {
630
- name: 'modified_by_user is false',
631
- modifiedByUser: false,
632
- },
633
- {
634
- name: 'modified_by_user is not provided',
635
- modifiedByUser: undefined,
636
- },
637
- ])('should not include modification message when $name', async ({ modifiedByUser }) => {
638
- const initialContent = 'This is some old text.';
639
- fs.writeFileSync(filePath, initialContent, 'utf8');
640
- const params = {
641
- file_path: filePath,
642
- old_string: 'old',
643
- new_string: 'new',
644
- ...(modifiedByUser !== undefined && {
645
- modified_by_user: modifiedByUser,
646
- }),
647
- };
648
- mockConfig.getApprovalMode.mockReturnValueOnce(ApprovalMode.AUTO_EDIT);
649
435
  const invocation = tool.build(params);
650
436
  const result = await invocation.execute(new AbortController().signal);
651
- expect(result.llmContent).not.toMatch(/User modified the `new_string` content/);
437
+ expect(result.error?.type).toBe(ToolErrorType.EDIT_NO_CHANGE_LLM_JUDGEMENT);
438
+ expect(result.llmContent).toMatch(/A secondary check by an LLM determined/);
439
+ expect(fs.readFileSync(filePath, 'utf8')).toBe(initialContent); // File is unchanged
652
440
  });
653
- it('should return error if old_string and new_string are identical', async () => {
654
- const initialContent = 'This is some identical text.';
655
- fs.writeFileSync(filePath, initialContent, 'utf8');
656
- const params = {
657
- file_path: filePath,
658
- old_string: 'identical',
659
- new_string: 'identical',
660
- };
661
- const invocation = tool.build(params);
662
- const result = await invocation.execute(new AbortController().signal);
663
- expect(result.llmContent).toMatch(/No changes to apply/);
664
- expect(result.returnDisplay).toMatch(/No changes to apply/);
441
+ });
442
+ describe('self-correction with content refresh to pull in external edits', () => {
443
+ const testFile = 'test.txt';
444
+ let filePath;
445
+ beforeEach(() => {
446
+ filePath = path.join(rootDir, testFile);
665
447
  });
666
- it('should return EDIT_NO_CHANGE error if replacement results in identical content', async () => {
667
- // This can happen if ensureCorrectEdit finds a fuzzy match, but the literal
668
- // string replacement with `replaceAll` results in no change.
669
- const initialContent = 'line 1\nline 2\nline 3'; // Note the double space
448
+ it('should use refreshed file content for self-correction if file was modified externally', async () => {
449
+ const initialContent = 'This is the original content.';
450
+ const externallyModifiedContent = 'This is the externally modified content.';
670
451
  fs.writeFileSync(filePath, initialContent, 'utf8');
671
452
  const params = {
672
453
  file_path: filePath,
673
- // old_string has a single space, so it won't be found by replaceAll
674
- old_string: 'line 1\nline 2\nline 3',
675
- new_string: 'line 1\nnew line 2\nline 3',
454
+ instruction: 'Replace "externally modified content" with "externally modified string"',
455
+ old_string: 'externally modified content', // This will fail the first attempt, triggering self-correction.
456
+ new_string: 'externally modified string',
676
457
  };
677
- // Mock ensureCorrectEdit to simulate it finding a match (e.g., via fuzzy matching)
678
- // but it doesn't correct the old_string to the literal content.
679
- mockEnsureCorrectEdit.mockResolvedValueOnce({ params, occurrences: 1 });
458
+ // Spy on `readTextFile` to simulate an external file change between reads.
459
+ const readTextFileSpy = vi
460
+ .spyOn(fileSystemService, 'readTextFile')
461
+ .mockResolvedValueOnce(initialContent) // First call in `calculateEdit`
462
+ .mockResolvedValueOnce(externallyModifiedContent); // Second call in `attemptSelfCorrection`
680
463
  const invocation = tool.build(params);
681
- const result = await invocation.execute(new AbortController().signal);
682
- expect(result.error?.type).toBe(ToolErrorType.EDIT_NO_CHANGE);
683
- expect(result.returnDisplay).toMatch(/No changes to apply. The new content is identical to the current content./);
684
- // Ensure the file was not actually changed
685
- expect(fs.readFileSync(filePath, 'utf8')).toBe(initialContent);
464
+ await invocation.execute(new AbortController().signal);
465
+ // Assert that the file was read twice (initial read, then re-read for hash comparison).
466
+ expect(readTextFileSpy).toHaveBeenCalledTimes(2);
467
+ // Assert that the self-correction LLM was called with the updated content and a specific message.
468
+ expect(mockFixLLMEditWithInstruction).toHaveBeenCalledWith(expect.any(String), // instruction
469
+ params.old_string, params.new_string, expect.stringContaining('However, the file has been modified by either the user or an external process'), // errorForLlmEditFixer
470
+ externallyModifiedContent, // The new content for correction
471
+ expect.any(Object), // baseLlmClient
472
+ expect.any(Object));
686
473
  });
687
474
  });
688
475
  describe('Error Scenarios', () => {
@@ -693,163 +480,85 @@ describe('EditTool', () => {
693
480
  });
694
481
  it.each([
695
482
  {
696
- name: 'FILE_NOT_FOUND error',
697
- setup: () => { },
698
- params: { file_path: '', old_string: 'any', new_string: 'new' },
483
+ name: 'FILE_NOT_FOUND',
484
+ setup: () => { }, // no file created
485
+ params: { old_string: 'any', new_string: 'new' },
699
486
  expectedError: ToolErrorType.FILE_NOT_FOUND,
700
- isAsyncTest: true,
701
487
  },
702
488
  {
703
- name: 'ATTEMPT_TO_CREATE_EXISTING_FILE error',
489
+ name: 'ATTEMPT_TO_CREATE_EXISTING_FILE',
704
490
  setup: (fp) => fs.writeFileSync(fp, 'existing content', 'utf8'),
705
- params: { file_path: '', old_string: '', new_string: 'new content' },
491
+ params: { old_string: '', new_string: 'new content' },
706
492
  expectedError: ToolErrorType.ATTEMPT_TO_CREATE_EXISTING_FILE,
707
- isAsyncTest: true,
708
493
  },
709
494
  {
710
- name: 'NO_OCCURRENCE_FOUND error',
495
+ name: 'NO_OCCURRENCE_FOUND',
711
496
  setup: (fp) => fs.writeFileSync(fp, 'content', 'utf8'),
712
- params: { file_path: '', old_string: 'not-found', new_string: 'new' },
497
+ params: { old_string: 'not-found', new_string: 'new' },
713
498
  expectedError: ToolErrorType.EDIT_NO_OCCURRENCE_FOUND,
714
- isAsyncTest: true,
715
499
  },
716
500
  {
717
- name: 'EXPECTED_OCCURRENCE_MISMATCH error',
501
+ name: 'EXPECTED_OCCURRENCE_MISMATCH',
718
502
  setup: (fp) => fs.writeFileSync(fp, 'one one two', 'utf8'),
719
- params: {
720
- file_path: '',
721
- old_string: 'one',
722
- new_string: 'new',
723
- expected_replacements: 3,
724
- },
503
+ params: { old_string: 'one', new_string: 'new' },
725
504
  expectedError: ToolErrorType.EDIT_EXPECTED_OCCURRENCE_MISMATCH,
726
- isAsyncTest: true,
727
- },
728
- {
729
- name: 'NO_CHANGE error',
730
- setup: (fp) => fs.writeFileSync(fp, 'content', 'utf8'),
731
- params: { file_path: '', old_string: 'content', new_string: 'content' },
732
- expectedError: ToolErrorType.EDIT_NO_CHANGE,
733
- isAsyncTest: true,
734
- },
735
- {
736
- name: 'relative path (should not throw)',
737
- setup: () => { },
738
- params: {
739
- file_path: 'relative/path.txt',
740
- old_string: 'a',
741
- new_string: 'b',
742
- },
743
- expectedError: null,
744
- isAsyncTest: false,
745
- },
746
- {
747
- name: 'FILE_WRITE_FAILURE on write error',
748
- setup: (fp) => {
749
- fs.writeFileSync(fp, 'content', 'utf8');
750
- fs.chmodSync(fp, '444');
751
- },
752
- params: {
753
- file_path: '',
754
- old_string: 'content',
755
- new_string: 'new content',
756
- },
757
- expectedError: ToolErrorType.FILE_WRITE_FAILURE,
758
- isAsyncTest: true,
759
505
  },
760
- ])('should return $name', async ({ setup, params, expectedError, isAsyncTest }) => {
761
- const testParams = {
762
- ...params,
763
- file_path: params.file_path || filePath,
764
- };
506
+ ])('should return $name error', async ({ setup, params, expectedError }) => {
765
507
  setup(filePath);
766
- if (!isAsyncTest) {
767
- expect(() => tool.build(testParams)).not.toThrow();
768
- }
769
- else {
770
- const invocation = tool.build(testParams);
771
- const result = await invocation.execute(new AbortController().signal);
772
- expect(result.error?.type).toBe(expectedError);
773
- }
508
+ const invocation = tool.build({
509
+ file_path: filePath,
510
+ instruction: 'test',
511
+ ...params,
512
+ });
513
+ const result = await invocation.execute(new AbortController().signal);
514
+ expect(result.error?.type).toBe(expectedError);
774
515
  });
775
516
  });
776
- describe('getDescription', () => {
517
+ describe('expected_replacements', () => {
518
+ const testFile = 'replacements_test.txt';
519
+ let filePath;
520
+ beforeEach(() => {
521
+ filePath = path.join(rootDir, testFile);
522
+ });
777
523
  it.each([
778
524
  {
779
- name: 'identical strings (no change)',
780
- fileName: 'test.txt',
781
- oldStr: 'identical_string',
782
- newStr: 'identical_string',
783
- expected: 'No file changes to test.txt',
784
- },
785
- {
786
- name: 'different strings (full)',
787
- fileName: 'test.txt',
788
- oldStr: 'this is the old string value',
789
- newStr: 'this is the new string value',
790
- expected: 'test.txt: this is the old string value => this is the new string value',
525
+ name: 'succeed when occurrences match expected_replacements',
526
+ content: 'foo foo foo',
527
+ expected: 3,
528
+ shouldSucceed: true,
529
+ finalContent: 'bar bar bar',
791
530
  },
792
531
  {
793
- name: 'very short strings',
794
- fileName: 'short.txt',
795
- oldStr: 'old',
796
- newStr: 'new',
797
- expected: 'short.txt: old => new',
532
+ name: 'fail when occurrences do not match expected_replacements',
533
+ content: 'foo foo foo',
534
+ expected: 2,
535
+ shouldSucceed: false,
798
536
  },
799
537
  {
800
- name: 'long strings (truncated)',
801
- fileName: 'long.txt',
802
- oldStr: 'this is a very long old string that will definitely be truncated',
803
- newStr: 'this is a very long new string that will also be truncated',
804
- expected: 'long.txt: this is a very long old string... => this is a very long new string...',
538
+ name: 'default to 1 expected replacement if not specified',
539
+ content: 'foo foo',
540
+ expected: undefined,
541
+ shouldSucceed: false,
805
542
  },
806
- ])('should handle $name', ({ fileName, oldStr, newStr, expected }) => {
543
+ ])('should $name', async ({ content, expected, shouldSucceed, finalContent }) => {
544
+ fs.writeFileSync(filePath, content, 'utf8');
807
545
  const params = {
808
- file_path: path.join(rootDir, fileName),
809
- old_string: oldStr,
810
- new_string: newStr,
546
+ file_path: filePath,
547
+ instruction: 'Replace all foo with bar',
548
+ old_string: 'foo',
549
+ new_string: 'bar',
550
+ ...(expected !== undefined && { expected_replacements: expected }),
811
551
  };
812
552
  const invocation = tool.build(params);
813
- expect(invocation.getDescription()).toBe(expected);
814
- });
815
- });
816
- describe('workspace boundary validation', () => {
817
- it('should validate paths are within workspace root', () => {
818
- const validPath = {
819
- file_path: path.join(rootDir, 'file.txt'),
820
- old_string: 'old',
821
- new_string: 'new',
822
- };
823
- expect(tool.validateToolParams(validPath)).toBeNull();
824
- });
825
- it('should reject paths outside workspace root', () => {
826
- const invalidPath = {
827
- file_path: '/etc/passwd',
828
- old_string: 'root',
829
- new_string: 'hacked',
830
- };
831
- const error = tool.validateToolParams(invalidPath);
832
- expect(error).toContain('File path must be within one of the workspace directories');
833
- expect(error).toContain(rootDir);
834
- });
835
- });
836
- describe('constructor', () => {
837
- afterEach(() => {
838
- vi.restoreAllMocks();
839
- });
840
- it('should use windows-style path examples on windows', () => {
841
- vi.spyOn(process, 'platform', 'get').mockReturnValue('win32');
842
- const tool = new EditTool({});
843
- const schema = tool.schema;
844
- expect(schema.parametersJsonSchema.properties
845
- .file_path.description).toBe('The path to the file to modify.');
846
- });
847
- it('should use unix-style path examples on non-windows platforms', () => {
848
- vi.spyOn(process, 'platform', 'get').mockReturnValue('linux');
849
- const tool = new EditTool({});
850
- const schema = tool.schema;
851
- expect(schema.parametersJsonSchema.properties
852
- .file_path.description).toBe('The path to the file to modify.');
553
+ const result = await invocation.execute(new AbortController().signal);
554
+ if (shouldSucceed) {
555
+ expect(result.error).toBeUndefined();
556
+ if (finalContent)
557
+ expect(fs.readFileSync(filePath, 'utf8')).toBe(finalContent);
558
+ }
559
+ else {
560
+ expect(result.error?.type).toBe(ToolErrorType.EDIT_EXPECTED_OCCURRENCE_MISMATCH);
561
+ }
853
562
  });
854
563
  });
855
564
  describe('IDE mode', () => {
@@ -872,13 +581,10 @@ describe('EditTool', () => {
872
581
  fs.writeFileSync(filePath, initialContent);
873
582
  const params = {
874
583
  file_path: filePath,
584
+ instruction: 'test',
875
585
  old_string: 'old',
876
586
  new_string: 'new',
877
587
  };
878
- mockEnsureCorrectEdit.mockResolvedValueOnce({
879
- params: { ...params, old_string: 'old', new_string: 'new' },
880
- occurrences: 1,
881
- });
882
588
  ideClient.openDiff.mockResolvedValueOnce({
883
589
  status: 'accepted',
884
590
  content: modifiedContent,
@@ -893,6 +599,30 @@ describe('EditTool', () => {
893
599
  expect(params.new_string).toBe(modifiedContent);
894
600
  });
895
601
  });
602
+ describe('shouldConfirmExecute', () => {
603
+ it('should rethrow calculateEdit errors when the abort signal is triggered', async () => {
604
+ const filePath = path.join(rootDir, 'abort-confirmation.txt');
605
+ const params = {
606
+ file_path: filePath,
607
+ instruction: 'Abort during confirmation',
608
+ old_string: 'old',
609
+ new_string: 'new',
610
+ };
611
+ const invocation = tool.build(params);
612
+ const abortController = new AbortController();
613
+ const abortError = new Error('Abort requested during edit confirmation');
614
+ const calculateSpy = vi
615
+ .spyOn(invocation, 'calculateEdit')
616
+ .mockImplementation(async () => {
617
+ if (!abortController.signal.aborted) {
618
+ abortController.abort();
619
+ }
620
+ throw abortError;
621
+ });
622
+ await expect(invocation.shouldConfirmExecute(abortController.signal)).rejects.toBe(abortError);
623
+ calculateSpy.mockRestore();
624
+ });
625
+ });
896
626
  describe('multiple file edits', () => {
897
627
  it('should perform multiple removals and report correct diff stats', async () => {
898
628
  const numFiles = 10;
@@ -923,8 +653,10 @@ describe('EditTool', () => {
923
653
  for (const file of files) {
924
654
  const params = {
925
655
  file_path: file.path,
656
+ instruction: `Remove lines from the file`,
926
657
  old_string: file.toRemove,
927
658
  new_string: '', // Removing the content
659
+ ai_proposed_content: '',
928
660
  };
929
661
  const invocation = tool.build(params);
930
662
  const result = await invocation.execute(new AbortController().signal);
@@ -935,7 +667,7 @@ describe('EditTool', () => {
935
667
  actualLinesRemoved.push(result.returnDisplay.diffStat?.model_removed_lines);
936
668
  }
937
669
  else if (result.error) {
938
- console.error(`Edit failed for ${file.path}:`, result.error);
670
+ throw result.error;
939
671
  }
940
672
  }
941
673
  // 3. Assert that the content was removed from each file