@machina.ai/cell-cli-core 1.0.13-rc9 → 1.0.21-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (484) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.js +3 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/package.json +12 -4
  5. package/dist/src/code_assist/converter.d.ts +6 -3
  6. package/dist/src/code_assist/converter.js +4 -2
  7. package/dist/src/code_assist/converter.js.map +1 -1
  8. package/dist/src/code_assist/converter.test.js +61 -11
  9. package/dist/src/code_assist/converter.test.js.map +1 -1
  10. package/dist/src/code_assist/oauth2.d.ts +1 -0
  11. package/dist/src/code_assist/oauth2.js +43 -18
  12. package/dist/src/code_assist/oauth2.js.map +1 -1
  13. package/dist/src/code_assist/oauth2.test.js +142 -9
  14. package/dist/src/code_assist/oauth2.test.js.map +1 -1
  15. package/dist/src/code_assist/server.d.ts +2 -2
  16. package/dist/src/code_assist/server.js +5 -5
  17. package/dist/src/code_assist/server.js.map +1 -1
  18. package/dist/src/code_assist/server.test.js +13 -10
  19. package/dist/src/code_assist/server.test.js.map +1 -1
  20. package/dist/src/code_assist/setup.js +49 -18
  21. package/dist/src/code_assist/setup.js.map +1 -1
  22. package/dist/src/code_assist/setup.test.js +115 -9
  23. package/dist/src/code_assist/setup.test.js.map +1 -1
  24. package/dist/src/config/config.d.ts +71 -13
  25. package/dist/src/config/config.js +154 -41
  26. package/dist/src/config/config.js.map +1 -1
  27. package/dist/src/config/config.test.js +206 -21
  28. package/dist/src/config/config.test.js.map +1 -1
  29. package/dist/src/config/flashFallback.test.js +19 -47
  30. package/dist/src/config/flashFallback.test.js.map +1 -1
  31. package/dist/src/config/models.d.ts +1 -0
  32. package/dist/src/config/models.js +1 -0
  33. package/dist/src/config/models.js.map +1 -1
  34. package/dist/src/core/client.d.ts +15 -16
  35. package/dist/src/core/client.js +247 -104
  36. package/dist/src/core/client.js.map +1 -1
  37. package/dist/src/core/client.test.js +798 -49
  38. package/dist/src/core/client.test.js.map +1 -1
  39. package/dist/src/core/contentGenerator.d.ts +2 -2
  40. package/dist/src/core/contentGenerator.js +23 -21
  41. package/dist/src/core/contentGenerator.js.map +1 -1
  42. package/dist/src/core/contentGenerator.test.js +30 -126
  43. package/dist/src/core/contentGenerator.test.js.map +1 -1
  44. package/dist/src/core/coreToolScheduler.d.ts +23 -10
  45. package/dist/src/core/coreToolScheduler.js +201 -77
  46. package/dist/src/core/coreToolScheduler.js.map +1 -1
  47. package/dist/src/core/coreToolScheduler.test.js +322 -94
  48. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  49. package/dist/src/core/geminiChat.d.ts +8 -6
  50. package/dist/src/core/geminiChat.js +49 -51
  51. package/dist/src/core/geminiChat.js.map +1 -1
  52. package/dist/src/core/geminiChat.test.js +2 -2
  53. package/dist/src/core/geminiChat.test.js.map +1 -1
  54. package/dist/src/core/geminiRequest.js +2 -37
  55. package/dist/src/core/geminiRequest.js.map +1 -1
  56. package/dist/src/core/logger.d.ts +24 -1
  57. package/dist/src/core/logger.js +128 -4
  58. package/dist/src/core/logger.js.map +1 -1
  59. package/dist/src/core/logger.test.js +157 -11
  60. package/dist/src/core/logger.test.js.map +1 -1
  61. package/dist/src/core/loggingContentGenerator.d.ts +25 -0
  62. package/dist/src/core/loggingContentGenerator.js +95 -0
  63. package/dist/src/core/loggingContentGenerator.js.map +1 -0
  64. package/dist/src/core/nonInteractiveToolExecutor.js +39 -4
  65. package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
  66. package/dist/src/core/nonInteractiveToolExecutor.test.js +85 -62
  67. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  68. package/dist/src/core/prompts.js +43 -19
  69. package/dist/src/core/prompts.js.map +1 -1
  70. package/dist/src/core/prompts.test.js +118 -1
  71. package/dist/src/core/prompts.test.js.map +1 -1
  72. package/dist/src/core/subagent.d.ts +230 -0
  73. package/dist/src/core/subagent.js +447 -0
  74. package/dist/src/core/subagent.js.map +1 -0
  75. package/dist/src/core/subagent.test.js +515 -0
  76. package/dist/src/core/subagent.test.js.map +1 -0
  77. package/dist/src/core/tokenLimits.js +1 -0
  78. package/dist/src/core/tokenLimits.js.map +1 -1
  79. package/dist/src/core/turn.d.ts +3 -0
  80. package/dist/src/core/turn.js +4 -0
  81. package/dist/src/core/turn.js.map +1 -1
  82. package/dist/src/core/turn.test.js +4 -0
  83. package/dist/src/core/turn.test.js.map +1 -1
  84. package/dist/src/generated/git-commit.d.ts +7 -0
  85. package/dist/src/generated/git-commit.js +10 -0
  86. package/dist/src/generated/git-commit.js.map +1 -0
  87. package/dist/src/ide/constants.d.ts +6 -0
  88. package/dist/src/ide/constants.js +7 -0
  89. package/dist/src/ide/constants.js.map +1 -0
  90. package/dist/src/ide/detect-ide.d.ts +20 -0
  91. package/dist/src/ide/detect-ide.js +86 -0
  92. package/dist/src/ide/detect-ide.js.map +1 -0
  93. package/dist/src/ide/detect-ide.test.js +65 -0
  94. package/dist/src/ide/detect-ide.test.js.map +1 -0
  95. package/dist/src/ide/ide-client.d.ts +63 -0
  96. package/dist/src/ide/ide-client.js +320 -0
  97. package/dist/src/ide/ide-client.js.map +1 -0
  98. package/dist/src/ide/ide-client.test.d.ts +6 -0
  99. package/dist/src/ide/ide-client.test.js +43 -0
  100. package/dist/src/ide/ide-client.test.js.map +1 -0
  101. package/dist/src/ide/ide-installer.d.ts +14 -0
  102. package/dist/src/ide/ide-installer.js +98 -0
  103. package/dist/src/ide/ide-installer.js.map +1 -0
  104. package/dist/src/ide/ide-installer.test.d.ts +6 -0
  105. package/dist/src/ide/ide-installer.test.js +53 -0
  106. package/dist/src/ide/ide-installer.test.js.map +1 -0
  107. package/dist/src/ide/ideContext.d.ts +374 -0
  108. package/dist/src/ide/ideContext.js +147 -0
  109. package/dist/src/ide/ideContext.js.map +1 -0
  110. package/dist/src/ide/ideContext.test.d.ts +6 -0
  111. package/dist/src/ide/ideContext.test.js +265 -0
  112. package/dist/src/ide/ideContext.test.js.map +1 -0
  113. package/dist/src/ide/process-utils.d.ts +14 -0
  114. package/dist/src/ide/process-utils.js +57 -0
  115. package/dist/src/ide/process-utils.js.map +1 -0
  116. package/dist/src/index.d.ts +17 -1
  117. package/dist/src/index.js +20 -1
  118. package/dist/src/index.js.map +1 -1
  119. package/dist/src/mcp/google-auth-provider.d.ts +23 -0
  120. package/dist/src/mcp/google-auth-provider.js +72 -0
  121. package/dist/src/mcp/google-auth-provider.js.map +1 -0
  122. package/dist/src/mcp/google-auth-provider.test.d.ts +6 -0
  123. package/dist/src/mcp/google-auth-provider.test.js +89 -0
  124. package/dist/src/mcp/google-auth-provider.test.js.map +1 -0
  125. package/dist/src/mcp/oauth-provider.d.ts +6 -2
  126. package/dist/src/mcp/oauth-provider.js +208 -53
  127. package/dist/src/mcp/oauth-provider.js.map +1 -1
  128. package/dist/src/mcp/oauth-provider.test.js +222 -70
  129. package/dist/src/mcp/oauth-provider.test.js.map +1 -1
  130. package/dist/src/mcp/oauth-token-storage.d.ts +3 -1
  131. package/dist/src/mcp/oauth-token-storage.js +3 -1
  132. package/dist/src/mcp/oauth-token-storage.js.map +1 -1
  133. package/dist/src/mcp/oauth-utils.d.ts +3 -1
  134. package/dist/src/mcp/oauth-utils.js +52 -14
  135. package/dist/src/mcp/oauth-utils.js.map +1 -1
  136. package/dist/src/mcp/oauth-utils.test.js +18 -3
  137. package/dist/src/mcp/oauth-utils.test.js.map +1 -1
  138. package/dist/src/mocks/msw.d.ts +6 -0
  139. package/dist/src/mocks/msw.js +8 -0
  140. package/dist/src/mocks/msw.js.map +1 -0
  141. package/dist/src/prompts/mcp-prompts.d.ts +8 -0
  142. package/dist/src/prompts/mcp-prompts.js +13 -0
  143. package/dist/src/prompts/mcp-prompts.js.map +1 -0
  144. package/dist/src/prompts/prompt-registry.d.ts +34 -0
  145. package/dist/src/prompts/prompt-registry.js +63 -0
  146. package/dist/src/prompts/prompt-registry.js.map +1 -0
  147. package/dist/src/services/chatRecordingService.d.ts +150 -0
  148. package/dist/src/services/chatRecordingService.js +318 -0
  149. package/dist/src/services/chatRecordingService.js.map +1 -0
  150. package/dist/src/services/chatRecordingService.test.d.ts +6 -0
  151. package/dist/src/services/chatRecordingService.test.js +288 -0
  152. package/dist/src/services/chatRecordingService.test.js.map +1 -0
  153. package/dist/src/services/fileDiscoveryService.test.js +101 -60
  154. package/dist/src/services/fileDiscoveryService.test.js.map +1 -1
  155. package/dist/src/services/fileSystemService.d.ts +31 -0
  156. package/dist/src/services/fileSystemService.js +18 -0
  157. package/dist/src/services/fileSystemService.js.map +1 -0
  158. package/dist/src/services/fileSystemService.test.d.ts +6 -0
  159. package/dist/src/services/fileSystemService.test.js +41 -0
  160. package/dist/src/services/fileSystemService.test.js.map +1 -0
  161. package/dist/src/services/gitService.test.js +67 -86
  162. package/dist/src/services/gitService.test.js.map +1 -1
  163. package/dist/src/services/loopDetectionService.d.ts +51 -5
  164. package/dist/src/services/loopDetectionService.js +152 -45
  165. package/dist/src/services/loopDetectionService.js.map +1 -1
  166. package/dist/src/services/loopDetectionService.test.js +286 -89
  167. package/dist/src/services/loopDetectionService.test.js.map +1 -1
  168. package/dist/src/services/shellExecutionService.d.ts +70 -0
  169. package/dist/src/services/shellExecutionService.js +175 -0
  170. package/dist/src/services/shellExecutionService.js.map +1 -0
  171. package/dist/src/services/shellExecutionService.test.d.ts +6 -0
  172. package/dist/src/services/shellExecutionService.test.js +282 -0
  173. package/dist/src/services/shellExecutionService.test.js.map +1 -0
  174. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +81 -9
  175. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +356 -182
  176. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  177. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +17 -0
  178. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +342 -0
  179. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -0
  180. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +19 -2
  181. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +51 -9
  182. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  183. package/dist/src/telemetry/constants.d.ts +4 -0
  184. package/dist/src/telemetry/constants.js +4 -0
  185. package/dist/src/telemetry/constants.js.map +1 -1
  186. package/dist/src/telemetry/file-exporters.d.ts +28 -0
  187. package/dist/src/telemetry/file-exporters.js +62 -0
  188. package/dist/src/telemetry/file-exporters.js.map +1 -0
  189. package/dist/src/telemetry/index.d.ts +2 -2
  190. package/dist/src/telemetry/index.js +2 -2
  191. package/dist/src/telemetry/index.js.map +1 -1
  192. package/dist/src/telemetry/integration.test.circular.js +1 -0
  193. package/dist/src/telemetry/integration.test.circular.js.map +1 -1
  194. package/dist/src/telemetry/loggers.d.ts +6 -1
  195. package/dist/src/telemetry/loggers.js +87 -6
  196. package/dist/src/telemetry/loggers.js.map +1 -1
  197. package/dist/src/telemetry/loggers.test.circular.js +9 -2
  198. package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
  199. package/dist/src/telemetry/loggers.test.js +51 -11
  200. package/dist/src/telemetry/loggers.test.js.map +1 -1
  201. package/dist/src/telemetry/metrics.d.ts +7 -2
  202. package/dist/src/telemetry/metrics.js +26 -6
  203. package/dist/src/telemetry/metrics.js.map +1 -1
  204. package/dist/src/telemetry/metrics.test.js +81 -1
  205. package/dist/src/telemetry/metrics.test.js.map +1 -1
  206. package/dist/src/telemetry/sdk.d.ts +1 -1
  207. package/dist/src/telemetry/sdk.js +77 -30
  208. package/dist/src/telemetry/sdk.js.map +1 -1
  209. package/dist/src/telemetry/sdk.test.d.ts +6 -0
  210. package/dist/src/telemetry/sdk.test.js +82 -0
  211. package/dist/src/telemetry/sdk.test.js.map +1 -0
  212. package/dist/src/telemetry/telemetry.test.js +2 -2
  213. package/dist/src/telemetry/telemetry.test.js.map +1 -1
  214. package/dist/src/telemetry/tool-call-decision.d.ts +13 -0
  215. package/dist/src/telemetry/tool-call-decision.js +29 -0
  216. package/dist/src/telemetry/tool-call-decision.js.map +1 -0
  217. package/dist/src/telemetry/types.d.ts +74 -18
  218. package/dist/src/telemetry/types.js +110 -32
  219. package/dist/src/telemetry/types.js.map +1 -1
  220. package/dist/src/telemetry/uiTelemetry.d.ts +8 -1
  221. package/dist/src/telemetry/uiTelemetry.js +17 -2
  222. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  223. package/dist/src/telemetry/uiTelemetry.test.js +60 -10
  224. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
  225. package/dist/src/test-utils/config.d.ts +16 -0
  226. package/dist/src/test-utils/config.js +32 -0
  227. package/dist/src/test-utils/config.js.map +1 -0
  228. package/dist/src/test-utils/mockWorkspaceContext.d.ts +13 -0
  229. package/dist/src/test-utils/mockWorkspaceContext.js +24 -0
  230. package/dist/src/test-utils/mockWorkspaceContext.js.map +1 -0
  231. package/dist/src/test-utils/tools.d.ts +44 -0
  232. package/dist/src/test-utils/tools.js +105 -0
  233. package/dist/src/test-utils/tools.js.map +1 -0
  234. package/dist/src/tools/diffOptions.d.ts +2 -0
  235. package/dist/src/tools/diffOptions.js +28 -0
  236. package/dist/src/tools/diffOptions.js.map +1 -1
  237. package/dist/src/tools/diffOptions.test.d.ts +6 -0
  238. package/dist/src/tools/diffOptions.test.js +119 -0
  239. package/dist/src/tools/diffOptions.test.js.map +1 -0
  240. package/dist/src/tools/edit.d.ts +10 -34
  241. package/dist/src/tools/edit.js +171 -131
  242. package/dist/src/tools/edit.js.map +1 -1
  243. package/dist/src/tools/edit.test.js +220 -43
  244. package/dist/src/tools/edit.test.js.map +1 -1
  245. package/dist/src/tools/glob.d.ts +4 -11
  246. package/dist/src/tools/glob.js +129 -97
  247. package/dist/src/tools/glob.js.map +1 -1
  248. package/dist/src/tools/glob.test.js +73 -17
  249. package/dist/src/tools/glob.test.js.map +1 -1
  250. package/dist/src/tools/grep.d.ts +5 -37
  251. package/dist/src/tools/grep.js +175 -100
  252. package/dist/src/tools/grep.js.map +1 -1
  253. package/dist/src/tools/grep.test.js +112 -28
  254. package/dist/src/tools/grep.test.js.map +1 -1
  255. package/dist/src/tools/ls.d.ts +4 -23
  256. package/dist/src/tools/ls.js +77 -78
  257. package/dist/src/tools/ls.js.map +1 -1
  258. package/dist/src/tools/ls.test.d.ts +6 -0
  259. package/dist/src/tools/ls.test.js +384 -0
  260. package/dist/src/tools/ls.test.js.map +1 -0
  261. package/dist/src/tools/mcp-client-manager.d.ts +38 -0
  262. package/dist/src/tools/mcp-client-manager.js +74 -0
  263. package/dist/src/tools/mcp-client-manager.js.map +1 -0
  264. package/dist/src/tools/mcp-client-manager.test.d.ts +6 -0
  265. package/dist/src/tools/mcp-client-manager.test.js +39 -0
  266. package/dist/src/tools/mcp-client-manager.test.js.map +1 -0
  267. package/dist/src/tools/mcp-client.d.ts +86 -4
  268. package/dist/src/tools/mcp-client.js +730 -59
  269. package/dist/src/tools/mcp-client.js.map +1 -1
  270. package/dist/src/tools/mcp-client.test.js +295 -22
  271. package/dist/src/tools/mcp-client.test.js.map +1 -1
  272. package/dist/src/tools/mcp-tool.d.ts +6 -13
  273. package/dist/src/tools/mcp-tool.js +163 -76
  274. package/dist/src/tools/mcp-tool.js.map +1 -1
  275. package/dist/src/tools/mcp-tool.test.js +400 -29
  276. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  277. package/dist/src/tools/memoryTool.d.ts +14 -3
  278. package/dist/src/tools/memoryTool.js +159 -40
  279. package/dist/src/tools/memoryTool.js.map +1 -1
  280. package/dist/src/tools/memoryTool.test.js +116 -11
  281. package/dist/src/tools/memoryTool.test.js.map +1 -1
  282. package/dist/src/tools/modifiable-tool.d.ts +9 -6
  283. package/dist/src/tools/modifiable-tool.js +6 -3
  284. package/dist/src/tools/modifiable-tool.js.map +1 -1
  285. package/dist/src/tools/modifiable-tool.test.js +63 -74
  286. package/dist/src/tools/modifiable-tool.test.js.map +1 -1
  287. package/dist/src/tools/read-file.d.ts +4 -6
  288. package/dist/src/tools/read-file.js +100 -53
  289. package/dist/src/tools/read-file.js.map +1 -1
  290. package/dist/src/tools/read-file.test.js +238 -111
  291. package/dist/src/tools/read-file.test.js.map +1 -1
  292. package/dist/src/tools/read-many-files.d.ts +3 -6
  293. package/dist/src/tools/read-many-files.js +232 -157
  294. package/dist/src/tools/read-many-files.js.map +1 -1
  295. package/dist/src/tools/read-many-files.test.js +231 -34
  296. package/dist/src/tools/read-many-files.test.js.map +1 -1
  297. package/dist/src/tools/shell.d.ts +6 -28
  298. package/dist/src/tools/shell.js +249 -366
  299. package/dist/src/tools/shell.js.map +1 -1
  300. package/dist/src/tools/shell.test.js +305 -384
  301. package/dist/src/tools/shell.test.js.map +1 -1
  302. package/dist/src/tools/tool-error.d.ts +27 -0
  303. package/dist/src/tools/tool-error.js +32 -0
  304. package/dist/src/tools/tool-error.js.map +1 -0
  305. package/dist/src/tools/tool-registry.d.ts +38 -23
  306. package/dist/src/tools/tool-registry.js +127 -99
  307. package/dist/src/tools/tool-registry.js.map +1 -1
  308. package/dist/src/tools/tool-registry.test.js +37 -212
  309. package/dist/src/tools/tool-registry.test.js.map +1 -1
  310. package/dist/src/tools/tools.d.ts +145 -92
  311. package/dist/src/tools/tools.js +188 -61
  312. package/dist/src/tools/tools.js.map +1 -1
  313. package/dist/src/tools/tools.test.d.ts +6 -0
  314. package/dist/src/tools/tools.test.js +206 -0
  315. package/dist/src/tools/tools.test.js.map +1 -0
  316. package/dist/src/tools/web-fetch.d.ts +4 -7
  317. package/dist/src/tools/web-fetch.js +58 -64
  318. package/dist/src/tools/web-fetch.js.map +1 -1
  319. package/dist/src/tools/web-fetch.test.js +8 -4
  320. package/dist/src/tools/web-fetch.test.js.map +1 -1
  321. package/dist/src/tools/web-search.d.ts +4 -5
  322. package/dist/src/tools/web-search.js +47 -51
  323. package/dist/src/tools/web-search.js.map +1 -1
  324. package/dist/src/tools/web-search.test.d.ts +6 -0
  325. package/dist/src/tools/web-search.test.js +139 -0
  326. package/dist/src/tools/web-search.test.js.map +1 -0
  327. package/dist/src/tools/write-file.d.ts +20 -11
  328. package/dist/src/tools/write-file.js +198 -141
  329. package/dist/src/tools/write-file.js.map +1 -1
  330. package/dist/src/tools/write-file.test.js +190 -76
  331. package/dist/src/tools/write-file.test.js.map +1 -1
  332. package/dist/src/utils/bfsFileSearch.js +51 -27
  333. package/dist/src/utils/bfsFileSearch.js.map +1 -1
  334. package/dist/src/utils/bfsFileSearch.test.js +137 -136
  335. package/dist/src/utils/bfsFileSearch.test.js.map +1 -1
  336. package/dist/src/utils/browser.js +4 -3
  337. package/dist/src/utils/browser.js.map +1 -1
  338. package/dist/src/utils/editCorrector.js +23 -24
  339. package/dist/src/utils/editCorrector.js.map +1 -1
  340. package/dist/src/utils/editor.d.ts +2 -2
  341. package/dist/src/utils/editor.js +23 -6
  342. package/dist/src/utils/editor.js.map +1 -1
  343. package/dist/src/utils/editor.test.js +67 -15
  344. package/dist/src/utils/editor.test.js.map +1 -1
  345. package/dist/src/utils/environmentContext.d.ts +21 -0
  346. package/dist/src/utils/environmentContext.js +90 -0
  347. package/dist/src/utils/environmentContext.js.map +1 -0
  348. package/dist/src/utils/environmentContext.test.d.ts +6 -0
  349. package/dist/src/utils/environmentContext.test.js +140 -0
  350. package/dist/src/utils/environmentContext.test.js.map +1 -0
  351. package/dist/src/utils/errorParsing.d.ts +8 -0
  352. package/dist/src/utils/errorParsing.js +93 -0
  353. package/dist/src/utils/errorParsing.js.map +1 -0
  354. package/dist/src/utils/errorParsing.test.d.ts +6 -0
  355. package/dist/src/utils/errorParsing.test.js +172 -0
  356. package/dist/src/utils/errorParsing.test.js.map +1 -0
  357. package/dist/src/utils/errorReporting.d.ts +1 -1
  358. package/dist/src/utils/errorReporting.js +2 -2
  359. package/dist/src/utils/errorReporting.js.map +1 -1
  360. package/dist/src/utils/errorReporting.test.js +44 -38
  361. package/dist/src/utils/errorReporting.test.js.map +1 -1
  362. package/dist/src/utils/fileUtils.d.ts +9 -1
  363. package/dist/src/utils/fileUtils.js +27 -13
  364. package/dist/src/utils/fileUtils.js.map +1 -1
  365. package/dist/src/utils/fileUtils.test.js +61 -18
  366. package/dist/src/utils/fileUtils.test.js.map +1 -1
  367. package/dist/src/utils/filesearch/crawlCache.d.ts +25 -0
  368. package/dist/src/utils/filesearch/crawlCache.js +57 -0
  369. package/dist/src/utils/filesearch/crawlCache.js.map +1 -0
  370. package/dist/src/utils/filesearch/crawlCache.test.d.ts +6 -0
  371. package/dist/src/utils/filesearch/crawlCache.test.js +103 -0
  372. package/dist/src/utils/filesearch/crawlCache.test.js.map +1 -0
  373. package/dist/src/utils/filesearch/crawler.d.ts +15 -0
  374. package/dist/src/utils/filesearch/crawler.js +50 -0
  375. package/dist/src/utils/filesearch/crawler.js.map +1 -0
  376. package/dist/src/utils/filesearch/crawler.test.d.ts +6 -0
  377. package/dist/src/utils/filesearch/crawler.test.js +468 -0
  378. package/dist/src/utils/filesearch/crawler.test.js.map +1 -0
  379. package/dist/src/utils/filesearch/fileSearch.d.ts +37 -0
  380. package/dist/src/utils/filesearch/fileSearch.js +186 -0
  381. package/dist/src/utils/filesearch/fileSearch.js.map +1 -0
  382. package/dist/src/utils/filesearch/fileSearch.test.d.ts +6 -0
  383. package/dist/src/utils/filesearch/fileSearch.test.js +552 -0
  384. package/dist/src/utils/filesearch/fileSearch.test.js.map +1 -0
  385. package/dist/src/utils/filesearch/ignore.d.ts +42 -0
  386. package/dist/src/utils/filesearch/ignore.js +106 -0
  387. package/dist/src/utils/filesearch/ignore.js.map +1 -0
  388. package/dist/src/utils/filesearch/ignore.test.d.ts +6 -0
  389. package/dist/src/utils/filesearch/ignore.test.js +144 -0
  390. package/dist/src/utils/filesearch/ignore.test.js.map +1 -0
  391. package/dist/src/utils/filesearch/result-cache.d.ts +33 -0
  392. package/dist/src/utils/filesearch/result-cache.js +59 -0
  393. package/dist/src/utils/filesearch/result-cache.js.map +1 -0
  394. package/dist/src/utils/filesearch/result-cache.test.d.ts +6 -0
  395. package/dist/src/utils/filesearch/result-cache.test.js +46 -0
  396. package/dist/src/utils/filesearch/result-cache.test.js.map +1 -0
  397. package/dist/src/utils/flashFallback.integration.test.js +6 -0
  398. package/dist/src/utils/flashFallback.integration.test.js.map +1 -1
  399. package/dist/src/utils/formatters.d.ts +6 -0
  400. package/dist/src/utils/formatters.js +16 -0
  401. package/dist/src/utils/formatters.js.map +1 -0
  402. package/dist/src/utils/getFolderStructure.test.js +11 -13
  403. package/dist/src/utils/getFolderStructure.test.js.map +1 -1
  404. package/dist/src/utils/gitIgnoreParser.js +5 -11
  405. package/dist/src/utils/gitIgnoreParser.js.map +1 -1
  406. package/dist/src/utils/gitIgnoreParser.test.js +58 -61
  407. package/dist/src/utils/gitIgnoreParser.test.js.map +1 -1
  408. package/dist/src/utils/memoryDiscovery.d.ts +1 -1
  409. package/dist/src/utils/memoryDiscovery.js +76 -83
  410. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  411. package/dist/src/utils/memoryDiscovery.test.js +122 -372
  412. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  413. package/dist/src/utils/memoryImportProcessor.d.ts +19 -12
  414. package/dist/src/utils/memoryImportProcessor.js +240 -85
  415. package/dist/src/utils/memoryImportProcessor.js.map +1 -1
  416. package/dist/src/utils/memoryImportProcessor.test.js +593 -51
  417. package/dist/src/utils/memoryImportProcessor.test.js.map +1 -1
  418. package/dist/src/utils/nextSpeakerChecker.js +4 -25
  419. package/dist/src/utils/nextSpeakerChecker.js.map +1 -1
  420. package/dist/src/utils/partUtils.d.ts +14 -0
  421. package/dist/src/utils/partUtils.js +65 -0
  422. package/dist/src/utils/partUtils.js.map +1 -0
  423. package/dist/src/utils/partUtils.test.d.ts +6 -0
  424. package/dist/src/utils/partUtils.test.js +130 -0
  425. package/dist/src/utils/partUtils.test.js.map +1 -0
  426. package/dist/src/utils/paths.d.ts +18 -2
  427. package/dist/src/utils/paths.js +39 -7
  428. package/dist/src/utils/paths.js.map +1 -1
  429. package/dist/src/utils/paths.test.d.ts +6 -0
  430. package/dist/src/utils/paths.test.js +225 -0
  431. package/dist/src/utils/paths.test.js.map +1 -0
  432. package/dist/src/utils/quotaErrorDetection.d.ts +1 -5
  433. package/dist/src/utils/quotaErrorDetection.js.map +1 -1
  434. package/dist/src/utils/retry.d.ts +3 -0
  435. package/dist/src/utils/retry.js.map +1 -1
  436. package/dist/src/utils/retry.test.js.map +1 -1
  437. package/dist/src/utils/schemaValidator.d.ts +1 -8
  438. package/dist/src/utils/schemaValidator.js +1 -32
  439. package/dist/src/utils/schemaValidator.js.map +1 -1
  440. package/dist/src/utils/secure-browser-launcher.d.ts +23 -0
  441. package/dist/src/utils/secure-browser-launcher.js +165 -0
  442. package/dist/src/utils/secure-browser-launcher.js.map +1 -0
  443. package/dist/src/utils/secure-browser-launcher.test.d.ts +6 -0
  444. package/dist/src/utils/secure-browser-launcher.test.js +149 -0
  445. package/dist/src/utils/secure-browser-launcher.test.js.map +1 -0
  446. package/dist/src/utils/shell-utils.d.ts +117 -0
  447. package/dist/src/utils/shell-utils.js +376 -0
  448. package/dist/src/utils/shell-utils.js.map +1 -0
  449. package/dist/src/utils/shell-utils.test.d.ts +6 -0
  450. package/dist/src/utils/shell-utils.test.js +328 -0
  451. package/dist/src/utils/shell-utils.test.js.map +1 -0
  452. package/dist/src/utils/summarizer.js +3 -32
  453. package/dist/src/utils/summarizer.js.map +1 -1
  454. package/dist/src/utils/systemEncoding.js +1 -1
  455. package/dist/src/utils/systemEncoding.js.map +1 -1
  456. package/dist/src/utils/systemEncoding.test.js +23 -23
  457. package/dist/src/utils/systemEncoding.test.js.map +1 -1
  458. package/dist/src/utils/textUtils.d.ts +13 -0
  459. package/dist/src/utils/textUtils.js +28 -0
  460. package/dist/src/utils/textUtils.js.map +1 -0
  461. package/dist/src/utils/user_account.js +58 -48
  462. package/dist/src/utils/user_account.js.map +1 -1
  463. package/dist/src/utils/user_account.test.js +76 -9
  464. package/dist/src/utils/user_account.test.js.map +1 -1
  465. package/dist/src/utils/workspaceContext.d.ts +66 -0
  466. package/dist/src/utils/workspaceContext.js +165 -0
  467. package/dist/src/utils/workspaceContext.js.map +1 -0
  468. package/dist/src/utils/workspaceContext.test.d.ts +6 -0
  469. package/dist/src/utils/workspaceContext.test.js +293 -0
  470. package/dist/src/utils/workspaceContext.test.js.map +1 -0
  471. package/dist/tsconfig.tsbuildinfo +1 -1
  472. package/package.json +14 -3
  473. package/dist/src/core/geminiRequest.test.js +0 -72
  474. package/dist/src/core/geminiRequest.test.js.map +0 -1
  475. package/dist/src/core/modelCheck.d.ts +0 -14
  476. package/dist/src/core/modelCheck.js +0 -62
  477. package/dist/src/core/modelCheck.js.map +0 -1
  478. package/dist/src/services/ideContext.d.ts +0 -178
  479. package/dist/src/services/ideContext.js +0 -105
  480. package/dist/src/services/ideContext.js.map +0 -1
  481. package/dist/src/services/ideContext.test.js +0 -111
  482. package/dist/src/services/ideContext.test.js.map +0 -1
  483. /package/dist/src/core/{geminiRequest.test.d.ts → subagent.test.d.ts} +0 -0
  484. /package/dist/src/{services/ideContext.test.d.ts → ide/detect-ide.test.d.ts} +0 -0
@@ -3,404 +3,325 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { expect, describe, it, vi, beforeEach } from 'vitest';
6
+ import { vi, describe, it, expect, beforeEach, afterEach, } from 'vitest';
7
+ const mockShellExecutionService = vi.hoisted(() => vi.fn());
8
+ vi.mock('../services/shellExecutionService.js', () => ({
9
+ ShellExecutionService: { execute: mockShellExecutionService },
10
+ }));
11
+ vi.mock('fs');
12
+ vi.mock('os');
13
+ vi.mock('crypto');
14
+ vi.mock('../utils/summarizer.js');
15
+ import { isCommandAllowed } from '../utils/shell-utils.js';
7
16
  import { ShellTool } from './shell.js';
17
+ import * as fs from 'fs';
18
+ import * as os from 'os';
19
+ import * as path from 'path';
20
+ import * as crypto from 'crypto';
8
21
  import * as summarizer from '../utils/summarizer.js';
22
+ import { ToolConfirmationOutcome } from './tools.js';
23
+ import { OUTPUT_UPDATE_INTERVAL_MS } from './shell.js';
24
+ import { createMockWorkspaceContext } from '../test-utils/mockWorkspaceContext.js';
9
25
  describe('ShellTool', () => {
10
- it('should allow a command if no restrictions are provided', async () => {
11
- const config = {
12
- getCoreTools: () => undefined,
13
- getExcludeTools: () => undefined,
14
- };
15
- const shellTool = new ShellTool(config);
16
- const result = shellTool.isCommandAllowed('ls -l');
17
- expect(result.allowed).toBe(true);
18
- });
19
- it('should allow a command if it is in the allowed list', async () => {
20
- const config = {
21
- getCoreTools: () => ['ShellTool(ls -l)'],
22
- getExcludeTools: () => undefined,
23
- };
24
- const shellTool = new ShellTool(config);
25
- const result = shellTool.isCommandAllowed('ls -l');
26
- expect(result.allowed).toBe(true);
27
- });
28
- it('should block a command if it is not in the allowed list', async () => {
29
- const config = {
30
- getCoreTools: () => ['ShellTool(ls -l)'],
31
- getExcludeTools: () => undefined,
32
- };
33
- const shellTool = new ShellTool(config);
34
- const result = shellTool.isCommandAllowed('rm -rf /');
35
- expect(result.allowed).toBe(false);
36
- expect(result.reason).toBe("Command 'rm -rf /' is not in the allowed commands list");
37
- });
38
- it('should block a command if it is in the blocked list', async () => {
39
- const config = {
40
- getCoreTools: () => undefined,
41
- getExcludeTools: () => ['ShellTool(rm -rf /)'],
42
- };
43
- const shellTool = new ShellTool(config);
44
- const result = shellTool.isCommandAllowed('rm -rf /');
45
- expect(result.allowed).toBe(false);
46
- expect(result.reason).toBe("Command 'rm -rf /' is blocked by configuration");
26
+ let shellTool;
27
+ let mockConfig;
28
+ let mockShellOutputCallback;
29
+ let resolveExecutionPromise;
30
+ beforeEach(() => {
31
+ vi.clearAllMocks();
32
+ mockConfig = {
33
+ getCoreTools: vi.fn().mockReturnValue([]),
34
+ getExcludeTools: vi.fn().mockReturnValue([]),
35
+ getDebugMode: vi.fn().mockReturnValue(false),
36
+ getTargetDir: vi.fn().mockReturnValue('/test/dir'),
37
+ getSummarizeToolOutputConfig: vi.fn().mockReturnValue(undefined),
38
+ getWorkspaceContext: () => createMockWorkspaceContext('.'),
39
+ getGeminiClient: vi.fn(),
40
+ };
41
+ shellTool = new ShellTool(mockConfig);
42
+ vi.mocked(os.platform).mockReturnValue('linux');
43
+ vi.mocked(os.tmpdir).mockReturnValue('/tmp');
44
+ vi.mocked(crypto.randomBytes).mockReturnValue(Buffer.from('abcdef', 'hex'));
45
+ // Capture the output callback to simulate streaming events from the service
46
+ mockShellExecutionService.mockImplementation((_cmd, _cwd, callback) => {
47
+ mockShellOutputCallback = callback;
48
+ return {
49
+ pid: 12345,
50
+ result: new Promise((resolve) => {
51
+ resolveExecutionPromise = resolve;
52
+ }),
53
+ };
54
+ });
55
+ });
56
+ describe('isCommandAllowed', () => {
57
+ it('should allow a command if no restrictions are provided', () => {
58
+ mockConfig.getCoreTools.mockReturnValue(undefined);
59
+ mockConfig.getExcludeTools.mockReturnValue(undefined);
60
+ expect(isCommandAllowed('ls -l', mockConfig).allowed).toBe(true);
61
+ });
62
+ it('should block a command with command substitution using $()', () => {
63
+ expect(isCommandAllowed('echo $(rm -rf /)', mockConfig).allowed).toBe(false);
64
+ });
65
+ });
66
+ describe('build', () => {
67
+ it('should return an invocation for a valid command', () => {
68
+ const invocation = shellTool.build({ command: 'ls -l' });
69
+ expect(invocation).toBeDefined();
70
+ });
71
+ it('should throw an error for an empty command', () => {
72
+ expect(() => shellTool.build({ command: ' ' })).toThrow('Command cannot be empty.');
73
+ });
74
+ it('should throw an error for a non-existent directory', () => {
75
+ vi.mocked(fs.existsSync).mockReturnValue(false);
76
+ expect(() => shellTool.build({ command: 'ls', directory: 'rel/path' })).toThrow("Directory 'rel/path' is not a registered workspace directory.");
77
+ });
78
+ });
79
+ describe('execute', () => {
80
+ const mockAbortSignal = new AbortController().signal;
81
+ const resolveShellExecution = (result = {}) => {
82
+ const fullResult = {
83
+ rawOutput: Buffer.from(result.output || ''),
84
+ output: 'Success',
85
+ stdout: 'Success',
86
+ stderr: '',
87
+ exitCode: 0,
88
+ signal: null,
89
+ error: null,
90
+ aborted: false,
91
+ pid: 12345,
92
+ ...result,
93
+ };
94
+ resolveExecutionPromise(fullResult);
95
+ };
96
+ it('should wrap command on linux and parse pgrep output', async () => {
97
+ const invocation = shellTool.build({ command: 'my-command &' });
98
+ const promise = invocation.execute(mockAbortSignal);
99
+ resolveShellExecution({ pid: 54321 });
100
+ vi.mocked(fs.existsSync).mockReturnValue(true);
101
+ vi.mocked(fs.readFileSync).mockReturnValue('54321\n54322\n'); // Service PID and background PID
102
+ const result = await promise;
103
+ const tmpFile = path.join(os.tmpdir(), 'shell_pgrep_abcdef.tmp');
104
+ const wrappedCommand = `{ my-command & }; __code=$?; pgrep -g 0 >${tmpFile} 2>&1; exit $__code;`;
105
+ expect(mockShellExecutionService).toHaveBeenCalledWith(wrappedCommand, expect.any(String), expect.any(Function), mockAbortSignal);
106
+ expect(result.llmContent).toContain('Background PIDs: 54322');
107
+ expect(vi.mocked(fs.unlinkSync)).toHaveBeenCalledWith(tmpFile);
108
+ });
109
+ it('should not wrap command on windows', async () => {
110
+ vi.mocked(os.platform).mockReturnValue('win32');
111
+ const invocation = shellTool.build({ command: 'dir' });
112
+ const promise = invocation.execute(mockAbortSignal);
113
+ resolveShellExecution({
114
+ rawOutput: Buffer.from(''),
115
+ output: '',
116
+ stdout: '',
117
+ stderr: '',
118
+ exitCode: 0,
119
+ signal: null,
120
+ error: null,
121
+ aborted: false,
122
+ pid: 12345,
123
+ });
124
+ await promise;
125
+ expect(mockShellExecutionService).toHaveBeenCalledWith('dir', expect.any(String), expect.any(Function), mockAbortSignal);
126
+ });
127
+ it('should format error messages correctly', async () => {
128
+ const error = new Error('wrapped command failed');
129
+ const invocation = shellTool.build({ command: 'user-command' });
130
+ const promise = invocation.execute(mockAbortSignal);
131
+ resolveShellExecution({
132
+ error,
133
+ exitCode: 1,
134
+ output: 'err',
135
+ stderr: 'err',
136
+ rawOutput: Buffer.from('err'),
137
+ stdout: '',
138
+ signal: null,
139
+ aborted: false,
140
+ pid: 12345,
141
+ });
142
+ const result = await promise;
143
+ // The final llmContent should contain the user's command, not the wrapper
144
+ expect(result.llmContent).toContain('Error: wrapped command failed');
145
+ expect(result.llmContent).not.toContain('pgrep');
146
+ });
147
+ it('should throw an error for invalid parameters', () => {
148
+ expect(() => shellTool.build({ command: '' })).toThrow('Command cannot be empty.');
149
+ });
150
+ it('should throw an error for invalid directory', () => {
151
+ vi.mocked(fs.existsSync).mockReturnValue(false);
152
+ expect(() => shellTool.build({ command: 'ls', directory: 'nonexistent' })).toThrow(`Directory 'nonexistent' is not a registered workspace directory.`);
153
+ });
154
+ it('should summarize output when configured', async () => {
155
+ mockConfig.getSummarizeToolOutputConfig.mockReturnValue({
156
+ [shellTool.name]: { tokenBudget: 1000 },
157
+ });
158
+ vi.mocked(summarizer.summarizeToolOutput).mockResolvedValue('summarized output');
159
+ const invocation = shellTool.build({ command: 'ls' });
160
+ const promise = invocation.execute(mockAbortSignal);
161
+ resolveExecutionPromise({
162
+ output: 'long output',
163
+ rawOutput: Buffer.from('long output'),
164
+ stdout: 'long output',
165
+ stderr: '',
166
+ exitCode: 0,
167
+ signal: null,
168
+ error: null,
169
+ aborted: false,
170
+ pid: 12345,
171
+ });
172
+ const result = await promise;
173
+ expect(summarizer.summarizeToolOutput).toHaveBeenCalledWith(expect.any(String), mockConfig.getGeminiClient(), mockAbortSignal, 1000);
174
+ expect(result.llmContent).toBe('summarized output');
175
+ expect(result.returnDisplay).toBe('long output');
176
+ });
177
+ it('should clean up the temp file on synchronous execution error', async () => {
178
+ const error = new Error('sync spawn error');
179
+ mockShellExecutionService.mockImplementation(() => {
180
+ throw error;
181
+ });
182
+ vi.mocked(fs.existsSync).mockReturnValue(true); // Pretend the file exists
183
+ const invocation = shellTool.build({ command: 'a-command' });
184
+ await expect(invocation.execute(mockAbortSignal)).rejects.toThrow(error);
185
+ const tmpFile = path.join(os.tmpdir(), 'shell_pgrep_abcdef.tmp');
186
+ expect(vi.mocked(fs.unlinkSync)).toHaveBeenCalledWith(tmpFile);
187
+ });
188
+ describe('Streaming to `updateOutput`', () => {
189
+ let updateOutputMock;
190
+ beforeEach(() => {
191
+ vi.useFakeTimers({ toFake: ['Date'] });
192
+ updateOutputMock = vi.fn();
193
+ });
194
+ afterEach(() => {
195
+ vi.useRealTimers();
196
+ });
197
+ it('should throttle text output updates', async () => {
198
+ const invocation = shellTool.build({ command: 'stream' });
199
+ const promise = invocation.execute(mockAbortSignal, updateOutputMock);
200
+ // First chunk, should be throttled.
201
+ mockShellOutputCallback({
202
+ type: 'data',
203
+ stream: 'stdout',
204
+ chunk: 'hello ',
205
+ });
206
+ expect(updateOutputMock).not.toHaveBeenCalled();
207
+ // Advance time past the throttle interval.
208
+ await vi.advanceTimersByTimeAsync(OUTPUT_UPDATE_INTERVAL_MS + 1);
209
+ // Send a second chunk. THIS event triggers the update with the CUMULATIVE content.
210
+ mockShellOutputCallback({
211
+ type: 'data',
212
+ stream: 'stderr',
213
+ chunk: 'world',
214
+ });
215
+ // It should have been called once now with the combined output.
216
+ expect(updateOutputMock).toHaveBeenCalledOnce();
217
+ expect(updateOutputMock).toHaveBeenCalledWith('hello \nworld');
218
+ resolveExecutionPromise({
219
+ rawOutput: Buffer.from(''),
220
+ output: '',
221
+ stdout: '',
222
+ stderr: '',
223
+ exitCode: 0,
224
+ signal: null,
225
+ error: null,
226
+ aborted: false,
227
+ pid: 12345,
228
+ });
229
+ await promise;
230
+ });
231
+ it('should immediately show binary detection message and throttle progress', async () => {
232
+ const invocation = shellTool.build({ command: 'cat img' });
233
+ const promise = invocation.execute(mockAbortSignal, updateOutputMock);
234
+ mockShellOutputCallback({ type: 'binary_detected' });
235
+ expect(updateOutputMock).toHaveBeenCalledOnce();
236
+ expect(updateOutputMock).toHaveBeenCalledWith('[Binary output detected. Halting stream...]');
237
+ mockShellOutputCallback({
238
+ type: 'binary_progress',
239
+ bytesReceived: 1024,
240
+ });
241
+ expect(updateOutputMock).toHaveBeenCalledOnce();
242
+ // Advance time past the throttle interval.
243
+ await vi.advanceTimersByTimeAsync(OUTPUT_UPDATE_INTERVAL_MS + 1);
244
+ // Send a SECOND progress event. This one will trigger the flush.
245
+ mockShellOutputCallback({
246
+ type: 'binary_progress',
247
+ bytesReceived: 2048,
248
+ });
249
+ // Now it should be called a second time with the latest progress.
250
+ expect(updateOutputMock).toHaveBeenCalledTimes(2);
251
+ expect(updateOutputMock).toHaveBeenLastCalledWith('[Receiving binary output... 2.0 KB received]');
252
+ resolveExecutionPromise({
253
+ rawOutput: Buffer.from(''),
254
+ output: '',
255
+ stdout: '',
256
+ stderr: '',
257
+ exitCode: 0,
258
+ signal: null,
259
+ error: null,
260
+ aborted: false,
261
+ pid: 12345,
262
+ });
263
+ await promise;
264
+ });
265
+ });
266
+ });
267
+ describe('shouldConfirmExecute', () => {
268
+ it('should request confirmation for a new command and whitelist it on "Always"', async () => {
269
+ const params = { command: 'npm install' };
270
+ const invocation = shellTool.build(params);
271
+ const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
272
+ expect(confirmation).not.toBe(false);
273
+ expect(confirmation && confirmation.type).toBe('exec');
274
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
275
+ await confirmation.onConfirm(ToolConfirmationOutcome.ProceedAlways);
276
+ // Should now be whitelisted
277
+ const secondInvocation = shellTool.build({ command: 'npm test' });
278
+ const secondConfirmation = await secondInvocation.shouldConfirmExecute(new AbortController().signal);
279
+ expect(secondConfirmation).toBe(false);
280
+ });
281
+ it('should throw an error if validation fails', () => {
282
+ expect(() => shellTool.build({ command: '' })).toThrow();
283
+ });
284
+ });
285
+ describe('getDescription', () => {
286
+ it('should return the windows description when on windows', () => {
287
+ vi.mocked(os.platform).mockReturnValue('win32');
288
+ const shellTool = new ShellTool(mockConfig);
289
+ expect(shellTool.description).toMatchSnapshot();
290
+ });
291
+ it('should return the non-windows description when not on windows', () => {
292
+ vi.mocked(os.platform).mockReturnValue('linux');
293
+ const shellTool = new ShellTool(mockConfig);
294
+ expect(shellTool.description).toMatchSnapshot();
295
+ });
47
296
  });
48
- it('should allow a command if it is not in the blocked list', async () => {
297
+ });
298
+ describe('build', () => {
299
+ it('should return an invocation for valid directory', () => {
49
300
  const config = {
50
301
  getCoreTools: () => undefined,
51
- getExcludeTools: () => ['ShellTool(rm -rf /)'],
52
- };
53
- const shellTool = new ShellTool(config);
54
- const result = shellTool.isCommandAllowed('ls -l');
55
- expect(result.allowed).toBe(true);
56
- });
57
- it('should block a command if it is in both the allowed and blocked lists', async () => {
58
- const config = {
59
- getCoreTools: () => ['ShellTool(rm -rf /)'],
60
- getExcludeTools: () => ['ShellTool(rm -rf /)'],
61
- };
62
- const shellTool = new ShellTool(config);
63
- const result = shellTool.isCommandAllowed('rm -rf /');
64
- expect(result.allowed).toBe(false);
65
- expect(result.reason).toBe("Command 'rm -rf /' is blocked by configuration");
66
- });
67
- it('should allow any command when ShellTool is in coreTools without specific commands', async () => {
68
- const config = {
69
- getCoreTools: () => ['ShellTool'],
70
- getExcludeTools: () => [],
71
- };
72
- const shellTool = new ShellTool(config);
73
- const result = shellTool.isCommandAllowed('any command');
74
- expect(result.allowed).toBe(true);
75
- });
76
- it('should block any command when ShellTool is in excludeTools without specific commands', async () => {
77
- const config = {
78
- getCoreTools: () => [],
79
- getExcludeTools: () => ['ShellTool'],
80
- };
81
- const shellTool = new ShellTool(config);
82
- const result = shellTool.isCommandAllowed('any command');
83
- expect(result.allowed).toBe(false);
84
- expect(result.reason).toBe('Shell tool is globally disabled in configuration');
85
- });
86
- it('should allow a command if it is in the allowed list using the public-facing name', async () => {
87
- const config = {
88
- getCoreTools: () => ['run_shell_command(ls -l)'],
89
302
  getExcludeTools: () => undefined,
303
+ getTargetDir: () => '/root',
304
+ getWorkspaceContext: () => createMockWorkspaceContext('/root', ['/users/test']),
90
305
  };
91
306
  const shellTool = new ShellTool(config);
92
- const result = shellTool.isCommandAllowed('ls -l');
93
- expect(result.allowed).toBe(true);
94
- });
95
- it('should block a command if it is in the blocked list using the public-facing name', async () => {
96
- const config = {
97
- getCoreTools: () => undefined,
98
- getExcludeTools: () => ['run_shell_command(rm -rf /)'],
99
- };
100
- const shellTool = new ShellTool(config);
101
- const result = shellTool.isCommandAllowed('rm -rf /');
102
- expect(result.allowed).toBe(false);
103
- expect(result.reason).toBe("Command 'rm -rf /' is blocked by configuration");
104
- });
105
- it('should block any command when ShellTool is in excludeTools using the public-facing name', async () => {
106
- const config = {
107
- getCoreTools: () => [],
108
- getExcludeTools: () => ['run_shell_command'],
109
- };
110
- const shellTool = new ShellTool(config);
111
- const result = shellTool.isCommandAllowed('any command');
112
- expect(result.allowed).toBe(false);
113
- expect(result.reason).toBe('Shell tool is globally disabled in configuration');
114
- });
115
- it('should block any command if coreTools contains an empty ShellTool command list using the public-facing name', async () => {
116
- const config = {
117
- getCoreTools: () => ['run_shell_command()'],
118
- getExcludeTools: () => [],
119
- };
120
- const shellTool = new ShellTool(config);
121
- const result = shellTool.isCommandAllowed('any command');
122
- expect(result.allowed).toBe(false);
123
- expect(result.reason).toBe("Command 'any command' is not in the allowed commands list");
124
- });
125
- it('should block any command if coreTools contains an empty ShellTool command list', async () => {
126
- const config = {
127
- getCoreTools: () => ['ShellTool()'],
128
- getExcludeTools: () => [],
129
- };
130
- const shellTool = new ShellTool(config);
131
- const result = shellTool.isCommandAllowed('any command');
132
- expect(result.allowed).toBe(false);
133
- expect(result.reason).toBe("Command 'any command' is not in the allowed commands list");
307
+ const invocation = shellTool.build({
308
+ command: 'ls',
309
+ directory: 'test',
310
+ });
311
+ expect(invocation).toBeDefined();
134
312
  });
135
- it('should block a command with extra whitespace if it is in the blocked list', async () => {
313
+ it('should throw an error for directory outside workspace', () => {
136
314
  const config = {
137
- getCoreTools: () => undefined,
138
- getExcludeTools: () => ['ShellTool(rm -rf /)'],
139
- };
140
- const shellTool = new ShellTool(config);
141
- const result = shellTool.isCommandAllowed(' rm -rf / ');
142
- expect(result.allowed).toBe(false);
143
- expect(result.reason).toBe("Command 'rm -rf /' is blocked by configuration");
144
- });
145
- it('should allow any command when ShellTool is present with specific commands', async () => {
146
- const config = {
147
- getCoreTools: () => ['ShellTool', 'ShellTool(ls)'],
148
- getExcludeTools: () => [],
149
- };
150
- const shellTool = new ShellTool(config);
151
- const result = shellTool.isCommandAllowed('any command');
152
- expect(result.allowed).toBe(true);
153
- });
154
- it('should block a command on the blocklist even with a wildcard allow', async () => {
155
- const config = {
156
- getCoreTools: () => ['ShellTool'],
157
- getExcludeTools: () => ['ShellTool(rm -rf /)'],
158
- };
159
- const shellTool = new ShellTool(config);
160
- const result = shellTool.isCommandAllowed('rm -rf /');
161
- expect(result.allowed).toBe(false);
162
- expect(result.reason).toBe("Command 'rm -rf /' is blocked by configuration");
163
- });
164
- it('should allow a command that starts with an allowed command prefix', async () => {
165
- const config = {
166
- getCoreTools: () => ['ShellTool(gh issue edit)'],
167
- getExcludeTools: () => [],
168
- };
169
- const shellTool = new ShellTool(config);
170
- const result = shellTool.isCommandAllowed('gh issue edit 1 --add-label "kind/feature"');
171
- expect(result.allowed).toBe(true);
172
- });
173
- it('should allow a command that starts with an allowed command prefix using the public-facing name', async () => {
174
- const config = {
175
- getCoreTools: () => ['run_shell_command(gh issue edit)'],
176
- getExcludeTools: () => [],
177
- };
178
- const shellTool = new ShellTool(config);
179
- const result = shellTool.isCommandAllowed('gh issue edit 1 --add-label "kind/feature"');
180
- expect(result.allowed).toBe(true);
181
- });
182
- it('should not allow a command that starts with an allowed command prefix but is chained with another command', async () => {
183
- const config = {
184
- getCoreTools: () => ['run_shell_command(gh issue edit)'],
185
- getExcludeTools: () => [],
186
- };
187
- const shellTool = new ShellTool(config);
188
- const result = shellTool.isCommandAllowed('gh issue edit&&rm -rf /');
189
- expect(result.allowed).toBe(false);
190
- expect(result.reason).toBe("Command 'rm -rf /' is not in the allowed commands list");
191
- });
192
- it('should not allow a command that is a prefix of an allowed command', async () => {
193
- const config = {
194
- getCoreTools: () => ['run_shell_command(gh issue edit)'],
195
- getExcludeTools: () => [],
196
- };
197
- const shellTool = new ShellTool(config);
198
- const result = shellTool.isCommandAllowed('gh issue');
199
- expect(result.allowed).toBe(false);
200
- expect(result.reason).toBe("Command 'gh issue' is not in the allowed commands list");
201
- });
202
- it('should not allow a command that is a prefix of a blocked command', async () => {
203
- const config = {
204
- getCoreTools: () => [],
205
- getExcludeTools: () => ['run_shell_command(gh issue edit)'],
206
- };
207
- const shellTool = new ShellTool(config);
208
- const result = shellTool.isCommandAllowed('gh issue');
209
- expect(result.allowed).toBe(true);
210
- });
211
- it('should not allow a command that is chained with a pipe', async () => {
212
- const config = {
213
- getCoreTools: () => ['run_shell_command(gh issue list)'],
214
- getExcludeTools: () => [],
215
- };
216
- const shellTool = new ShellTool(config);
217
- const result = shellTool.isCommandAllowed('gh issue list | rm -rf /');
218
- expect(result.allowed).toBe(false);
219
- expect(result.reason).toBe("Command 'rm -rf /' is not in the allowed commands list");
220
- });
221
- it('should not allow a command that is chained with a semicolon', async () => {
222
- const config = {
223
- getCoreTools: () => ['run_shell_command(gh issue list)'],
224
- getExcludeTools: () => [],
225
- };
226
- const shellTool = new ShellTool(config);
227
- const result = shellTool.isCommandAllowed('gh issue list; rm -rf /');
228
- expect(result.allowed).toBe(false);
229
- expect(result.reason).toBe("Command 'rm -rf /' is not in the allowed commands list");
230
- });
231
- it('should block a chained command if any part is blocked', async () => {
232
- const config = {
233
- getCoreTools: () => ['run_shell_command(echo "hello")'],
234
- getExcludeTools: () => ['run_shell_command(rm)'],
235
- };
236
- const shellTool = new ShellTool(config);
237
- const result = shellTool.isCommandAllowed('echo "hello" && rm -rf /');
238
- expect(result.allowed).toBe(false);
239
- expect(result.reason).toBe("Command 'rm -rf /' is blocked by configuration");
240
- });
241
- it('should block a command if its prefix is on the blocklist, even if the command itself is on the allowlist', async () => {
242
- const config = {
243
- getCoreTools: () => ['run_shell_command(git push)'],
244
- getExcludeTools: () => ['run_shell_command(git)'],
245
- };
246
- const shellTool = new ShellTool(config);
247
- const result = shellTool.isCommandAllowed('git push');
248
- expect(result.allowed).toBe(false);
249
- expect(result.reason).toBe("Command 'git push' is blocked by configuration");
250
- });
251
- it('should be case-sensitive in its matching', async () => {
252
- const config = {
253
- getCoreTools: () => ['run_shell_command(echo)'],
254
- getExcludeTools: () => [],
255
- };
256
- const shellTool = new ShellTool(config);
257
- const result = shellTool.isCommandAllowed('ECHO "hello"');
258
- expect(result.allowed).toBe(false);
259
- expect(result.reason).toBe('Command \'ECHO "hello"\' is not in the allowed commands list');
260
- });
261
- it('should correctly handle commands with extra whitespace around chaining operators', async () => {
262
- const config = {
263
- getCoreTools: () => ['run_shell_command(ls -l)'],
264
- getExcludeTools: () => ['run_shell_command(rm)'],
265
- };
266
- const shellTool = new ShellTool(config);
267
- const result = shellTool.isCommandAllowed('ls -l ; rm -rf /');
268
- expect(result.allowed).toBe(false);
269
- expect(result.reason).toBe("Command 'rm -rf /' is blocked by configuration");
270
- });
271
- it('should allow a chained command if all parts are allowed', async () => {
272
- const config = {
273
- getCoreTools: () => [
274
- 'run_shell_command(echo)',
275
- 'run_shell_command(ls -l)',
276
- ],
277
- getExcludeTools: () => [],
278
- };
279
- const shellTool = new ShellTool(config);
280
- const result = shellTool.isCommandAllowed('echo "hello" && ls -l');
281
- expect(result.allowed).toBe(true);
282
- });
283
- it('should allow a command with command substitution using backticks', async () => {
284
- const config = {
285
- getCoreTools: () => ['run_shell_command(echo)'],
286
- getExcludeTools: () => [],
287
- };
288
- const shellTool = new ShellTool(config);
289
- const result = shellTool.isCommandAllowed('echo `rm -rf /`');
290
- expect(result.allowed).toBe(true);
291
- });
292
- it('should block a command with command substitution using $()', async () => {
293
- const config = {
294
- getCoreTools: () => ['run_shell_command(echo)'],
295
- getExcludeTools: () => [],
296
- };
297
- const shellTool = new ShellTool(config);
298
- const result = shellTool.isCommandAllowed('echo $(rm -rf /)');
299
- expect(result.allowed).toBe(false);
300
- expect(result.reason).toBe('Command substitution using $() is not allowed for security reasons');
301
- });
302
- it('should allow a command with I/O redirection', async () => {
303
- const config = {
304
- getCoreTools: () => ['run_shell_command(echo)'],
305
- getExcludeTools: () => [],
306
- };
307
- const shellTool = new ShellTool(config);
308
- const result = shellTool.isCommandAllowed('echo "hello" > file.txt');
309
- expect(result.allowed).toBe(true);
310
- });
311
- it('should not allow a command that is chained with a double pipe', async () => {
312
- const config = {
313
- getCoreTools: () => ['run_shell_command(gh issue list)'],
314
- getExcludeTools: () => [],
315
- };
316
- const shellTool = new ShellTool(config);
317
- const result = shellTool.isCommandAllowed('gh issue list || rm -rf /');
318
- expect(result.allowed).toBe(false);
319
- expect(result.reason).toBe("Command 'rm -rf /' is not in the allowed commands list");
320
- });
321
- });
322
- describe('ShellTool Bug Reproduction', () => {
323
- let shellTool;
324
- let config;
325
- beforeEach(() => {
326
- config = {
327
- getCoreTools: () => undefined,
328
- getExcludeTools: () => undefined,
329
- getDebugMode: () => false,
330
- getGeminiClient: () => ({}),
331
- getTargetDir: () => '.',
332
- getSummarizeToolOutputConfig: () => ({
333
- [shellTool.name]: {},
334
- }),
335
- };
336
- shellTool = new ShellTool(config);
337
- });
338
- it('should not let the summarizer override the return display', async () => {
339
- const summarizeSpy = vi
340
- .spyOn(summarizer, 'summarizeToolOutput')
341
- .mockResolvedValue('summarized output');
342
- const abortSignal = new AbortController().signal;
343
- const result = await shellTool.execute({ command: 'echo "hello"' }, abortSignal);
344
- expect(result.returnDisplay).toBe('hello\n');
345
- expect(result.llmContent).toBe('summarized output');
346
- expect(summarizeSpy).toHaveBeenCalled();
347
- });
348
- it('should not call summarizer if disabled in config', async () => {
349
- config = {
350
- getCoreTools: () => undefined,
351
- getExcludeTools: () => undefined,
352
- getDebugMode: () => false,
353
- getGeminiClient: () => ({}),
354
- getTargetDir: () => '.',
355
- getSummarizeToolOutputConfig: () => ({}),
356
- };
357
- shellTool = new ShellTool(config);
358
- const summarizeSpy = vi
359
- .spyOn(summarizer, 'summarizeToolOutput')
360
- .mockResolvedValue('summarized output');
361
- const abortSignal = new AbortController().signal;
362
- const result = await shellTool.execute({ command: 'echo "hello"' }, abortSignal);
363
- expect(result.returnDisplay).toBe('hello\n');
364
- expect(result.llmContent).not.toBe('summarized output');
365
- expect(summarizeSpy).not.toHaveBeenCalled();
366
- });
367
- it('should pass token budget to summarizer', async () => {
368
- config = {
369
315
  getCoreTools: () => undefined,
370
316
  getExcludeTools: () => undefined,
371
- getDebugMode: () => false,
372
- getGeminiClient: () => ({}),
373
- getTargetDir: () => '.',
374
- getSummarizeToolOutputConfig: () => ({
375
- [shellTool.name]: { tokenBudget: 1000 },
376
- }),
317
+ getTargetDir: () => '/root',
318
+ getWorkspaceContext: () => createMockWorkspaceContext('/root', ['/users/test']),
377
319
  };
378
- shellTool = new ShellTool(config);
379
- const summarizeSpy = vi
380
- .spyOn(summarizer, 'summarizeToolOutput')
381
- .mockResolvedValue('summarized output');
382
- const abortSignal = new AbortController().signal;
383
- await shellTool.execute({ command: 'echo "hello"' }, abortSignal);
384
- expect(summarizeSpy).toHaveBeenCalledWith(expect.any(String), expect.any(Object), expect.any(Object), 1000);
385
- });
386
- it('should use default token budget if not specified', async () => {
387
- config = {
388
- getCoreTools: () => undefined,
389
- getExcludeTools: () => undefined,
390
- getDebugMode: () => false,
391
- getGeminiClient: () => ({}),
392
- getTargetDir: () => '.',
393
- getSummarizeToolOutputConfig: () => ({
394
- [shellTool.name]: {},
395
- }),
396
- };
397
- shellTool = new ShellTool(config);
398
- const summarizeSpy = vi
399
- .spyOn(summarizer, 'summarizeToolOutput')
400
- .mockResolvedValue('summarized output');
401
- const abortSignal = new AbortController().signal;
402
- await shellTool.execute({ command: 'echo "hello"' }, abortSignal);
403
- expect(summarizeSpy).toHaveBeenCalledWith(expect.any(String), expect.any(Object), expect.any(Object), undefined);
320
+ const shellTool = new ShellTool(config);
321
+ expect(() => shellTool.build({
322
+ command: 'ls',
323
+ directory: 'test2',
324
+ })).toThrow('is not a registered workspace directory');
404
325
  });
405
326
  });
406
327
  //# sourceMappingURL=shell.test.js.map