@machina.ai/cell-cli-core 1.13.0-rc5 → 1.16.0-rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +2 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/package.json +1 -1
  5. package/dist/src/agents/codebase-investigator.test.d.ts +6 -0
  6. package/dist/src/agents/codebase-investigator.test.js +35 -0
  7. package/dist/src/agents/codebase-investigator.test.js.map +1 -0
  8. package/dist/src/agents/executor.d.ts +3 -0
  9. package/dist/src/agents/executor.js +21 -0
  10. package/dist/src/agents/executor.js.map +1 -1
  11. package/dist/src/agents/executor.test.js +358 -3
  12. package/dist/src/agents/executor.test.js.map +1 -1
  13. package/dist/src/code_assist/codeAssist.test.d.ts +6 -0
  14. package/dist/src/code_assist/codeAssist.test.js +99 -0
  15. package/dist/src/code_assist/codeAssist.test.js.map +1 -0
  16. package/dist/src/code_assist/experiments/client_metadata.js +2 -1
  17. package/dist/src/code_assist/experiments/client_metadata.js.map +1 -1
  18. package/dist/src/code_assist/experiments/client_metadata.test.d.ts +6 -0
  19. package/dist/src/code_assist/experiments/client_metadata.test.js +99 -0
  20. package/dist/src/code_assist/experiments/client_metadata.test.js.map +1 -0
  21. package/dist/src/code_assist/experiments/experiments.js +2 -2
  22. package/dist/src/code_assist/experiments/experiments.js.map +1 -1
  23. package/dist/src/code_assist/experiments/experiments.test.d.ts +6 -0
  24. package/dist/src/code_assist/experiments/experiments.test.js +92 -0
  25. package/dist/src/code_assist/experiments/experiments.test.js.map +1 -0
  26. package/dist/src/code_assist/experiments/flagNames.d.ts +13 -0
  27. package/dist/src/code_assist/experiments/flagNames.js +13 -0
  28. package/dist/src/code_assist/experiments/flagNames.js.map +1 -0
  29. package/dist/src/code_assist/experiments/types.d.ts +1 -1
  30. package/dist/src/code_assist/oauth-credential-storage.test.js +49 -0
  31. package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -1
  32. package/dist/src/code_assist/server.js +5 -8
  33. package/dist/src/code_assist/server.js.map +1 -1
  34. package/dist/src/code_assist/server.test.js +109 -28
  35. package/dist/src/code_assist/server.test.js.map +1 -1
  36. package/dist/src/config/config.d.ts +34 -2
  37. package/dist/src/config/config.js +147 -26
  38. package/dist/src/config/config.js.map +1 -1
  39. package/dist/src/config/config.test.js +436 -19
  40. package/dist/src/config/config.test.js.map +1 -1
  41. package/dist/src/config/defaultModelConfigs.d.ts +7 -0
  42. package/dist/src/config/defaultModelConfigs.js +158 -0
  43. package/dist/src/config/defaultModelConfigs.js.map +1 -0
  44. package/dist/src/config/models.d.ts +22 -1
  45. package/dist/src/config/models.js +49 -6
  46. package/dist/src/config/models.js.map +1 -1
  47. package/dist/src/config/models.test.js +71 -10
  48. package/dist/src/config/models.test.js.map +1 -1
  49. package/dist/src/confirmation-bus/message-bus.d.ts +1 -1
  50. package/dist/src/confirmation-bus/message-bus.js +2 -2
  51. package/dist/src/confirmation-bus/message-bus.js.map +1 -1
  52. package/dist/src/confirmation-bus/message-bus.test.js +30 -24
  53. package/dist/src/confirmation-bus/message-bus.test.js.map +1 -1
  54. package/dist/src/confirmation-bus/types.d.ts +1 -0
  55. package/dist/src/core/baseLlmClient.d.ts +4 -8
  56. package/dist/src/core/baseLlmClient.js +3 -8
  57. package/dist/src/core/baseLlmClient.js.map +1 -1
  58. package/dist/src/core/baseLlmClient.test.js +22 -27
  59. package/dist/src/core/baseLlmClient.test.js.map +1 -1
  60. package/dist/src/core/client.d.ts +6 -5
  61. package/dist/src/core/client.js +33 -33
  62. package/dist/src/core/client.js.map +1 -1
  63. package/dist/src/core/client.test.js +40 -22
  64. package/dist/src/core/client.test.js.map +1 -1
  65. package/dist/src/core/coreToolScheduler.js +7 -1
  66. package/dist/src/core/coreToolScheduler.js.map +1 -1
  67. package/dist/src/core/coreToolScheduler.test.js +151 -357
  68. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  69. package/dist/src/core/geminiChat.d.ts +6 -4
  70. package/dist/src/core/geminiChat.js +106 -29
  71. package/dist/src/core/geminiChat.js.map +1 -1
  72. package/dist/src/core/geminiChat.test.js +317 -16
  73. package/dist/src/core/geminiChat.test.js.map +1 -1
  74. package/dist/src/core/logger.d.ts +7 -2
  75. package/dist/src/core/logger.js +15 -9
  76. package/dist/src/core/logger.js.map +1 -1
  77. package/dist/src/core/logger.test.js +31 -16
  78. package/dist/src/core/logger.test.js.map +1 -1
  79. package/dist/src/core/loggingContentGenerator.test.d.ts +6 -0
  80. package/dist/src/core/loggingContentGenerator.test.js +180 -0
  81. package/dist/src/core/loggingContentGenerator.test.js.map +1 -0
  82. package/dist/src/core/nonInteractiveToolExecutor.test.js +1 -0
  83. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  84. package/dist/src/core/prompts.js +8 -11
  85. package/dist/src/core/prompts.js.map +1 -1
  86. package/dist/src/core/tokenLimits.test.d.ts +6 -0
  87. package/dist/src/core/tokenLimits.test.js +26 -0
  88. package/dist/src/core/tokenLimits.test.js.map +1 -0
  89. package/dist/src/fallback/handler.js +52 -7
  90. package/dist/src/fallback/handler.js.map +1 -1
  91. package/dist/src/fallback/handler.test.js +69 -16
  92. package/dist/src/fallback/handler.test.js.map +1 -1
  93. package/dist/src/fallback/types.d.ts +1 -1
  94. package/dist/src/generated/git-commit.d.ts +2 -2
  95. package/dist/src/generated/git-commit.js +2 -2
  96. package/dist/src/hooks/hookAggregator.d.ts +68 -0
  97. package/dist/src/hooks/hookAggregator.js +262 -0
  98. package/dist/src/hooks/hookAggregator.js.map +1 -0
  99. package/dist/src/hooks/hookAggregator.test.d.ts +6 -0
  100. package/dist/src/hooks/hookAggregator.test.js +387 -0
  101. package/dist/src/hooks/hookAggregator.test.js.map +1 -0
  102. package/dist/src/hooks/hookRunner.d.ts +42 -0
  103. package/dist/src/hooks/hookRunner.js +272 -0
  104. package/dist/src/hooks/hookRunner.js.map +1 -0
  105. package/dist/src/hooks/hookRunner.test.d.ts +6 -0
  106. package/dist/src/hooks/hookRunner.test.js +468 -0
  107. package/dist/src/hooks/hookRunner.test.js.map +1 -0
  108. package/dist/src/hooks/hookTranslator.d.ts +3 -3
  109. package/dist/src/hooks/types.js +1 -1
  110. package/dist/src/hooks/types.js.map +1 -1
  111. package/dist/src/hooks/types.test.js +280 -2
  112. package/dist/src/hooks/types.test.js.map +1 -1
  113. package/dist/src/ide/detect-ide.d.ts +4 -0
  114. package/dist/src/ide/detect-ide.js +6 -1
  115. package/dist/src/ide/detect-ide.js.map +1 -1
  116. package/dist/src/ide/detect-ide.test.js +5 -0
  117. package/dist/src/ide/detect-ide.test.js.map +1 -1
  118. package/dist/src/ide/ide-client.d.ts +3 -1
  119. package/dist/src/ide/ide-client.js +66 -59
  120. package/dist/src/ide/ide-client.js.map +1 -1
  121. package/dist/src/ide/ide-client.test.js +159 -0
  122. package/dist/src/ide/ide-client.test.js.map +1 -1
  123. package/dist/src/ide/ide-installer.js +71 -21
  124. package/dist/src/ide/ide-installer.js.map +1 -1
  125. package/dist/src/ide/ide-installer.test.js +42 -1
  126. package/dist/src/ide/ide-installer.test.js.map +1 -1
  127. package/dist/src/ide/process-utils.js +16 -0
  128. package/dist/src/ide/process-utils.js.map +1 -1
  129. package/dist/src/ide/process-utils.test.js +3 -3
  130. package/dist/src/ide/process-utils.test.js.map +1 -1
  131. package/dist/src/ide/types.d.ts +1 -1
  132. package/dist/src/ide/types.js +1 -1
  133. package/dist/src/index.d.ts +2 -0
  134. package/dist/src/index.js +2 -0
  135. package/dist/src/index.js.map +1 -1
  136. package/dist/src/mcp/google-auth-provider.js +1 -1
  137. package/dist/src/mcp/google-auth-provider.js.map +1 -1
  138. package/dist/src/mcp/oauth-provider.js +2 -2
  139. package/dist/src/mcp/oauth-provider.js.map +1 -1
  140. package/dist/src/mcp/oauth-provider.test.js +177 -0
  141. package/dist/src/mcp/oauth-provider.test.js.map +1 -1
  142. package/dist/src/mcp/sa-impersonation-provider.js +1 -1
  143. package/dist/src/mcp/sa-impersonation-provider.js.map +1 -1
  144. package/dist/src/policy/config.js +3 -1
  145. package/dist/src/policy/config.js.map +1 -1
  146. package/dist/src/policy/config.test.js +135 -1
  147. package/dist/src/policy/config.test.js.map +1 -1
  148. package/dist/src/policy/policies/discovered.toml +8 -0
  149. package/dist/src/policy/policies/write.toml +10 -0
  150. package/dist/src/policy/policy-engine.d.ts +12 -3
  151. package/dist/src/policy/policy-engine.js +71 -9
  152. package/dist/src/policy/policy-engine.js.map +1 -1
  153. package/dist/src/policy/policy-engine.test.js +460 -76
  154. package/dist/src/policy/policy-engine.test.js.map +1 -1
  155. package/dist/src/policy/toml-loader.d.ts +2 -1
  156. package/dist/src/policy/toml-loader.js +103 -6
  157. package/dist/src/policy/toml-loader.js.map +1 -1
  158. package/dist/src/policy/toml-loader.test.js +222 -368
  159. package/dist/src/policy/toml-loader.test.js.map +1 -1
  160. package/dist/src/policy/types.d.ts +65 -0
  161. package/dist/src/policy/types.js +4 -0
  162. package/dist/src/policy/types.js.map +1 -1
  163. package/dist/src/prompts/mcp-prompts.test.d.ts +6 -0
  164. package/dist/src/prompts/mcp-prompts.test.js +40 -0
  165. package/dist/src/prompts/mcp-prompts.test.js.map +1 -0
  166. package/dist/src/prompts/prompt-registry.test.d.ts +6 -0
  167. package/dist/src/prompts/prompt-registry.test.js +111 -0
  168. package/dist/src/prompts/prompt-registry.test.js.map +1 -0
  169. package/dist/src/routing/modelRouterService.js +15 -0
  170. package/dist/src/routing/modelRouterService.js.map +1 -1
  171. package/dist/src/routing/modelRouterService.test.js +62 -0
  172. package/dist/src/routing/modelRouterService.test.js.map +1 -1
  173. package/dist/src/routing/strategies/classifierStrategy.d.ts +1 -1
  174. package/dist/src/routing/strategies/classifierStrategy.js +6 -14
  175. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  176. package/dist/src/routing/strategies/classifierStrategy.test.js +13 -10
  177. package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
  178. package/dist/src/routing/strategies/fallbackStrategy.js +1 -1
  179. package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
  180. package/dist/src/routing/strategies/fallbackStrategy.test.js +4 -0
  181. package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -1
  182. package/dist/src/routing/strategies/overrideStrategy.js +2 -2
  183. package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
  184. package/dist/src/routing/strategies/overrideStrategy.test.js +3 -0
  185. package/dist/src/routing/strategies/overrideStrategy.test.js.map +1 -1
  186. package/dist/src/safety/built-in.d.ts +21 -0
  187. package/dist/src/safety/built-in.js +106 -0
  188. package/dist/src/safety/built-in.js.map +1 -0
  189. package/dist/src/safety/built-in.test.d.ts +6 -0
  190. package/dist/src/safety/built-in.test.js +199 -0
  191. package/dist/src/safety/built-in.test.js.map +1 -0
  192. package/dist/src/safety/checker-runner.d.ts +48 -0
  193. package/dist/src/safety/checker-runner.js +208 -0
  194. package/dist/src/safety/checker-runner.js.map +1 -0
  195. package/dist/src/safety/checker-runner.test.d.ts +6 -0
  196. package/dist/src/safety/checker-runner.test.js +238 -0
  197. package/dist/src/safety/checker-runner.test.js.map +1 -0
  198. package/dist/src/safety/context-builder.d.ts +23 -0
  199. package/dist/src/safety/context-builder.js +47 -0
  200. package/dist/src/safety/context-builder.js.map +1 -0
  201. package/dist/src/safety/context-builder.test.d.ts +6 -0
  202. package/dist/src/safety/context-builder.test.js +49 -0
  203. package/dist/src/safety/context-builder.test.js.map +1 -0
  204. package/dist/src/safety/protocol.d.ts +88 -0
  205. package/dist/src/safety/protocol.js +15 -0
  206. package/dist/src/safety/protocol.js.map +1 -0
  207. package/dist/src/safety/registry.d.ts +26 -0
  208. package/dist/src/safety/registry.js +65 -0
  209. package/dist/src/safety/registry.js.map +1 -0
  210. package/dist/src/safety/registry.test.d.ts +6 -0
  211. package/dist/src/safety/registry.test.js +31 -0
  212. package/dist/src/safety/registry.test.js.map +1 -0
  213. package/dist/src/services/chatCompressionService.test.js +1 -0
  214. package/dist/src/services/chatCompressionService.test.js.map +1 -1
  215. package/dist/src/services/gitService.js +1 -1
  216. package/dist/src/services/gitService.js.map +1 -1
  217. package/dist/src/services/gitService.test.js +1 -1
  218. package/dist/src/services/gitService.test.js.map +1 -1
  219. package/dist/src/services/loopDetectionService.d.ts +3 -0
  220. package/dist/src/services/loopDetectionService.js +81 -42
  221. package/dist/src/services/loopDetectionService.js.map +1 -1
  222. package/dist/src/services/loopDetectionService.test.js +101 -1
  223. package/dist/src/services/loopDetectionService.test.js.map +1 -1
  224. package/dist/src/services/modelConfig.golden.test.d.ts +6 -0
  225. package/dist/src/services/modelConfig.golden.test.js +42 -0
  226. package/dist/src/services/modelConfig.golden.test.js.map +1 -0
  227. package/dist/src/services/modelConfig.integration.test.d.ts +6 -0
  228. package/dist/src/services/modelConfig.integration.test.js +213 -0
  229. package/dist/src/services/modelConfig.integration.test.js.map +1 -0
  230. package/dist/src/services/modelConfigService.d.ts +46 -0
  231. package/dist/src/services/modelConfigService.js +146 -0
  232. package/dist/src/services/modelConfigService.js.map +1 -0
  233. package/dist/src/services/modelConfigService.test.d.ts +6 -0
  234. package/dist/src/services/modelConfigService.test.js +509 -0
  235. package/dist/src/services/modelConfigService.test.js.map +1 -0
  236. package/dist/src/services/test-data/resolved-aliases.golden.json +169 -0
  237. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +11 -9
  238. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +174 -150
  239. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  240. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +1 -0
  241. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +76 -20
  242. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
  243. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +6 -1
  244. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +18 -5
  245. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  246. package/dist/src/telemetry/index.d.ts +2 -2
  247. package/dist/src/telemetry/index.js +2 -2
  248. package/dist/src/telemetry/index.js.map +1 -1
  249. package/dist/src/telemetry/loggers.d.ts +7 -7
  250. package/dist/src/telemetry/loggers.js +23 -23
  251. package/dist/src/telemetry/loggers.js.map +1 -1
  252. package/dist/src/telemetry/loggers.test.circular.js +0 -1
  253. package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
  254. package/dist/src/telemetry/loggers.test.js +72 -18
  255. package/dist/src/telemetry/loggers.test.js.map +1 -1
  256. package/dist/src/telemetry/metrics.d.ts +8 -4
  257. package/dist/src/telemetry/metrics.js +10 -4
  258. package/dist/src/telemetry/metrics.js.map +1 -1
  259. package/dist/src/telemetry/metrics.test.js +42 -0
  260. package/dist/src/telemetry/metrics.test.js.map +1 -1
  261. package/dist/src/telemetry/telemetryAttributes.js +1 -0
  262. package/dist/src/telemetry/telemetryAttributes.js.map +1 -1
  263. package/dist/src/telemetry/types.d.ts +17 -11
  264. package/dist/src/telemetry/types.js +53 -28
  265. package/dist/src/telemetry/types.js.map +1 -1
  266. package/dist/src/tools/base-tool-invocation.test.d.ts +6 -0
  267. package/dist/src/tools/base-tool-invocation.test.js +85 -0
  268. package/dist/src/tools/base-tool-invocation.test.js.map +1 -0
  269. package/dist/src/tools/edit.d.ts +1 -1
  270. package/dist/src/tools/edit.js +31 -33
  271. package/dist/src/tools/edit.js.map +1 -1
  272. package/dist/src/tools/edit.test.js +31 -20
  273. package/dist/src/tools/edit.test.js.map +1 -1
  274. package/dist/src/tools/glob.d.ts +1 -1
  275. package/dist/src/tools/glob.js +7 -7
  276. package/dist/src/tools/glob.js.map +1 -1
  277. package/dist/src/tools/glob.test.js +20 -17
  278. package/dist/src/tools/glob.test.js.map +1 -1
  279. package/dist/src/tools/grep.d.ts +1 -1
  280. package/dist/src/tools/grep.js +9 -9
  281. package/dist/src/tools/grep.js.map +1 -1
  282. package/dist/src/tools/grep.test.js +15 -12
  283. package/dist/src/tools/grep.test.js.map +1 -1
  284. package/dist/src/tools/ls.d.ts +1 -1
  285. package/dist/src/tools/ls.js +14 -15
  286. package/dist/src/tools/ls.js.map +1 -1
  287. package/dist/src/tools/ls.test.js +32 -33
  288. package/dist/src/tools/ls.test.js.map +1 -1
  289. package/dist/src/tools/mcp-client.js +24 -52
  290. package/dist/src/tools/mcp-client.js.map +1 -1
  291. package/dist/src/tools/mcp-client.test.js +18 -0
  292. package/dist/src/tools/mcp-client.test.js.map +1 -1
  293. package/dist/src/tools/mcp-tool.d.ts +1 -0
  294. package/dist/src/tools/mcp-tool.js +5 -2
  295. package/dist/src/tools/mcp-tool.js.map +1 -1
  296. package/dist/src/tools/memoryTool.js +1 -1
  297. package/dist/src/tools/memoryTool.js.map +1 -1
  298. package/dist/src/tools/memoryTool.test.js +1 -1
  299. package/dist/src/tools/memoryTool.test.js.map +1 -1
  300. package/dist/src/tools/modifiable-tool.d.ts +5 -1
  301. package/dist/src/tools/modifiable-tool.js +34 -13
  302. package/dist/src/tools/modifiable-tool.js.map +1 -1
  303. package/dist/src/tools/modifiable-tool.test.js +56 -22
  304. package/dist/src/tools/modifiable-tool.test.js.map +1 -1
  305. package/dist/src/tools/read-file.d.ts +2 -2
  306. package/dist/src/tools/read-file.js +20 -24
  307. package/dist/src/tools/read-file.js.map +1 -1
  308. package/dist/src/tools/read-file.test.js +63 -51
  309. package/dist/src/tools/read-file.test.js.map +1 -1
  310. package/dist/src/tools/read-many-files.d.ts +2 -9
  311. package/dist/src/tools/read-many-files.js +10 -21
  312. package/dist/src/tools/read-many-files.js.map +1 -1
  313. package/dist/src/tools/read-many-files.test.js +37 -35
  314. package/dist/src/tools/read-many-files.test.js.map +1 -1
  315. package/dist/src/tools/ripGrep.d.ts +25 -8
  316. package/dist/src/tools/ripGrep.js +148 -176
  317. package/dist/src/tools/ripGrep.js.map +1 -1
  318. package/dist/src/tools/ripGrep.test.js +383 -58
  319. package/dist/src/tools/ripGrep.test.js.map +1 -1
  320. package/dist/src/tools/shell.d.ts +1 -1
  321. package/dist/src/tools/shell.js +17 -15
  322. package/dist/src/tools/shell.js.map +1 -1
  323. package/dist/src/tools/shell.test.js +66 -36
  324. package/dist/src/tools/shell.test.js.map +1 -1
  325. package/dist/src/tools/smart-edit.d.ts +6 -1
  326. package/dist/src/tools/smart-edit.js +18 -17
  327. package/dist/src/tools/smart-edit.js.map +1 -1
  328. package/dist/src/tools/smart-edit.test.js +49 -24
  329. package/dist/src/tools/smart-edit.test.js.map +1 -1
  330. package/dist/src/tools/tool-registry.d.ts +29 -4
  331. package/dist/src/tools/tool-registry.js +108 -29
  332. package/dist/src/tools/tool-registry.js.map +1 -1
  333. package/dist/src/tools/tool-registry.test.js +134 -4
  334. package/dist/src/tools/tool-registry.test.js.map +1 -1
  335. package/dist/src/tools/tools.d.ts +2 -1
  336. package/dist/src/tools/tools.js +5 -2
  337. package/dist/src/tools/tools.js.map +1 -1
  338. package/dist/src/tools/web-fetch.js +2 -4
  339. package/dist/src/tools/web-fetch.js.map +1 -1
  340. package/dist/src/tools/web-fetch.test.js +11 -4
  341. package/dist/src/tools/web-fetch.test.js.map +1 -1
  342. package/dist/src/tools/web-search.js +1 -2
  343. package/dist/src/tools/web-search.js.map +1 -1
  344. package/dist/src/tools/web-search.test.js +11 -5
  345. package/dist/src/tools/web-search.test.js.map +1 -1
  346. package/dist/src/tools/write-file.js +31 -31
  347. package/dist/src/tools/write-file.js.map +1 -1
  348. package/dist/src/tools/write-file.test.js +24 -5
  349. package/dist/src/tools/write-file.test.js.map +1 -1
  350. package/dist/src/tools/write-todos.d.ts +29 -0
  351. package/dist/src/tools/write-todos.js +34 -1
  352. package/dist/src/tools/write-todos.js.map +1 -1
  353. package/dist/src/utils/editCorrector.js +4 -15
  354. package/dist/src/utils/editCorrector.js.map +1 -1
  355. package/dist/src/utils/editCorrector.test.js +16 -0
  356. package/dist/src/utils/editCorrector.test.js.map +1 -1
  357. package/dist/src/utils/editor.d.ts +3 -1
  358. package/dist/src/utils/editor.js +18 -1
  359. package/dist/src/utils/editor.js.map +1 -1
  360. package/dist/src/utils/editor.test.js +11 -0
  361. package/dist/src/utils/editor.test.js.map +1 -1
  362. package/dist/src/utils/environmentContext.js +3 -1
  363. package/dist/src/utils/environmentContext.js.map +1 -1
  364. package/dist/src/utils/environmentContext.test.js +6 -0
  365. package/dist/src/utils/environmentContext.test.js.map +1 -1
  366. package/dist/src/utils/events.d.ts +14 -11
  367. package/dist/src/utils/events.js +1 -14
  368. package/dist/src/utils/events.js.map +1 -1
  369. package/dist/src/utils/extensionLoader.d.ts +8 -0
  370. package/dist/src/utils/extensionLoader.js +61 -15
  371. package/dist/src/utils/extensionLoader.js.map +1 -1
  372. package/dist/src/utils/extensionLoader.test.js +83 -19
  373. package/dist/src/utils/extensionLoader.test.js.map +1 -1
  374. package/dist/src/utils/fileUtils.test.js +75 -60
  375. package/dist/src/utils/fileUtils.test.js.map +1 -1
  376. package/dist/src/utils/flashFallback.test.js +2 -2
  377. package/dist/src/utils/flashFallback.test.js.map +1 -1
  378. package/dist/src/utils/googleQuotaErrors.d.ts +2 -1
  379. package/dist/src/utils/googleQuotaErrors.js +20 -12
  380. package/dist/src/utils/googleQuotaErrors.js.map +1 -1
  381. package/dist/src/utils/httpErrors.d.ts +18 -0
  382. package/dist/src/utils/httpErrors.js +36 -0
  383. package/dist/src/utils/httpErrors.js.map +1 -0
  384. package/dist/src/utils/llm-edit-fixer.js +1 -2
  385. package/dist/src/utils/llm-edit-fixer.js.map +1 -1
  386. package/dist/src/utils/llm-edit-fixer.test.js +16 -1
  387. package/dist/src/utils/llm-edit-fixer.test.js.map +1 -1
  388. package/dist/src/utils/memoryDiscovery.d.ts +8 -0
  389. package/dist/src/utils/memoryDiscovery.js +24 -0
  390. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  391. package/dist/src/utils/memoryDiscovery.test.js +43 -1
  392. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  393. package/dist/src/utils/nextSpeakerChecker.js +1 -2
  394. package/dist/src/utils/nextSpeakerChecker.js.map +1 -1
  395. package/dist/src/utils/nextSpeakerChecker.test.js +10 -4
  396. package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
  397. package/dist/src/utils/pathReader.js +4 -4
  398. package/dist/src/utils/pathReader.js.map +1 -1
  399. package/dist/src/utils/pathReader.test.js +44 -1
  400. package/dist/src/utils/pathReader.test.js.map +1 -1
  401. package/dist/src/utils/paths.d.ts +1 -1
  402. package/dist/src/utils/paths.js +5 -3
  403. package/dist/src/utils/paths.js.map +1 -1
  404. package/dist/src/utils/retry.d.ts +0 -9
  405. package/dist/src/utils/retry.js +24 -28
  406. package/dist/src/utils/retry.js.map +1 -1
  407. package/dist/src/utils/retry.test.js +51 -0
  408. package/dist/src/utils/retry.test.js.map +1 -1
  409. package/dist/src/utils/shell-utils.js +5 -3
  410. package/dist/src/utils/shell-utils.js.map +1 -1
  411. package/dist/src/utils/shell-utils.test.js +9 -9
  412. package/dist/src/utils/shell-utils.test.js.map +1 -1
  413. package/dist/src/utils/summarizer.d.ts +4 -2
  414. package/dist/src/utils/summarizer.js +6 -8
  415. package/dist/src/utils/summarizer.js.map +1 -1
  416. package/dist/src/utils/summarizer.test.js +32 -11
  417. package/dist/src/utils/summarizer.test.js.map +1 -1
  418. package/dist/src/utils/workspaceContext.d.ts +4 -3
  419. package/dist/src/utils/workspaceContext.js +10 -11
  420. package/dist/src/utils/workspaceContext.js.map +1 -1
  421. package/dist/src/utils/workspaceContext.test.js +1 -1
  422. package/dist/src/utils/workspaceContext.test.js.map +1 -1
  423. package/dist/tsconfig.tsbuildinfo +1 -1
  424. package/package.json +1 -1
@@ -3,22 +3,27 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { describe, it, expect, beforeEach } from 'vitest';
6
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
7
7
  import { PolicyEngine } from './policy-engine.js';
8
- import { PolicyDecision, } from './types.js';
8
+ import { PolicyDecision, InProcessCheckerType, } from './types.js';
9
+ import { SafetyCheckDecision } from '../safety/protocol.js';
9
10
  describe('PolicyEngine', () => {
10
11
  let engine;
12
+ let mockCheckerRunner;
11
13
  beforeEach(() => {
12
- engine = new PolicyEngine();
14
+ mockCheckerRunner = {
15
+ runChecker: vi.fn(),
16
+ };
17
+ engine = new PolicyEngine({}, mockCheckerRunner);
13
18
  });
14
19
  describe('constructor', () => {
15
- it('should use default config when none provided', () => {
16
- const decision = engine.check({ name: 'test' });
20
+ it('should use default config when none provided', async () => {
21
+ const { decision } = await engine.check({ name: 'test' }, undefined);
17
22
  expect(decision).toBe(PolicyDecision.ASK_USER);
18
23
  });
19
- it('should respect custom default decision', () => {
24
+ it('should respect custom default decision', async () => {
20
25
  engine = new PolicyEngine({ defaultDecision: PolicyDecision.DENY });
21
- const decision = engine.check({ name: 'test' });
26
+ const { decision } = await engine.check({ name: 'test' }, undefined);
22
27
  expect(decision).toBe(PolicyDecision.DENY);
23
28
  });
24
29
  it('should sort rules by priority', () => {
@@ -35,17 +40,17 @@ describe('PolicyEngine', () => {
35
40
  });
36
41
  });
37
42
  describe('check', () => {
38
- it('should match tool by name', () => {
43
+ it('should match tool by name', async () => {
39
44
  const rules = [
40
45
  { toolName: 'shell', decision: PolicyDecision.ALLOW },
41
46
  { toolName: 'edit', decision: PolicyDecision.DENY },
42
47
  ];
43
48
  engine = new PolicyEngine({ rules });
44
- expect(engine.check({ name: 'shell' })).toBe(PolicyDecision.ALLOW);
45
- expect(engine.check({ name: 'edit' })).toBe(PolicyDecision.DENY);
46
- expect(engine.check({ name: 'other' })).toBe(PolicyDecision.ASK_USER);
49
+ expect((await engine.check({ name: 'shell' }, undefined)).decision).toBe(PolicyDecision.ALLOW);
50
+ expect((await engine.check({ name: 'edit' }, undefined)).decision).toBe(PolicyDecision.DENY);
51
+ expect((await engine.check({ name: 'other' }, undefined)).decision).toBe(PolicyDecision.ASK_USER);
47
52
  });
48
- it('should match by args pattern', () => {
53
+ it('should match by args pattern', async () => {
49
54
  const rules = [
50
55
  {
51
56
  toolName: 'shell',
@@ -66,28 +71,28 @@ describe('PolicyEngine', () => {
66
71
  name: 'shell',
67
72
  args: { command: 'ls -la' },
68
73
  };
69
- expect(engine.check(dangerousCall)).toBe(PolicyDecision.DENY);
70
- expect(engine.check(safeCall)).toBe(PolicyDecision.ALLOW);
74
+ expect((await engine.check(dangerousCall, undefined)).decision).toBe(PolicyDecision.DENY);
75
+ expect((await engine.check(safeCall, undefined)).decision).toBe(PolicyDecision.ALLOW);
71
76
  });
72
- it('should apply rules by priority', () => {
77
+ it('should apply rules by priority', async () => {
73
78
  const rules = [
74
79
  { toolName: 'shell', decision: PolicyDecision.DENY, priority: 1 },
75
80
  { toolName: 'shell', decision: PolicyDecision.ALLOW, priority: 10 },
76
81
  ];
77
82
  engine = new PolicyEngine({ rules });
78
83
  // Higher priority rule (ALLOW) should win
79
- expect(engine.check({ name: 'shell' })).toBe(PolicyDecision.ALLOW);
84
+ expect((await engine.check({ name: 'shell' }, undefined)).decision).toBe(PolicyDecision.ALLOW);
80
85
  });
81
- it('should apply wildcard rules (no toolName)', () => {
86
+ it('should apply wildcard rules (no toolName)', async () => {
82
87
  const rules = [
83
88
  { decision: PolicyDecision.DENY }, // Applies to all tools
84
89
  { toolName: 'safe-tool', decision: PolicyDecision.ALLOW, priority: 10 },
85
90
  ];
86
91
  engine = new PolicyEngine({ rules });
87
- expect(engine.check({ name: 'safe-tool' })).toBe(PolicyDecision.ALLOW);
88
- expect(engine.check({ name: 'any-other-tool' })).toBe(PolicyDecision.DENY);
92
+ expect((await engine.check({ name: 'safe-tool' }, undefined)).decision).toBe(PolicyDecision.ALLOW);
93
+ expect((await engine.check({ name: 'any-other-tool' }, undefined)).decision).toBe(PolicyDecision.DENY);
89
94
  });
90
- it('should handle non-interactive mode', () => {
95
+ it('should handle non-interactive mode', async () => {
91
96
  const config = {
92
97
  nonInteractive: true,
93
98
  rules: [
@@ -97,11 +102,11 @@ describe('PolicyEngine', () => {
97
102
  };
98
103
  engine = new PolicyEngine(config);
99
104
  // ASK_USER should become DENY in non-interactive mode
100
- expect(engine.check({ name: 'interactive-tool' })).toBe(PolicyDecision.DENY);
105
+ expect((await engine.check({ name: 'interactive-tool' }, undefined)).decision).toBe(PolicyDecision.DENY);
101
106
  // ALLOW should remain ALLOW
102
- expect(engine.check({ name: 'allowed-tool' })).toBe(PolicyDecision.ALLOW);
107
+ expect((await engine.check({ name: 'allowed-tool' }, undefined)).decision).toBe(PolicyDecision.ALLOW);
103
108
  // Default ASK_USER should also become DENY
104
- expect(engine.check({ name: 'unknown-tool' })).toBe(PolicyDecision.DENY);
109
+ expect((await engine.check({ name: 'unknown-tool' }, undefined)).decision).toBe(PolicyDecision.DENY);
105
110
  });
106
111
  });
107
112
  describe('addRule', () => {
@@ -127,10 +132,10 @@ describe('PolicyEngine', () => {
127
132
  expect(rules[1].priority).toBe(5);
128
133
  expect(rules[2].priority).toBe(1);
129
134
  });
130
- it('should apply newly added rules', () => {
131
- expect(engine.check({ name: 'new-tool' })).toBe(PolicyDecision.ASK_USER);
135
+ it('should apply newly added rules', async () => {
136
+ expect((await engine.check({ name: 'new-tool' }, undefined)).decision).toBe(PolicyDecision.ASK_USER);
132
137
  engine.addRule({ toolName: 'new-tool', decision: PolicyDecision.ALLOW });
133
- expect(engine.check({ name: 'new-tool' })).toBe(PolicyDecision.ALLOW);
138
+ expect((await engine.check({ name: 'new-tool' }, undefined)).decision).toBe(PolicyDecision.ALLOW);
134
139
  });
135
140
  });
136
141
  describe('removeRulesForTool', () => {
@@ -169,7 +174,7 @@ describe('PolicyEngine', () => {
169
174
  });
170
175
  });
171
176
  describe('MCP server wildcard patterns', () => {
172
- it('should match MCP server wildcard patterns', () => {
177
+ it('should match MCP server wildcard patterns', async () => {
173
178
  const rules = [
174
179
  {
175
180
  toolName: 'my-server__*',
@@ -184,17 +189,21 @@ describe('PolicyEngine', () => {
184
189
  ];
185
190
  engine = new PolicyEngine({ rules });
186
191
  // Should match my-server tools
187
- expect(engine.check({ name: 'my-server__tool1' })).toBe(PolicyDecision.ALLOW);
188
- expect(engine.check({ name: 'my-server__another_tool' })).toBe(PolicyDecision.ALLOW);
192
+ expect((await engine.check({ name: 'my-server__tool1' }, undefined)).decision).toBe(PolicyDecision.ALLOW);
193
+ expect((await engine.check({ name: 'my-server__another_tool' }, undefined))
194
+ .decision).toBe(PolicyDecision.ALLOW);
189
195
  // Should match blocked-server tools
190
- expect(engine.check({ name: 'blocked-server__tool1' })).toBe(PolicyDecision.DENY);
191
- expect(engine.check({ name: 'blocked-server__dangerous' })).toBe(PolicyDecision.DENY);
196
+ expect((await engine.check({ name: 'blocked-server__tool1' }, undefined))
197
+ .decision).toBe(PolicyDecision.DENY);
198
+ expect((await engine.check({ name: 'blocked-server__dangerous' }, undefined))
199
+ .decision).toBe(PolicyDecision.DENY);
192
200
  // Should not match other patterns
193
- expect(engine.check({ name: 'other-server__tool' })).toBe(PolicyDecision.ASK_USER);
194
- expect(engine.check({ name: 'my-server-tool' })).toBe(PolicyDecision.ASK_USER); // No __ separator
195
- expect(engine.check({ name: 'my-server' })).toBe(PolicyDecision.ASK_USER); // No tool name
201
+ expect((await engine.check({ name: 'other-server__tool' }, undefined))
202
+ .decision).toBe(PolicyDecision.ASK_USER);
203
+ expect((await engine.check({ name: 'my-server-tool' }, undefined)).decision).toBe(PolicyDecision.ASK_USER); // No __ separator
204
+ expect((await engine.check({ name: 'my-server' }, undefined)).decision).toBe(PolicyDecision.ASK_USER); // No tool name
196
205
  });
197
- it('should prioritize specific tool rules over server wildcards', () => {
206
+ it('should prioritize specific tool rules over server wildcards', async () => {
198
207
  const rules = [
199
208
  {
200
209
  toolName: 'my-server__*',
@@ -209,12 +218,54 @@ describe('PolicyEngine', () => {
209
218
  ];
210
219
  engine = new PolicyEngine({ rules });
211
220
  // Specific tool deny should override server allow
212
- expect(engine.check({ name: 'my-server__dangerous-tool' })).toBe(PolicyDecision.DENY);
213
- expect(engine.check({ name: 'my-server__safe-tool' })).toBe(PolicyDecision.ALLOW);
221
+ expect((await engine.check({ name: 'my-server__dangerous-tool' }, undefined))
222
+ .decision).toBe(PolicyDecision.DENY);
223
+ expect((await engine.check({ name: 'my-server__safe-tool' }, undefined))
224
+ .decision).toBe(PolicyDecision.ALLOW);
225
+ });
226
+ it('should NOT match spoofed server names when using wildcards', async () => {
227
+ // Vulnerability: A rule for 'prefix__*' matches 'prefix__suffix__tool'
228
+ // effectively allowing a server named 'prefix__suffix' to spoof 'prefix'.
229
+ const rules = [
230
+ {
231
+ toolName: 'safe_server__*',
232
+ decision: PolicyDecision.ALLOW,
233
+ },
234
+ ];
235
+ engine = new PolicyEngine({ rules });
236
+ // A tool from a different server 'safe_server__malicious'
237
+ const spoofedToolCall = { name: 'safe_server__malicious__tool' };
238
+ // CURRENT BEHAVIOR (FIXED): Matches because it starts with 'safe_server__' BUT serverName doesn't match 'safe_server'
239
+ // We expect this to FAIL matching the ALLOW rule, thus falling back to default (ASK_USER)
240
+ expect((await engine.check(spoofedToolCall, 'safe_server__malicious'))
241
+ .decision).toBe(PolicyDecision.ASK_USER);
242
+ });
243
+ it('should verify tool name prefix even if serverName matches', async () => {
244
+ const rules = [
245
+ {
246
+ toolName: 'safe_server__*',
247
+ decision: PolicyDecision.ALLOW,
248
+ },
249
+ ];
250
+ engine = new PolicyEngine({ rules });
251
+ // serverName matches, but tool name does not start with prefix
252
+ const invalidToolCall = { name: 'other_server__tool' };
253
+ expect((await engine.check(invalidToolCall, 'safe_server')).decision).toBe(PolicyDecision.ASK_USER);
254
+ });
255
+ it('should allow when both serverName and tool name prefix match', async () => {
256
+ const rules = [
257
+ {
258
+ toolName: 'safe_server__*',
259
+ decision: PolicyDecision.ALLOW,
260
+ },
261
+ ];
262
+ engine = new PolicyEngine({ rules });
263
+ const validToolCall = { name: 'safe_server__tool' };
264
+ expect((await engine.check(validToolCall, 'safe_server')).decision).toBe(PolicyDecision.ALLOW);
214
265
  });
215
266
  });
216
267
  describe('complex scenarios', () => {
217
- it('should handle multiple matching rules with different priorities', () => {
268
+ it('should handle multiple matching rules with different priorities', async () => {
218
269
  const rules = [
219
270
  { decision: PolicyDecision.DENY, priority: 0 }, // Default deny all
220
271
  { toolName: 'shell', decision: PolicyDecision.ASK_USER, priority: 5 },
@@ -227,13 +278,13 @@ describe('PolicyEngine', () => {
227
278
  ];
228
279
  engine = new PolicyEngine({ rules });
229
280
  // Matches highest priority rule (ls command)
230
- expect(engine.check({ name: 'shell', args: { command: 'ls -la' } })).toBe(PolicyDecision.ALLOW);
281
+ expect((await engine.check({ name: 'shell', args: { command: 'ls -la' } }, undefined)).decision).toBe(PolicyDecision.ALLOW);
231
282
  // Matches middle priority rule (shell without ls)
232
- expect(engine.check({ name: 'shell', args: { command: 'pwd' } })).toBe(PolicyDecision.ASK_USER);
283
+ expect((await engine.check({ name: 'shell', args: { command: 'pwd' } }, undefined)).decision).toBe(PolicyDecision.ASK_USER);
233
284
  // Matches lowest priority rule (not shell)
234
- expect(engine.check({ name: 'edit' })).toBe(PolicyDecision.DENY);
285
+ expect((await engine.check({ name: 'edit' }, undefined)).decision).toBe(PolicyDecision.DENY);
235
286
  });
236
- it('should handle tools with no args', () => {
287
+ it('should handle tools with no args', async () => {
237
288
  const rules = [
238
289
  {
239
290
  toolName: 'read',
@@ -243,13 +294,13 @@ describe('PolicyEngine', () => {
243
294
  ];
244
295
  engine = new PolicyEngine({ rules });
245
296
  // Tool call without args should not match pattern
246
- expect(engine.check({ name: 'read' })).toBe(PolicyDecision.ASK_USER);
297
+ expect((await engine.check({ name: 'read' }, undefined)).decision).toBe(PolicyDecision.ASK_USER);
247
298
  // Tool call with args not matching pattern
248
- expect(engine.check({ name: 'read', args: { file: 'public.txt' } })).toBe(PolicyDecision.ASK_USER);
299
+ expect((await engine.check({ name: 'read', args: { file: 'public.txt' } }, undefined)).decision).toBe(PolicyDecision.ASK_USER);
249
300
  // Tool call with args matching pattern
250
- expect(engine.check({ name: 'read', args: { file: 'secret.txt' } })).toBe(PolicyDecision.DENY);
301
+ expect((await engine.check({ name: 'read', args: { file: 'secret.txt' } }, undefined)).decision).toBe(PolicyDecision.DENY);
251
302
  });
252
- it('should match args pattern regardless of property order', () => {
303
+ it('should match args pattern regardless of property order', async () => {
253
304
  const rules = [
254
305
  {
255
306
  toolName: 'shell',
@@ -262,13 +313,16 @@ describe('PolicyEngine', () => {
262
313
  // Same args with different property order should both match
263
314
  const args1 = { command: 'rm -rf /', path: '/home' };
264
315
  const args2 = { path: '/home', command: 'rm -rf /' };
265
- expect(engine.check({ name: 'shell', args: args1 })).toBe(PolicyDecision.DENY);
266
- expect(engine.check({ name: 'shell', args: args2 })).toBe(PolicyDecision.DENY);
316
+ expect((await engine.check({ name: 'shell', args: args1 }, undefined))
317
+ .decision).toBe(PolicyDecision.DENY);
318
+ expect((await engine.check({ name: 'shell', args: args2 }, undefined))
319
+ .decision).toBe(PolicyDecision.DENY);
267
320
  // Verify safe command doesn't match
268
321
  const safeArgs = { command: 'ls -la', path: '/home' };
269
- expect(engine.check({ name: 'shell', args: safeArgs })).toBe(PolicyDecision.ASK_USER);
322
+ expect((await engine.check({ name: 'shell', args: safeArgs }, undefined))
323
+ .decision).toBe(PolicyDecision.ASK_USER);
270
324
  });
271
- it('should handle nested objects in args with stable stringification', () => {
325
+ it('should handle nested objects in args with stable stringification', async () => {
272
326
  const rules = [
273
327
  {
274
328
  toolName: 'api',
@@ -286,10 +340,10 @@ describe('PolicyEngine', () => {
286
340
  method: 'POST',
287
341
  data: { value: 'secret', sensitive: true },
288
342
  };
289
- expect(engine.check({ name: 'api', args: args1 })).toBe(PolicyDecision.DENY);
290
- expect(engine.check({ name: 'api', args: args2 })).toBe(PolicyDecision.DENY);
343
+ expect((await engine.check({ name: 'api', args: args1 }, undefined)).decision).toBe(PolicyDecision.DENY);
344
+ expect((await engine.check({ name: 'api', args: args2 }, undefined)).decision).toBe(PolicyDecision.DENY);
291
345
  });
292
- it('should handle circular references without stack overflow', () => {
346
+ it('should handle circular references without stack overflow', async () => {
293
347
  const rules = [
294
348
  {
295
349
  toolName: 'test',
@@ -306,14 +360,16 @@ describe('PolicyEngine', () => {
306
360
  circularArgs.data['self'] =
307
361
  circularArgs.data;
308
362
  // Should not throw stack overflow error
309
- expect(() => engine.check({ name: 'test', args: circularArgs })).not.toThrow();
363
+ await expect(engine.check({ name: 'test', args: circularArgs }, undefined)).resolves.not.toThrow();
310
364
  // Should detect the circular reference pattern
311
- expect(engine.check({ name: 'test', args: circularArgs })).toBe(PolicyDecision.DENY);
365
+ expect((await engine.check({ name: 'test', args: circularArgs }, undefined))
366
+ .decision).toBe(PolicyDecision.DENY);
312
367
  // Non-circular object should not match
313
368
  const normalArgs = { name: 'test', data: { value: 'normal' } };
314
- expect(engine.check({ name: 'test', args: normalArgs })).toBe(PolicyDecision.ASK_USER);
369
+ expect((await engine.check({ name: 'test', args: normalArgs }, undefined))
370
+ .decision).toBe(PolicyDecision.ASK_USER);
315
371
  });
316
- it('should handle deep circular references', () => {
372
+ it('should handle deep circular references', async () => {
317
373
  const rules = [
318
374
  {
319
375
  toolName: 'deep',
@@ -333,11 +389,12 @@ describe('PolicyEngine', () => {
333
389
  const level3 = deepCircular.level1.level2.level3;
334
390
  level3['back'] = deepCircular.level1;
335
391
  // Should handle without stack overflow
336
- expect(() => engine.check({ name: 'deep', args: deepCircular })).not.toThrow();
392
+ await expect(engine.check({ name: 'deep', args: deepCircular }, undefined)).resolves.not.toThrow();
337
393
  // Should detect the circular reference
338
- expect(engine.check({ name: 'deep', args: deepCircular })).toBe(PolicyDecision.DENY);
394
+ expect((await engine.check({ name: 'deep', args: deepCircular }, undefined))
395
+ .decision).toBe(PolicyDecision.DENY);
339
396
  });
340
- it('should handle repeated non-circular objects correctly', () => {
397
+ it('should handle repeated non-circular objects correctly', async () => {
341
398
  const rules = [
342
399
  {
343
400
  toolName: 'test',
@@ -360,9 +417,9 @@ describe('PolicyEngine', () => {
360
417
  third: { nested: sharedObj },
361
418
  };
362
419
  // Should NOT mark repeated objects as circular, and should match the shared value pattern
363
- expect(engine.check({ name: 'test', args })).toBe(PolicyDecision.ALLOW);
420
+ expect((await engine.check({ name: 'test', args }, undefined)).decision).toBe(PolicyDecision.ALLOW);
364
421
  });
365
- it('should omit undefined and function values from objects', () => {
422
+ it('should omit undefined and function values from objects', async () => {
366
423
  const rules = [
367
424
  {
368
425
  toolName: 'test',
@@ -378,7 +435,7 @@ describe('PolicyEngine', () => {
378
435
  nullValue: null,
379
436
  };
380
437
  // Should match pattern with defined value, undefined and functions omitted
381
- expect(engine.check({ name: 'test', args })).toBe(PolicyDecision.ALLOW);
438
+ expect((await engine.check({ name: 'test', args }, undefined)).decision).toBe(PolicyDecision.ALLOW);
382
439
  // Check that the pattern would NOT match if undefined was included
383
440
  const rulesWithUndefined = [
384
441
  {
@@ -388,7 +445,7 @@ describe('PolicyEngine', () => {
388
445
  },
389
446
  ];
390
447
  engine = new PolicyEngine({ rules: rulesWithUndefined });
391
- expect(engine.check({ name: 'test', args })).toBe(PolicyDecision.ASK_USER);
448
+ expect((await engine.check({ name: 'test', args }, undefined)).decision).toBe(PolicyDecision.ASK_USER);
392
449
  // Check that the pattern would NOT match if function was included
393
450
  const rulesWithFunction = [
394
451
  {
@@ -398,9 +455,9 @@ describe('PolicyEngine', () => {
398
455
  },
399
456
  ];
400
457
  engine = new PolicyEngine({ rules: rulesWithFunction });
401
- expect(engine.check({ name: 'test', args })).toBe(PolicyDecision.ASK_USER);
458
+ expect((await engine.check({ name: 'test', args }, undefined)).decision).toBe(PolicyDecision.ASK_USER);
402
459
  });
403
- it('should convert undefined and functions to null in arrays', () => {
460
+ it('should convert undefined and functions to null in arrays', async () => {
404
461
  const rules = [
405
462
  {
406
463
  toolName: 'test',
@@ -413,9 +470,9 @@ describe('PolicyEngine', () => {
413
470
  array: ['value', undefined, () => 'hello', null],
414
471
  };
415
472
  // Should match pattern with undefined and functions converted to null
416
- expect(engine.check({ name: 'test', args })).toBe(PolicyDecision.ALLOW);
473
+ expect((await engine.check({ name: 'test', args }, undefined)).decision).toBe(PolicyDecision.ALLOW);
417
474
  });
418
- it('should produce valid JSON for all inputs', () => {
475
+ it('should produce valid JSON for all inputs', async () => {
419
476
  const testCases = [
420
477
  { input: { simple: 'string' }, desc: 'simple object' },
421
478
  {
@@ -443,12 +500,13 @@ describe('PolicyEngine', () => {
443
500
  ];
444
501
  engine = new PolicyEngine({ rules });
445
502
  // Should not throw when checking (which internally uses stableStringify)
446
- expect(() => engine.check({ name: 'test', args: input })).not.toThrow();
503
+ await expect(engine.check({ name: 'test', args: input }, undefined)).resolves.not.toThrow();
447
504
  // The check should succeed
448
- expect(engine.check({ name: 'test', args: input })).toBe(PolicyDecision.ALLOW);
505
+ expect((await engine.check({ name: 'test', args: input }, undefined))
506
+ .decision).toBe(PolicyDecision.ALLOW);
449
507
  }
450
508
  });
451
- it('should respect toJSON methods on objects', () => {
509
+ it('should respect toJSON methods on objects', async () => {
452
510
  const rules = [
453
511
  {
454
512
  toolName: 'test',
@@ -470,9 +528,9 @@ describe('PolicyEngine', () => {
470
528
  },
471
529
  };
472
530
  // Should match the sanitized pattern, not the dangerous one
473
- expect(engine.check({ name: 'test', args })).toBe(PolicyDecision.ALLOW);
531
+ expect((await engine.check({ name: 'test', args }, undefined)).decision).toBe(PolicyDecision.ALLOW);
474
532
  });
475
- it('should handle toJSON that returns primitives', () => {
533
+ it('should handle toJSON that returns primitives', async () => {
476
534
  const rules = [
477
535
  {
478
536
  toolName: 'test',
@@ -488,9 +546,9 @@ describe('PolicyEngine', () => {
488
546
  },
489
547
  };
490
548
  // toJSON returns a string, which should be properly stringified
491
- expect(engine.check({ name: 'test', args })).toBe(PolicyDecision.ALLOW);
549
+ expect((await engine.check({ name: 'test', args }, undefined)).decision).toBe(PolicyDecision.ALLOW);
492
550
  });
493
- it('should handle toJSON that throws an error', () => {
551
+ it('should handle toJSON that throws an error', async () => {
494
552
  const rules = [
495
553
  {
496
554
  toolName: 'test',
@@ -508,7 +566,333 @@ describe('PolicyEngine', () => {
508
566
  },
509
567
  };
510
568
  // Should fall back to regular object serialization when toJSON throws
511
- expect(engine.check({ name: 'test', args })).toBe(PolicyDecision.ALLOW);
569
+ expect((await engine.check({ name: 'test', args }, undefined)).decision).toBe(PolicyDecision.ALLOW);
570
+ });
571
+ });
572
+ describe('safety checker integration', () => {
573
+ it('should call checker when rule allows and has safety_checker', async () => {
574
+ const rules = [
575
+ {
576
+ toolName: 'test-tool',
577
+ decision: PolicyDecision.ALLOW,
578
+ },
579
+ ];
580
+ const checkers = [
581
+ {
582
+ toolName: 'test-tool',
583
+ checker: {
584
+ type: 'external',
585
+ name: 'test-checker',
586
+ config: { content: 'test-content' },
587
+ },
588
+ },
589
+ ];
590
+ engine = new PolicyEngine({ rules, checkers }, mockCheckerRunner);
591
+ vi.mocked(mockCheckerRunner.runChecker).mockResolvedValue({
592
+ decision: SafetyCheckDecision.ALLOW,
593
+ });
594
+ const result = await engine.check({ name: 'test-tool', args: { foo: 'bar' } }, undefined);
595
+ expect(result.decision).toBe(PolicyDecision.ALLOW);
596
+ expect(mockCheckerRunner.runChecker).toHaveBeenCalledWith({ name: 'test-tool', args: { foo: 'bar' } }, {
597
+ type: 'external',
598
+ name: 'test-checker',
599
+ config: { content: 'test-content' },
600
+ });
601
+ });
602
+ it('should handle checker errors as DENY', async () => {
603
+ const rules = [
604
+ {
605
+ toolName: 'test',
606
+ decision: PolicyDecision.ALLOW,
607
+ },
608
+ ];
609
+ const checkers = [
610
+ {
611
+ toolName: 'test',
612
+ checker: {
613
+ type: 'in-process',
614
+ name: InProcessCheckerType.ALLOWED_PATH,
615
+ },
616
+ },
617
+ ];
618
+ mockCheckerRunner.runChecker = vi
619
+ .fn()
620
+ .mockRejectedValue(new Error('Checker failed'));
621
+ engine = new PolicyEngine({ rules, checkers }, mockCheckerRunner);
622
+ const { decision } = await engine.check({ name: 'test' }, undefined);
623
+ expect(decision).toBe(PolicyDecision.DENY);
624
+ });
625
+ it('should return DENY when checker denies', async () => {
626
+ const rules = [
627
+ {
628
+ toolName: 'test-tool',
629
+ decision: PolicyDecision.ALLOW,
630
+ },
631
+ ];
632
+ const checkers = [
633
+ {
634
+ toolName: 'test-tool',
635
+ checker: {
636
+ type: 'external',
637
+ name: 'test-checker',
638
+ config: { content: 'test-content' },
639
+ },
640
+ },
641
+ ];
642
+ engine = new PolicyEngine({ rules, checkers }, mockCheckerRunner);
643
+ vi.mocked(mockCheckerRunner.runChecker).mockResolvedValue({
644
+ decision: SafetyCheckDecision.DENY,
645
+ reason: 'test reason',
646
+ });
647
+ const result = await engine.check({ name: 'test-tool', args: { foo: 'bar' } }, undefined);
648
+ expect(result.decision).toBe(PolicyDecision.DENY);
649
+ expect(mockCheckerRunner.runChecker).toHaveBeenCalled();
650
+ });
651
+ it('should not call checker if decision is not ALLOW', async () => {
652
+ const rules = [
653
+ {
654
+ toolName: 'test-tool',
655
+ decision: PolicyDecision.ASK_USER,
656
+ },
657
+ ];
658
+ const checkers = [
659
+ {
660
+ toolName: 'test-tool',
661
+ checker: {
662
+ type: 'external',
663
+ name: 'test-checker',
664
+ config: { content: 'test-content' },
665
+ },
666
+ },
667
+ ];
668
+ engine = new PolicyEngine({ rules, checkers }, mockCheckerRunner);
669
+ vi.mocked(mockCheckerRunner.runChecker).mockResolvedValue({
670
+ decision: SafetyCheckDecision.ALLOW,
671
+ });
672
+ const result = await engine.check({ name: 'test-tool', args: { foo: 'bar' } }, undefined);
673
+ expect(result.decision).toBe(PolicyDecision.ASK_USER);
674
+ expect(mockCheckerRunner.runChecker).toHaveBeenCalled();
675
+ });
676
+ it('should run checkers when rule allows', async () => {
677
+ const rules = [
678
+ {
679
+ toolName: 'test',
680
+ decision: PolicyDecision.ALLOW,
681
+ },
682
+ ];
683
+ const checkers = [
684
+ {
685
+ toolName: 'test',
686
+ checker: {
687
+ type: 'in-process',
688
+ name: InProcessCheckerType.ALLOWED_PATH,
689
+ },
690
+ },
691
+ ];
692
+ mockCheckerRunner.runChecker = vi.fn().mockResolvedValue({
693
+ decision: SafetyCheckDecision.ALLOW,
694
+ });
695
+ engine = new PolicyEngine({ rules, checkers }, mockCheckerRunner);
696
+ const { decision } = await engine.check({ name: 'test' }, undefined);
697
+ expect(decision).toBe(PolicyDecision.ALLOW);
698
+ expect(mockCheckerRunner.runChecker).toHaveBeenCalledTimes(1);
699
+ });
700
+ it('should not call checker if rule has no safety_checker', async () => {
701
+ const rules = [
702
+ {
703
+ toolName: 'test-tool',
704
+ decision: PolicyDecision.ALLOW,
705
+ },
706
+ ];
707
+ engine = new PolicyEngine({ rules }, mockCheckerRunner);
708
+ const result = await engine.check({ name: 'test-tool', args: { foo: 'bar' } }, undefined);
709
+ expect(result.decision).toBe(PolicyDecision.ALLOW);
710
+ expect(mockCheckerRunner.runChecker).not.toHaveBeenCalled();
711
+ });
712
+ });
713
+ describe('serverName requirement', () => {
714
+ it('should require serverName for checks', async () => {
715
+ // @ts-expect-error - intentionally testing missing serverName
716
+ expect((await engine.check({ name: 'test' })).decision).toBe(PolicyDecision.ASK_USER);
717
+ // When serverName is provided (even undefined), it should work
718
+ expect((await engine.check({ name: 'test' }, undefined)).decision).toBe(PolicyDecision.ASK_USER);
719
+ expect((await engine.check({ name: 'test' }, 'some-server')).decision).toBe(PolicyDecision.ASK_USER);
720
+ });
721
+ it('should run multiple checkers in priority order and stop at first denial', async () => {
722
+ const rules = [
723
+ {
724
+ toolName: 'test',
725
+ decision: PolicyDecision.ALLOW,
726
+ },
727
+ ];
728
+ const checkers = [
729
+ {
730
+ toolName: 'test',
731
+ priority: 10,
732
+ checker: { type: 'external', name: 'checker1' },
733
+ },
734
+ {
735
+ toolName: 'test',
736
+ priority: 20, // Should run first
737
+ checker: { type: 'external', name: 'checker2' },
738
+ },
739
+ ];
740
+ mockCheckerRunner.runChecker = vi
741
+ .fn()
742
+ .mockImplementation(async (_toolCall, config) => {
743
+ if (config.name === 'checker2') {
744
+ return {
745
+ decision: SafetyCheckDecision.DENY,
746
+ reason: 'checker2 denied',
747
+ };
748
+ }
749
+ return { decision: SafetyCheckDecision.ALLOW };
750
+ });
751
+ engine = new PolicyEngine({ rules, checkers }, mockCheckerRunner);
752
+ const { decision, rule } = await engine.check({ name: 'test' }, undefined);
753
+ expect(decision).toBe(PolicyDecision.DENY);
754
+ expect(rule).toBeDefined();
755
+ expect(mockCheckerRunner.runChecker).toHaveBeenCalledTimes(1);
756
+ expect(mockCheckerRunner.runChecker).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ name: 'checker2' }));
757
+ });
758
+ });
759
+ describe('addChecker', () => {
760
+ it('should add a new checker and maintain priority order', () => {
761
+ const checker1 = {
762
+ checker: { type: 'external', name: 'checker1' },
763
+ priority: 5,
764
+ };
765
+ const checker2 = {
766
+ checker: { type: 'external', name: 'checker2' },
767
+ priority: 10,
768
+ };
769
+ engine.addChecker(checker1);
770
+ engine.addChecker(checker2);
771
+ const checkers = engine.getCheckers();
772
+ expect(checkers).toHaveLength(2);
773
+ expect(checkers[0].priority).toBe(10);
774
+ expect(checkers[0].checker.name).toBe('checker2');
775
+ expect(checkers[1].priority).toBe(5);
776
+ expect(checkers[1].checker.name).toBe('checker1');
777
+ });
778
+ });
779
+ describe('checker matching logic', () => {
780
+ it('should match checkers using toolName and argsPattern', async () => {
781
+ const rules = [
782
+ { toolName: 'tool', decision: PolicyDecision.ALLOW },
783
+ ];
784
+ const matchingChecker = {
785
+ checker: { type: 'external', name: 'matching' },
786
+ toolName: 'tool',
787
+ argsPattern: /"safe":true/,
788
+ };
789
+ const nonMatchingChecker = {
790
+ checker: { type: 'external', name: 'non-matching' },
791
+ toolName: 'other',
792
+ };
793
+ engine = new PolicyEngine({ rules, checkers: [matchingChecker, nonMatchingChecker] }, mockCheckerRunner);
794
+ vi.mocked(mockCheckerRunner.runChecker).mockResolvedValue({
795
+ decision: SafetyCheckDecision.ALLOW,
796
+ });
797
+ await engine.check({ name: 'tool', args: { safe: true } }, undefined);
798
+ expect(mockCheckerRunner.runChecker).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ name: 'matching' }));
799
+ expect(mockCheckerRunner.runChecker).not.toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ name: 'non-matching' }));
800
+ });
801
+ it('should support wildcard patterns for checkers', async () => {
802
+ const rules = [
803
+ { toolName: 'server__tool', decision: PolicyDecision.ALLOW },
804
+ ];
805
+ const wildcardChecker = {
806
+ checker: { type: 'external', name: 'wildcard' },
807
+ toolName: 'server__*',
808
+ };
809
+ engine = new PolicyEngine({ rules, checkers: [wildcardChecker] }, mockCheckerRunner);
810
+ vi.mocked(mockCheckerRunner.runChecker).mockResolvedValue({
811
+ decision: SafetyCheckDecision.ALLOW,
812
+ });
813
+ await engine.check({ name: 'server__tool' }, 'server');
814
+ expect(mockCheckerRunner.runChecker).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ name: 'wildcard' }));
815
+ });
816
+ it('should run safety checkers when decision is ASK_USER and downgrade to DENY on failure', async () => {
817
+ const rules = [
818
+ { toolName: 'tool', decision: PolicyDecision.ASK_USER },
819
+ ];
820
+ const checkers = [
821
+ {
822
+ checker: {
823
+ type: 'in-process',
824
+ name: InProcessCheckerType.ALLOWED_PATH,
825
+ },
826
+ },
827
+ ];
828
+ engine = new PolicyEngine({ rules, checkers }, mockCheckerRunner);
829
+ vi.mocked(mockCheckerRunner.runChecker).mockResolvedValue({
830
+ decision: SafetyCheckDecision.DENY,
831
+ reason: 'Safety check failed',
832
+ });
833
+ const result = await engine.check({ name: 'tool' }, undefined);
834
+ expect(result.decision).toBe(PolicyDecision.DENY);
835
+ expect(mockCheckerRunner.runChecker).toHaveBeenCalled();
836
+ });
837
+ it('should run safety checkers when decision is ASK_USER and keep ASK_USER on success', async () => {
838
+ const rules = [
839
+ { toolName: 'tool', decision: PolicyDecision.ASK_USER },
840
+ ];
841
+ const checkers = [
842
+ {
843
+ checker: {
844
+ type: 'in-process',
845
+ name: InProcessCheckerType.ALLOWED_PATH,
846
+ },
847
+ },
848
+ ];
849
+ engine = new PolicyEngine({ rules, checkers }, mockCheckerRunner);
850
+ vi.mocked(mockCheckerRunner.runChecker).mockResolvedValue({
851
+ decision: SafetyCheckDecision.ALLOW,
852
+ });
853
+ const result = await engine.check({ name: 'tool' }, undefined);
854
+ expect(result.decision).toBe(PolicyDecision.ASK_USER);
855
+ expect(mockCheckerRunner.runChecker).toHaveBeenCalled();
856
+ });
857
+ it('should downgrade ALLOW to ASK_USER if checker returns ASK_USER', async () => {
858
+ const rules = [
859
+ { toolName: 'tool', decision: PolicyDecision.ALLOW },
860
+ ];
861
+ const checkers = [
862
+ {
863
+ checker: {
864
+ type: 'in-process',
865
+ name: InProcessCheckerType.ALLOWED_PATH,
866
+ },
867
+ },
868
+ ];
869
+ engine = new PolicyEngine({ rules, checkers }, mockCheckerRunner);
870
+ vi.mocked(mockCheckerRunner.runChecker).mockResolvedValue({
871
+ decision: SafetyCheckDecision.ASK_USER,
872
+ reason: 'Suspicious path',
873
+ });
874
+ const result = await engine.check({ name: 'tool' }, undefined);
875
+ expect(result.decision).toBe(PolicyDecision.ASK_USER);
876
+ });
877
+ it('should DENY if checker returns ASK_USER in non-interactive mode', async () => {
878
+ const rules = [
879
+ { toolName: 'tool', decision: PolicyDecision.ALLOW },
880
+ ];
881
+ const checkers = [
882
+ {
883
+ checker: {
884
+ type: 'in-process',
885
+ name: InProcessCheckerType.ALLOWED_PATH,
886
+ },
887
+ },
888
+ ];
889
+ engine = new PolicyEngine({ rules, checkers, nonInteractive: true }, mockCheckerRunner);
890
+ vi.mocked(mockCheckerRunner.runChecker).mockResolvedValue({
891
+ decision: SafetyCheckDecision.ASK_USER,
892
+ reason: 'Suspicious path',
893
+ });
894
+ const result = await engine.check({ name: 'tool' }, undefined);
895
+ expect(result.decision).toBe(PolicyDecision.DENY);
512
896
  });
513
897
  });
514
898
  });