@qduc/term2 0.4.0 → 0.6.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 (529) hide show
  1. package/dist/agent.d.ts +5 -0
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +61 -8
  4. package/dist/agent.js.map +1 -1
  5. package/dist/agent.test.js +122 -10
  6. package/dist/agent.test.js.map +1 -1
  7. package/dist/app.d.ts +13 -0
  8. package/dist/app.d.ts.map +1 -1
  9. package/dist/app.js +71 -18
  10. package/dist/app.js.map +1 -1
  11. package/dist/app.startup-banner.test.js +48 -1
  12. package/dist/app.startup-banner.test.js.map +1 -1
  13. package/dist/cli.js +197 -28
  14. package/dist/cli.js.map +1 -1
  15. package/dist/components/ApprovalPrompt.d.ts.map +1 -1
  16. package/dist/components/ApprovalPrompt.js +11 -34
  17. package/dist/components/ApprovalPrompt.js.map +1 -1
  18. package/dist/components/Banner.d.ts.map +1 -1
  19. package/dist/components/Banner.js +2 -7
  20. package/dist/components/Banner.js.map +1 -1
  21. package/dist/components/BottomArea.d.ts +7 -1
  22. package/dist/components/BottomArea.d.ts.map +1 -1
  23. package/dist/components/BottomArea.js +2 -2
  24. package/dist/components/BottomArea.js.map +1 -1
  25. package/dist/components/ChatMessage.d.ts.map +1 -1
  26. package/dist/components/ChatMessage.js +2 -1
  27. package/dist/components/ChatMessage.js.map +1 -1
  28. package/dist/components/CommandMessage.d.ts.map +1 -1
  29. package/dist/components/CommandMessage.js +45 -48
  30. package/dist/components/CommandMessage.js.map +1 -1
  31. package/dist/components/CommandMessage.test.js +69 -0
  32. package/dist/components/CommandMessage.test.js.map +1 -1
  33. package/dist/components/DiffView.d.ts +7 -0
  34. package/dist/components/DiffView.d.ts.map +1 -0
  35. package/dist/components/DiffView.js +80 -0
  36. package/dist/components/DiffView.js.map +1 -0
  37. package/dist/components/DiffView.test.d.ts +2 -0
  38. package/dist/components/DiffView.test.d.ts.map +1 -0
  39. package/dist/components/DiffView.test.js +45 -0
  40. package/dist/components/DiffView.test.js.map +1 -0
  41. package/dist/components/Input/PopupManager.d.ts +7 -0
  42. package/dist/components/Input/PopupManager.d.ts.map +1 -1
  43. package/dist/components/Input/PopupManager.js +4 -2
  44. package/dist/components/Input/PopupManager.js.map +1 -1
  45. package/dist/components/Input/determine-active-menu.d.ts.map +1 -1
  46. package/dist/components/Input/determine-active-menu.js +4 -1
  47. package/dist/components/Input/determine-active-menu.js.map +1 -1
  48. package/dist/components/Input/popup-props.d.ts +3 -1
  49. package/dist/components/Input/popup-props.d.ts.map +1 -1
  50. package/dist/components/Input/popup-props.js +7 -1
  51. package/dist/components/Input/popup-props.js.map +1 -1
  52. package/dist/components/InputBox.d.ts +6 -0
  53. package/dist/components/InputBox.d.ts.map +1 -1
  54. package/dist/components/InputBox.js +74 -11
  55. package/dist/components/InputBox.js.map +1 -1
  56. package/dist/components/InputBox.menu-logic.test.js +16 -1
  57. package/dist/components/InputBox.menu-logic.test.js.map +1 -1
  58. package/dist/components/InputBox.test.js +1 -0
  59. package/dist/components/InputBox.test.js.map +1 -1
  60. package/dist/components/MarkdownRenderer.d.ts.map +1 -1
  61. package/dist/components/MarkdownRenderer.js +65 -7
  62. package/dist/components/MarkdownRenderer.js.map +1 -1
  63. package/dist/components/MessageList.d.ts.map +1 -1
  64. package/dist/components/MessageList.js +62 -9
  65. package/dist/components/MessageList.js.map +1 -1
  66. package/dist/components/MessageList.test.js +128 -0
  67. package/dist/components/MessageList.test.js.map +1 -1
  68. package/dist/components/SettingsValueSelectionMenu.d.ts.map +1 -1
  69. package/dist/components/SettingsValueSelectionMenu.js +4 -1
  70. package/dist/components/SettingsValueSelectionMenu.js.map +1 -1
  71. package/dist/components/StatusBar.d.ts.map +1 -1
  72. package/dist/components/StatusBar.js +8 -6
  73. package/dist/components/StatusBar.js.map +1 -1
  74. package/dist/components/StatusBar.test.js +24 -0
  75. package/dist/components/StatusBar.test.js.map +1 -1
  76. package/dist/components/SubagentActivityMessage.d.ts +12 -0
  77. package/dist/components/SubagentActivityMessage.d.ts.map +1 -0
  78. package/dist/components/SubagentActivityMessage.js +30 -0
  79. package/dist/components/SubagentActivityMessage.js.map +1 -0
  80. package/dist/components/UndoSelectionMenu.d.ts +12 -0
  81. package/dist/components/UndoSelectionMenu.d.ts.map +1 -0
  82. package/dist/components/UndoSelectionMenu.js +46 -0
  83. package/dist/components/UndoSelectionMenu.js.map +1 -0
  84. package/dist/components/UndoSelectionMenu.test.d.ts +2 -0
  85. package/dist/components/UndoSelectionMenu.test.d.ts.map +1 -0
  86. package/dist/components/UndoSelectionMenu.test.js +40 -0
  87. package/dist/components/UndoSelectionMenu.test.js.map +1 -0
  88. package/dist/components/theme.d.ts +8 -0
  89. package/dist/components/theme.d.ts.map +1 -0
  90. package/dist/components/theme.js +20 -0
  91. package/dist/components/theme.js.map +1 -0
  92. package/dist/context/InputContext.d.ts +1 -1
  93. package/dist/context/InputContext.d.ts.map +1 -1
  94. package/dist/context/InputContext.js.map +1 -1
  95. package/dist/hooks/use-app-commands.d.ts +20 -2
  96. package/dist/hooks/use-app-commands.d.ts.map +1 -1
  97. package/dist/hooks/use-app-commands.js +138 -52
  98. package/dist/hooks/use-app-commands.js.map +1 -1
  99. package/dist/hooks/use-app-commands.test.js +264 -1
  100. package/dist/hooks/use-app-commands.test.js.map +1 -1
  101. package/dist/hooks/use-conversation.d.ts +20 -2
  102. package/dist/hooks/use-conversation.d.ts.map +1 -1
  103. package/dist/hooks/use-conversation.js +114 -14
  104. package/dist/hooks/use-conversation.js.map +1 -1
  105. package/dist/hooks/use-conversation.resume.test.d.ts +2 -0
  106. package/dist/hooks/use-conversation.resume.test.d.ts.map +1 -0
  107. package/dist/hooks/use-conversation.resume.test.js +26 -0
  108. package/dist/hooks/use-conversation.resume.test.js.map +1 -0
  109. package/dist/hooks/use-escape-key.d.ts.map +1 -1
  110. package/dist/hooks/use-escape-key.js +14 -7
  111. package/dist/hooks/use-escape-key.js.map +1 -1
  112. package/dist/hooks/use-escape-key.test.js +168 -4
  113. package/dist/hooks/use-escape-key.test.js.map +1 -1
  114. package/dist/hooks/use-input-history.d.ts +4 -3
  115. package/dist/hooks/use-input-history.d.ts.map +1 -1
  116. package/dist/hooks/use-input-history.js +10 -8
  117. package/dist/hooks/use-input-history.js.map +1 -1
  118. package/dist/hooks/use-input-history.test.d.ts +2 -0
  119. package/dist/hooks/use-input-history.test.d.ts.map +1 -0
  120. package/dist/hooks/use-input-history.test.js +43 -0
  121. package/dist/hooks/use-input-history.test.js.map +1 -0
  122. package/dist/hooks/use-mode-handlers.d.ts +10 -2
  123. package/dist/hooks/use-mode-handlers.d.ts.map +1 -1
  124. package/dist/hooks/use-mode-handlers.js +21 -2
  125. package/dist/hooks/use-mode-handlers.js.map +1 -1
  126. package/dist/hooks/use-model-selection.d.ts +3 -0
  127. package/dist/hooks/use-model-selection.d.ts.map +1 -1
  128. package/dist/hooks/use-model-selection.js +3 -0
  129. package/dist/hooks/use-model-selection.js.map +1 -1
  130. package/dist/hooks/use-path-completion.test.d.ts +2 -0
  131. package/dist/hooks/use-path-completion.test.d.ts.map +1 -0
  132. package/dist/hooks/use-path-completion.test.js +54 -0
  133. package/dist/hooks/use-path-completion.test.js.map +1 -0
  134. package/dist/hooks/use-runtime-settings.d.ts.map +1 -1
  135. package/dist/hooks/use-runtime-settings.js +15 -6
  136. package/dist/hooks/use-runtime-settings.js.map +1 -1
  137. package/dist/hooks/use-settings-completion.d.ts.map +1 -1
  138. package/dist/hooks/use-settings-completion.js +0 -1
  139. package/dist/hooks/use-settings-completion.js.map +1 -1
  140. package/dist/hooks/use-settings-value-completion.d.ts +1 -0
  141. package/dist/hooks/use-settings-value-completion.d.ts.map +1 -1
  142. package/dist/hooks/use-settings-value-completion.js +25 -19
  143. package/dist/hooks/use-settings-value-completion.js.map +1 -1
  144. package/dist/hooks/use-shell-mode.d.ts.map +1 -1
  145. package/dist/hooks/use-shell-mode.js +2 -1
  146. package/dist/hooks/use-shell-mode.js.map +1 -1
  147. package/dist/hooks/use-slash-commands.d.ts +1 -0
  148. package/dist/hooks/use-slash-commands.d.ts.map +1 -1
  149. package/dist/hooks/use-slash-commands.js +10 -1
  150. package/dist/hooks/use-slash-commands.js.map +1 -1
  151. package/dist/hooks/use-undo-selection.d.ts +19 -0
  152. package/dist/hooks/use-undo-selection.d.ts.map +1 -0
  153. package/dist/hooks/use-undo-selection.js +76 -0
  154. package/dist/hooks/use-undo-selection.js.map +1 -0
  155. package/dist/lib/editor-impl.test.js +63 -50
  156. package/dist/lib/editor-impl.test.js.map +1 -1
  157. package/dist/lib/openai-agent-client.d.ts +9 -0
  158. package/dist/lib/openai-agent-client.d.ts.map +1 -1
  159. package/dist/lib/openai-agent-client.js +104 -134
  160. package/dist/lib/openai-agent-client.js.map +1 -1
  161. package/dist/lib/openai-agent-client.test.js +30 -0
  162. package/dist/lib/openai-agent-client.test.js.map +1 -1
  163. package/dist/non-interactive.d.ts +6 -1
  164. package/dist/non-interactive.d.ts.map +1 -1
  165. package/dist/non-interactive.js +70 -4
  166. package/dist/non-interactive.js.map +1 -1
  167. package/dist/non-interactive.test.js +176 -1
  168. package/dist/non-interactive.test.js.map +1 -1
  169. package/dist/prompts/orchestrator.md +46 -0
  170. package/dist/prompts/plan-mode-info.md +17 -0
  171. package/dist/prompts/prompt-selector.d.ts +2 -1
  172. package/dist/prompts/prompt-selector.d.ts.map +1 -1
  173. package/dist/prompts/prompt-selector.js +5 -1
  174. package/dist/prompts/prompt-selector.js.map +1 -1
  175. package/dist/prompts/prompt-selector.test.js +7 -0
  176. package/dist/prompts/prompt-selector.test.js.map +1 -1
  177. package/dist/prompts/prompt-selector.test.ts +18 -0
  178. package/dist/prompts/prompt-selector.ts +7 -1
  179. package/dist/prompts/reasoning-efficiency.d.ts +7 -0
  180. package/dist/prompts/reasoning-efficiency.d.ts.map +1 -0
  181. package/dist/prompts/reasoning-efficiency.js +19 -0
  182. package/dist/prompts/reasoning-efficiency.js.map +1 -0
  183. package/dist/prompts/reasoning-efficiency.ts +18 -0
  184. package/dist/prompts/search-via-shell.test.js +17 -17
  185. package/dist/prompts/search-via-shell.test.js.map +1 -1
  186. package/dist/prompts/search-via-shell.test.ts +17 -17
  187. package/dist/prompts/shell-auto-approval.d.ts +3 -0
  188. package/dist/prompts/shell-auto-approval.d.ts.map +1 -0
  189. package/dist/prompts/shell-auto-approval.js +23 -0
  190. package/dist/prompts/shell-auto-approval.js.map +1 -0
  191. package/dist/prompts/shell-auto-approval.ts +23 -0
  192. package/dist/prompts/subagent-delegation.d.ts +10 -0
  193. package/dist/prompts/subagent-delegation.d.ts.map +1 -0
  194. package/dist/prompts/subagent-delegation.js +33 -0
  195. package/dist/prompts/subagent-delegation.js.map +1 -0
  196. package/dist/prompts/subagent-delegation.test.d.ts +2 -0
  197. package/dist/prompts/subagent-delegation.test.d.ts.map +1 -0
  198. package/dist/prompts/subagent-delegation.test.js +17 -0
  199. package/dist/prompts/subagent-delegation.test.js.map +1 -0
  200. package/dist/prompts/subagent-delegation.test.ts +21 -0
  201. package/dist/prompts/subagent-delegation.ts +40 -0
  202. package/dist/prompts/subagents/explorer.md +41 -0
  203. package/dist/prompts/subagents/mentor.md +15 -0
  204. package/dist/prompts/subagents/researcher.md +39 -0
  205. package/dist/prompts/subagents/worker.md +40 -0
  206. package/dist/providers/ai-sdk-anthropic.provider.d.ts +1 -0
  207. package/dist/providers/ai-sdk-anthropic.provider.d.ts.map +1 -1
  208. package/dist/providers/ai-sdk-anthropic.provider.js +67 -1
  209. package/dist/providers/ai-sdk-anthropic.provider.js.map +1 -1
  210. package/dist/providers/ai-sdk-anthropic.provider.test.d.ts +2 -0
  211. package/dist/providers/ai-sdk-anthropic.provider.test.d.ts.map +1 -0
  212. package/dist/providers/ai-sdk-anthropic.provider.test.js +87 -0
  213. package/dist/providers/ai-sdk-anthropic.provider.test.js.map +1 -0
  214. package/dist/providers/common/openai-compatible-messages.d.ts +1 -1
  215. package/dist/providers/common/openai-compatible-messages.d.ts.map +1 -1
  216. package/dist/providers/common/openai-compatible-messages.js +47 -16
  217. package/dist/providers/common/openai-compatible-messages.js.map +1 -1
  218. package/dist/providers/common/openai-compatible-messages.test.js +77 -14
  219. package/dist/providers/common/openai-compatible-messages.test.js.map +1 -1
  220. package/dist/providers/custom-provider-adapter.test.js +39 -1
  221. package/dist/providers/custom-provider-adapter.test.js.map +1 -1
  222. package/dist/providers/openai-compatible.provider.d.ts +11 -1
  223. package/dist/providers/openai-compatible.provider.d.ts.map +1 -1
  224. package/dist/providers/openai-compatible.provider.js +124 -10
  225. package/dist/providers/openai-compatible.provider.js.map +1 -1
  226. package/dist/providers/openai-compatible.provider.test.js +62 -1
  227. package/dist/providers/openai-compatible.provider.test.js.map +1 -1
  228. package/dist/providers/openrouter.provider.js +1 -1
  229. package/dist/providers/openrouter.provider.js.map +1 -1
  230. package/dist/scripts/eval-auto-approval/dataset.d.ts +1 -1
  231. package/dist/services/agent-stream.d.ts +1 -0
  232. package/dist/services/agent-stream.d.ts.map +1 -1
  233. package/dist/services/approval-flow-coordinator.d.ts +2 -0
  234. package/dist/services/approval-flow-coordinator.d.ts.map +1 -1
  235. package/dist/services/approval-flow-coordinator.js +45 -18
  236. package/dist/services/approval-flow-coordinator.js.map +1 -1
  237. package/dist/services/approval-flow-coordinator.test.js +87 -0
  238. package/dist/services/approval-flow-coordinator.test.js.map +1 -1
  239. package/dist/services/approval-rejection-interceptor.test.js +5 -0
  240. package/dist/services/approval-rejection-interceptor.test.js.map +1 -1
  241. package/dist/services/approval-state.d.ts +4 -0
  242. package/dist/services/approval-state.d.ts.map +1 -1
  243. package/dist/services/approval-state.js +2 -0
  244. package/dist/services/approval-state.js.map +1 -1
  245. package/dist/services/approval-state.test.js +21 -0
  246. package/dist/services/approval-state.test.js.map +1 -1
  247. package/dist/services/conversation-events.d.ts +25 -1
  248. package/dist/services/conversation-events.d.ts.map +1 -1
  249. package/dist/services/conversation-persistence.d.ts +52 -0
  250. package/dist/services/conversation-persistence.d.ts.map +1 -0
  251. package/dist/services/conversation-persistence.js +163 -0
  252. package/dist/services/conversation-persistence.js.map +1 -0
  253. package/dist/services/conversation-persistence.test.d.ts +2 -0
  254. package/dist/services/conversation-persistence.test.d.ts.map +1 -0
  255. package/dist/services/conversation-persistence.test.js +418 -0
  256. package/dist/services/conversation-persistence.test.js.map +1 -0
  257. package/dist/services/conversation-result-builder.d.ts.map +1 -1
  258. package/dist/services/conversation-result-builder.js +13 -0
  259. package/dist/services/conversation-result-builder.js.map +1 -1
  260. package/dist/services/conversation-service.d.ts +22 -0
  261. package/dist/services/conversation-service.d.ts.map +1 -1
  262. package/dist/services/conversation-service.js +18 -0
  263. package/dist/services/conversation-service.js.map +1 -1
  264. package/dist/services/conversation-session.auto-approval.test.js +57 -9
  265. package/dist/services/conversation-session.auto-approval.test.js.map +1 -1
  266. package/dist/services/conversation-session.d.ts +25 -0
  267. package/dist/services/conversation-session.d.ts.map +1 -1
  268. package/dist/services/conversation-session.js +208 -40
  269. package/dist/services/conversation-session.js.map +1 -1
  270. package/dist/services/conversation-store.d.ts +42 -0
  271. package/dist/services/conversation-store.d.ts.map +1 -1
  272. package/dist/services/conversation-store.js +182 -1
  273. package/dist/services/conversation-store.js.map +1 -1
  274. package/dist/services/conversation-store.test.js +310 -1
  275. package/dist/services/conversation-store.test.js.map +1 -1
  276. package/dist/services/history-service.d.ts +6 -1
  277. package/dist/services/history-service.d.ts.map +1 -1
  278. package/dist/services/history-service.js +46 -6
  279. package/dist/services/history-service.js.map +1 -1
  280. package/dist/services/history-service.test.d.ts +2 -0
  281. package/dist/services/history-service.test.d.ts.map +1 -0
  282. package/dist/services/history-service.test.js +77 -0
  283. package/dist/services/history-service.test.js.map +1 -0
  284. package/dist/services/input-surge-guard.d.ts +37 -0
  285. package/dist/services/input-surge-guard.d.ts.map +1 -0
  286. package/dist/services/input-surge-guard.js +190 -0
  287. package/dist/services/input-surge-guard.js.map +1 -0
  288. package/dist/services/input-surge-guard.test.d.ts +2 -0
  289. package/dist/services/input-surge-guard.test.d.ts.map +1 -0
  290. package/dist/services/input-surge-guard.test.js +126 -0
  291. package/dist/services/input-surge-guard.test.js.map +1 -0
  292. package/dist/services/interruption-info.js +1 -1
  293. package/dist/services/interruption-info.js.map +1 -1
  294. package/dist/services/interruption-info.test.js +12 -0
  295. package/dist/services/interruption-info.test.js.map +1 -1
  296. package/dist/services/mode-notices.d.ts +11 -0
  297. package/dist/services/mode-notices.d.ts.map +1 -0
  298. package/dist/services/mode-notices.js +16 -0
  299. package/dist/services/mode-notices.js.map +1 -0
  300. package/dist/services/plan-mode-interceptor.d.ts +10 -0
  301. package/dist/services/plan-mode-interceptor.d.ts.map +1 -0
  302. package/dist/services/plan-mode-interceptor.js +38 -0
  303. package/dist/services/plan-mode-interceptor.js.map +1 -0
  304. package/dist/services/plan-mode-interceptor.test.d.ts +2 -0
  305. package/dist/services/plan-mode-interceptor.test.d.ts.map +1 -0
  306. package/dist/services/plan-mode-interceptor.test.js +55 -0
  307. package/dist/services/plan-mode-interceptor.test.js.map +1 -0
  308. package/dist/services/rtk-service.d.ts.map +1 -1
  309. package/dist/services/rtk-service.js +104 -9
  310. package/dist/services/rtk-service.js.map +1 -1
  311. package/dist/services/rtk-service.test.js +55 -20
  312. package/dist/services/rtk-service.test.js.map +1 -1
  313. package/dist/services/service-interfaces.d.ts +4 -1
  314. package/dist/services/service-interfaces.d.ts.map +1 -1
  315. package/dist/services/settings-persistence.js +1 -1
  316. package/dist/services/settings-schema.d.ts +107 -4
  317. package/dist/services/settings-schema.d.ts.map +1 -1
  318. package/dist/services/settings-schema.js +103 -5
  319. package/dist/services/settings-schema.js.map +1 -1
  320. package/dist/services/settings-schema.test.js +35 -1
  321. package/dist/services/settings-schema.test.js.map +1 -1
  322. package/dist/services/settings-service.d.ts +3 -1
  323. package/dist/services/settings-service.d.ts.map +1 -1
  324. package/dist/services/settings-service.js +40 -4
  325. package/dist/services/settings-service.js.map +1 -1
  326. package/dist/services/settings-sources.d.ts.map +1 -1
  327. package/dist/services/settings-sources.js +2 -1
  328. package/dist/services/settings-sources.js.map +1 -1
  329. package/dist/services/settings-sources.test.js +4 -0
  330. package/dist/services/settings-sources.test.js.map +1 -1
  331. package/dist/services/shell-auto-approval-evaluator.d.ts +2 -1
  332. package/dist/services/shell-auto-approval-evaluator.d.ts.map +1 -1
  333. package/dist/services/shell-auto-approval-evaluator.js +12 -29
  334. package/dist/services/shell-auto-approval-evaluator.js.map +1 -1
  335. package/dist/services/shell-auto-approval-evaluator.test.js +2 -4
  336. package/dist/services/shell-auto-approval-evaluator.test.js.map +1 -1
  337. package/dist/services/shell-auto-approval-resolver.test.js +1 -1
  338. package/dist/services/shell-auto-approval-resolver.test.js.map +1 -1
  339. package/dist/services/stream-event-processor.d.ts.map +1 -1
  340. package/dist/services/stream-event-processor.js +24 -8
  341. package/dist/services/stream-event-processor.js.map +1 -1
  342. package/dist/services/stream-event-processor.test.js +27 -0
  343. package/dist/services/stream-event-processor.test.js.map +1 -1
  344. package/dist/services/subagents/subagent-manager.d.ts +16 -0
  345. package/dist/services/subagents/subagent-manager.d.ts.map +1 -0
  346. package/dist/services/subagents/subagent-manager.js +670 -0
  347. package/dist/services/subagents/subagent-manager.js.map +1 -0
  348. package/dist/services/subagents/subagent-manager.test.d.ts +2 -0
  349. package/dist/services/subagents/subagent-manager.test.d.ts.map +1 -0
  350. package/dist/services/subagents/subagent-manager.test.js +880 -0
  351. package/dist/services/subagents/subagent-manager.test.js.map +1 -0
  352. package/dist/services/subagents/subagent-session.d.ts +26 -0
  353. package/dist/services/subagents/subagent-session.d.ts.map +1 -0
  354. package/dist/services/subagents/subagent-session.js +86 -0
  355. package/dist/services/subagents/subagent-session.js.map +1 -0
  356. package/dist/services/subagents/subagent-session.test.d.ts +2 -0
  357. package/dist/services/subagents/subagent-session.test.d.ts.map +1 -0
  358. package/dist/services/subagents/subagent-session.test.js +118 -0
  359. package/dist/services/subagents/subagent-session.test.js.map +1 -0
  360. package/dist/services/subagents/types.d.ts +41 -0
  361. package/dist/services/subagents/types.d.ts.map +1 -0
  362. package/dist/services/subagents/types.js +2 -0
  363. package/dist/services/subagents/types.js.map +1 -0
  364. package/dist/services/terminal-result-collector.d.ts.map +1 -1
  365. package/dist/services/terminal-result-collector.js +31 -5
  366. package/dist/services/terminal-result-collector.js.map +1 -1
  367. package/dist/services/terminal-result-collector.test.js +81 -0
  368. package/dist/services/terminal-result-collector.test.js.map +1 -1
  369. package/dist/slash-commands.d.ts +1 -0
  370. package/dist/slash-commands.d.ts.map +1 -1
  371. package/dist/slash-commands.js +8 -1
  372. package/dist/slash-commands.js.map +1 -1
  373. package/dist/slash-commands.test.d.ts +2 -0
  374. package/dist/slash-commands.test.d.ts.map +1 -0
  375. package/dist/slash-commands.test.js +22 -0
  376. package/dist/slash-commands.test.js.map +1 -0
  377. package/dist/tools/apply-patch.d.ts +2 -2
  378. package/dist/tools/apply-patch.d.ts.map +1 -1
  379. package/dist/tools/apply-patch.js +2 -6
  380. package/dist/tools/apply-patch.js.map +1 -1
  381. package/dist/tools/apply-patch.test.js +2 -13
  382. package/dist/tools/apply-patch.test.js.map +1 -1
  383. package/dist/tools/ask-mentor.d.ts +2 -2
  384. package/dist/tools/ask-mentor.d.ts.map +1 -1
  385. package/dist/tools/ask-mentor.js.map +1 -1
  386. package/dist/tools/code-context.d.ts +3 -3
  387. package/dist/tools/code-context.d.ts.map +1 -1
  388. package/dist/tools/code-context.js.map +1 -1
  389. package/dist/tools/code-context.test.js +2 -2
  390. package/dist/tools/code-context.test.js.map +1 -1
  391. package/dist/tools/command-message-formatters.d.ts +5 -0
  392. package/dist/tools/command-message-formatters.d.ts.map +1 -0
  393. package/dist/tools/command-message-formatters.js +13 -0
  394. package/dist/tools/command-message-formatters.js.map +1 -0
  395. package/dist/tools/command-message-formatters.test.d.ts +2 -0
  396. package/dist/tools/command-message-formatters.test.d.ts.map +1 -0
  397. package/dist/tools/command-message-formatters.test.js +23 -0
  398. package/dist/tools/command-message-formatters.test.js.map +1 -0
  399. package/dist/tools/create-file.d.ts +2 -2
  400. package/dist/tools/create-file.d.ts.map +1 -1
  401. package/dist/tools/create-file.js +14 -9
  402. package/dist/tools/create-file.js.map +1 -1
  403. package/dist/tools/create-file.test.js +13 -13
  404. package/dist/tools/create-file.test.js.map +1 -1
  405. package/dist/tools/edit-healing.d.ts +3 -3
  406. package/dist/tools/edit-healing.d.ts.map +1 -1
  407. package/dist/tools/edit-healing.js.map +1 -1
  408. package/dist/tools/edit-healing.test.js +0 -1
  409. package/dist/tools/edit-healing.test.js.map +1 -1
  410. package/dist/tools/file-locks.d.ts +1 -0
  411. package/dist/tools/file-locks.d.ts.map +1 -1
  412. package/dist/tools/file-locks.js +16 -0
  413. package/dist/tools/file-locks.js.map +1 -1
  414. package/dist/tools/find-files.d.ts +2 -2
  415. package/dist/tools/find-files.d.ts.map +1 -1
  416. package/dist/tools/find-files.js.map +1 -1
  417. package/dist/tools/find-files.test.js +1 -1
  418. package/dist/tools/find-files.test.js.map +1 -1
  419. package/dist/tools/grep.d.ts +3 -2
  420. package/dist/tools/grep.d.ts.map +1 -1
  421. package/dist/tools/grep.js +65 -4
  422. package/dist/tools/grep.js.map +1 -1
  423. package/dist/tools/grep.test.d.ts +2 -0
  424. package/dist/tools/grep.test.d.ts.map +1 -0
  425. package/dist/tools/grep.test.js +39 -0
  426. package/dist/tools/grep.test.js.map +1 -0
  427. package/dist/tools/read-file.d.ts +3 -2
  428. package/dist/tools/read-file.d.ts.map +1 -1
  429. package/dist/tools/read-file.js +7 -5
  430. package/dist/tools/read-file.js.map +1 -1
  431. package/dist/tools/read-file.test.js +2 -2
  432. package/dist/tools/read-file.test.js.map +1 -1
  433. package/dist/tools/run-subagent.d.ts +14 -0
  434. package/dist/tools/run-subagent.d.ts.map +1 -0
  435. package/dist/tools/run-subagent.js +200 -0
  436. package/dist/tools/run-subagent.js.map +1 -0
  437. package/dist/tools/run-subagent.test.d.ts +2 -0
  438. package/dist/tools/run-subagent.test.d.ts.map +1 -0
  439. package/dist/tools/run-subagent.test.js +206 -0
  440. package/dist/tools/run-subagent.test.js.map +1 -0
  441. package/dist/tools/search-replace.d.ts +15 -7
  442. package/dist/tools/search-replace.d.ts.map +1 -1
  443. package/dist/tools/search-replace.js +93 -119
  444. package/dist/tools/search-replace.js.map +1 -1
  445. package/dist/tools/search-replace.test.js +330 -217
  446. package/dist/tools/search-replace.test.js.map +1 -1
  447. package/dist/tools/search.d.ts.map +1 -1
  448. package/dist/tools/search.js.map +1 -1
  449. package/dist/tools/shell.d.ts +3 -2
  450. package/dist/tools/shell.d.ts.map +1 -1
  451. package/dist/tools/shell.js +14 -6
  452. package/dist/tools/shell.js.map +1 -1
  453. package/dist/tools/shell.test.js +189 -11
  454. package/dist/tools/shell.test.js.map +1 -1
  455. package/dist/tools/tool-parameter-schema.test.js +13 -4
  456. package/dist/tools/tool-parameter-schema.test.js.map +1 -1
  457. package/dist/tools/types.d.ts +3 -2
  458. package/dist/tools/types.d.ts.map +1 -1
  459. package/dist/tools/utils.d.ts.map +1 -1
  460. package/dist/tools/utils.js +11 -1
  461. package/dist/tools/utils.js.map +1 -1
  462. package/dist/tools/utils.test.js +13 -0
  463. package/dist/tools/utils.test.js.map +1 -1
  464. package/dist/tools/web-fetch.d.ts +2 -2
  465. package/dist/tools/web-fetch.d.ts.map +1 -1
  466. package/dist/tools/web-fetch.js +29 -4
  467. package/dist/tools/web-fetch.js.map +1 -1
  468. package/dist/tools/web-fetch.test.js +64 -0
  469. package/dist/tools/web-fetch.test.js.map +1 -1
  470. package/dist/tools/web-search.d.ts +2 -2
  471. package/dist/tools/web-search.d.ts.map +1 -1
  472. package/dist/tools/web-search.js.map +1 -1
  473. package/dist/utils/clipboard.d.ts +2 -2
  474. package/dist/utils/clipboard.d.ts.map +1 -1
  475. package/dist/utils/clipboard.js +58 -22
  476. package/dist/utils/clipboard.js.map +1 -1
  477. package/dist/utils/clipboard.test.js +25 -6
  478. package/dist/utils/clipboard.test.js.map +1 -1
  479. package/dist/utils/command-safety/handlers/sed-handler.d.ts.map +1 -1
  480. package/dist/utils/command-safety/handlers/sed-handler.js +10 -7
  481. package/dist/utils/command-safety/handlers/sed-handler.js.map +1 -1
  482. package/dist/utils/command-safety/index.d.ts.map +1 -1
  483. package/dist/utils/command-safety/index.js +153 -117
  484. package/dist/utils/command-safety/index.js.map +1 -1
  485. package/dist/utils/command-safety/path-analysis.d.ts.map +1 -1
  486. package/dist/utils/command-safety/path-analysis.js +6 -2
  487. package/dist/utils/command-safety/path-analysis.js.map +1 -1
  488. package/dist/utils/command-safety/utils.js +2 -2
  489. package/dist/utils/command-safety/utils.js.map +1 -1
  490. package/dist/utils/command-safety.path.test.js +15 -1
  491. package/dist/utils/command-safety.path.test.js.map +1 -1
  492. package/dist/utils/conversation-event-handler.d.ts +15 -0
  493. package/dist/utils/conversation-event-handler.d.ts.map +1 -1
  494. package/dist/utils/conversation-event-handler.js +108 -8
  495. package/dist/utils/conversation-event-handler.js.map +1 -1
  496. package/dist/utils/conversation-event-handler.test.js +207 -2
  497. package/dist/utils/conversation-event-handler.test.js.map +1 -1
  498. package/dist/utils/conversation-utils.d.ts +1 -0
  499. package/dist/utils/conversation-utils.d.ts.map +1 -1
  500. package/dist/utils/conversation-utils.js +1 -0
  501. package/dist/utils/conversation-utils.js.map +1 -1
  502. package/dist/utils/execute-shell.d.ts +2 -0
  503. package/dist/utils/execute-shell.d.ts.map +1 -1
  504. package/dist/utils/execute-shell.js +25 -1
  505. package/dist/utils/execute-shell.js.map +1 -1
  506. package/dist/utils/execute-shell.test.js +22 -0
  507. package/dist/utils/execute-shell.test.js.map +1 -1
  508. package/dist/utils/extract-command-messages.d.ts.map +1 -1
  509. package/dist/utils/extract-command-messages.js +2 -21
  510. package/dist/utils/extract-command-messages.js.map +1 -1
  511. package/dist/utils/model-settings.d.ts.map +1 -1
  512. package/dist/utils/model-settings.js +18 -0
  513. package/dist/utils/model-settings.js.map +1 -1
  514. package/dist/utils/output-trim.d.ts.map +1 -1
  515. package/dist/utils/output-trim.js +14 -6
  516. package/dist/utils/output-trim.js.map +1 -1
  517. package/dist/utils/streaming-session-factory.d.ts.map +1 -1
  518. package/dist/utils/streaming-session-factory.js +16 -1
  519. package/dist/utils/streaming-session-factory.js.map +1 -1
  520. package/dist/utils/streaming-session-factory.test.js +42 -0
  521. package/dist/utils/streaming-session-factory.test.js.map +1 -1
  522. package/dist/utils/token-usage.d.ts +13 -0
  523. package/dist/utils/token-usage.d.ts.map +1 -1
  524. package/dist/utils/token-usage.js +92 -1
  525. package/dist/utils/token-usage.js.map +1 -1
  526. package/dist/utils/token-usage.test.js +93 -1
  527. package/dist/utils/token-usage.test.js.map +1 -1
  528. package/package.json +4 -4
  529. package/readme.md +271 -44
@@ -35,68 +35,80 @@ function createTool(settingsService = createMockSettingsService(), editHealing)
35
35
  ...(editHealing ? { editHealing } : {}),
36
36
  });
37
37
  }
38
- test.serial('needsApproval auto-approves creation when search_content is empty and file is missing in edit mode', async (t) => {
38
+ test.serial('needsApproval auto-approves creation when search_content is empty and file is missing', async (t) => {
39
39
  await withTempDir(async () => {
40
- const tool = createTool(createMockSettingsService({ app: { editMode: true } }));
40
+ const tool = createTool(createMockSettingsService());
41
41
  const filePath = 'new-file.txt';
42
42
  const result = await tool.needsApproval({
43
43
  path: filePath,
44
- search_content: '',
45
- replace_content: 'initial content',
46
- replace_all: false,
44
+ replacements: [
45
+ {
46
+ search_content: '',
47
+ replace_content: 'initial content',
48
+ },
49
+ ],
47
50
  });
48
51
  t.false(result);
49
52
  });
50
53
  });
51
- test.serial('needsApproval auto-approves a unique exact match in edit mode', async (t) => {
54
+ test.serial('needsApproval auto-approves a unique exact match', async (t) => {
52
55
  await withTempDir(async (dir) => {
53
- const tool = createTool(createMockSettingsService({ app: { editMode: true } }));
56
+ const tool = createTool(createMockSettingsService());
54
57
  const filePath = 'sample.txt';
55
58
  const absPath = path.join(dir, filePath);
56
59
  await fs.writeFile(absPath, 'hello world');
57
60
  const result = await tool.needsApproval({
58
61
  path: filePath,
59
- search_content: 'hello',
60
- replace_content: 'hi',
61
- replace_all: false,
62
+ replacements: [
63
+ {
64
+ search_content: 'hello',
65
+ replace_content: 'hi',
66
+ },
67
+ ],
62
68
  });
63
69
  t.false(result);
64
70
  });
65
71
  });
66
- test.serial('needsApproval requires approval when multiple exact matches and replace_all is false', async (t) => {
72
+ test.serial('needsApproval auto-approves when multiple exact matches are found', async (t) => {
67
73
  await withTempDir(async (dir) => {
68
- const tool = createTool(createMockSettingsService({ app: { editMode: true } }));
74
+ const tool = createTool(createMockSettingsService());
69
75
  const filePath = 'sample.txt';
70
76
  const absPath = path.join(dir, filePath);
71
77
  await fs.writeFile(absPath, 'hello world hello');
72
78
  const result = await tool.needsApproval({
73
79
  path: filePath,
74
- search_content: 'hello',
75
- replace_content: 'hi',
76
- replace_all: false,
80
+ replacements: [
81
+ {
82
+ search_content: 'hello',
83
+ replace_content: 'hi',
84
+ },
85
+ ],
77
86
  });
78
- t.true(result);
87
+ t.false(result);
79
88
  });
80
89
  });
81
- test.serial('execute replaces only the first exact match when replace_all is false', async (t) => {
90
+ test.serial('execute replaces only a unique exact match', async (t) => {
82
91
  await withTempDir(async (dir) => {
83
92
  const tool = createTool();
84
93
  const filePath = 'content.txt';
85
94
  const absPath = path.join(dir, filePath);
86
- await fs.writeFile(absPath, 'target before target after');
95
+ await fs.writeFile(absPath, 'target before unique after');
87
96
  const result = await tool.execute({
88
97
  path: filePath,
89
- search_content: 'target',
90
- replace_content: 'done',
91
- replace_all: false,
98
+ replacements: [
99
+ {
100
+ search_content: 'target',
101
+ replace_content: 'done',
102
+ },
103
+ ],
92
104
  });
93
105
  const parsed = JSON.parse(result);
94
106
  t.true(parsed.output[0].success);
95
107
  const updated = await fs.readFile(absPath, 'utf8');
96
- t.is(updated, 'done before target after');
108
+ t.is(updated, 'done before unique after');
97
109
  });
98
110
  });
99
- test.serial('execute replaces all exact matches when replace_all is true', async (t) => {
111
+ test.serial('execute fails when multiple exact matches are found', async (t) => {
100
112
  await withTempDir(async (dir) => {
101
113
  const tool = createTool();
102
114
  const filePath = 'content.txt';
@@ -104,14 +116,18 @@ test.serial('execute replaces all exact matches when replace_all is true', async
104
116
  await fs.writeFile(absPath, 'foo foo foo');
105
117
  const result = await tool.execute({
106
118
  path: filePath,
107
- search_content: 'foo',
108
- replace_content: 'bar',
109
- replace_all: true,
119
+ replacements: [
120
+ {
121
+ search_content: 'foo',
122
+ replace_content: 'bar',
123
+ },
124
+ ],
110
125
  });
111
126
  const parsed = JSON.parse(result);
112
- t.true(parsed.output[0].success);
127
+ t.false(parsed.output[0].success);
128
+ t.regex(parsed.output[0].error, /Found 3 exact matches/);
113
129
  const updated = await fs.readFile(absPath, 'utf8');
114
- t.is(updated, 'bar bar bar');
130
+ t.is(updated, 'foo foo foo');
115
131
  });
116
132
  });
117
133
  test.serial('execute performs relaxed match replacement when exact match is not found', async (t) => {
@@ -122,9 +138,12 @@ test.serial('execute performs relaxed match replacement when exact match is not
122
138
  await fs.writeFile(absPath, ' line one\n\tline two\nremainder');
123
139
  const result = await tool.execute({
124
140
  path: filePath,
125
- search_content: 'line one\nline two',
126
- replace_content: 'new block\n',
127
- replace_all: false,
141
+ replacements: [
142
+ {
143
+ search_content: 'line one\nline two',
144
+ replace_content: 'new block\n',
145
+ },
146
+ ],
128
147
  });
129
148
  const parsed = JSON.parse(result);
130
149
  t.true(parsed.output[0].success);
@@ -132,25 +151,7 @@ test.serial('execute performs relaxed match replacement when exact match is not
132
151
  t.is(updated, 'new block\nremainder');
133
152
  });
134
153
  });
135
- test.serial('execute replaces first of multiple exact matches when replace_all is false', async (t) => {
136
- await withTempDir(async (dir) => {
137
- const tool = createTool();
138
- const filePath = 'content.txt';
139
- const absPath = path.join(dir, filePath);
140
- await fs.writeFile(absPath, 'foo\nbar\n---\nfoo\nbar\n');
141
- const result = await tool.execute({
142
- path: filePath,
143
- search_content: 'foo\nbar',
144
- replace_content: 'replacement',
145
- replace_all: false,
146
- });
147
- const parsed = JSON.parse(result);
148
- t.true(parsed.output[0].success);
149
- const updated = await fs.readFile(absPath, 'utf8');
150
- t.is(updated, 'replacement\n---\nfoo\nbar\n');
151
- });
152
- });
153
- test.serial('execute rejects multiple relaxed matches when replace_all is false', async (t) => {
154
+ test.serial('execute fails when multiple relaxed matches are found', async (t) => {
154
155
  await withTempDir(async (dir) => {
155
156
  const tool = createTool();
156
157
  const filePath = 'content.txt';
@@ -158,36 +159,20 @@ test.serial('execute rejects multiple relaxed matches when replace_all is false'
158
159
  await fs.writeFile(absPath, ' foo \n\tbar\n---\n foo \n\tbar\n');
159
160
  const result = await tool.execute({
160
161
  path: filePath,
161
- search_content: 'foo\nbar',
162
- replace_content: 'replacement',
163
- replace_all: false,
162
+ replacements: [
163
+ {
164
+ search_content: 'foo\nbar',
165
+ replace_content: 'replacement',
166
+ },
167
+ ],
164
168
  });
165
169
  const parsed = JSON.parse(result);
166
170
  t.false(parsed.output[0].success);
167
- t.true(parsed.output[0].error.includes('relaxed matches'));
171
+ t.true(parsed.output[0].error.includes('relaxed matches') || parsed.output[0].error.includes('Found 2 relaxed matches'));
168
172
  const unchanged = await fs.readFile(absPath, 'utf8');
169
173
  t.is(unchanged, ' foo \n\tbar\n---\n foo \n\tbar\n');
170
174
  });
171
175
  });
172
- test.serial('execute replaces all relaxed matches when replace_all is true', async (t) => {
173
- await withTempDir(async (dir) => {
174
- const tool = createTool(createMockSettingsService({ 'tools.enableEditHealing': false }));
175
- const filePath = 'content.txt';
176
- const absPath = path.join(dir, filePath);
177
- await fs.writeFile(absPath, ' foo \n\tbar\n---\n foo \n\tbar\n');
178
- const result = await tool.execute({
179
- path: filePath,
180
- search_content: 'foo\nbar',
181
- replace_content: 'replacement',
182
- replace_all: true,
183
- });
184
- const parsed = JSON.parse(result);
185
- t.true(parsed.output[0].success);
186
- t.true(parsed.output[0].message.includes('relaxed'));
187
- const updated = await fs.readFile(absPath, 'utf8');
188
- t.is(updated, 'replacement---\nreplacement');
189
- });
190
- });
191
176
  test.serial('execute creates a new file when search_content is empty and file is missing', async (t) => {
192
177
  await withTempDir(async (dir) => {
193
178
  const tool = createTool();
@@ -195,9 +180,12 @@ test.serial('execute creates a new file when search_content is empty and file is
195
180
  const absPath = path.join(dir, filePath);
196
181
  const result = await tool.execute({
197
182
  path: filePath,
198
- search_content: '',
199
- replace_content: 'new content',
200
- replace_all: false,
183
+ replacements: [
184
+ {
185
+ search_content: '',
186
+ replace_content: 'new content',
187
+ },
188
+ ],
201
189
  });
202
190
  const parsed = JSON.parse(result);
203
191
  t.true(parsed.output[0].success);
@@ -210,13 +198,16 @@ test.serial('execute preserves parallel edits to different regions of the same f
210
198
  const tool = createTool(createMockSettingsService({ 'tools.enableEditHealing': false }));
211
199
  const filePath = 'parallel.txt';
212
200
  const absPath = path.join(dir, filePath);
213
- const tokens = Array.from({ length: 20 }, (_, index) => `token_${index}`);
201
+ const tokens = Array.from({ length: 20 }, (_, index) => `token_${String(index).padStart(2, '0')}`);
214
202
  await fs.writeFile(absPath, tokens.join('\n'));
215
203
  const results = await Promise.all(tokens.map((token, index) => tool.execute({
216
204
  path: filePath,
217
- search_content: token,
218
- replace_content: `done_${index}`,
219
- replace_all: false,
205
+ replacements: [
206
+ {
207
+ search_content: token,
208
+ replace_content: `done_${index}`,
209
+ },
210
+ ],
220
211
  })));
221
212
  for (const result of results) {
222
213
  const parsed = JSON.parse(result);
@@ -233,18 +224,15 @@ test.serial('execute applies batched replacements to one file with a single resu
233
224
  const absPath = path.join(dir, filePath);
234
225
  await fs.writeFile(absPath, 'alpha\nbeta\ngamma\n');
235
226
  const result = await tool.execute({
227
+ path: filePath,
236
228
  replacements: [
237
229
  {
238
- path: filePath,
239
230
  search_content: 'alpha',
240
231
  replace_content: 'ALPHA',
241
- replace_all: false,
242
232
  },
243
233
  {
244
- path: filePath,
245
234
  search_content: 'gamma',
246
235
  replace_content: 'GAMMA',
247
- replace_all: false,
248
236
  },
249
237
  ],
250
238
  });
@@ -263,24 +251,19 @@ test.serial('execute keeps successful batched replacements when another replacem
263
251
  const originalContent = 'alpha\nbeta\ngamma\n';
264
252
  await fs.writeFile(absPath, originalContent);
265
253
  const result = await tool.execute({
254
+ path: filePath,
266
255
  replacements: [
267
256
  {
268
- path: filePath,
269
257
  search_content: 'alpha',
270
258
  replace_content: 'ALPHA',
271
- replace_all: false,
272
259
  },
273
260
  {
274
- path: filePath,
275
261
  search_content: 'missing',
276
262
  replace_content: 'MISSING',
277
- replace_all: false,
278
263
  },
279
264
  {
280
- path: filePath,
281
265
  search_content: 'gamma',
282
266
  replace_content: 'GAMMA',
283
- replace_all: false,
284
267
  },
285
268
  ],
286
269
  });
@@ -301,9 +284,12 @@ test.serial('execute reports failure when search string is not found', async (t)
301
284
  await fs.writeFile(absPath, originalContent);
302
285
  const result = await tool.execute({
303
286
  path: filePath,
304
- search_content: 'nonexistent',
305
- replace_content: 'replacement',
306
- replace_all: false,
287
+ replacements: [
288
+ {
289
+ search_content: 'nonexistent',
290
+ replace_content: 'replacement',
291
+ },
292
+ ],
307
293
  });
308
294
  const parsed = JSON.parse(result);
309
295
  t.false(parsed.output[0].success);
@@ -326,9 +312,12 @@ test.serial('execute heals search content when no match is found', async (t) =>
326
312
  }));
327
313
  const result = await tool.execute({
328
314
  path: filePath,
329
- search_content: 'const foo = 2;\n',
330
- replace_content: 'const foo = 3;\n',
331
- replace_all: false,
315
+ replacements: [
316
+ {
317
+ search_content: 'const foo = 2;\n',
318
+ replace_content: 'const foo = 3;\n',
319
+ },
320
+ ],
332
321
  });
333
322
  const parsed = JSON.parse(result);
334
323
  t.true(parsed.output[0].success);
@@ -350,9 +339,12 @@ test.serial('execute includes auto-healing failure reason when healing does not
350
339
  }));
351
340
  const result = await tool.execute({
352
341
  path: filePath,
353
- search_content: 'const foo = 2;\n',
354
- replace_content: 'const foo = 3;\n',
355
- replace_all: false,
342
+ replacements: [
343
+ {
344
+ search_content: 'const foo = 2;\n',
345
+ replace_content: 'const foo = 3;\n',
346
+ },
347
+ ],
356
348
  });
357
349
  const parsed = JSON.parse(result);
358
350
  t.false(parsed.output[0].success);
@@ -369,9 +361,12 @@ test.serial('execute treats special regex characters literally', async (t) => {
369
361
  await fs.writeFile(absPath, originalContent);
370
362
  const result = await tool.execute({
371
363
  path: filePath,
372
- search_content: '[1, 2, 3]',
373
- replace_content: '[4, 5, 6]',
374
- replace_all: false,
364
+ replacements: [
365
+ {
366
+ search_content: '[1, 2, 3]',
367
+ replace_content: '[4, 5, 6]',
368
+ },
369
+ ],
375
370
  });
376
371
  const parsed = JSON.parse(result);
377
372
  t.true(parsed.output[0].success);
@@ -387,9 +382,12 @@ test.serial('execute deletes content when replacement is empty string', async (t
387
382
  await fs.writeFile(absPath, 'before DELETE_ME after');
388
383
  const result = await tool.execute({
389
384
  path: filePath,
390
- search_content: 'DELETE_ME ',
391
- replace_content: '',
392
- replace_all: false,
385
+ replacements: [
386
+ {
387
+ search_content: 'DELETE_ME ',
388
+ replace_content: '',
389
+ },
390
+ ],
393
391
  });
394
392
  const parsed = JSON.parse(result);
395
393
  t.true(parsed.output[0].success);
@@ -405,9 +403,12 @@ test.serial('execute performs exact multi-line match without whitespace normaliz
405
403
  await fs.writeFile(absPath, 'line one\nline two\nline three');
406
404
  const result = await tool.execute({
407
405
  path: filePath,
408
- search_content: 'line one\nline two',
409
- replace_content: 'new content',
410
- replace_all: false,
406
+ replacements: [
407
+ {
408
+ search_content: 'line one\nline two',
409
+ replace_content: 'new content',
410
+ },
411
+ ],
411
412
  });
412
413
  const parsed = JSON.parse(result);
413
414
  t.true(parsed.output[0].success);
@@ -423,9 +424,12 @@ test.serial('execute handles leading/trailing whitespace differences in relaxed
423
424
  await fs.writeFile(absPath, ' foo \nbar');
424
425
  const result = await tool.execute({
425
426
  path: filePath,
426
- search_content: 'foo\nbar',
427
- replace_content: 'replaced',
428
- replace_all: false,
427
+ replacements: [
428
+ {
429
+ search_content: 'foo\nbar',
430
+ replace_content: 'replaced',
431
+ },
432
+ ],
429
433
  });
430
434
  const parsed = JSON.parse(result);
431
435
  t.true(parsed.output[0].success);
@@ -441,9 +445,12 @@ test.serial('execute performs normalized whitespace match across line breaks', a
441
445
  await fs.writeFile(absPath, 'const foo = 1;\nconst bar = 2;\nconst baz = 3;\n');
442
446
  const result = await tool.execute({
443
447
  path: filePath,
444
- search_content: 'const foo = 1; const bar = 2;',
445
- replace_content: 'const foo = 1;\nconst bar = 42;',
446
- replace_all: false,
448
+ replacements: [
449
+ {
450
+ search_content: 'const foo = 1; const bar = 2;',
451
+ replace_content: 'const foo = 1;\nconst bar = 42;',
452
+ },
453
+ ],
447
454
  });
448
455
  const parsed = JSON.parse(result);
449
456
  t.true(parsed.output[0].success);
@@ -451,7 +458,7 @@ test.serial('execute performs normalized whitespace match across line breaks', a
451
458
  t.is(updated, 'const foo = 1;\nconst bar = 42;\nconst baz = 3;\n');
452
459
  });
453
460
  });
454
- test.serial('execute rejects multiple normalized matches when replace_all is false', async (t) => {
461
+ test.serial('execute rejects multiple normalized matches', async (t) => {
455
462
  await withTempDir(async (dir) => {
456
463
  const tool = createTool(createMockSettingsService({ 'tools.enableEditHealing': false }));
457
464
  const filePath = 'normalized-multi.txt';
@@ -459,9 +466,12 @@ test.serial('execute rejects multiple normalized matches when replace_all is fal
459
466
  await fs.writeFile(absPath, 'const foo = 1;\n const bar = 2;\n---\nconst foo = 1;\tconst bar = 2;\n');
460
467
  const result = await tool.execute({
461
468
  path: filePath,
462
- search_content: 'const foo = 1; const bar = 2;',
463
- replace_content: 'const foo = 9; const bar = 9;',
464
- replace_all: false,
469
+ replacements: [
470
+ {
471
+ search_content: 'const foo = 1; const bar = 2;',
472
+ replace_content: 'const foo = 9; const bar = 9;',
473
+ },
474
+ ],
465
475
  });
466
476
  const parsed = JSON.parse(result);
467
477
  t.false(parsed.output[0].success);
@@ -477,14 +487,14 @@ test.serial('execute does not match substrings in relaxed mode', async (t) => {
477
487
  const absPath = path.join(dir, filePath);
478
488
  const originalContent = ' formatted text ';
479
489
  await fs.writeFile(absPath, originalContent);
480
- // Search for "format" with different whitespace to trigger relaxed mode
481
- // Relaxed mode should NOT match because it compares entire trimmed lines,
482
- // and "format" (trimmed) !== "formatted text" (trimmed)
483
490
  const result = await tool.execute({
484
491
  path: filePath,
485
- search_content: ' format ',
486
- replace_content: 'replacement',
487
- replace_all: false,
492
+ replacements: [
493
+ {
494
+ search_content: ' format ',
495
+ replace_content: 'replacement',
496
+ },
497
+ ],
488
498
  });
489
499
  const parsed = JSON.parse(result);
490
500
  t.false(parsed.output[0].success);
@@ -492,22 +502,20 @@ test.serial('execute does not match substrings in relaxed mode', async (t) => {
492
502
  t.is(unchanged, originalContent);
493
503
  });
494
504
  });
495
- // ============================================================================
496
- // EOL Normalization Tests
497
- // ============================================================================
498
505
  test.serial('execute normalizes CRLF search content to match LF file', async (t) => {
499
506
  await withTempDir(async (dir) => {
500
507
  const tool = createTool();
501
508
  const filePath = 'lf-file.txt';
502
509
  const absPath = path.join(dir, filePath);
503
- // File has LF line endings
504
510
  await fs.writeFile(absPath, 'line one\nline two\nline three');
505
- // Search content has CRLF line endings
506
511
  const result = await tool.execute({
507
512
  path: filePath,
508
- search_content: 'line one\r\nline two',
509
- replace_content: 'replaced',
510
- replace_all: false,
513
+ replacements: [
514
+ {
515
+ search_content: 'line one\r\nline two',
516
+ replace_content: 'replaced',
517
+ },
518
+ ],
511
519
  });
512
520
  const parsed = JSON.parse(result);
513
521
  t.true(parsed.output[0].success);
@@ -520,19 +528,19 @@ test.serial('execute normalizes LF search content to match CRLF file', async (t)
520
528
  const tool = createTool();
521
529
  const filePath = 'crlf-file.txt';
522
530
  const absPath = path.join(dir, filePath);
523
- // File has CRLF line endings
524
531
  await fs.writeFile(absPath, 'line one\r\nline two\r\nline three');
525
- // Search content has LF line endings
526
532
  const result = await tool.execute({
527
533
  path: filePath,
528
- search_content: 'line one\nline two',
529
- replace_content: 'replaced',
530
- replace_all: false,
534
+ replacements: [
535
+ {
536
+ search_content: 'line one\nline two',
537
+ replace_content: 'replaced',
538
+ },
539
+ ],
531
540
  });
532
541
  const parsed = JSON.parse(result);
533
542
  t.true(parsed.output[0].success);
534
543
  const updated = await fs.readFile(absPath, 'utf8');
535
- // Result should preserve the file's CRLF style
536
544
  t.is(updated, 'replaced\r\nline three');
537
545
  });
538
546
  });
@@ -541,36 +549,36 @@ test.serial('execute preserves CRLF in replacement content when file uses CRLF',
541
549
  const tool = createTool();
542
550
  const filePath = 'crlf-file.txt';
543
551
  const absPath = path.join(dir, filePath);
544
- // File has CRLF line endings
545
552
  await fs.writeFile(absPath, 'hello\r\nworld');
546
553
  const result = await tool.execute({
547
554
  path: filePath,
548
- search_content: 'hello',
549
- replace_content: 'new\nline', // LF in replacement
550
- replace_all: false,
555
+ replacements: [
556
+ {
557
+ search_content: 'hello',
558
+ replace_content: 'new\nline',
559
+ },
560
+ ],
551
561
  });
552
562
  const parsed = JSON.parse(result);
553
563
  t.true(parsed.output[0].success);
554
564
  const updated = await fs.readFile(absPath, 'utf8');
555
- // Replacement should use file's CRLF style
556
565
  t.is(updated, 'new\r\nline\r\nworld');
557
566
  });
558
567
  });
559
- // ============================================================================
560
- // Leading Filepath Comment Stripping Tests
561
- // ============================================================================
562
568
  test.serial('execute strips leading filepath comment from search content', async (t) => {
563
569
  await withTempDir(async (dir) => {
564
570
  const tool = createTool();
565
571
  const filePath = 'sample.ts';
566
572
  const absPath = path.join(dir, filePath);
567
573
  await fs.writeFile(absPath, 'const x = 1;\nconst y = 2;');
568
- // Search content has a leading filepath comment (common model behavior)
569
574
  const result = await tool.execute({
570
575
  path: filePath,
571
- search_content: '// sample.ts\nconst x = 1;',
572
- replace_content: 'const x = 42;',
573
- replace_all: false,
576
+ replacements: [
577
+ {
578
+ search_content: '// sample.ts\nconst x = 1;',
579
+ replace_content: 'const x = 42;',
580
+ },
581
+ ],
574
582
  });
575
583
  const parsed = JSON.parse(result);
576
584
  t.true(parsed.output[0].success);
@@ -586,9 +594,12 @@ test.serial('execute strips leading hash filepath comment from search content',
586
594
  await fs.writeFile(absPath, 'x = 1\ny = 2');
587
595
  const result = await tool.execute({
588
596
  path: filePath,
589
- search_content: '# script.py\nx = 1',
590
- replace_content: 'x = 42',
591
- replace_all: false,
597
+ replacements: [
598
+ {
599
+ search_content: '# script.py\nx = 1',
600
+ replace_content: 'x = 42',
601
+ },
602
+ ],
592
603
  });
593
604
  const parsed = JSON.parse(result);
594
605
  t.true(parsed.output[0].success);
@@ -602,12 +613,14 @@ test.serial('execute does not strip non-filepath leading comments', async (t) =>
602
613
  const filePath = 'code.ts';
603
614
  const absPath = path.join(dir, filePath);
604
615
  await fs.writeFile(absPath, '// TODO: fix this\nconst x = 1;');
605
- // This comment is not a filepath, so it should NOT be stripped
606
616
  const result = await tool.execute({
607
617
  path: filePath,
608
- search_content: '// TODO: fix this\nconst x = 1;',
609
- replace_content: 'const x = 42;',
610
- replace_all: false,
618
+ replacements: [
619
+ {
620
+ search_content: '// TODO: fix this\nconst x = 1;',
621
+ replace_content: 'const x = 42;',
622
+ },
623
+ ],
611
624
  });
612
625
  const parsed = JSON.parse(result);
613
626
  t.true(parsed.output[0].success);
@@ -615,9 +628,6 @@ test.serial('execute does not strip non-filepath leading comments', async (t) =>
615
628
  t.is(updated, 'const x = 42;');
616
629
  });
617
630
  });
618
- // ============================================================================
619
- // Summarization Marker Detection Tests
620
- // ============================================================================
621
631
  test.serial('execute rejects search content with "Lines X-Y omitted" marker', async (t) => {
622
632
  await withTempDir(async (dir) => {
623
633
  const tool = createTool();
@@ -626,9 +636,12 @@ test.serial('execute rejects search content with "Lines X-Y omitted" marker', as
626
636
  await fs.writeFile(absPath, 'line 1\nline 2\nline 3');
627
637
  const result = await tool.execute({
628
638
  path: filePath,
629
- search_content: 'line 1\nLines 2-50 omitted\nline 3',
630
- replace_content: 'replaced',
631
- replace_all: false,
639
+ replacements: [
640
+ {
641
+ search_content: 'line 1\nLines 2-50 omitted\nline 3',
642
+ replace_content: 'replaced',
643
+ },
644
+ ],
632
645
  });
633
646
  const parsed = JSON.parse(result);
634
647
  t.false(parsed.output[0].success);
@@ -643,9 +656,12 @@ test.serial('execute rejects search content with ellipsis marker {…}', async (
643
656
  await fs.writeFile(absPath, 'function foo() { /* code */ }');
644
657
  const result = await tool.execute({
645
658
  path: filePath,
646
- search_content: 'function foo() {…}',
647
- replace_content: 'replaced',
648
- replace_all: false,
659
+ replacements: [
660
+ {
661
+ search_content: 'function foo() {…}',
662
+ replace_content: 'replaced',
663
+ },
664
+ ],
649
665
  });
650
666
  const parsed = JSON.parse(result);
651
667
  t.false(parsed.output[0].success);
@@ -660,9 +676,12 @@ test.serial('execute rejects search content with /*...*/ marker', async (t) => {
660
676
  await fs.writeFile(absPath, 'function foo() { return 1; }');
661
677
  const result = await tool.execute({
662
678
  path: filePath,
663
- search_content: 'function foo() { /*...*/ }',
664
- replace_content: 'replaced',
665
- replace_all: false,
679
+ replacements: [
680
+ {
681
+ search_content: 'function foo() { /*...*/ }',
682
+ replace_content: 'replaced',
683
+ },
684
+ ],
666
685
  });
667
686
  const parsed = JSON.parse(result);
668
687
  t.false(parsed.output[0].success);
@@ -677,18 +696,56 @@ test.serial('execute rejects search content with // ... marker', async (t) => {
677
696
  await fs.writeFile(absPath, 'const a = 1;\nconst b = 2;\nconst c = 3;');
678
697
  const result = await tool.execute({
679
698
  path: filePath,
680
- search_content: 'const a = 1;\n// ...\nconst c = 3;',
681
- replace_content: 'replaced',
682
- replace_all: false,
699
+ replacements: [
700
+ {
701
+ search_content: 'const a = 1;\n// ...\nconst c = 3;',
702
+ replace_content: 'replaced',
703
+ },
704
+ ],
683
705
  });
684
706
  const parsed = JSON.parse(result);
685
707
  t.false(parsed.output[0].success);
686
708
  t.true(parsed.output[0].error.includes('// ...'));
687
709
  });
688
710
  });
689
- // ============================================================================
690
- // Gap Matching Tests (<...> marker)
691
- // ============================================================================
711
+ test.serial('execute rejects search content when search and replace content are identical', async (t) => {
712
+ await withTempDir(async (dir) => {
713
+ const tool = createTool();
714
+ const filePath = 'content.txt';
715
+ const absPath = path.join(dir, filePath);
716
+ await fs.writeFile(absPath, 'hello world');
717
+ const result = await tool.execute({
718
+ path: filePath,
719
+ replacements: [
720
+ {
721
+ search_content: 'hello',
722
+ replace_content: 'hello',
723
+ },
724
+ ],
725
+ });
726
+ const parsed = JSON.parse(result);
727
+ t.false(parsed.output[0].success);
728
+ t.regex(parsed.output[0].error, /identical/i);
729
+ });
730
+ });
731
+ test.serial('needsApproval auto-approves when search and replace content are identical', async (t) => {
732
+ await withTempDir(async (dir) => {
733
+ const tool = createTool();
734
+ const filePath = 'content.txt';
735
+ const absPath = path.join(dir, filePath);
736
+ await fs.writeFile(absPath, 'hello world');
737
+ const result = await tool.needsApproval({
738
+ path: filePath,
739
+ replacements: [
740
+ {
741
+ search_content: 'hello',
742
+ replace_content: 'hello',
743
+ },
744
+ ],
745
+ });
746
+ t.false(result);
747
+ });
748
+ });
692
749
  test.serial('execute performs gap match: head <...> tail replaces entire region', async (t) => {
693
750
  await withTempDir(async (dir) => {
694
751
  const tool = createTool(createMockSettingsService({ 'tools.enableEditHealing': false }));
@@ -697,9 +754,12 @@ test.serial('execute performs gap match: head <...> tail replaces entire region'
697
754
  await fs.writeFile(absPath, 'function foo() {\n const x = 1;\n const y = 2;\n const z = 3;\n return x + y + z;\n}\n');
698
755
  const result = await tool.execute({
699
756
  path: filePath,
700
- search_content: 'function foo() {\n const x = 1;\n<...>\n return x + y + z;\n}',
701
- replace_content: 'function foo() {\n return 6;\n}\n',
702
- replace_all: false,
757
+ replacements: [
758
+ {
759
+ search_content: 'function foo() {\n const x = 1;\n<...>\n return x + y + z;\n}',
760
+ replace_content: 'function foo() {\n return 6;\n}\n',
761
+ },
762
+ ],
703
763
  });
704
764
  const parsed = JSON.parse(result);
705
765
  t.true(parsed.output[0].success);
@@ -716,9 +776,12 @@ test.serial('execute performs gap match with single-line head and tail', async (
716
776
  await fs.writeFile(absPath, 'start\nmiddle1\nmiddle2\nend\nafter');
717
777
  const result = await tool.execute({
718
778
  path: filePath,
719
- search_content: 'start\n<...>\nend',
720
- replace_content: 'replaced\n',
721
- replace_all: false,
779
+ replacements: [
780
+ {
781
+ search_content: 'start\n<...>\nend',
782
+ replace_content: 'replaced\n',
783
+ },
784
+ ],
722
785
  });
723
786
  const parsed = JSON.parse(result);
724
787
  t.true(parsed.output[0].success);
@@ -734,9 +797,12 @@ test.serial('execute performs gap match with adjacent head and tail (no actual g
734
797
  await fs.writeFile(absPath, 'line1\nline2\nline3');
735
798
  const result = await tool.execute({
736
799
  path: filePath,
737
- search_content: 'line1\n<...>\nline2',
738
- replace_content: 'replaced\n',
739
- replace_all: false,
800
+ replacements: [
801
+ {
802
+ search_content: 'line1\n<...>\nline2',
803
+ replace_content: 'replaced\n',
804
+ },
805
+ ],
740
806
  });
741
807
  const parsed = JSON.parse(result);
742
808
  t.true(parsed.output[0].success);
@@ -752,9 +818,12 @@ test.serial('execute performs gap match with multiple <...> markers', async (t)
752
818
  await fs.writeFile(absPath, 'function a() {\n body1;\n}\nfunction b() {\n body2;\n}\nfunction c() {\n body3;\n}\n');
753
819
  const result = await tool.execute({
754
820
  path: filePath,
755
- search_content: 'function a() {\n<...>\nfunction b() {\n<...>\nfunction c() {\n body3;\n}',
756
- replace_content: 'function abc() {\n combined;\n}\n',
757
- replace_all: false,
821
+ replacements: [
822
+ {
823
+ search_content: 'function a() {\n<...>\nfunction b() {\n<...>\nfunction c() {\n body3;\n}',
824
+ replace_content: 'function abc() {\n combined;\n}\n',
825
+ },
826
+ ],
758
827
  });
759
828
  const parsed = JSON.parse(result);
760
829
  t.true(parsed.output[0].success);
@@ -770,9 +839,12 @@ test.serial('execute gap match fails when head is not found', async (t) => {
770
839
  await fs.writeFile(absPath, 'line1\nline2\nline3');
771
840
  const result = await tool.execute({
772
841
  path: filePath,
773
- search_content: 'nonexistent\n<...>\nline3',
774
- replace_content: 'replaced',
775
- replace_all: false,
842
+ replacements: [
843
+ {
844
+ search_content: 'nonexistent\n<...>\nline3',
845
+ replace_content: 'replaced',
846
+ },
847
+ ],
776
848
  });
777
849
  const parsed = JSON.parse(result);
778
850
  t.false(parsed.output[0].success);
@@ -786,32 +858,66 @@ test.serial('execute gap match fails when tail is not found after head', async (
786
858
  await fs.writeFile(absPath, 'line1\nline2\nline3');
787
859
  const result = await tool.execute({
788
860
  path: filePath,
789
- search_content: 'line1\n<...>\nnonexistent',
790
- replace_content: 'replaced',
791
- replace_all: false,
861
+ replacements: [
862
+ {
863
+ search_content: 'line1\n<...>\nnonexistent',
864
+ replace_content: 'replaced',
865
+ },
866
+ ],
792
867
  });
793
868
  const parsed = JSON.parse(result);
794
869
  t.false(parsed.output[0].success);
795
870
  });
796
871
  });
797
- test.serial('execute gap match rejects multiple matches when replace_all is false', async (t) => {
872
+ test.serial('execute does not invoke healing for a failed gap pattern and reports a head-anchor diagnostic', async (t) => {
798
873
  await withTempDir(async (dir) => {
799
- const tool = createTool(createMockSettingsService({ 'tools.enableEditHealing': false }));
874
+ let healingCalls = 0;
875
+ const tool = createTool(createMockSettingsService({ 'tools.enableEditHealing': true }), async (params) => {
876
+ healingCalls++;
877
+ return { params, wasModified: false, confidence: 0 };
878
+ });
800
879
  const filePath = 'content.txt';
801
- const absPath = path.join(dir, filePath);
802
- await fs.writeFile(absPath, 'start\nmiddle1\nend\nstart\nmiddle2\nend\n');
880
+ await fs.writeFile(path.join(dir, filePath), 'line1\nline2\nline3');
803
881
  const result = await tool.execute({
804
882
  path: filePath,
805
- search_content: 'start\n<...>\nend',
806
- replace_content: 'replaced',
807
- replace_all: false,
883
+ replacements: [
884
+ {
885
+ search_content: 'nonexistent\n<...>\nline3',
886
+ replace_content: 'replaced',
887
+ },
888
+ ],
808
889
  });
809
890
  const parsed = JSON.parse(result);
810
891
  t.false(parsed.output[0].success);
811
- t.true(parsed.output[0].error.includes('gap'));
892
+ t.is(healingCalls, 0);
893
+ t.regex(parsed.output[0].error, /head anchor/i);
894
+ t.regex(parsed.output[0].error, /not auto-healed/i);
812
895
  });
813
896
  });
814
- test.serial('execute gap match replaces all when replace_all is true', async (t) => {
897
+ test.serial('execute gap diagnostic identifies the anchor that failed after the head matched', async (t) => {
898
+ await withTempDir(async (dir) => {
899
+ const tool = createTool(createMockSettingsService({ 'tools.enableEditHealing': true }), async (params) => ({
900
+ params,
901
+ wasModified: false,
902
+ confidence: 0,
903
+ }));
904
+ const filePath = 'content.txt';
905
+ await fs.writeFile(path.join(dir, filePath), 'line1\nline2\nline3');
906
+ const result = await tool.execute({
907
+ path: filePath,
908
+ replacements: [
909
+ {
910
+ search_content: 'line1\n<...>\nnonexistent',
911
+ replace_content: 'replaced',
912
+ },
913
+ ],
914
+ });
915
+ const parsed = JSON.parse(result);
916
+ t.false(parsed.output[0].success);
917
+ t.regex(parsed.output[0].error, /not found after/i);
918
+ });
919
+ });
920
+ test.serial('execute gap match rejects multiple matches', async (t) => {
815
921
  await withTempDir(async (dir) => {
816
922
  const tool = createTool(createMockSettingsService({ 'tools.enableEditHealing': false }));
817
923
  const filePath = 'content.txt';
@@ -819,14 +925,16 @@ test.serial('execute gap match replaces all when replace_all is true', async (t)
819
925
  await fs.writeFile(absPath, 'start\nmiddle1\nend\nstart\nmiddle2\nend\n');
820
926
  const result = await tool.execute({
821
927
  path: filePath,
822
- search_content: 'start\n<...>\nend',
823
- replace_content: 'replaced\n',
824
- replace_all: true,
928
+ replacements: [
929
+ {
930
+ search_content: 'start\n<...>\nend',
931
+ replace_content: 'replaced',
932
+ },
933
+ ],
825
934
  });
826
935
  const parsed = JSON.parse(result);
827
- t.true(parsed.output[0].success);
828
- const updated = await fs.readFile(absPath, 'utf8');
829
- t.is(updated, 'replaced\nreplaced\n');
936
+ t.false(parsed.output[0].success);
937
+ t.true(parsed.output[0].error.includes('gap'));
830
938
  });
831
939
  });
832
940
  test.serial('execute gap match works with relaxed whitespace matching in segments', async (t) => {
@@ -837,9 +945,12 @@ test.serial('execute gap match works with relaxed whitespace matching in segment
837
945
  await fs.writeFile(absPath, ' function foo() {\n const x = 1;\n const y = 2;\n return x;\n }\n');
838
946
  const result = await tool.execute({
839
947
  path: filePath,
840
- search_content: 'function foo() {\n<...>\nreturn x;\n}',
841
- replace_content: 'function foo() {\n return 42;\n}\n',
842
- replace_all: false,
948
+ replacements: [
949
+ {
950
+ search_content: 'function foo() {\n<...>\nreturn x;\n}',
951
+ replace_content: 'function foo() {\n return 42;\n}\n',
952
+ },
953
+ ],
843
954
  });
844
955
  const parsed = JSON.parse(result);
845
956
  t.true(parsed.output[0].success);
@@ -847,19 +958,21 @@ test.serial('execute gap match works with relaxed whitespace matching in segment
847
958
  t.is(updated, 'function foo() {\n return 42;\n}\n');
848
959
  });
849
960
  });
850
- test.serial('needsApproval handles gap match in edit mode', async (t) => {
961
+ test.serial('needsApproval handles gap match', async (t) => {
851
962
  await withTempDir(async (dir) => {
852
- const tool = createTool(createMockSettingsService({ app: { editMode: true } }));
963
+ const tool = createTool(createMockSettingsService());
853
964
  const filePath = 'content.txt';
854
965
  const absPath = path.join(dir, filePath);
855
966
  await fs.writeFile(absPath, 'start\nmiddle\nend\n');
856
967
  const result = await tool.needsApproval({
857
968
  path: filePath,
858
- search_content: 'start\n<...>\nend',
859
- replace_content: 'replaced',
860
- replace_all: false,
969
+ replacements: [
970
+ {
971
+ search_content: 'start\n<...>\nend',
972
+ replace_content: 'replaced',
973
+ },
974
+ ],
861
975
  });
862
- // In edit mode with a unique gap match, should auto-approve
863
976
  t.false(result);
864
977
  });
865
978
  });