onbuzz 4.9.13 → 4.10.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 (451) hide show
  1. package/node_modules/glob/README.md +31 -5
  2. package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
  3. package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
  4. package/node_modules/glob/dist/commonjs/glob.js +2 -1
  5. package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
  6. package/node_modules/glob/dist/commonjs/index.min.js +3 -3
  7. package/node_modules/glob/dist/commonjs/index.min.js.map +4 -4
  8. package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
  9. package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
  10. package/node_modules/glob/dist/commonjs/pattern.js +4 -0
  11. package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
  12. package/node_modules/glob/dist/esm/glob.d.ts +8 -0
  13. package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
  14. package/node_modules/glob/dist/esm/glob.js +2 -1
  15. package/node_modules/glob/dist/esm/glob.js.map +1 -1
  16. package/node_modules/glob/dist/esm/index.min.js +3 -3
  17. package/node_modules/glob/dist/esm/index.min.js.map +4 -4
  18. package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
  19. package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
  20. package/node_modules/glob/dist/esm/pattern.js +4 -0
  21. package/node_modules/glob/dist/esm/pattern.js.map +1 -1
  22. package/node_modules/{@isaacs → glob/node_modules}/balanced-match/README.md +7 -10
  23. package/node_modules/{@isaacs → glob/node_modules}/balanced-match/package.json +7 -18
  24. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/README.md +3 -6
  25. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.js +6 -4
  26. package/node_modules/glob/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -0
  27. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.js +6 -4
  28. package/node_modules/glob/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
  29. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/package.json +11 -7
  30. package/node_modules/glob/node_modules/minimatch/README.md +76 -1
  31. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +1 -1
  32. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -1
  33. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -1
  34. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +4 -2
  35. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -1
  36. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +309 -55
  37. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -1
  38. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -1
  39. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +2 -4
  40. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -1
  41. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +1 -1
  42. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -1
  43. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +4 -4
  44. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -1
  45. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +81 -1
  46. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -1
  47. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +232 -134
  48. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -1
  49. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +1 -1
  50. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -1
  51. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +8 -8
  52. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -1
  53. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +1 -1
  54. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -1
  55. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
  56. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +4 -2
  57. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -1
  58. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +309 -55
  59. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -1
  60. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -1
  61. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +2 -4
  62. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
  63. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +1 -1
  64. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -1
  65. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +4 -4
  66. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -1
  67. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +81 -1
  68. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -1
  69. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +232 -134
  70. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -1
  71. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +1 -1
  72. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -1
  73. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +8 -8
  74. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
  75. package/node_modules/glob/node_modules/minimatch/package.json +17 -11
  76. package/node_modules/glob/package.json +10 -13
  77. package/node_modules/minipass/LICENSE.md +55 -0
  78. package/node_modules/minipass/dist/commonjs/index.d.ts +12 -16
  79. package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -1
  80. package/node_modules/minipass/dist/commonjs/index.js +13 -3
  81. package/node_modules/minipass/dist/commonjs/index.js.map +1 -1
  82. package/node_modules/minipass/dist/esm/index.d.ts +12 -16
  83. package/node_modules/minipass/dist/esm/index.d.ts.map +1 -1
  84. package/node_modules/minipass/dist/esm/index.js +3 -1
  85. package/node_modules/minipass/dist/esm/index.js.map +1 -1
  86. package/node_modules/minipass/package.json +9 -14
  87. package/node_modules/path-scurry/node_modules/lru-cache/README.md +96 -10
  88. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.d.ts.map +1 -0
  89. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.js.map +1 -0
  90. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.d.ts +5 -0
  91. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
  92. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts +1400 -0
  93. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -0
  94. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
  95. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -0
  96. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
  97. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +7 -0
  98. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts +12 -0
  99. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts.map +1 -0
  100. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
  101. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js.map +1 -0
  102. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.cjs.map +1 -0
  103. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
  104. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.d.ts +5 -0
  105. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
  106. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +109 -32
  107. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
  108. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +334 -197
  109. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
  110. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
  111. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +4 -4
  112. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.d.ts.map +1 -0
  113. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.js.map +1 -0
  114. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.d.ts +5 -0
  115. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
  116. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts +1400 -0
  117. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -0
  118. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
  119. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -0
  120. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
  121. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +7 -0
  122. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts +12 -0
  123. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts.map +1 -0
  124. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
  125. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js.map +1 -0
  126. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts +12 -0
  127. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts.map +1 -0
  128. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
  129. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js.map +1 -0
  130. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.d.ts.map +1 -0
  131. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.js.map +1 -0
  132. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.d.ts +5 -0
  133. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
  134. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts +1400 -0
  135. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -0
  136. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
  137. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -0
  138. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
  139. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js.map +7 -0
  140. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts +12 -0
  141. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts.map +1 -0
  142. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
  143. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js.map +1 -0
  144. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
  145. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.mjs.map +1 -0
  146. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.d.ts +5 -0
  147. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
  148. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +109 -32
  149. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
  150. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +333 -196
  151. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -1
  152. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +1 -1
  153. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +4 -4
  154. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.d.ts.map +1 -0
  155. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.js.map +1 -0
  156. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.d.ts +5 -0
  157. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
  158. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts +1400 -0
  159. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -0
  160. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
  161. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js.map +1 -0
  162. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
  163. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js.map +7 -0
  164. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts +12 -0
  165. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts.map +1 -0
  166. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
  167. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js.map +1 -0
  168. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts +12 -0
  169. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts.map +1 -0
  170. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
  171. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js.map +1 -0
  172. package/node_modules/path-scurry/node_modules/lru-cache/package.json +71 -18
  173. package/node_modules/path-scurry/package.json +8 -24
  174. package/package.json +1 -1
  175. package/scripts/debug-balance-probe.mjs +35 -35
  176. package/scripts/push-image.sh +43 -43
  177. package/scripts/setup-acr.sh +65 -65
  178. package/scripts/verify-optional-deps.js +96 -1
  179. package/src/__tests__/composioCliFlags.test.js +239 -239
  180. package/src/analyzers/CSSAnalyzer.js +298 -297
  181. package/src/analyzers/ConfigValidator.js +691 -690
  182. package/src/analyzers/ESLintAnalyzer.js +320 -320
  183. package/src/analyzers/JavaScriptAnalyzer.js +260 -261
  184. package/src/analyzers/PrettierFormatter.js +246 -247
  185. package/src/analyzers/PythonAnalyzer.js +283 -283
  186. package/src/analyzers/SecurityAnalyzer.js +729 -729
  187. package/src/analyzers/SparrowAnalyzer.js +341 -341
  188. package/src/analyzers/TypeScriptAnalyzer.js +247 -247
  189. package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -41
  190. package/src/analyzers/__tests__/ConfigValidator.test.js +362 -362
  191. package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -40
  192. package/src/analyzers/__tests__/PythonAnalyzer.test.js +205 -208
  193. package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -303
  194. package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -187
  195. package/src/analyzers/codeCloneDetector/analyzer.js +344 -344
  196. package/src/analyzers/codeCloneDetector/detector.js +250 -250
  197. package/src/analyzers/codeCloneDetector/index.js +194 -192
  198. package/src/analyzers/codeCloneDetector/parser.js +199 -199
  199. package/src/core/__tests__/agentPool.test.js +866 -866
  200. package/src/core/__tests__/agentPoolAutoResume.test.js +209 -209
  201. package/src/core/__tests__/agentPoolWakeOnMessage.test.js +315 -315
  202. package/src/core/__tests__/agentScheduler.emptyResponseChatStall.test.js +213 -213
  203. package/src/core/__tests__/agentScheduler.errorCategorisation.test.js +246 -246
  204. package/src/core/__tests__/agentScheduler.firstChunkTimeout.test.js +138 -138
  205. package/src/core/__tests__/agentScheduler.modeTransitions.test.js +233 -233
  206. package/src/core/__tests__/agentScheduler.nativePromptPick.test.js +319 -319
  207. package/src/core/__tests__/agentScheduler.taskLifecycleInstruction.test.js +78 -78
  208. package/src/core/__tests__/agentScheduler.visualizer.test.js +258 -258
  209. package/src/core/__tests__/flowCheckpointStore.test.js +140 -140
  210. package/src/core/__tests__/flowEndToEnd.test.js +565 -565
  211. package/src/core/__tests__/flowFieldMapping.test.js +188 -189
  212. package/src/core/__tests__/flowLintClientMirror.test.js +96 -98
  213. package/src/core/__tests__/flowSavePayload.test.js +170 -169
  214. package/src/core/__tests__/flowTemplates.test.js +311 -311
  215. package/src/core/__tests__/flowVersionStore.test.js +123 -123
  216. package/src/core/__tests__/messageProcessor.test.js +669 -669
  217. package/src/core/__tests__/stateManager.test.js +0 -1
  218. package/src/core/agentPool.js +2474 -2475
  219. package/src/core/agentScheduler.js +1 -4
  220. package/src/core/contextManager.js +708 -708
  221. package/src/core/flowExecutor.js +1510 -1510
  222. package/src/core/flowFieldMapping.js +136 -138
  223. package/src/core/messageProcessor.js +953 -954
  224. package/src/core/orchestrator.js +593 -595
  225. package/src/core/stateManager.js +1765 -1752
  226. package/src/index.js +1221 -1221
  227. package/src/interfaces/__tests__/archivedAgentDelete.test.js +207 -207
  228. package/src/interfaces/__tests__/bulkAgentRoute.test.js +361 -361
  229. package/src/interfaces/__tests__/imageServing.test.js +228 -228
  230. package/src/interfaces/__tests__/remoteSessionAuth.test.js +308 -308
  231. package/src/interfaces/__tests__/videoJobsRoutes.test.js +178 -179
  232. package/src/interfaces/__tests__/webServer.marketplace.test.js +629 -629
  233. package/src/interfaces/schedulerRoutes.js +50 -50
  234. package/src/interfaces/terminal/__tests__/smoke/connection.test.js +341 -350
  235. package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -156
  236. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +325 -330
  237. package/src/interfaces/terminal/__tests__/smoke/tools.test.js +385 -388
  238. package/src/interfaces/terminal/api/session.js +265 -266
  239. package/src/interfaces/terminal/api/websocket.js +496 -497
  240. package/src/interfaces/terminal/components/AgentCreator.js +691 -705
  241. package/src/interfaces/terminal/components/AgentEditor.js +676 -678
  242. package/src/interfaces/terminal/components/AgentSwitcher.js +331 -330
  243. package/src/interfaces/terminal/components/ErrorPanel.js +263 -264
  244. package/src/interfaces/terminal/components/Header.js +28 -28
  245. package/src/interfaces/terminal/components/Layout.js +598 -603
  246. package/src/interfaces/terminal/components/MessageList.js +280 -281
  247. package/src/interfaces/terminal/components/SettingsPanel.js +410 -415
  248. package/src/interfaces/terminal/components/StatusBar.js +2 -0
  249. package/src/interfaces/terminal/index.js +168 -168
  250. package/src/interfaces/terminal/state/useAgentControl.js +496 -496
  251. package/src/interfaces/terminal/state/useAgents.js +537 -537
  252. package/src/interfaces/terminal/state/useMessages.js +629 -630
  253. package/src/interfaces/terminal/state/useTools.js +554 -554
  254. package/src/interfaces/terminal/utils/debugLogger.js +44 -44
  255. package/src/interfaces/terminal/utils/settingsStorage.js +232 -232
  256. package/src/interfaces/webServer.js +7578 -7579
  257. package/src/interfaces/webServer.js.bak +7046 -7046
  258. package/src/modules/fileExplorer/__tests__/zipDownload.test.js +237 -237
  259. package/src/modules/fileExplorer/controller.js +470 -469
  260. package/src/modules/fileExplorer/routes.js +285 -286
  261. package/src/modules/widget/__tests__/isDisabled.test.js +41 -41
  262. package/src/modules/widget/__tests__/routes.test.js +677 -678
  263. package/src/modules/widget/__tests__/runtime.test.js +401 -401
  264. package/src/modules/widget/__tests__/versioning.test.js +309 -309
  265. package/src/modules/widget/__tests__/webComponentRuntime.test.js +565 -565
  266. package/src/modules/widget/__tests__/widgetTool.test.js +316 -316
  267. package/src/modules/widget/routes.js +435 -435
  268. package/src/modules/widget/runtime/bundle.js +640 -640
  269. package/src/modules/widget/runtime/webComponentBundle.js +470 -470
  270. package/src/modules/widget/schema.js +182 -181
  271. package/src/modules/widget/widgetTool.js +1389 -1389
  272. package/src/services/__tests__/agentActivityService.test.js +401 -402
  273. package/src/services/__tests__/benchmarkService.test.js +184 -184
  274. package/src/services/__tests__/contextInjectionService.test.js +246 -246
  275. package/src/services/__tests__/conversationQuery.test.js +721 -723
  276. package/src/services/__tests__/credentialVault.test.js +469 -469
  277. package/src/services/__tests__/discordService.integration.test.js +638 -639
  278. package/src/services/__tests__/flowContextService.test.js +590 -590
  279. package/src/services/__tests__/memoryService.test.js +1 -1
  280. package/src/services/__tests__/messageSource.test.js +380 -380
  281. package/src/services/__tests__/modelRouterNaming.test.js +111 -111
  282. package/src/services/__tests__/projectDetector.test.js +34 -34
  283. package/src/services/__tests__/promptService.test.js +242 -242
  284. package/src/services/__tests__/telegramService.test.js +941 -941
  285. package/src/services/__tests__/tokenCountingService.test.js +48 -48
  286. package/src/services/agentActivityService.js +419 -420
  287. package/src/services/aiService.js +2997 -3001
  288. package/src/services/apiKeyManager.js +359 -359
  289. package/src/services/benchmarkService.js +196 -196
  290. package/src/services/codebaseKnowledgeService.js +2 -2
  291. package/src/services/composioService.js +738 -738
  292. package/src/services/conversationCompactionService.js +1258 -1257
  293. package/src/services/credentialVault.js +685 -685
  294. package/src/services/discordService.js +792 -793
  295. package/src/services/embeddings/__tests__/azureCustomProvider.test.js +232 -232
  296. package/src/services/embeddings/__tests__/embeddingService.test.js +417 -417
  297. package/src/services/embeddings/__tests__/localProvider.test.js +263 -263
  298. package/src/services/embeddings/autoRecall.js +218 -219
  299. package/src/services/embeddings/indexers/__tests__/agentIndexer.test.js +232 -232
  300. package/src/services/embeddings/indexers/__tests__/memoryIndexer.test.js +418 -418
  301. package/src/services/embeddings/indexers/__tests__/reminisceIndexer.test.js +356 -357
  302. package/src/services/embeddings/indexers/__tests__/skillsIndexer.test.js +145 -145
  303. package/src/services/embeddings/indexers/__tests__/taskIndexer.test.js +146 -146
  304. package/src/services/embeddings/indexers/composioIndexer.js +279 -279
  305. package/src/services/embeddings/providerInterface.js +206 -206
  306. package/src/services/embeddings/providers/localProvider.js +11 -7
  307. package/src/services/embeddings/providers/openaiProvider.js +101 -101
  308. package/src/services/embeddings/vectorStore/inMemoryJsonStore.js +356 -356
  309. package/src/services/errorHandler.js +809 -809
  310. package/src/services/flowContextService.js +586 -586
  311. package/src/services/grounding/MockAdapter.js +125 -125
  312. package/src/services/modelRouterService.js +26 -31
  313. package/src/services/modelsService.js +322 -322
  314. package/src/services/ollamaService.js +452 -452
  315. package/src/services/projectDetector.js +403 -404
  316. package/src/services/promptService.js +418 -418
  317. package/src/services/qualityInspector.js +795 -795
  318. package/src/services/scheduleService.js +726 -726
  319. package/src/services/serviceRegistry.js +386 -386
  320. package/src/services/telegrafBot.js +174 -174
  321. package/src/services/telegramService.js +1972 -1972
  322. package/src/services/visualEditorBridge.js +1033 -1033
  323. package/src/services/visualEditorServer.js +1769 -1774
  324. package/src/services/whatsappService.js +667 -668
  325. package/src/tools/__tests__/agentCommunicationTool.findAgent.test.js +226 -226
  326. package/src/tools/__tests__/agentCommunicationTool.test.js +3 -3
  327. package/src/tools/__tests__/agentDelayTool.test.js +342 -342
  328. package/src/tools/__tests__/baseTool.test.js +3 -3
  329. package/src/tools/__tests__/codeMapTool.test.js +915 -915
  330. package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -309
  331. package/src/tools/__tests__/fileTreeTool.test.js +274 -274
  332. package/src/tools/__tests__/filesystemTool.test.js +815 -815
  333. package/src/tools/__tests__/foundryWebSearchTool.test.js +252 -252
  334. package/src/tools/__tests__/imageTool.validator.test.js +194 -194
  335. package/src/tools/__tests__/jobDoneTool.test.js +580 -581
  336. package/src/tools/__tests__/memoryTool.forgetStale.test.js +272 -272
  337. package/src/tools/__tests__/memoryTool.reminisce.test.js +2 -2
  338. package/src/tools/__tests__/memoryTool.reminisceSemanticSearch.test.js +301 -301
  339. package/src/tools/__tests__/memoryTool.semanticSearch.test.js +405 -405
  340. package/src/tools/__tests__/memoryTool.teamPool.test.js +293 -293
  341. package/src/tools/__tests__/memoryTool.test.js +1 -1
  342. package/src/tools/__tests__/seekTool.test.js +282 -282
  343. package/src/tools/__tests__/skillsTool.search.test.js +164 -164
  344. package/src/tools/__tests__/skillsTool.test.js +226 -226
  345. package/src/tools/__tests__/staticAnalysisTool.test.js +509 -509
  346. package/src/tools/__tests__/taskManagerTool.discipline.test.js +137 -137
  347. package/src/tools/__tests__/taskManagerTool.search.test.js +143 -143
  348. package/src/tools/__tests__/taskManagerTool.test.js +866 -866
  349. package/src/tools/__tests__/terminalTool.test.js +448 -448
  350. package/src/tools/__tests__/toolShapeForgiveness.test.js +259 -260
  351. package/src/tools/__tests__/userPromptTool.test.js +297 -297
  352. package/src/tools/__tests__/videoTool.jobs.test.js +147 -147
  353. package/src/tools/__tests__/webTool.e2e.test.js +609 -603
  354. package/src/tools/__tests__/webTool.unit.test.js +195 -195
  355. package/src/tools/__tests__/webTool.visionModel.test.js +75 -75
  356. package/src/tools/agentCommunicationTool.js +8 -10
  357. package/src/tools/agentDelayTool.js +496 -497
  358. package/src/tools/asyncToolManager.js +602 -603
  359. package/src/tools/baseTool.js +12 -11
  360. package/src/tools/cloneDetectionTool.js +576 -581
  361. package/src/tools/codeMapTool.js +0 -6
  362. package/src/tools/composioTool.js +617 -617
  363. package/src/tools/dependencyResolverTool.js +1211 -1212
  364. package/src/tools/desktop/DesktopTool.js +629 -638
  365. package/src/tools/desktop/__tests__/DesktopTool.e2e.test.js +306 -306
  366. package/src/tools/desktop/__tests__/DesktopTool.test.js +507 -507
  367. package/src/tools/desktop/__tests__/osController.test.js +364 -364
  368. package/src/tools/desktop/osController.js +491 -491
  369. package/src/tools/docxTool.js +623 -623
  370. package/src/tools/excelTool.js +636 -636
  371. package/src/tools/fileContentReplaceTool.js +5 -7
  372. package/src/tools/fileSystemTool.js +12 -19
  373. package/src/tools/fileTreeTool.js +840 -840
  374. package/src/tools/foundryWebSearchTool.js +273 -273
  375. package/src/tools/helpTool.js +198 -198
  376. package/src/tools/imageTool.js +1397 -1397
  377. package/src/tools/importAnalyzerTool.js +1056 -1056
  378. package/src/tools/jobDoneTool.js +495 -495
  379. package/src/tools/memoryTool.js +1 -1
  380. package/src/tools/office/pres/__tests__/presSystem.test.js +365 -365
  381. package/src/tools/office/pres/archetypes/agenda.js +61 -61
  382. package/src/tools/office/pres/archetypes/bentoGrid.js +218 -219
  383. package/src/tools/office/pres/archetypes/bigStat.js +140 -142
  384. package/src/tools/office/pres/archetypes/closing.js +70 -70
  385. package/src/tools/office/pres/archetypes/hero.js +70 -70
  386. package/src/tools/office/pres/archetypes/productHero.js +93 -94
  387. package/src/tools/office/pres/archetypes/table.js +73 -74
  388. package/src/tools/office/pres/backgrounds/orb.js +66 -66
  389. package/src/tools/office/pres/components.js +422 -423
  390. package/src/tools/officeTool.js +441 -441
  391. package/src/tools/pdfTool.js +625 -627
  392. package/src/tools/platformControlTool.js +1081 -1081
  393. package/src/tools/seekTool.js +917 -918
  394. package/src/tools/skillsTool.js +1 -1
  395. package/src/tools/staticAnalysisTool.js +2143 -2146
  396. package/src/tools/taskManagerTool.js +3324 -3324
  397. package/src/tools/terminalTool.js +2615 -2618
  398. package/src/tools/videoTool.js +1303 -1303
  399. package/src/tools/visionTool.js +508 -508
  400. package/src/tools/visualEditorTool.js +1289 -1290
  401. package/src/tools/webTool.js +3368 -3368
  402. package/src/tools/whatsappTool.js +464 -464
  403. package/src/types/__tests__/agent.test.js +499 -499
  404. package/src/types/__tests__/contextReference.test.js +606 -606
  405. package/src/types/__tests__/conversation.test.js +555 -555
  406. package/src/types/__tests__/toolCommand.test.js +584 -584
  407. package/src/types/contextReference.js +974 -971
  408. package/src/types/conversation.js +729 -729
  409. package/src/types/toolCommand.js +746 -746
  410. package/src/utilities/__tests__/attachmentValidator.test.js +80 -80
  411. package/src/utilities/__tests__/auditReport.test.js +328 -328
  412. package/src/utilities/__tests__/directoryAccessManager.test.js +388 -388
  413. package/src/utilities/__tests__/jsonRepair.test.js +103 -104
  414. package/src/utilities/__tests__/modeTransitionReasons.test.js +105 -105
  415. package/src/utilities/__tests__/platformUtils.test.js +80 -87
  416. package/src/utilities/__tests__/structuredFileValidator.test.js +261 -263
  417. package/src/utilities/__tests__/toolConstants.test.js +92 -94
  418. package/src/utilities/__tests__/useIsTouchDevice.detect.test.js +114 -114
  419. package/src/utilities/__tests__/webUiUtilSync.test.js +117 -117
  420. package/src/utilities/attachmentValidator.js +284 -288
  421. package/src/utilities/authCache.js.backup-1779570472481 +121 -121
  422. package/src/utilities/browserStealth.js +631 -630
  423. package/src/utilities/configManager.js +616 -617
  424. package/src/utilities/directoryAccessManager.js +564 -565
  425. package/src/utilities/fileProcessor.js +308 -307
  426. package/src/utilities/humanBehavior.js +454 -453
  427. package/src/utilities/logger.js +479 -479
  428. package/src/utilities/structuredFileValidator.js +696 -699
  429. package/src/utilities/tagParser.js +5 -10
  430. package/src/utilities/userDataDir.js +308 -308
  431. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +0 -1
  432. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +0 -1
  433. package/node_modules/minipass/LICENSE +0 -15
  434. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/LICENSE.md +0 -0
  435. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts +0 -0
  436. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts.map +0 -0
  437. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js +0 -0
  438. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js.map +0 -0
  439. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/package.json +0 -0
  440. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts +0 -0
  441. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts.map +0 -0
  442. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js +0 -0
  443. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js.map +0 -0
  444. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/package.json +0 -0
  445. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/LICENSE +0 -0
  446. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts +0 -0
  447. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts.map +0 -0
  448. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/package.json +0 -0
  449. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts +0 -0
  450. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts.map +0 -0
  451. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/package.json +0 -0
@@ -1,1212 +1,1211 @@
1
- /**
2
- * @file tools/dependencyResolverTool.js
3
- * @description Modern tool for resolving Node.js dependency conflicts by checking and updating to latest compatible versions
4
- */
5
-
6
- import { promises as fs } from 'fs';
7
- import path from 'path';
8
- import { exec as execCb } from 'child_process';
9
- import { promisify } from 'util';
10
- import { BaseTool } from './baseTool.js';
11
- import TagParser from '../utilities/tagParser.js';
12
-
13
- const exec = promisify(execCb);
14
-
15
- /**
16
- * Configuration constants for the dependency resolver
17
- */
18
- const RESOLVER_CONFIG = {
19
- DEFAULT_MODE: 'check',
20
- VALID_MODES: ['check', 'fix', 'auto'],
21
- NPM_COMMAND_TIMEOUT: 300000, // 5 minutes for npm commands
22
- REGISTRY_TIMEOUT: 10000, // 10 seconds for registry requests
23
- MAX_CONCURRENT_CHECKS: 5, // Max parallel registry checks
24
- BACKUP_EXTENSION: '.backup.json', // Backup file extension
25
- CREATE_BACKUPS: true, // Always create backups
26
- RETRY_ATTEMPTS: 3, // Registry request retry attempts
27
- RETRY_DELAY: 1000, // Delay between retries (ms)
28
- MAX_DEPENDENCIES: 500, // Safety limit
29
- NPM_REGISTRY_URL: 'https://registry.npmjs.org'
30
- };
31
-
32
- /**
33
- * DependencyResolverTool - Modern implementation
34
- * Resolves Node.js package dependency conflicts with improved security and reliability
35
- */
36
- export class DependencyResolverTool extends BaseTool {
37
- constructor(config = {}, logger = null) {
38
- super(config, logger);
39
-
40
- // Override tool ID to match documentation (with hyphen)
41
- this.id = 'dependency-resolver';
42
- }
43
-
44
- /**
45
- * Get tool description for agent system prompt
46
- * @returns {string} Formatted tool description
47
- */
48
- getDescription() {
49
- return `Tool: Dependency Resolver - Resolve Node.js package dependency conflicts
50
-
51
- **Purpose:** Checks npm dependencies for updates and optionally updates package.json to latest compatible versions automatically.
52
-
53
- **USAGE:**
54
- \`\`\`json
55
- {
56
- "toolId": "dependency-resolver",
57
- "parameters": {
58
- "path": "./my-project",
59
- "mode": "check",
60
- "includeDev": true
61
- }
62
- }
63
- \`\`\`
64
-
65
- **Parameters:**
66
- - **path** (string, optional): Path to project directory with package.json. Default: "."
67
- - **mode** (string, optional): Operation mode. Options:
68
- - "check" - Only check for updates (default)
69
- - "fix" - Update package.json and run npm install
70
- - "auto" - Automatically fix all conflicts
71
- - **includeDev** (boolean, optional): Include devDependencies. Default: true
72
-
73
- **What It Does:**
74
- - Checks npm registry for latest compatible versions
75
- - Respects semver ranges (^, ~, >=, etc.)
76
- - Creates automatic backups before modifications
77
- - Runs npm install after updates (in fix/auto mode)
78
- - Provides detailed update report
79
-
80
- **Examples:**
81
-
82
- 1. Check for updates:
83
- \`\`\`json
84
- {
85
- "toolId": "dependency-resolver",
86
- "parameters": { "mode": "check" }
87
- }
88
- \`\`\`
89
-
90
- 2. Fix outdated dependencies:
91
- \`\`\`json
92
- {
93
- "toolId": "dependency-resolver",
94
- "parameters": { "path": "./my-project", "mode": "fix" }
95
- }
96
- \`\`\`
97
-
98
- 3. Auto-fix with devDependencies:
99
- \`\`\`json
100
- {
101
- "toolId": "dependency-resolver",
102
- "parameters": { "mode": "auto", "includeDev": true }
103
- }
104
- \`\`\`
105
-
106
- **Notes:**
107
- - Always creates backup (.backup.json) before modifications
108
- - Network requests have timeout and retry logic
109
- - npm install runs with timeout protection (5 minutes max)
110
- - Supports complex semver ranges (||, &&, etc.)`;
111
- }
112
-
113
- /**
114
- * Parse tool parameters from raw content (XML or JSON)
115
- * @param {string|Object} content - Raw tool content or parsed object
116
- * @returns {Object} Parsed parameters
117
- */
118
- parseParameters(content) {
119
- // If already an object, validate and return
120
- if (typeof content === 'object' && content !== null) {
121
- return {
122
- path: content.path || '.',
123
- mode: content.mode || RESOLVER_CONFIG.DEFAULT_MODE,
124
- includeDev: content.includeDev !== undefined ? content.includeDev : true
125
- };
126
- }
127
-
128
- // Parse XML content
129
- if (typeof content === 'string') {
130
- // Try modern XML format first: <dependency-resolve>...</dependency-resolve>
131
- const modernPattern = /<dependency-resolve([^>]*)>([\s\S]*?)<\/dependency-resolve>/i;
132
- const modernMatch = modernPattern.exec(content);
133
-
134
- if (modernMatch) {
135
- const attributesStr = modernMatch[1];
136
- const innerContent = modernMatch[2];
137
-
138
- // Parse attributes from opening tag
139
- const pathAttr = /path=["']([^"']*)["']/i.exec(attributesStr);
140
- const modeAttr = /mode=["']([^"']*)["']/i.exec(attributesStr);
141
- const includeDevAttr = /include-dev=["']([^"']*)["']/i.exec(attributesStr);
142
-
143
- // Extract from inner content
144
- const pathPattern = /<path>(.*?)<\/path>/i;
145
- const pathMatch = pathPattern.exec(innerContent);
146
-
147
- const modePattern = /<mode>(.*?)<\/mode>/i;
148
- const modeMatch = modePattern.exec(innerContent);
149
-
150
- const includeDevPattern = /<include-dev>(.*?)<\/include-dev>/i;
151
- const includeDevMatch = includeDevPattern.exec(innerContent);
152
-
153
- // Content takes precedence over attributes
154
- const extractedPath = (pathMatch ? pathMatch[1].trim() : null) || (pathAttr ? pathAttr[1] : '.');
155
- const extractedMode = (modeMatch ? modeMatch[1].trim() : null) || (modeAttr ? modeAttr[1] : RESOLVER_CONFIG.DEFAULT_MODE);
156
- const extractedIncludeDev = (includeDevMatch ? includeDevMatch[1].trim() : null) || (includeDevAttr ? includeDevAttr[1] : 'true');
157
-
158
- return {
159
- path: extractedPath,
160
- mode: extractedMode,
161
- includeDev: this._parseBoolean(extractedIncludeDev, true)
162
- };
163
- }
164
-
165
- // Try legacy format: [resolve path="..." mode="..."]
166
- const legacyPattern = /\[resolve\s+([^\]]*)\]/i;
167
- const legacyMatch = legacyPattern.exec(content);
168
-
169
- if (legacyMatch) {
170
- const attrString = legacyMatch[1];
171
-
172
- // Parse attributes manually
173
- const pathAttr = /path=["']([^"']*)["']/i.exec(attrString);
174
- const modeAttr = /mode=["']([^"']*)["']/i.exec(attrString);
175
- const includeDevAttr = /include-dev=["']([^"']*)["']/i.exec(attrString);
176
-
177
- return {
178
- path: pathAttr ? pathAttr[1] : '.',
179
- mode: modeAttr ? modeAttr[1] : RESOLVER_CONFIG.DEFAULT_MODE,
180
- includeDev: this._parseBoolean(includeDevAttr ? includeDevAttr[1] : 'true', true)
181
- };
182
- }
183
-
184
- throw new Error('Invalid dependency-resolve format. Use <dependency-resolve> tags or JSON format.');
185
- }
186
-
187
- throw new Error('Invalid parameter format. Expected string (XML) or object (JSON).');
188
- }
189
-
190
- /**
191
- * Parse boolean from string or boolean
192
- * @param {any} value - Value to parse
193
- * @param {boolean} defaultValue - Default if undefined
194
- * @returns {boolean}
195
- * @private
196
- */
197
- _parseBoolean(value, defaultValue = false) {
198
- if (value === undefined || value === null) return defaultValue;
199
- if (typeof value === 'boolean') return value;
200
- if (typeof value === 'string') {
201
- return value.toLowerCase() === 'true' || value === '1';
202
- }
203
- return defaultValue;
204
- }
205
-
206
- /**
207
- * Validate parameters
208
- * @param {Object} params - Parameters to validate
209
- * @throws {Error} If validation fails
210
- * @private
211
- */
212
- _validateParameters(params) {
213
- if (!params || typeof params !== 'object') {
214
- throw new Error('Parameters must be an object');
215
- }
216
-
217
- if (params.path && typeof params.path !== 'string') {
218
- throw new Error('path must be a string');
219
- }
220
-
221
- if (params.mode && !RESOLVER_CONFIG.VALID_MODES.includes(params.mode)) {
222
- throw new Error(`Invalid mode: ${params.mode}. Must be one of: ${RESOLVER_CONFIG.VALID_MODES.join(', ')}`);
223
- }
224
-
225
- if (params.includeDev !== undefined && typeof params.includeDev !== 'boolean') {
226
- throw new Error('includeDev must be a boolean');
227
- }
228
- }
229
-
230
- /**
231
- * Validate and resolve file path
232
- * @param {string} targetPath - Target path from parameters
233
- * @param {Object} context - Execution context
234
- * @returns {string} Resolved absolute path
235
- * @throws {Error} If path is invalid or inaccessible
236
- * @private
237
- */
238
- _resolveAndValidatePath(targetPath, context) {
239
- const { projectDir, directoryAccess } = context;
240
-
241
- // Determine working directory
242
- let workingDirectory = projectDir || process.cwd();
243
-
244
- if (directoryAccess && directoryAccess.workingDirectory) {
245
- workingDirectory = directoryAccess.workingDirectory;
246
- }
247
-
248
- // Resolve the target path
249
- const resolvedPath = path.isAbsolute(targetPath)
250
- ? path.normalize(targetPath)
251
- : path.normalize(path.join(workingDirectory, targetPath));
252
-
253
- // Security: Check for path traversal
254
- const realWorkingDir = path.normalize(workingDirectory);
255
- if (!resolvedPath.startsWith(realWorkingDir)) {
256
- throw new Error(`Path traversal detected: ${targetPath} resolves outside working directory`);
257
- }
258
-
259
- return resolvedPath;
260
- }
261
-
262
- /**
263
- * Create backup of package.json
264
- * @param {string} pkgPath - Path to package.json
265
- * @returns {Promise<string|null>} Backup file path or null if failed
266
- * @private
267
- */
268
- async _createBackup(pkgPath) {
269
- if (!RESOLVER_CONFIG.CREATE_BACKUPS) {
270
- return null;
271
- }
272
-
273
- try {
274
- const backupPath = pkgPath + RESOLVER_CONFIG.BACKUP_EXTENSION;
275
- await fs.copyFile(pkgPath, backupPath);
276
- this.logger?.info('Created backup', { backupPath });
277
- return backupPath;
278
- } catch (error) {
279
- this.logger?.warn('Failed to create backup', { error: error.message });
280
- return null;
281
- }
282
- }
283
-
284
- /**
285
- * Fetch package info from npm registry with retries
286
- * @param {string} packageName - Package name
287
- * @returns {Promise<Object|null>} Package data or null if failed
288
- * @private
289
- */
290
- async _fetchPackageInfo(packageName) {
291
- const url = `${RESOLVER_CONFIG.NPM_REGISTRY_URL}/${encodeURIComponent(packageName)}`;
292
-
293
- for (let attempt = 1; attempt <= RESOLVER_CONFIG.RETRY_ATTEMPTS; attempt++) {
294
- try {
295
- const controller = new AbortController();
296
- const timeout = setTimeout(() => controller.abort(), RESOLVER_CONFIG.REGISTRY_TIMEOUT);
297
-
298
- const response = await fetch(url, { signal: controller.signal });
299
- clearTimeout(timeout);
300
-
301
- if (!response.ok) {
302
- if (response.status === 404) {
303
- this.logger?.warn(`Package not found: ${packageName}`);
304
- return null;
305
- }
306
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
307
- }
308
-
309
- const data = await response.json();
310
-
311
- // Validate response structure
312
- if (!data || typeof data !== 'object' || !data['dist-tags']) {
313
- throw new Error('Invalid registry response format');
314
- }
315
-
316
- return data;
317
-
318
- } catch (error) {
319
- if (attempt < RESOLVER_CONFIG.RETRY_ATTEMPTS) {
320
- this.logger?.debug(`Retry ${attempt}/${RESOLVER_CONFIG.RETRY_ATTEMPTS} for ${packageName}`);
321
- await new Promise(resolve => setTimeout(resolve, RESOLVER_CONFIG.RETRY_DELAY * attempt));
322
- } else {
323
- this.logger?.error(`Failed to fetch ${packageName}:`, error.message);
324
- return null;
325
- }
326
- }
327
- }
328
-
329
- return null;
330
- }
331
-
332
- /**
333
- * Get latest compatible version for a package
334
- * Enhanced self-contained semver logic supporting:
335
- * - Caret ranges (^) with 0.x.y and 0.0.x special cases
336
- * - Tilde ranges (~)
337
- * - Comparison operators (>, >=, <, <=)
338
- * - X-ranges (4.x, 4.*, etc.)
339
- * - Pre-release versions
340
- * - Complex range expressions (AND/OR)
341
- * - Exact versions
342
- *
343
- * @param {string} packageName - Package name
344
- * @param {string} currentRange - Current version range
345
- * @returns {Promise<string|null>} Latest version or null if no update needed
346
- * @private
347
- */
348
- async _getLatestCompatibleVersion(packageName, currentRange) {
349
- const data = await this._fetchPackageInfo(packageName);
350
-
351
- if (!data) {
352
- return null;
353
- }
354
-
355
- const latest = data['dist-tags']?.latest;
356
-
357
- if (!latest) {
358
- return null;
359
- }
360
-
361
- // Parse latest version
362
- const latestParsed = this._parseVersion(latest);
363
-
364
- if (!latestParsed) {
365
- return null; // Can't parse latest
366
- }
367
-
368
- // Check if it's a complex range (AND/OR)
369
- if (this._isComplexRange(currentRange)) {
370
- const complexRange = this._parseComplexRange(currentRange);
371
- const isUpdateAvailable = this._satisfiesComplexRange(complexRange, latestParsed);
372
-
373
- if (isUpdateAvailable) {
374
- // For complex ranges, preserve the original format
375
- return latest;
376
- }
377
- return null;
378
- }
379
-
380
- // Simple range - parse normally
381
- const rangeInfo = this._parseVersionRange(currentRange);
382
-
383
- if (!rangeInfo) {
384
- return null; // Can't parse, skip
385
- }
386
-
387
- // Check if update is available and compatible
388
- const isUpdateAvailable = this._isUpdateAvailable(rangeInfo, latestParsed);
389
-
390
- if (isUpdateAvailable) {
391
- // Preserve the original prefix
392
- return rangeInfo.prefix + latest;
393
- }
394
-
395
- return null;
396
- }
397
-
398
- /**
399
- * Parse a version string into components including pre-release and build metadata
400
- * @param {string} version - Version string (e.g., "4.17.1", "1.0.0-alpha.1", "1.0.0+build.123")
401
- * @returns {Object|null} Parsed version or null
402
- * @private
403
- */
404
- _parseVersion(version) {
405
- // Match: major.minor.patch[-prerelease][+build]
406
- // Pre-release and build are optional
407
- const pattern = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
408
- const match = pattern.exec(version);
409
-
410
- if (!match) {
411
- // Fallback: try simple X.Y.Z pattern without pre-release/build
412
- const simpleMatch = version.match(/^(\d+)\.(\d+)\.(\d+)/);
413
- if (simpleMatch) {
414
- return {
415
- major: parseInt(simpleMatch[1], 10),
416
- minor: parseInt(simpleMatch[2], 10),
417
- patch: parseInt(simpleMatch[3], 10),
418
- prerelease: null,
419
- build: null
420
- };
421
- }
422
- return null;
423
- }
424
-
425
- return {
426
- major: parseInt(match[1], 10),
427
- minor: parseInt(match[2], 10),
428
- patch: parseInt(match[3], 10),
429
- prerelease: match[4] ? match[4].split('.') : null,
430
- build: match[5] || null
431
- };
432
- }
433
-
434
- /**
435
- * Compare two versions according to semver rules
436
- * Returns: < 0 if v1 < v2, 0 if v1 === v2, > 0 if v1 > v2
437
- * Handles pre-release versions correctly:
438
- * - 1.0.0 > 1.0.0-alpha (release > pre-release)
439
- * - 1.0.0-alpha < 1.0.0-beta (lexical comparison)
440
- * - 1.0.0-1 < 1.0.0-2 (numeric comparison)
441
- * @param {Object} v1 - First version
442
- * @param {Object} v2 - Second version
443
- * @returns {number} Comparison result
444
- * @private
445
- */
446
- _compareVersions(v1, v2) {
447
- // Compare major.minor.patch first
448
- if (v1.major !== v2.major) return v1.major - v2.major;
449
- if (v1.minor !== v2.minor) return v1.minor - v2.minor;
450
- if (v1.patch !== v2.patch) return v1.patch - v2.patch;
451
-
452
- // When major.minor.patch are equal, check pre-release
453
- // According to semver spec:
454
- // 1. Release version (no prerelease) > prerelease version
455
- // 2. If both have prerelease, compare identifiers
456
-
457
- if (!v1.prerelease && !v2.prerelease) {
458
- // Both are release versions, equal
459
- return 0;
460
- }
461
-
462
- if (!v1.prerelease && v2.prerelease) {
463
- // v1 is release, v2 is pre-release → v1 > v2
464
- return 1;
465
- }
466
-
467
- if (v1.prerelease && !v2.prerelease) {
468
- // v1 is pre-release, v2 is release → v1 < v2
469
- return -1;
470
- }
471
-
472
- // Both have pre-release, compare them
473
- return this._comparePrerelease(v1.prerelease, v2.prerelease);
474
- }
475
-
476
- /**
477
- * Compare pre-release version identifiers according to semver spec
478
- * Identifiers are compared as:
479
- * 1. Numeric identifiers are compared numerically
480
- * 2. Alphanumeric identifiers are compared lexically (ASCII sort)
481
- * 3. Numeric identifiers have lower precedence than alphanumeric
482
- * 4. Larger set of identifiers has higher precedence if all preceding are equal
483
- * @param {Array<string>} pre1 - First pre-release identifiers
484
- * @param {Array<string>} pre2 - Second pre-release identifiers
485
- * @returns {number} Comparison result
486
- * @private
487
- */
488
- _comparePrerelease(pre1, pre2) {
489
- const len = Math.max(pre1.length, pre2.length);
490
-
491
- for (let i = 0; i < len; i++) {
492
- // If one pre-release has fewer identifiers, it has lower precedence
493
- if (i >= pre1.length) return -1; // pre1 is shorter, pre1 < pre2
494
- if (i >= pre2.length) return 1; // pre2 is shorter, pre1 > pre2
495
-
496
- const part1 = pre1[i];
497
- const part2 = pre2[i];
498
-
499
- // Check if parts are numeric
500
- const num1 = /^\d+$/.test(part1) ? parseInt(part1, 10) : null;
501
- const num2 = /^\d+$/.test(part2) ? parseInt(part2, 10) : null;
502
-
503
- // Both numeric: compare numerically
504
- if (num1 !== null && num2 !== null) {
505
- if (num1 !== num2) return num1 - num2;
506
- continue;
507
- }
508
-
509
- // One numeric, one alphanumeric: numeric has lower precedence
510
- if (num1 !== null && num2 === null) return -1;
511
- if (num1 === null && num2 !== null) return 1;
512
-
513
- // Both alphanumeric: compare lexically
514
- if (part1 < part2) return -1;
515
- if (part1 > part2) return 1;
516
- }
517
-
518
- // All identifiers equal
519
- return 0;
520
- }
521
-
522
- /**
523
- * Parse version range into components
524
- * @param {string} range - Version range (e.g., "^4.17.1", "~4.17.1", "<5.0.0", "4.x", "4.*")
525
- * @returns {Object|null} Parsed range or null
526
- * @private
527
- */
528
- _parseVersionRange(range) {
529
- const trimmed = range.trim();
530
-
531
- // Check for X-ranges first: 4.x, 4.*, 4.17.x, 4.17.*, or just 4
532
- // X-ranges use 'x', '*', or missing parts as wildcards
533
- const xRangePattern = /^(\d+|\*|x)(\.(\d+|\*|x))?(\.(\d+|\*|x))?$/i;
534
- const xMatch = xRangePattern.exec(trimmed);
535
-
536
- if (xMatch) {
537
- const majorStr = xMatch[1];
538
- const minorStr = xMatch[3];
539
- const patchStr = xMatch[5];
540
-
541
- // Check if any part is wildcard or missing (making it an X-range)
542
- const isXRange =
543
- majorStr === '*' || majorStr.toLowerCase() === 'x' ||
544
- minorStr === undefined || minorStr === '*' || minorStr.toLowerCase() === 'x' ||
545
- patchStr === undefined || patchStr === '*' || patchStr.toLowerCase() === 'x';
546
-
547
- if (isXRange) {
548
- return {
549
- prefix: '',
550
- operator: 'x-range',
551
- major: (majorStr === '*' || majorStr.toLowerCase() === 'x') ? null : parseInt(majorStr, 10),
552
- minor: (!minorStr || minorStr === '*' || minorStr.toLowerCase() === 'x') ? null : parseInt(minorStr, 10),
553
- patch: (!patchStr || patchStr === '*' || patchStr.toLowerCase() === 'x') ? null : parseInt(patchStr, 10),
554
- original: range
555
- };
556
- }
557
- }
558
-
559
- // Extract prefix and version for other operators
560
- let prefix = '';
561
- let version = trimmed;
562
- let operator = '=';
563
-
564
- if (trimmed.startsWith('^')) {
565
- prefix = '^';
566
- version = trimmed.slice(1);
567
- operator = '^';
568
- } else if (trimmed.startsWith('~')) {
569
- prefix = '~';
570
- version = trimmed.slice(1);
571
- operator = '~';
572
- } else if (trimmed.startsWith('>=')) {
573
- prefix = '>=';
574
- version = trimmed.slice(2).trim();
575
- operator = '>=';
576
- } else if (trimmed.startsWith('>')) {
577
- prefix = '>';
578
- version = trimmed.slice(1).trim();
579
- operator = '>';
580
- } else if (trimmed.startsWith('<=')) {
581
- prefix = '<=';
582
- version = trimmed.slice(2).trim();
583
- operator = '<=';
584
- } else if (trimmed.startsWith('<')) {
585
- prefix = '<';
586
- version = trimmed.slice(1).trim();
587
- operator = '<';
588
- }
589
-
590
- // Parse version X.Y.Z (including pre-release and build)
591
- const parsed = this._parseVersion(version);
592
-
593
- if (!parsed) {
594
- return null;
595
- }
596
-
597
- return {
598
- prefix,
599
- operator,
600
- major: parsed.major,
601
- minor: parsed.minor,
602
- patch: parsed.patch,
603
- prerelease: parsed.prerelease,
604
- build: parsed.build,
605
- original: range
606
- };
607
- }
608
-
609
- /**
610
- * Check if update is available and compatible with range
611
- * @param {Object} rangeInfo - Parsed range information
612
- * @param {Object} latest - Parsed latest version
613
- * @returns {boolean} True if update available
614
- * @private
615
- */
616
- _isUpdateAvailable(rangeInfo, latest) {
617
- const current = {
618
- major: rangeInfo.major,
619
- minor: rangeInfo.minor,
620
- patch: rangeInfo.patch,
621
- prerelease: rangeInfo.prerelease || null,
622
- build: rangeInfo.build || null
623
- };
624
-
625
- // Check based on operator
626
- switch (rangeInfo.operator) {
627
- case '^':
628
- return this._isCaretUpdateAvailable(current, latest);
629
-
630
- case '~':
631
- return this._isTildeUpdateAvailable(current, latest);
632
-
633
- case '>=':
634
- case '>':
635
- return this._isSimpleUpdateAvailable(current, latest);
636
-
637
- case '<':
638
- case '<=':
639
- return this._isLessThanUpdateAvailable(rangeInfo, latest);
640
-
641
- case 'x-range':
642
- return this._isXRangeUpdateAvailable(rangeInfo, latest);
643
-
644
- case '=':
645
- default:
646
- return this._isExactUpdateAvailable(current, latest);
647
- }
648
- }
649
-
650
- /**
651
- * Check if update is available for caret range (^)
652
- * Handles special cases:
653
- * - ^0.0.X → Only patch updates in 0.0.*
654
- * - ^0.X.Y → Only patch updates in 0.X.*
655
- * - ^X.Y.Z Minor and patch updates in X.*.*
656
- * Also handles pre-release versions correctly
657
- * @private
658
- */
659
- _isCaretUpdateAvailable(current, latest) {
660
- // First check if latest is actually greater than current
661
- const cmp = this._compareVersions(latest, current);
662
- if (cmp <= 0) {
663
- // latest is not greater than current
664
- return false;
665
- }
666
-
667
- // ^0.0.X Only patch updates in 0.0.*
668
- if (current.major === 0 && current.minor === 0) {
669
- return latest.major === 0 && latest.minor === 0;
670
- }
671
-
672
- // ^0.X.Y Only patch updates in 0.X.*
673
- if (current.major === 0) {
674
- return latest.major === 0 && latest.minor === current.minor;
675
- }
676
-
677
- // ^X.Y.Z Any minor/patch update in X.*.*
678
- return latest.major === current.major;
679
- }
680
-
681
- /**
682
- * Check if update is available for tilde range (~)
683
- * ~X.Y.Z Only patch updates in X.Y.*
684
- * Also handles pre-release versions correctly
685
- * @private
686
- */
687
- _isTildeUpdateAvailable(current, latest) {
688
- // First check if latest is actually greater than current
689
- const cmp = this._compareVersions(latest, current);
690
- if (cmp <= 0) {
691
- return false;
692
- }
693
-
694
- // Must be same major and minor
695
- return latest.major === current.major && latest.minor === current.minor;
696
- }
697
-
698
- /**
699
- * Check if update is available for simple comparison (>, >=)
700
- * Also handles pre-release versions correctly
701
- * @private
702
- */
703
- _isSimpleUpdateAvailable(current, latest) {
704
- // Use proper version comparison that handles pre-release
705
- const cmp = this._compareVersions(latest, current);
706
- return cmp > 0;
707
- }
708
-
709
- /**
710
- * Check if update is available for exact version
711
- * Suggests update if latest is newer
712
- * @private
713
- */
714
- _isExactUpdateAvailable(current, latest) {
715
- return this._isSimpleUpdateAvailable(current, latest);
716
- }
717
-
718
- /**
719
- * Check if update is available for less than operators (<, <=)
720
- * <X.Y.Z → latest must be < range
721
- * <=X.Y.Z latest must be <= range
722
- * Also handles pre-release versions correctly
723
- * @param {Object} rangeInfo - Parsed range information
724
- * @param {Object} latest - Parsed latest version
725
- * @returns {boolean} True if update satisfies constraint
726
- * @private
727
- */
728
- _isLessThanUpdateAvailable(rangeInfo, latest) {
729
- // Use proper version comparison that handles pre-release
730
- const cmp = this._compareVersions(latest, rangeInfo);
731
-
732
- if (rangeInfo.operator === '<') {
733
- // latest must be < range
734
- return cmp < 0;
735
- } else if (rangeInfo.operator === '<=') {
736
- // latest must be <= range
737
- return cmp <= 0;
738
- }
739
- return false;
740
- }
741
-
742
- /**
743
- * Check if update is available for X-ranges (4.x, 4.*, 4.17.x, etc.)
744
- * X-ranges match any version within the specified parts
745
- * 4.x or 4.* → any version 4.Y.Z
746
- * 4.17.x or 4.17.* → any version 4.17.Z
747
- * *.*.* any version
748
- * @param {Object} rangeInfo - Parsed range information
749
- * @param {Object} latest - Parsed latest version
750
- * @returns {boolean} True if update is within range
751
- * @private
752
- */
753
- _isXRangeUpdateAvailable(rangeInfo, latest) {
754
- // Check major version if specified
755
- if (rangeInfo.major !== null && latest.major !== rangeInfo.major) {
756
- return false;
757
- }
758
-
759
- // Check minor version if specified
760
- if (rangeInfo.minor !== null && latest.minor !== rangeInfo.minor) {
761
- return false;
762
- }
763
-
764
- // Check patch version if specified
765
- if (rangeInfo.patch !== null && latest.patch !== rangeInfo.patch) {
766
- return false;
767
- }
768
-
769
- // All specified parts match, update is within range
770
- return true;
771
- }
772
-
773
- /**
774
- * Check if a range string is a complex range (contains AND/OR operators)
775
- * Complex ranges include:
776
- * - OR ranges: "^4.0.0 || ^5.0.0"
777
- * - AND ranges: ">=4.0.0 <5.0.0" (space-separated, multiple constraints)
778
- * @param {string} range - Version range string
779
- * @returns {boolean} True if complex range
780
- * @private
781
- */
782
- _isComplexRange(range) {
783
- // Check for OR operator
784
- if (range.includes('||')) {
785
- return true;
786
- }
787
-
788
- // Check for AND (multiple space-separated ranges)
789
- // Need to detect patterns like ">=4.0.0 <5.0.0"
790
- // But NOT "^4.0.0" or "~4.0.0" (single ranges with spaces after)
791
- const trimmed = range.trim();
792
-
793
- // Remove single operator prefixes to see what's left
794
- if (trimmed.startsWith('^') || trimmed.startsWith('~')) {
795
- return false; // Single caret or tilde range
796
- }
797
-
798
- // Split by whitespace and check if there are multiple parts
799
- const parts = trimmed.split(/\s+/).filter(p => p.length > 0);
800
-
801
- // If we have multiple parts, it might be a complex AND range
802
- // Examples: [">=4.0.0", "<5.0.0"], [">1.0.0", "<2.0.0"]
803
- if (parts.length > 1) {
804
- // Check if each part looks like a range operator
805
- const rangeOperators = /^(>=?|<=?|\^|~|[0-9])/;
806
- return parts.every(part => rangeOperators.test(part));
807
- }
808
-
809
- return false;
810
- }
811
-
812
- /**
813
- * Parse a complex range expression into a structured format
814
- * Handles:
815
- * - OR: "^4.0.0 || ^5.0.0" → {type: 'or', ranges: [...]}
816
- * - AND: ">=4.0.0 <5.0.0" → {type: 'and', ranges: [...]}
817
- * - Simple: "^4.0.0" {type: 'simple', range: {...}}
818
- * @param {string} range - Complex range string
819
- * @returns {Object} Parsed complex range structure
820
- * @private
821
- */
822
- _parseComplexRange(range) {
823
- // Split by OR first (|| has precedence)
824
- if (range.includes('||')) {
825
- const orParts = range.split('||').map(p => p.trim());
826
- return {
827
- type: 'or',
828
- ranges: orParts.map(part => this._parseComplexRange(part))
829
- };
830
- }
831
-
832
- // Split by AND (space-separated, no || present)
833
- const andParts = range.trim().split(/\s+/).filter(p => p.length > 0);
834
-
835
- if (andParts.length > 1) {
836
- return {
837
- type: 'and',
838
- ranges: andParts.map(part => this._parseVersionRange(part))
839
- };
840
- }
841
-
842
- // Simple range
843
- return {
844
- type: 'simple',
845
- range: this._parseVersionRange(range)
846
- };
847
- }
848
-
849
- /**
850
- * Check if a version satisfies a single range
851
- * @param {Object} rangeInfo - Parsed range information
852
- * @param {Object} version - Parsed version to check
853
- * @returns {boolean} True if version satisfies range
854
- * @private
855
- */
856
- _satisfiesRange(rangeInfo, version) {
857
- if (!rangeInfo || !version) {
858
- return false;
859
- }
860
-
861
- // Use the existing _isUpdateAvailable logic which handles all operators
862
- return this._isUpdateAvailable(rangeInfo, version);
863
- }
864
-
865
- /**
866
- * Check if a version satisfies a complex range expression
867
- * Handles AND/OR logic recursively
868
- * @param {Object} complexRange - Parsed complex range structure
869
- * @param {Object} version - Parsed version to check
870
- * @returns {boolean} True if version satisfies the complex range
871
- * @private
872
- */
873
- _satisfiesComplexRange(complexRange, version) {
874
- if (!complexRange || !version) {
875
- return false;
876
- }
877
-
878
- switch (complexRange.type) {
879
- case 'simple':
880
- return this._satisfiesRange(complexRange.range, version);
881
-
882
- case 'and':
883
- // ALL ranges must be satisfied
884
- return complexRange.ranges.every(range => this._satisfiesRange(range, version));
885
-
886
- case 'or':
887
- // AT LEAST ONE range must be satisfied
888
- return complexRange.ranges.some(range => this._satisfiesComplexRange(range, version));
889
-
890
- default:
891
- return false;
892
- }
893
- }
894
-
895
- /**
896
- * Check dependencies in batches to avoid overwhelming the registry
897
- * @param {Object} dependencies - Map of package names to versions
898
- * @returns {Promise<Object>} Map of packages with available updates
899
- * @private
900
- */
901
- async _checkDependencies(dependencies) {
902
- const updates = {};
903
- const entries = Object.entries(dependencies);
904
-
905
- // Process in batches
906
- for (let i = 0; i < entries.length; i += RESOLVER_CONFIG.MAX_CONCURRENT_CHECKS) {
907
- const batch = entries.slice(i, i + RESOLVER_CONFIG.MAX_CONCURRENT_CHECKS);
908
-
909
- const promises = batch.map(async ([pkg, range]) => {
910
- try {
911
- const latest = await this._getLatestCompatibleVersion(pkg, range);
912
-
913
- if (latest) {
914
- // Preserve the range prefix (^, ~, etc.)
915
- const prefix = range.match(/^[\^~]/)?.[0] || '^';
916
- return { pkg, newVersion: `${prefix}${latest}`, oldVersion: range };
917
- }
918
-
919
- return null;
920
- } catch (error) {
921
- this.logger?.error(`Error checking ${pkg}:`, error.message);
922
- return null;
923
- }
924
- });
925
-
926
- const results = await Promise.all(promises);
927
-
928
- results.forEach(result => {
929
- if (result) {
930
- updates[result.pkg] = result.newVersion;
931
- }
932
- });
933
- }
934
-
935
- return updates;
936
- }
937
-
938
- /**
939
- * Execute tool with parsed parameters
940
- * @param {Object} params - Parsed parameters
941
- * @param {Object} context - Execution context
942
- * @returns {Promise<Object>} Execution result
943
- */
944
- async execute(params, context = {}) {
945
- try {
946
- // Validate parameters
947
- this._validateParameters(params);
948
-
949
- const { path: targetPath, mode, includeDev } = params;
950
- const { projectDir, agentId, directoryAccess } = context;
951
-
952
- // Resolve and validate path
953
- const resolvedPath = this._resolveAndValidatePath(targetPath, context);
954
- const pkgPath = path.join(resolvedPath, 'package.json');
955
-
956
- this.logger?.info('Dependency resolver executing', {
957
- mode,
958
- resolvedPath,
959
- includeDev,
960
- agentId
961
- });
962
-
963
- const output = [];
964
- output.push(`🔍 Checking dependencies in: ${resolvedPath}`);
965
- output.push(`Mode: ${mode}`);
966
-
967
- // Check if package.json exists
968
- try {
969
- await fs.access(pkgPath);
970
- } catch {
971
- return {
972
- success: false,
973
- error: `No package.json found at: ${pkgPath}`,
974
- output: output.join('\n')
975
- };
976
- }
977
-
978
- // Read package.json
979
- output.push('\n📦 Reading package.json...');
980
- const pkgContent = await fs.readFile(pkgPath, 'utf-8');
981
- const pkgData = JSON.parse(pkgContent);
982
-
983
- // Collect dependencies
984
- const allDeps = { ...pkgData.dependencies };
985
-
986
- if (includeDev && pkgData.devDependencies) {
987
- Object.assign(allDeps, pkgData.devDependencies);
988
- }
989
-
990
- if (Object.keys(allDeps).length === 0) {
991
- return {
992
- success: true,
993
- mode,
994
- message: 'No dependencies found in package.json',
995
- statistics: {
996
- totalDependencies: 0,
997
- updatesAvailable: 0,
998
- updatesApplied: 0,
999
- errors: 0
1000
- },
1001
- output: output.join('\n')
1002
- };
1003
- }
1004
-
1005
- // Safety check
1006
- if (Object.keys(allDeps).length > RESOLVER_CONFIG.MAX_DEPENDENCIES) {
1007
- return {
1008
- success: false,
1009
- error: `Too many dependencies (${Object.keys(allDeps).length}), max allowed: ${RESOLVER_CONFIG.MAX_DEPENDENCIES}`,
1010
- output: output.join('\n')
1011
- };
1012
- }
1013
-
1014
- output.push(`📊 Found ${Object.keys(allDeps).length} dependencies to check`);
1015
- output.push('🌐 Querying npm registry for updates...');
1016
-
1017
- // Check for updates
1018
- const updates = await this._checkDependencies(allDeps);
1019
-
1020
- output.push(`\n✅ Registry check complete`);
1021
- output.push(`📈 Updates available: ${Object.keys(updates).length}`);
1022
-
1023
- if (Object.keys(updates).length > 0) {
1024
- output.push('\n📋 Available updates:');
1025
- for (const [pkg, newVersion] of Object.entries(updates)) {
1026
- const oldVersion = allDeps[pkg];
1027
- output.push(` • ${pkg}: ${oldVersion} → ${newVersion}`);
1028
- }
1029
- } else {
1030
- output.push('\n🎉 All dependencies are up-to-date!');
1031
- }
1032
-
1033
- // Handle modes
1034
- if (mode === 'check') {
1035
- // Check mode - just report
1036
- if (Object.keys(updates).length > 0) {
1037
- output.push('\n💡 Run with mode="fix" or mode="auto" to apply updates');
1038
- }
1039
-
1040
- return {
1041
- success: true,
1042
- mode: 'check',
1043
- message: `Found ${Object.keys(updates).length} package(s) with available updates`,
1044
- statistics: {
1045
- totalDependencies: Object.keys(allDeps).length,
1046
- updatesAvailable: Object.keys(updates).length,
1047
- updatesApplied: 0,
1048
- errors: 0
1049
- },
1050
- updates,
1051
- output: output.join('\n')
1052
- };
1053
- }
1054
-
1055
- // Fix or auto mode - apply updates
1056
- if ((mode === 'fix' || mode === 'auto') && Object.keys(updates).length > 0) {
1057
- // Create backup
1058
- output.push('\n💾 Creating backup of package.json...');
1059
- const backupPath = await this._createBackup(pkgPath);
1060
-
1061
- if (backupPath) {
1062
- output.push(`✅ Backup created: ${path.basename(backupPath)}`);
1063
- } else {
1064
- output.push('⚠️ Backup creation failed, continuing anyway...');
1065
- }
1066
-
1067
- // Update package.json
1068
- output.push('\n🛠 Updating package.json...');
1069
-
1070
- for (const [pkg, newVersion] of Object.entries(updates)) {
1071
- if (pkgData.dependencies?.[pkg]) {
1072
- pkgData.dependencies[pkg] = newVersion;
1073
- }
1074
- if (pkgData.devDependencies?.[pkg]) {
1075
- pkgData.devDependencies[pkg] = newVersion;
1076
- }
1077
- }
1078
-
1079
- // Write updated package.json
1080
- await fs.writeFile(pkgPath, JSON.stringify(pkgData, null, 2) + '\n');
1081
- output.push('✅ package.json updated');
1082
-
1083
- // Run npm install
1084
- output.push('\n📥 Installing dependencies (this may take a while)...');
1085
-
1086
- try {
1087
- const { stdout, stderr } = await exec('npm install', {
1088
- cwd: resolvedPath,
1089
- timeout: RESOLVER_CONFIG.NPM_COMMAND_TIMEOUT,
1090
- maxBuffer: 1024 * 1024 * 10 // 10MB buffer
1091
- });
1092
-
1093
- if (stdout) {
1094
- // Only show summary, not full output
1095
- const lines = stdout.trim().split('\n');
1096
- if (lines.length > 10) {
1097
- output.push(' ' + lines.slice(-5).join('\n '));
1098
- } else {
1099
- output.push(' ' + stdout.trim());
1100
- }
1101
- }
1102
-
1103
- if (stderr && !stderr.includes('npm WARN')) {
1104
- output.push(`⚠️ ${stderr.trim()}`);
1105
- }
1106
-
1107
- output.push('✅ Installation complete');
1108
-
1109
- // Show installed versions
1110
- try {
1111
- const { stdout: lsOutput } = await exec('npm ls --depth=0 --json', {
1112
- cwd: resolvedPath,
1113
- timeout: 30000
1114
- });
1115
-
1116
- const lsData = JSON.parse(lsOutput);
1117
-
1118
- if (lsData.dependencies) {
1119
- output.push('\n📦 Installed versions:');
1120
- for (const pkg of Object.keys(updates)) {
1121
- if (lsData.dependencies[pkg]) {
1122
- output.push(` • ${pkg}@${lsData.dependencies[pkg].version}`);
1123
- }
1124
- }
1125
- }
1126
- } catch (lsError) {
1127
- // npm ls might fail with peer dependency warnings - that's okay
1128
- this.logger?.debug('npm ls failed:', lsError.message);
1129
- }
1130
-
1131
- return {
1132
- success: true,
1133
- mode,
1134
- message: `Successfully updated ${Object.keys(updates).length} package(s)`,
1135
- statistics: {
1136
- totalDependencies: Object.keys(allDeps).length,
1137
- updatesAvailable: Object.keys(updates).length,
1138
- updatesApplied: Object.keys(updates).length,
1139
- errors: 0
1140
- },
1141
- updates,
1142
- backupCreated: backupPath !== null,
1143
- backupPath,
1144
- output: output.join('\n')
1145
- };
1146
-
1147
- } catch (installError) {
1148
- output.push(`\n❌ npm install failed: ${installError.message}`);
1149
-
1150
- // Try to restore from backup
1151
- if (backupPath) {
1152
- try {
1153
- await fs.copyFile(backupPath, pkgPath);
1154
- output.push('🔄 Restored package.json from backup');
1155
- } catch (restoreError) {
1156
- output.push('⚠️ Failed to restore backup');
1157
- }
1158
- }
1159
-
1160
- return {
1161
- success: false,
1162
- mode,
1163
- error: `npm install failed: ${installError.message}`,
1164
- statistics: {
1165
- totalDependencies: Object.keys(allDeps).length,
1166
- updatesAvailable: Object.keys(updates).length,
1167
- updatesApplied: 0,
1168
- errors: 1
1169
- },
1170
- updates,
1171
- backupCreated: backupPath !== null,
1172
- output: output.join('\n')
1173
- };
1174
- }
1175
- } else if (mode === 'fix' || mode === 'auto') {
1176
- // No updates needed
1177
- output.push('\n✨ No updates needed');
1178
-
1179
- return {
1180
- success: true,
1181
- mode,
1182
- message: 'All dependencies are up-to-date',
1183
- statistics: {
1184
- totalDependencies: Object.keys(allDeps).length,
1185
- updatesAvailable: 0,
1186
- updatesApplied: 0,
1187
- errors: 0
1188
- },
1189
- updates: {},
1190
- output: output.join('\n')
1191
- };
1192
- }
1193
-
1194
- } catch (error) {
1195
- this.logger?.error('Dependency resolver error:', error);
1196
-
1197
- return {
1198
- success: false,
1199
- error: error.message,
1200
- statistics: {
1201
- totalDependencies: 0,
1202
- updatesAvailable: 0,
1203
- updatesApplied: 0,
1204
- errors: 1
1205
- },
1206
- output: error.message
1207
- };
1208
- }
1209
- }
1210
- }
1211
-
1212
- export default DependencyResolverTool;
1
+ /**
2
+ * @file tools/dependencyResolverTool.js
3
+ * @description Modern tool for resolving Node.js dependency conflicts by checking and updating to latest compatible versions
4
+ */
5
+
6
+ import { promises as fs } from 'fs';
7
+ import path from 'path';
8
+ import { exec as execCb } from 'child_process';
9
+ import { promisify } from 'util';
10
+ import { BaseTool } from './baseTool.js';
11
+
12
+ const exec = promisify(execCb);
13
+
14
+ /**
15
+ * Configuration constants for the dependency resolver
16
+ */
17
+ const RESOLVER_CONFIG = {
18
+ DEFAULT_MODE: 'check',
19
+ VALID_MODES: ['check', 'fix', 'auto'],
20
+ NPM_COMMAND_TIMEOUT: 300000, // 5 minutes for npm commands
21
+ REGISTRY_TIMEOUT: 10000, // 10 seconds for registry requests
22
+ MAX_CONCURRENT_CHECKS: 5, // Max parallel registry checks
23
+ BACKUP_EXTENSION: '.backup.json', // Backup file extension
24
+ CREATE_BACKUPS: true, // Always create backups
25
+ RETRY_ATTEMPTS: 3, // Registry request retry attempts
26
+ RETRY_DELAY: 1000, // Delay between retries (ms)
27
+ MAX_DEPENDENCIES: 500, // Safety limit
28
+ NPM_REGISTRY_URL: 'https://registry.npmjs.org'
29
+ };
30
+
31
+ /**
32
+ * DependencyResolverTool - Modern implementation
33
+ * Resolves Node.js package dependency conflicts with improved security and reliability
34
+ */
35
+ export class DependencyResolverTool extends BaseTool {
36
+ constructor(config = {}, logger = null) {
37
+ super(config, logger);
38
+
39
+ // Override tool ID to match documentation (with hyphen)
40
+ this.id = 'dependency-resolver';
41
+ }
42
+
43
+ /**
44
+ * Get tool description for agent system prompt
45
+ * @returns {string} Formatted tool description
46
+ */
47
+ getDescription() {
48
+ return `Tool: Dependency Resolver - Resolve Node.js package dependency conflicts
49
+
50
+ **Purpose:** Checks npm dependencies for updates and optionally updates package.json to latest compatible versions automatically.
51
+
52
+ **USAGE:**
53
+ \`\`\`json
54
+ {
55
+ "toolId": "dependency-resolver",
56
+ "parameters": {
57
+ "path": "./my-project",
58
+ "mode": "check",
59
+ "includeDev": true
60
+ }
61
+ }
62
+ \`\`\`
63
+
64
+ **Parameters:**
65
+ - **path** (string, optional): Path to project directory with package.json. Default: "."
66
+ - **mode** (string, optional): Operation mode. Options:
67
+ - "check" - Only check for updates (default)
68
+ - "fix" - Update package.json and run npm install
69
+ - "auto" - Automatically fix all conflicts
70
+ - **includeDev** (boolean, optional): Include devDependencies. Default: true
71
+
72
+ **What It Does:**
73
+ - Checks npm registry for latest compatible versions
74
+ - Respects semver ranges (^, ~, >=, etc.)
75
+ - Creates automatic backups before modifications
76
+ - Runs npm install after updates (in fix/auto mode)
77
+ - Provides detailed update report
78
+
79
+ **Examples:**
80
+
81
+ 1. Check for updates:
82
+ \`\`\`json
83
+ {
84
+ "toolId": "dependency-resolver",
85
+ "parameters": { "mode": "check" }
86
+ }
87
+ \`\`\`
88
+
89
+ 2. Fix outdated dependencies:
90
+ \`\`\`json
91
+ {
92
+ "toolId": "dependency-resolver",
93
+ "parameters": { "path": "./my-project", "mode": "fix" }
94
+ }
95
+ \`\`\`
96
+
97
+ 3. Auto-fix with devDependencies:
98
+ \`\`\`json
99
+ {
100
+ "toolId": "dependency-resolver",
101
+ "parameters": { "mode": "auto", "includeDev": true }
102
+ }
103
+ \`\`\`
104
+
105
+ **Notes:**
106
+ - Always creates backup (.backup.json) before modifications
107
+ - Network requests have timeout and retry logic
108
+ - npm install runs with timeout protection (5 minutes max)
109
+ - Supports complex semver ranges (||, &&, etc.)`;
110
+ }
111
+
112
+ /**
113
+ * Parse tool parameters from raw content (XML or JSON)
114
+ * @param {string|Object} content - Raw tool content or parsed object
115
+ * @returns {Object} Parsed parameters
116
+ */
117
+ parseParameters(content) {
118
+ // If already an object, validate and return
119
+ if (typeof content === 'object' && content !== null) {
120
+ return {
121
+ path: content.path || '.',
122
+ mode: content.mode || RESOLVER_CONFIG.DEFAULT_MODE,
123
+ includeDev: content.includeDev !== undefined ? content.includeDev : true
124
+ };
125
+ }
126
+
127
+ // Parse XML content
128
+ if (typeof content === 'string') {
129
+ // Try modern XML format first: <dependency-resolve>...</dependency-resolve>
130
+ const modernPattern = /<dependency-resolve([^>]*)>([\s\S]*?)<\/dependency-resolve>/i;
131
+ const modernMatch = modernPattern.exec(content);
132
+
133
+ if (modernMatch) {
134
+ const attributesStr = modernMatch[1];
135
+ const innerContent = modernMatch[2];
136
+
137
+ // Parse attributes from opening tag
138
+ const pathAttr = /path=["']([^"']*)["']/i.exec(attributesStr);
139
+ const modeAttr = /mode=["']([^"']*)["']/i.exec(attributesStr);
140
+ const includeDevAttr = /include-dev=["']([^"']*)["']/i.exec(attributesStr);
141
+
142
+ // Extract from inner content
143
+ const pathPattern = /<path>(.*?)<\/path>/i;
144
+ const pathMatch = pathPattern.exec(innerContent);
145
+
146
+ const modePattern = /<mode>(.*?)<\/mode>/i;
147
+ const modeMatch = modePattern.exec(innerContent);
148
+
149
+ const includeDevPattern = /<include-dev>(.*?)<\/include-dev>/i;
150
+ const includeDevMatch = includeDevPattern.exec(innerContent);
151
+
152
+ // Content takes precedence over attributes
153
+ const extractedPath = (pathMatch ? pathMatch[1].trim() : null) || (pathAttr ? pathAttr[1] : '.');
154
+ const extractedMode = (modeMatch ? modeMatch[1].trim() : null) || (modeAttr ? modeAttr[1] : RESOLVER_CONFIG.DEFAULT_MODE);
155
+ const extractedIncludeDev = (includeDevMatch ? includeDevMatch[1].trim() : null) || (includeDevAttr ? includeDevAttr[1] : 'true');
156
+
157
+ return {
158
+ path: extractedPath,
159
+ mode: extractedMode,
160
+ includeDev: this._parseBoolean(extractedIncludeDev, true)
161
+ };
162
+ }
163
+
164
+ // Try legacy format: [resolve path="..." mode="..."]
165
+ const legacyPattern = /\[resolve\s+([^\]]*)\]/i;
166
+ const legacyMatch = legacyPattern.exec(content);
167
+
168
+ if (legacyMatch) {
169
+ const attrString = legacyMatch[1];
170
+
171
+ // Parse attributes manually
172
+ const pathAttr = /path=["']([^"']*)["']/i.exec(attrString);
173
+ const modeAttr = /mode=["']([^"']*)["']/i.exec(attrString);
174
+ const includeDevAttr = /include-dev=["']([^"']*)["']/i.exec(attrString);
175
+
176
+ return {
177
+ path: pathAttr ? pathAttr[1] : '.',
178
+ mode: modeAttr ? modeAttr[1] : RESOLVER_CONFIG.DEFAULT_MODE,
179
+ includeDev: this._parseBoolean(includeDevAttr ? includeDevAttr[1] : 'true', true)
180
+ };
181
+ }
182
+
183
+ throw new Error('Invalid dependency-resolve format. Use <dependency-resolve> tags or JSON format.');
184
+ }
185
+
186
+ throw new Error('Invalid parameter format. Expected string (XML) or object (JSON).');
187
+ }
188
+
189
+ /**
190
+ * Parse boolean from string or boolean
191
+ * @param {any} value - Value to parse
192
+ * @param {boolean} defaultValue - Default if undefined
193
+ * @returns {boolean}
194
+ * @private
195
+ */
196
+ _parseBoolean(value, defaultValue = false) {
197
+ if (value === undefined || value === null) return defaultValue;
198
+ if (typeof value === 'boolean') return value;
199
+ if (typeof value === 'string') {
200
+ return value.toLowerCase() === 'true' || value === '1';
201
+ }
202
+ return defaultValue;
203
+ }
204
+
205
+ /**
206
+ * Validate parameters
207
+ * @param {Object} params - Parameters to validate
208
+ * @throws {Error} If validation fails
209
+ * @private
210
+ */
211
+ _validateParameters(params) {
212
+ if (!params || typeof params !== 'object') {
213
+ throw new Error('Parameters must be an object');
214
+ }
215
+
216
+ if (params.path && typeof params.path !== 'string') {
217
+ throw new Error('path must be a string');
218
+ }
219
+
220
+ if (params.mode && !RESOLVER_CONFIG.VALID_MODES.includes(params.mode)) {
221
+ throw new Error(`Invalid mode: ${params.mode}. Must be one of: ${RESOLVER_CONFIG.VALID_MODES.join(', ')}`);
222
+ }
223
+
224
+ if (params.includeDev !== undefined && typeof params.includeDev !== 'boolean') {
225
+ throw new Error('includeDev must be a boolean');
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Validate and resolve file path
231
+ * @param {string} targetPath - Target path from parameters
232
+ * @param {Object} context - Execution context
233
+ * @returns {string} Resolved absolute path
234
+ * @throws {Error} If path is invalid or inaccessible
235
+ * @private
236
+ */
237
+ _resolveAndValidatePath(targetPath, context) {
238
+ const { projectDir, directoryAccess } = context;
239
+
240
+ // Determine working directory
241
+ let workingDirectory = projectDir || process.cwd();
242
+
243
+ if (directoryAccess && directoryAccess.workingDirectory) {
244
+ workingDirectory = directoryAccess.workingDirectory;
245
+ }
246
+
247
+ // Resolve the target path
248
+ const resolvedPath = path.isAbsolute(targetPath)
249
+ ? path.normalize(targetPath)
250
+ : path.normalize(path.join(workingDirectory, targetPath));
251
+
252
+ // Security: Check for path traversal
253
+ const realWorkingDir = path.normalize(workingDirectory);
254
+ if (!resolvedPath.startsWith(realWorkingDir)) {
255
+ throw new Error(`Path traversal detected: ${targetPath} resolves outside working directory`);
256
+ }
257
+
258
+ return resolvedPath;
259
+ }
260
+
261
+ /**
262
+ * Create backup of package.json
263
+ * @param {string} pkgPath - Path to package.json
264
+ * @returns {Promise<string|null>} Backup file path or null if failed
265
+ * @private
266
+ */
267
+ async _createBackup(pkgPath) {
268
+ if (!RESOLVER_CONFIG.CREATE_BACKUPS) {
269
+ return null;
270
+ }
271
+
272
+ try {
273
+ const backupPath = pkgPath + RESOLVER_CONFIG.BACKUP_EXTENSION;
274
+ await fs.copyFile(pkgPath, backupPath);
275
+ this.logger?.info('Created backup', { backupPath });
276
+ return backupPath;
277
+ } catch (error) {
278
+ this.logger?.warn('Failed to create backup', { error: error.message });
279
+ return null;
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Fetch package info from npm registry with retries
285
+ * @param {string} packageName - Package name
286
+ * @returns {Promise<Object|null>} Package data or null if failed
287
+ * @private
288
+ */
289
+ async _fetchPackageInfo(packageName) {
290
+ const url = `${RESOLVER_CONFIG.NPM_REGISTRY_URL}/${encodeURIComponent(packageName)}`;
291
+
292
+ for (let attempt = 1; attempt <= RESOLVER_CONFIG.RETRY_ATTEMPTS; attempt++) {
293
+ try {
294
+ const controller = new AbortController();
295
+ const timeout = setTimeout(() => controller.abort(), RESOLVER_CONFIG.REGISTRY_TIMEOUT);
296
+
297
+ const response = await fetch(url, { signal: controller.signal });
298
+ clearTimeout(timeout);
299
+
300
+ if (!response.ok) {
301
+ if (response.status === 404) {
302
+ this.logger?.warn(`Package not found: ${packageName}`);
303
+ return null;
304
+ }
305
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
306
+ }
307
+
308
+ const data = await response.json();
309
+
310
+ // Validate response structure
311
+ if (!data || typeof data !== 'object' || !data['dist-tags']) {
312
+ throw new Error('Invalid registry response format');
313
+ }
314
+
315
+ return data;
316
+
317
+ } catch (error) {
318
+ if (attempt < RESOLVER_CONFIG.RETRY_ATTEMPTS) {
319
+ this.logger?.debug(`Retry ${attempt}/${RESOLVER_CONFIG.RETRY_ATTEMPTS} for ${packageName}`);
320
+ await new Promise(resolve => setTimeout(resolve, RESOLVER_CONFIG.RETRY_DELAY * attempt));
321
+ } else {
322
+ this.logger?.error(`Failed to fetch ${packageName}:`, error.message);
323
+ return null;
324
+ }
325
+ }
326
+ }
327
+
328
+ return null;
329
+ }
330
+
331
+ /**
332
+ * Get latest compatible version for a package
333
+ * Enhanced self-contained semver logic supporting:
334
+ * - Caret ranges (^) with 0.x.y and 0.0.x special cases
335
+ * - Tilde ranges (~)
336
+ * - Comparison operators (>, >=, <, <=)
337
+ * - X-ranges (4.x, 4.*, etc.)
338
+ * - Pre-release versions
339
+ * - Complex range expressions (AND/OR)
340
+ * - Exact versions
341
+ *
342
+ * @param {string} packageName - Package name
343
+ * @param {string} currentRange - Current version range
344
+ * @returns {Promise<string|null>} Latest version or null if no update needed
345
+ * @private
346
+ */
347
+ async _getLatestCompatibleVersion(packageName, currentRange) {
348
+ const data = await this._fetchPackageInfo(packageName);
349
+
350
+ if (!data) {
351
+ return null;
352
+ }
353
+
354
+ const latest = data['dist-tags']?.latest;
355
+
356
+ if (!latest) {
357
+ return null;
358
+ }
359
+
360
+ // Parse latest version
361
+ const latestParsed = this._parseVersion(latest);
362
+
363
+ if (!latestParsed) {
364
+ return null; // Can't parse latest
365
+ }
366
+
367
+ // Check if it's a complex range (AND/OR)
368
+ if (this._isComplexRange(currentRange)) {
369
+ const complexRange = this._parseComplexRange(currentRange);
370
+ const isUpdateAvailable = this._satisfiesComplexRange(complexRange, latestParsed);
371
+
372
+ if (isUpdateAvailable) {
373
+ // For complex ranges, preserve the original format
374
+ return latest;
375
+ }
376
+ return null;
377
+ }
378
+
379
+ // Simple range - parse normally
380
+ const rangeInfo = this._parseVersionRange(currentRange);
381
+
382
+ if (!rangeInfo) {
383
+ return null; // Can't parse, skip
384
+ }
385
+
386
+ // Check if update is available and compatible
387
+ const isUpdateAvailable = this._isUpdateAvailable(rangeInfo, latestParsed);
388
+
389
+ if (isUpdateAvailable) {
390
+ // Preserve the original prefix
391
+ return rangeInfo.prefix + latest;
392
+ }
393
+
394
+ return null;
395
+ }
396
+
397
+ /**
398
+ * Parse a version string into components including pre-release and build metadata
399
+ * @param {string} version - Version string (e.g., "4.17.1", "1.0.0-alpha.1", "1.0.0+build.123")
400
+ * @returns {Object|null} Parsed version or null
401
+ * @private
402
+ */
403
+ _parseVersion(version) {
404
+ // Match: major.minor.patch[-prerelease][+build]
405
+ // Pre-release and build are optional
406
+ const pattern = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
407
+ const match = pattern.exec(version);
408
+
409
+ if (!match) {
410
+ // Fallback: try simple X.Y.Z pattern without pre-release/build
411
+ const simpleMatch = version.match(/^(\d+)\.(\d+)\.(\d+)/);
412
+ if (simpleMatch) {
413
+ return {
414
+ major: parseInt(simpleMatch[1], 10),
415
+ minor: parseInt(simpleMatch[2], 10),
416
+ patch: parseInt(simpleMatch[3], 10),
417
+ prerelease: null,
418
+ build: null
419
+ };
420
+ }
421
+ return null;
422
+ }
423
+
424
+ return {
425
+ major: parseInt(match[1], 10),
426
+ minor: parseInt(match[2], 10),
427
+ patch: parseInt(match[3], 10),
428
+ prerelease: match[4] ? match[4].split('.') : null,
429
+ build: match[5] || null
430
+ };
431
+ }
432
+
433
+ /**
434
+ * Compare two versions according to semver rules
435
+ * Returns: < 0 if v1 < v2, 0 if v1 === v2, > 0 if v1 > v2
436
+ * Handles pre-release versions correctly:
437
+ * - 1.0.0 > 1.0.0-alpha (release > pre-release)
438
+ * - 1.0.0-alpha < 1.0.0-beta (lexical comparison)
439
+ * - 1.0.0-1 < 1.0.0-2 (numeric comparison)
440
+ * @param {Object} v1 - First version
441
+ * @param {Object} v2 - Second version
442
+ * @returns {number} Comparison result
443
+ * @private
444
+ */
445
+ _compareVersions(v1, v2) {
446
+ // Compare major.minor.patch first
447
+ if (v1.major !== v2.major) return v1.major - v2.major;
448
+ if (v1.minor !== v2.minor) return v1.minor - v2.minor;
449
+ if (v1.patch !== v2.patch) return v1.patch - v2.patch;
450
+
451
+ // When major.minor.patch are equal, check pre-release
452
+ // According to semver spec:
453
+ // 1. Release version (no prerelease) > prerelease version
454
+ // 2. If both have prerelease, compare identifiers
455
+
456
+ if (!v1.prerelease && !v2.prerelease) {
457
+ // Both are release versions, equal
458
+ return 0;
459
+ }
460
+
461
+ if (!v1.prerelease && v2.prerelease) {
462
+ // v1 is release, v2 is pre-release → v1 > v2
463
+ return 1;
464
+ }
465
+
466
+ if (v1.prerelease && !v2.prerelease) {
467
+ // v1 is pre-release, v2 is release → v1 < v2
468
+ return -1;
469
+ }
470
+
471
+ // Both have pre-release, compare them
472
+ return this._comparePrerelease(v1.prerelease, v2.prerelease);
473
+ }
474
+
475
+ /**
476
+ * Compare pre-release version identifiers according to semver spec
477
+ * Identifiers are compared as:
478
+ * 1. Numeric identifiers are compared numerically
479
+ * 2. Alphanumeric identifiers are compared lexically (ASCII sort)
480
+ * 3. Numeric identifiers have lower precedence than alphanumeric
481
+ * 4. Larger set of identifiers has higher precedence if all preceding are equal
482
+ * @param {Array<string>} pre1 - First pre-release identifiers
483
+ * @param {Array<string>} pre2 - Second pre-release identifiers
484
+ * @returns {number} Comparison result
485
+ * @private
486
+ */
487
+ _comparePrerelease(pre1, pre2) {
488
+ const len = Math.max(pre1.length, pre2.length);
489
+
490
+ for (let i = 0; i < len; i++) {
491
+ // If one pre-release has fewer identifiers, it has lower precedence
492
+ if (i >= pre1.length) return -1; // pre1 is shorter, pre1 < pre2
493
+ if (i >= pre2.length) return 1; // pre2 is shorter, pre1 > pre2
494
+
495
+ const part1 = pre1[i];
496
+ const part2 = pre2[i];
497
+
498
+ // Check if parts are numeric
499
+ const num1 = /^\d+$/.test(part1) ? parseInt(part1, 10) : null;
500
+ const num2 = /^\d+$/.test(part2) ? parseInt(part2, 10) : null;
501
+
502
+ // Both numeric: compare numerically
503
+ if (num1 !== null && num2 !== null) {
504
+ if (num1 !== num2) return num1 - num2;
505
+ continue;
506
+ }
507
+
508
+ // One numeric, one alphanumeric: numeric has lower precedence
509
+ if (num1 !== null && num2 === null) return -1;
510
+ if (num1 === null && num2 !== null) return 1;
511
+
512
+ // Both alphanumeric: compare lexically
513
+ if (part1 < part2) return -1;
514
+ if (part1 > part2) return 1;
515
+ }
516
+
517
+ // All identifiers equal
518
+ return 0;
519
+ }
520
+
521
+ /**
522
+ * Parse version range into components
523
+ * @param {string} range - Version range (e.g., "^4.17.1", "~4.17.1", "<5.0.0", "4.x", "4.*")
524
+ * @returns {Object|null} Parsed range or null
525
+ * @private
526
+ */
527
+ _parseVersionRange(range) {
528
+ const trimmed = range.trim();
529
+
530
+ // Check for X-ranges first: 4.x, 4.*, 4.17.x, 4.17.*, or just 4
531
+ // X-ranges use 'x', '*', or missing parts as wildcards
532
+ const xRangePattern = /^(\d+|\*|x)(\.(\d+|\*|x))?(\.(\d+|\*|x))?$/i;
533
+ const xMatch = xRangePattern.exec(trimmed);
534
+
535
+ if (xMatch) {
536
+ const majorStr = xMatch[1];
537
+ const minorStr = xMatch[3];
538
+ const patchStr = xMatch[5];
539
+
540
+ // Check if any part is wildcard or missing (making it an X-range)
541
+ const isXRange =
542
+ majorStr === '*' || majorStr.toLowerCase() === 'x' ||
543
+ minorStr === undefined || minorStr === '*' || minorStr.toLowerCase() === 'x' ||
544
+ patchStr === undefined || patchStr === '*' || patchStr.toLowerCase() === 'x';
545
+
546
+ if (isXRange) {
547
+ return {
548
+ prefix: '',
549
+ operator: 'x-range',
550
+ major: (majorStr === '*' || majorStr.toLowerCase() === 'x') ? null : parseInt(majorStr, 10),
551
+ minor: (!minorStr || minorStr === '*' || minorStr.toLowerCase() === 'x') ? null : parseInt(minorStr, 10),
552
+ patch: (!patchStr || patchStr === '*' || patchStr.toLowerCase() === 'x') ? null : parseInt(patchStr, 10),
553
+ original: range
554
+ };
555
+ }
556
+ }
557
+
558
+ // Extract prefix and version for other operators
559
+ let prefix = '';
560
+ let version = trimmed;
561
+ let operator = '=';
562
+
563
+ if (trimmed.startsWith('^')) {
564
+ prefix = '^';
565
+ version = trimmed.slice(1);
566
+ operator = '^';
567
+ } else if (trimmed.startsWith('~')) {
568
+ prefix = '~';
569
+ version = trimmed.slice(1);
570
+ operator = '~';
571
+ } else if (trimmed.startsWith('>=')) {
572
+ prefix = '>=';
573
+ version = trimmed.slice(2).trim();
574
+ operator = '>=';
575
+ } else if (trimmed.startsWith('>')) {
576
+ prefix = '>';
577
+ version = trimmed.slice(1).trim();
578
+ operator = '>';
579
+ } else if (trimmed.startsWith('<=')) {
580
+ prefix = '<=';
581
+ version = trimmed.slice(2).trim();
582
+ operator = '<=';
583
+ } else if (trimmed.startsWith('<')) {
584
+ prefix = '<';
585
+ version = trimmed.slice(1).trim();
586
+ operator = '<';
587
+ }
588
+
589
+ // Parse version X.Y.Z (including pre-release and build)
590
+ const parsed = this._parseVersion(version);
591
+
592
+ if (!parsed) {
593
+ return null;
594
+ }
595
+
596
+ return {
597
+ prefix,
598
+ operator,
599
+ major: parsed.major,
600
+ minor: parsed.minor,
601
+ patch: parsed.patch,
602
+ prerelease: parsed.prerelease,
603
+ build: parsed.build,
604
+ original: range
605
+ };
606
+ }
607
+
608
+ /**
609
+ * Check if update is available and compatible with range
610
+ * @param {Object} rangeInfo - Parsed range information
611
+ * @param {Object} latest - Parsed latest version
612
+ * @returns {boolean} True if update available
613
+ * @private
614
+ */
615
+ _isUpdateAvailable(rangeInfo, latest) {
616
+ const current = {
617
+ major: rangeInfo.major,
618
+ minor: rangeInfo.minor,
619
+ patch: rangeInfo.patch,
620
+ prerelease: rangeInfo.prerelease || null,
621
+ build: rangeInfo.build || null
622
+ };
623
+
624
+ // Check based on operator
625
+ switch (rangeInfo.operator) {
626
+ case '^':
627
+ return this._isCaretUpdateAvailable(current, latest);
628
+
629
+ case '~':
630
+ return this._isTildeUpdateAvailable(current, latest);
631
+
632
+ case '>=':
633
+ case '>':
634
+ return this._isSimpleUpdateAvailable(current, latest);
635
+
636
+ case '<':
637
+ case '<=':
638
+ return this._isLessThanUpdateAvailable(rangeInfo, latest);
639
+
640
+ case 'x-range':
641
+ return this._isXRangeUpdateAvailable(rangeInfo, latest);
642
+
643
+ case '=':
644
+ default:
645
+ return this._isExactUpdateAvailable(current, latest);
646
+ }
647
+ }
648
+
649
+ /**
650
+ * Check if update is available for caret range (^)
651
+ * Handles special cases:
652
+ * - ^0.0.X → Only patch updates in 0.0.*
653
+ * - ^0.X.Y → Only patch updates in 0.X.*
654
+ * - ^X.Y.ZMinor and patch updates in X.*.*
655
+ * Also handles pre-release versions correctly
656
+ * @private
657
+ */
658
+ _isCaretUpdateAvailable(current, latest) {
659
+ // First check if latest is actually greater than current
660
+ const cmp = this._compareVersions(latest, current);
661
+ if (cmp <= 0) {
662
+ // latest is not greater than current
663
+ return false;
664
+ }
665
+
666
+ // ^0.0.X → Only patch updates in 0.0.*
667
+ if (current.major === 0 && current.minor === 0) {
668
+ return latest.major === 0 && latest.minor === 0;
669
+ }
670
+
671
+ // ^0.X.Y → Only patch updates in 0.X.*
672
+ if (current.major === 0) {
673
+ return latest.major === 0 && latest.minor === current.minor;
674
+ }
675
+
676
+ // ^X.Y.Z → Any minor/patch update in X.*.*
677
+ return latest.major === current.major;
678
+ }
679
+
680
+ /**
681
+ * Check if update is available for tilde range (~)
682
+ * ~X.Y.Z Only patch updates in X.Y.*
683
+ * Also handles pre-release versions correctly
684
+ * @private
685
+ */
686
+ _isTildeUpdateAvailable(current, latest) {
687
+ // First check if latest is actually greater than current
688
+ const cmp = this._compareVersions(latest, current);
689
+ if (cmp <= 0) {
690
+ return false;
691
+ }
692
+
693
+ // Must be same major and minor
694
+ return latest.major === current.major && latest.minor === current.minor;
695
+ }
696
+
697
+ /**
698
+ * Check if update is available for simple comparison (>, >=)
699
+ * Also handles pre-release versions correctly
700
+ * @private
701
+ */
702
+ _isSimpleUpdateAvailable(current, latest) {
703
+ // Use proper version comparison that handles pre-release
704
+ const cmp = this._compareVersions(latest, current);
705
+ return cmp > 0;
706
+ }
707
+
708
+ /**
709
+ * Check if update is available for exact version
710
+ * Suggests update if latest is newer
711
+ * @private
712
+ */
713
+ _isExactUpdateAvailable(current, latest) {
714
+ return this._isSimpleUpdateAvailable(current, latest);
715
+ }
716
+
717
+ /**
718
+ * Check if update is available for less than operators (<, <=)
719
+ * <X.Y.Z latest must be < range
720
+ * <=X.Y.Z → latest must be <= range
721
+ * Also handles pre-release versions correctly
722
+ * @param {Object} rangeInfo - Parsed range information
723
+ * @param {Object} latest - Parsed latest version
724
+ * @returns {boolean} True if update satisfies constraint
725
+ * @private
726
+ */
727
+ _isLessThanUpdateAvailable(rangeInfo, latest) {
728
+ // Use proper version comparison that handles pre-release
729
+ const cmp = this._compareVersions(latest, rangeInfo);
730
+
731
+ if (rangeInfo.operator === '<') {
732
+ // latest must be < range
733
+ return cmp < 0;
734
+ } else if (rangeInfo.operator === '<=') {
735
+ // latest must be <= range
736
+ return cmp <= 0;
737
+ }
738
+ return false;
739
+ }
740
+
741
+ /**
742
+ * Check if update is available for X-ranges (4.x, 4.*, 4.17.x, etc.)
743
+ * X-ranges match any version within the specified parts
744
+ * 4.x or 4.* → any version 4.Y.Z
745
+ * 4.17.x or 4.17.* → any version 4.17.Z
746
+ * *.*.* → any version
747
+ * @param {Object} rangeInfo - Parsed range information
748
+ * @param {Object} latest - Parsed latest version
749
+ * @returns {boolean} True if update is within range
750
+ * @private
751
+ */
752
+ _isXRangeUpdateAvailable(rangeInfo, latest) {
753
+ // Check major version if specified
754
+ if (rangeInfo.major !== null && latest.major !== rangeInfo.major) {
755
+ return false;
756
+ }
757
+
758
+ // Check minor version if specified
759
+ if (rangeInfo.minor !== null && latest.minor !== rangeInfo.minor) {
760
+ return false;
761
+ }
762
+
763
+ // Check patch version if specified
764
+ if (rangeInfo.patch !== null && latest.patch !== rangeInfo.patch) {
765
+ return false;
766
+ }
767
+
768
+ // All specified parts match, update is within range
769
+ return true;
770
+ }
771
+
772
+ /**
773
+ * Check if a range string is a complex range (contains AND/OR operators)
774
+ * Complex ranges include:
775
+ * - OR ranges: "^4.0.0 || ^5.0.0"
776
+ * - AND ranges: ">=4.0.0 <5.0.0" (space-separated, multiple constraints)
777
+ * @param {string} range - Version range string
778
+ * @returns {boolean} True if complex range
779
+ * @private
780
+ */
781
+ _isComplexRange(range) {
782
+ // Check for OR operator
783
+ if (range.includes('||')) {
784
+ return true;
785
+ }
786
+
787
+ // Check for AND (multiple space-separated ranges)
788
+ // Need to detect patterns like ">=4.0.0 <5.0.0"
789
+ // But NOT "^4.0.0" or "~4.0.0" (single ranges with spaces after)
790
+ const trimmed = range.trim();
791
+
792
+ // Remove single operator prefixes to see what's left
793
+ if (trimmed.startsWith('^') || trimmed.startsWith('~')) {
794
+ return false; // Single caret or tilde range
795
+ }
796
+
797
+ // Split by whitespace and check if there are multiple parts
798
+ const parts = trimmed.split(/\s+/).filter(p => p.length > 0);
799
+
800
+ // If we have multiple parts, it might be a complex AND range
801
+ // Examples: [">=4.0.0", "<5.0.0"], [">1.0.0", "<2.0.0"]
802
+ if (parts.length > 1) {
803
+ // Check if each part looks like a range operator
804
+ const rangeOperators = /^(>=?|<=?|\^|~|[0-9])/;
805
+ return parts.every(part => rangeOperators.test(part));
806
+ }
807
+
808
+ return false;
809
+ }
810
+
811
+ /**
812
+ * Parse a complex range expression into a structured format
813
+ * Handles:
814
+ * - OR: "^4.0.0 || ^5.0.0" → {type: 'or', ranges: [...]}
815
+ * - AND: ">=4.0.0 <5.0.0" → {type: 'and', ranges: [...]}
816
+ * - Simple: "^4.0.0" → {type: 'simple', range: {...}}
817
+ * @param {string} range - Complex range string
818
+ * @returns {Object} Parsed complex range structure
819
+ * @private
820
+ */
821
+ _parseComplexRange(range) {
822
+ // Split by OR first (|| has precedence)
823
+ if (range.includes('||')) {
824
+ const orParts = range.split('||').map(p => p.trim());
825
+ return {
826
+ type: 'or',
827
+ ranges: orParts.map(part => this._parseComplexRange(part))
828
+ };
829
+ }
830
+
831
+ // Split by AND (space-separated, no || present)
832
+ const andParts = range.trim().split(/\s+/).filter(p => p.length > 0);
833
+
834
+ if (andParts.length > 1) {
835
+ return {
836
+ type: 'and',
837
+ ranges: andParts.map(part => this._parseVersionRange(part))
838
+ };
839
+ }
840
+
841
+ // Simple range
842
+ return {
843
+ type: 'simple',
844
+ range: this._parseVersionRange(range)
845
+ };
846
+ }
847
+
848
+ /**
849
+ * Check if a version satisfies a single range
850
+ * @param {Object} rangeInfo - Parsed range information
851
+ * @param {Object} version - Parsed version to check
852
+ * @returns {boolean} True if version satisfies range
853
+ * @private
854
+ */
855
+ _satisfiesRange(rangeInfo, version) {
856
+ if (!rangeInfo || !version) {
857
+ return false;
858
+ }
859
+
860
+ // Use the existing _isUpdateAvailable logic which handles all operators
861
+ return this._isUpdateAvailable(rangeInfo, version);
862
+ }
863
+
864
+ /**
865
+ * Check if a version satisfies a complex range expression
866
+ * Handles AND/OR logic recursively
867
+ * @param {Object} complexRange - Parsed complex range structure
868
+ * @param {Object} version - Parsed version to check
869
+ * @returns {boolean} True if version satisfies the complex range
870
+ * @private
871
+ */
872
+ _satisfiesComplexRange(complexRange, version) {
873
+ if (!complexRange || !version) {
874
+ return false;
875
+ }
876
+
877
+ switch (complexRange.type) {
878
+ case 'simple':
879
+ return this._satisfiesRange(complexRange.range, version);
880
+
881
+ case 'and':
882
+ // ALL ranges must be satisfied
883
+ return complexRange.ranges.every(range => this._satisfiesRange(range, version));
884
+
885
+ case 'or':
886
+ // AT LEAST ONE range must be satisfied
887
+ return complexRange.ranges.some(range => this._satisfiesComplexRange(range, version));
888
+
889
+ default:
890
+ return false;
891
+ }
892
+ }
893
+
894
+ /**
895
+ * Check dependencies in batches to avoid overwhelming the registry
896
+ * @param {Object} dependencies - Map of package names to versions
897
+ * @returns {Promise<Object>} Map of packages with available updates
898
+ * @private
899
+ */
900
+ async _checkDependencies(dependencies) {
901
+ const updates = {};
902
+ const entries = Object.entries(dependencies);
903
+
904
+ // Process in batches
905
+ for (let i = 0; i < entries.length; i += RESOLVER_CONFIG.MAX_CONCURRENT_CHECKS) {
906
+ const batch = entries.slice(i, i + RESOLVER_CONFIG.MAX_CONCURRENT_CHECKS);
907
+
908
+ const promises = batch.map(async ([pkg, range]) => {
909
+ try {
910
+ const latest = await this._getLatestCompatibleVersion(pkg, range);
911
+
912
+ if (latest) {
913
+ // Preserve the range prefix (^, ~, etc.)
914
+ const prefix = range.match(/^[\^~]/)?.[0] || '^';
915
+ return { pkg, newVersion: `${prefix}${latest}`, oldVersion: range };
916
+ }
917
+
918
+ return null;
919
+ } catch (error) {
920
+ this.logger?.error(`Error checking ${pkg}:`, error.message);
921
+ return null;
922
+ }
923
+ });
924
+
925
+ const results = await Promise.all(promises);
926
+
927
+ results.forEach(result => {
928
+ if (result) {
929
+ updates[result.pkg] = result.newVersion;
930
+ }
931
+ });
932
+ }
933
+
934
+ return updates;
935
+ }
936
+
937
+ /**
938
+ * Execute tool with parsed parameters
939
+ * @param {Object} params - Parsed parameters
940
+ * @param {Object} context - Execution context
941
+ * @returns {Promise<Object>} Execution result
942
+ */
943
+ async execute(params, context = {}) {
944
+ try {
945
+ // Validate parameters
946
+ this._validateParameters(params);
947
+
948
+ const { path: targetPath, mode, includeDev } = params;
949
+ const { agentId } = context;
950
+
951
+ // Resolve and validate path
952
+ const resolvedPath = this._resolveAndValidatePath(targetPath, context);
953
+ const pkgPath = path.join(resolvedPath, 'package.json');
954
+
955
+ this.logger?.info('Dependency resolver executing', {
956
+ mode,
957
+ resolvedPath,
958
+ includeDev,
959
+ agentId
960
+ });
961
+
962
+ const output = [];
963
+ output.push(`🔍 Checking dependencies in: ${resolvedPath}`);
964
+ output.push(`Mode: ${mode}`);
965
+
966
+ // Check if package.json exists
967
+ try {
968
+ await fs.access(pkgPath);
969
+ } catch {
970
+ return {
971
+ success: false,
972
+ error: `No package.json found at: ${pkgPath}`,
973
+ output: output.join('\n')
974
+ };
975
+ }
976
+
977
+ // Read package.json
978
+ output.push('\n📦 Reading package.json...');
979
+ const pkgContent = await fs.readFile(pkgPath, 'utf-8');
980
+ const pkgData = JSON.parse(pkgContent);
981
+
982
+ // Collect dependencies
983
+ const allDeps = { ...pkgData.dependencies };
984
+
985
+ if (includeDev && pkgData.devDependencies) {
986
+ Object.assign(allDeps, pkgData.devDependencies);
987
+ }
988
+
989
+ if (Object.keys(allDeps).length === 0) {
990
+ return {
991
+ success: true,
992
+ mode,
993
+ message: 'No dependencies found in package.json',
994
+ statistics: {
995
+ totalDependencies: 0,
996
+ updatesAvailable: 0,
997
+ updatesApplied: 0,
998
+ errors: 0
999
+ },
1000
+ output: output.join('\n')
1001
+ };
1002
+ }
1003
+
1004
+ // Safety check
1005
+ if (Object.keys(allDeps).length > RESOLVER_CONFIG.MAX_DEPENDENCIES) {
1006
+ return {
1007
+ success: false,
1008
+ error: `Too many dependencies (${Object.keys(allDeps).length}), max allowed: ${RESOLVER_CONFIG.MAX_DEPENDENCIES}`,
1009
+ output: output.join('\n')
1010
+ };
1011
+ }
1012
+
1013
+ output.push(`📊 Found ${Object.keys(allDeps).length} dependencies to check`);
1014
+ output.push('🌐 Querying npm registry for updates...');
1015
+
1016
+ // Check for updates
1017
+ const updates = await this._checkDependencies(allDeps);
1018
+
1019
+ output.push(`\n✅ Registry check complete`);
1020
+ output.push(`📈 Updates available: ${Object.keys(updates).length}`);
1021
+
1022
+ if (Object.keys(updates).length > 0) {
1023
+ output.push('\n📋 Available updates:');
1024
+ for (const [pkg, newVersion] of Object.entries(updates)) {
1025
+ const oldVersion = allDeps[pkg];
1026
+ output.push(` • ${pkg}: ${oldVersion} ${newVersion}`);
1027
+ }
1028
+ } else {
1029
+ output.push('\n🎉 All dependencies are up-to-date!');
1030
+ }
1031
+
1032
+ // Handle modes
1033
+ if (mode === 'check') {
1034
+ // Check mode - just report
1035
+ if (Object.keys(updates).length > 0) {
1036
+ output.push('\n💡 Run with mode="fix" or mode="auto" to apply updates');
1037
+ }
1038
+
1039
+ return {
1040
+ success: true,
1041
+ mode: 'check',
1042
+ message: `Found ${Object.keys(updates).length} package(s) with available updates`,
1043
+ statistics: {
1044
+ totalDependencies: Object.keys(allDeps).length,
1045
+ updatesAvailable: Object.keys(updates).length,
1046
+ updatesApplied: 0,
1047
+ errors: 0
1048
+ },
1049
+ updates,
1050
+ output: output.join('\n')
1051
+ };
1052
+ }
1053
+
1054
+ // Fix or auto mode - apply updates
1055
+ if ((mode === 'fix' || mode === 'auto') && Object.keys(updates).length > 0) {
1056
+ // Create backup
1057
+ output.push('\n💾 Creating backup of package.json...');
1058
+ const backupPath = await this._createBackup(pkgPath);
1059
+
1060
+ if (backupPath) {
1061
+ output.push(`✅ Backup created: ${path.basename(backupPath)}`);
1062
+ } else {
1063
+ output.push('⚠️ Backup creation failed, continuing anyway...');
1064
+ }
1065
+
1066
+ // Update package.json
1067
+ output.push('\n🛠 Updating package.json...');
1068
+
1069
+ for (const [pkg, newVersion] of Object.entries(updates)) {
1070
+ if (pkgData.dependencies?.[pkg]) {
1071
+ pkgData.dependencies[pkg] = newVersion;
1072
+ }
1073
+ if (pkgData.devDependencies?.[pkg]) {
1074
+ pkgData.devDependencies[pkg] = newVersion;
1075
+ }
1076
+ }
1077
+
1078
+ // Write updated package.json
1079
+ await fs.writeFile(pkgPath, JSON.stringify(pkgData, null, 2) + '\n');
1080
+ output.push('✅ package.json updated');
1081
+
1082
+ // Run npm install
1083
+ output.push('\n📥 Installing dependencies (this may take a while)...');
1084
+
1085
+ try {
1086
+ const { stdout, stderr } = await exec('npm install', {
1087
+ cwd: resolvedPath,
1088
+ timeout: RESOLVER_CONFIG.NPM_COMMAND_TIMEOUT,
1089
+ maxBuffer: 1024 * 1024 * 10 // 10MB buffer
1090
+ });
1091
+
1092
+ if (stdout) {
1093
+ // Only show summary, not full output
1094
+ const lines = stdout.trim().split('\n');
1095
+ if (lines.length > 10) {
1096
+ output.push(' ' + lines.slice(-5).join('\n '));
1097
+ } else {
1098
+ output.push(' ' + stdout.trim());
1099
+ }
1100
+ }
1101
+
1102
+ if (stderr && !stderr.includes('npm WARN')) {
1103
+ output.push(`⚠️ ${stderr.trim()}`);
1104
+ }
1105
+
1106
+ output.push('✅ Installation complete');
1107
+
1108
+ // Show installed versions
1109
+ try {
1110
+ const { stdout: lsOutput } = await exec('npm ls --depth=0 --json', {
1111
+ cwd: resolvedPath,
1112
+ timeout: 30000
1113
+ });
1114
+
1115
+ const lsData = JSON.parse(lsOutput);
1116
+
1117
+ if (lsData.dependencies) {
1118
+ output.push('\n📦 Installed versions:');
1119
+ for (const pkg of Object.keys(updates)) {
1120
+ if (lsData.dependencies[pkg]) {
1121
+ output.push(` • ${pkg}@${lsData.dependencies[pkg].version}`);
1122
+ }
1123
+ }
1124
+ }
1125
+ } catch (lsError) {
1126
+ // npm ls might fail with peer dependency warnings - that's okay
1127
+ this.logger?.debug('npm ls failed:', lsError.message);
1128
+ }
1129
+
1130
+ return {
1131
+ success: true,
1132
+ mode,
1133
+ message: `Successfully updated ${Object.keys(updates).length} package(s)`,
1134
+ statistics: {
1135
+ totalDependencies: Object.keys(allDeps).length,
1136
+ updatesAvailable: Object.keys(updates).length,
1137
+ updatesApplied: Object.keys(updates).length,
1138
+ errors: 0
1139
+ },
1140
+ updates,
1141
+ backupCreated: backupPath !== null,
1142
+ backupPath,
1143
+ output: output.join('\n')
1144
+ };
1145
+
1146
+ } catch (installError) {
1147
+ output.push(`\n❌ npm install failed: ${installError.message}`);
1148
+
1149
+ // Try to restore from backup
1150
+ if (backupPath) {
1151
+ try {
1152
+ await fs.copyFile(backupPath, pkgPath);
1153
+ output.push('🔄 Restored package.json from backup');
1154
+ } catch {
1155
+ output.push('⚠️ Failed to restore backup');
1156
+ }
1157
+ }
1158
+
1159
+ return {
1160
+ success: false,
1161
+ mode,
1162
+ error: `npm install failed: ${installError.message}`,
1163
+ statistics: {
1164
+ totalDependencies: Object.keys(allDeps).length,
1165
+ updatesAvailable: Object.keys(updates).length,
1166
+ updatesApplied: 0,
1167
+ errors: 1
1168
+ },
1169
+ updates,
1170
+ backupCreated: backupPath !== null,
1171
+ output: output.join('\n')
1172
+ };
1173
+ }
1174
+ } else if (mode === 'fix' || mode === 'auto') {
1175
+ // No updates needed
1176
+ output.push('\n✨ No updates needed');
1177
+
1178
+ return {
1179
+ success: true,
1180
+ mode,
1181
+ message: 'All dependencies are up-to-date',
1182
+ statistics: {
1183
+ totalDependencies: Object.keys(allDeps).length,
1184
+ updatesAvailable: 0,
1185
+ updatesApplied: 0,
1186
+ errors: 0
1187
+ },
1188
+ updates: {},
1189
+ output: output.join('\n')
1190
+ };
1191
+ }
1192
+
1193
+ } catch (error) {
1194
+ this.logger?.error('Dependency resolver error:', error);
1195
+
1196
+ return {
1197
+ success: false,
1198
+ error: error.message,
1199
+ statistics: {
1200
+ totalDependencies: 0,
1201
+ updatesAvailable: 0,
1202
+ updatesApplied: 0,
1203
+ errors: 1
1204
+ },
1205
+ output: error.message
1206
+ };
1207
+ }
1208
+ }
1209
+ }
1210
+
1211
+ export default DependencyResolverTool;