@theowlops/boombot 0.0.0-stage → 1.1.1

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 (544) hide show
  1. package/README.md +69 -3
  2. package/dist/LICENSE +21 -0
  3. package/dist/bin/boombot.js +16 -0
  4. package/dist/cli/api-key.js +123 -0
  5. package/dist/cli/bin-bun.js +17 -0
  6. package/dist/cli/bin.js +34 -0
  7. package/dist/cli/cli/cli-error.js +61 -0
  8. package/dist/cli/cli/dispatch.js +138 -0
  9. package/dist/cli/cli/help.js +83 -0
  10. package/dist/cli/cli/parser.js +89 -0
  11. package/dist/cli/cli/registry.js +185 -0
  12. package/dist/cli/cli/spec.js +41 -0
  13. package/dist/cli/cli/suggest.js +125 -0
  14. package/dist/cli/cli/version.js +30 -0
  15. package/dist/cli/cli/vocabulary.js +87 -0
  16. package/dist/cli/commands/doctor.js +365 -0
  17. package/dist/cli/commands/gateway.js +290 -0
  18. package/dist/cli/commands/migrate.js +270 -0
  19. package/dist/cli/commands/plugin.js +97 -0
  20. package/dist/cli/commands/setup.js +244 -0
  21. package/dist/cli/config-file.js +133 -0
  22. package/dist/cli/master-key.js +78 -0
  23. package/dist/cli/open-browser.js +59 -0
  24. package/dist/cli/web-assets.js +58 -0
  25. package/dist/gateway/auth.js +46 -0
  26. package/dist/gateway/config-format.js +202 -0
  27. package/dist/gateway/config.js +427 -0
  28. package/dist/gateway/context.js +1 -0
  29. package/dist/gateway/env-file.js +129 -0
  30. package/dist/gateway/events.js +172 -0
  31. package/dist/gateway/guard/tagging.js +505 -0
  32. package/dist/gateway/handlers/index.js +66 -0
  33. package/dist/gateway/handlers/memory.js +54 -0
  34. package/dist/gateway/handlers/providers.js +73 -0
  35. package/dist/gateway/handlers/registry.js +39 -0
  36. package/dist/gateway/handlers/sessions.js +188 -0
  37. package/dist/gateway/handlers/system.js +12 -0
  38. package/dist/gateway/handlers/tasks.js +191 -0
  39. package/dist/gateway/handlers/turn.js +100 -0
  40. package/dist/gateway/index.js +126 -0
  41. package/dist/gateway/main.js +23 -0
  42. package/dist/gateway/server.js +354 -0
  43. package/dist/gateway/services/context-limits.js +75 -0
  44. package/dist/gateway/services/history-normalizer.js +42 -0
  45. package/dist/gateway/services/image-pipeline.js +56 -0
  46. package/dist/gateway/services/key-roulette-client.js +43 -0
  47. package/dist/gateway/services/llm-client.js +593 -0
  48. package/dist/gateway/services/llm-decoder.js +200 -0
  49. package/dist/gateway/services/local-fallback.js +135 -0
  50. package/dist/gateway/services/memory.js +511 -0
  51. package/dist/gateway/services/persistent-memory.js +196 -0
  52. package/dist/gateway/services/providers.js +44 -0
  53. package/dist/gateway/services/tasks.js +237 -0
  54. package/dist/gateway/services/verification.js +124 -0
  55. package/dist/gateway/services/vision-payload.js +48 -0
  56. package/dist/gateway/services.js +257 -0
  57. package/dist/gateway/session-snapshot.js +19 -0
  58. package/dist/gateway/sessions.js +701 -0
  59. package/dist/gateway/static.js +239 -0
  60. package/dist/gateway/turns/clarify-tool.js +57 -0
  61. package/dist/gateway/turns/cronjob-tool.js +96 -0
  62. package/dist/gateway/turns/deferred-tools.js +154 -0
  63. package/dist/gateway/turns/execute-code-tool.js +84 -0
  64. package/dist/gateway/turns/file-tool-defs.js +178 -0
  65. package/dist/gateway/turns/file-tool-ops.js +178 -0
  66. package/dist/gateway/turns/file-tool-scan.js +182 -0
  67. package/dist/gateway/turns/file-tool.js +302 -0
  68. package/dist/gateway/turns/index.js +416 -0
  69. package/dist/gateway/turns/jev-middleware.js +91 -0
  70. package/dist/gateway/turns/local-reply.js +156 -0
  71. package/dist/gateway/turns/mcp-client.js +643 -0
  72. package/dist/gateway/turns/memory-tool.js +171 -0
  73. package/dist/gateway/turns/middleware.js +96 -0
  74. package/dist/gateway/turns/python-core.js +397 -0
  75. package/dist/gateway/turns/routing/index.js +3 -0
  76. package/dist/gateway/turns/routing/intent-classifier.js +60 -0
  77. package/dist/gateway/turns/routing/planner-adapter.js +91 -0
  78. package/dist/gateway/turns/routing/types.js +1 -0
  79. package/dist/gateway/turns/runtime-deps.js +228 -0
  80. package/dist/gateway/turns/self-config-tool.js +277 -0
  81. package/dist/gateway/turns/service-types.js +11 -0
  82. package/dist/gateway/turns/service.js +820 -0
  83. package/dist/gateway/turns/session-adapter.js +158 -0
  84. package/dist/gateway/turns/session-tool.js +49 -0
  85. package/dist/gateway/turns/shell-tool.js +190 -0
  86. package/dist/gateway/turns/skill-tool.js +239 -0
  87. package/dist/gateway/turns/spillover.js +201 -0
  88. package/dist/gateway/turns/subagent-tool.js +140 -0
  89. package/dist/gateway/turns/system-prompt.js +121 -0
  90. package/dist/gateway/turns/tool-schema.js +90 -0
  91. package/dist/gateway/turns/turn-utils.js +58 -0
  92. package/dist/gateway/turns/vision-tool.js +120 -0
  93. package/dist/gateway/types.js +1 -0
  94. package/dist/native/win32-x64/boombot-native-host.exe +0 -0
  95. package/dist/node_modules/@boombot/channels/handlers.d.ts +9 -0
  96. package/dist/node_modules/@boombot/channels/handlers.js +109 -0
  97. package/dist/node_modules/@boombot/channels/index.d.ts +12 -0
  98. package/dist/node_modules/@boombot/channels/index.js +12 -0
  99. package/dist/node_modules/@boombot/channels/package.json +9 -0
  100. package/dist/node_modules/@boombot/channels/service.d.ts +62 -0
  101. package/dist/node_modules/@boombot/channels/service.js +608 -0
  102. package/dist/node_modules/@boombot/channels/tool.d.ts +10 -0
  103. package/dist/node_modules/@boombot/channels/tool.js +46 -0
  104. package/dist/node_modules/@boombot/channels/transports/common/dedup.d.ts +17 -0
  105. package/dist/node_modules/@boombot/channels/transports/common/dedup.js +42 -0
  106. package/dist/node_modules/@boombot/channels/transports/common/handoff.d.ts +19 -0
  107. package/dist/node_modules/@boombot/channels/transports/common/handoff.js +32 -0
  108. package/dist/node_modules/@boombot/channels/transports/common/index.d.ts +3 -0
  109. package/dist/node_modules/@boombot/channels/transports/common/index.js +3 -0
  110. package/dist/node_modules/@boombot/channels/transports/common/types.d.ts +99 -0
  111. package/dist/node_modules/@boombot/channels/transports/common/types.js +54 -0
  112. package/dist/node_modules/@boombot/channels/transports/common.d.ts +2 -0
  113. package/dist/node_modules/@boombot/channels/transports/common.js +2 -0
  114. package/dist/node_modules/@boombot/channels/transports/dedup/index.d.ts +1 -0
  115. package/dist/node_modules/@boombot/channels/transports/dedup/index.js +1 -0
  116. package/dist/node_modules/@boombot/channels/transports/dedup.d.ts +2 -0
  117. package/dist/node_modules/@boombot/channels/transports/dedup.js +2 -0
  118. package/dist/node_modules/@boombot/channels/transports/discord/index.d.ts +1 -0
  119. package/dist/node_modules/@boombot/channels/transports/discord/index.js +1 -0
  120. package/dist/node_modules/@boombot/channels/transports/discord/transport.d.ts +36 -0
  121. package/dist/node_modules/@boombot/channels/transports/discord/transport.js +199 -0
  122. package/dist/node_modules/@boombot/channels/transports/discord.d.ts +2 -0
  123. package/dist/node_modules/@boombot/channels/transports/discord.js +2 -0
  124. package/dist/node_modules/@boombot/channels/transports/handoff.d.ts +2 -0
  125. package/dist/node_modules/@boombot/channels/transports/handoff.js +2 -0
  126. package/dist/node_modules/@boombot/channels/transports/messenger/index.d.ts +1 -0
  127. package/dist/node_modules/@boombot/channels/transports/messenger/index.js +1 -0
  128. package/dist/node_modules/@boombot/channels/transports/messenger/transport.d.ts +34 -0
  129. package/dist/node_modules/@boombot/channels/transports/messenger/transport.js +180 -0
  130. package/dist/node_modules/@boombot/channels/transports/messenger.d.ts +2 -0
  131. package/dist/node_modules/@boombot/channels/transports/messenger.js +2 -0
  132. package/dist/node_modules/@boombot/channels/transports/slack/index.d.ts +1 -0
  133. package/dist/node_modules/@boombot/channels/transports/slack/index.js +1 -0
  134. package/dist/node_modules/@boombot/channels/transports/slack/transport.d.ts +35 -0
  135. package/dist/node_modules/@boombot/channels/transports/slack/transport.js +183 -0
  136. package/dist/node_modules/@boombot/channels/transports/slack.d.ts +2 -0
  137. package/dist/node_modules/@boombot/channels/transports/slack.js +2 -0
  138. package/dist/node_modules/@boombot/channels/transports/streamer/index.d.ts +1 -0
  139. package/dist/node_modules/@boombot/channels/transports/streamer/index.js +1 -0
  140. package/dist/node_modules/@boombot/channels/transports/streamer/streamer.d.ts +31 -0
  141. package/dist/node_modules/@boombot/channels/transports/streamer/streamer.js +141 -0
  142. package/dist/node_modules/@boombot/channels/transports/streamer.d.ts +2 -0
  143. package/dist/node_modules/@boombot/channels/transports/streamer.js +2 -0
  144. package/dist/node_modules/@boombot/channels/transports/telegram/index.d.ts +1 -0
  145. package/dist/node_modules/@boombot/channels/transports/telegram/index.js +1 -0
  146. package/dist/node_modules/@boombot/channels/transports/telegram/transport.d.ts +75 -0
  147. package/dist/node_modules/@boombot/channels/transports/telegram/transport.js +395 -0
  148. package/dist/node_modules/@boombot/channels/transports/telegram.d.ts +2 -0
  149. package/dist/node_modules/@boombot/channels/transports/telegram.js +2 -0
  150. package/dist/node_modules/@boombot/channels/transports/tiktok/index.d.ts +1 -0
  151. package/dist/node_modules/@boombot/channels/transports/tiktok/index.js +1 -0
  152. package/dist/node_modules/@boombot/channels/transports/tiktok/transport.d.ts +27 -0
  153. package/dist/node_modules/@boombot/channels/transports/tiktok/transport.js +97 -0
  154. package/dist/node_modules/@boombot/channels/transports/tiktok.d.ts +2 -0
  155. package/dist/node_modules/@boombot/channels/transports/tiktok.js +2 -0
  156. package/dist/node_modules/@boombot/channels/transports/whatsapp/index.d.ts +1 -0
  157. package/dist/node_modules/@boombot/channels/transports/whatsapp/index.js +1 -0
  158. package/dist/node_modules/@boombot/channels/transports/whatsapp/transport.d.ts +38 -0
  159. package/dist/node_modules/@boombot/channels/transports/whatsapp/transport.js +189 -0
  160. package/dist/node_modules/@boombot/channels/transports/whatsapp.d.ts +2 -0
  161. package/dist/node_modules/@boombot/channels/transports/whatsapp.js +2 -0
  162. package/dist/node_modules/@boombot/channels/transports/zalo/index.d.ts +2 -0
  163. package/dist/node_modules/@boombot/channels/transports/zalo/index.js +2 -0
  164. package/dist/node_modules/@boombot/channels/transports/zalo/oa.d.ts +28 -0
  165. package/dist/node_modules/@boombot/channels/transports/zalo/oa.js +94 -0
  166. package/dist/node_modules/@boombot/channels/transports/zalo/personal.d.ts +106 -0
  167. package/dist/node_modules/@boombot/channels/transports/zalo/personal.js +171 -0
  168. package/dist/node_modules/@boombot/channels/transports/zalo-personal.d.ts +2 -0
  169. package/dist/node_modules/@boombot/channels/transports/zalo-personal.js +2 -0
  170. package/dist/node_modules/@boombot/channels/transports/zalo.d.ts +2 -0
  171. package/dist/node_modules/@boombot/channels/transports/zalo.js +2 -0
  172. package/dist/node_modules/@boombot/core/agent-core/index.d.ts +12 -0
  173. package/dist/node_modules/@boombot/core/agent-core/index.js +12 -0
  174. package/dist/node_modules/@boombot/core/agent-core/middleware.d.ts +60 -0
  175. package/dist/node_modules/@boombot/core/agent-core/middleware.js +152 -0
  176. package/dist/node_modules/@boombot/core/agent-core/routing.d.ts +128 -0
  177. package/dist/node_modules/@boombot/core/agent-core/routing.js +437 -0
  178. package/dist/node_modules/@boombot/core/agent-core/runtime-deps.d.ts +88 -0
  179. package/dist/node_modules/@boombot/core/agent-core/runtime-deps.js +8 -0
  180. package/dist/node_modules/@boombot/core/agent-core/tool-errors.d.ts +44 -0
  181. package/dist/node_modules/@boombot/core/agent-core/tool-errors.js +60 -0
  182. package/dist/node_modules/@boombot/core/agent-core/turn-loop.d.ts +21 -0
  183. package/dist/node_modules/@boombot/core/agent-core/turn-loop.js +561 -0
  184. package/dist/node_modules/@boombot/core/agent-core/types.d.ts +189 -0
  185. package/dist/node_modules/@boombot/core/agent-core/types.js +7 -0
  186. package/dist/node_modules/@boombot/core/compaction/compactor.d.ts +19 -0
  187. package/dist/node_modules/@boombot/core/compaction/compactor.js +127 -0
  188. package/dist/node_modules/@boombot/core/compaction/estimator.d.ts +10 -0
  189. package/dist/node_modules/@boombot/core/compaction/estimator.js +48 -0
  190. package/dist/node_modules/@boombot/core/compaction/index.d.ts +3 -0
  191. package/dist/node_modules/@boombot/core/compaction/index.js +3 -0
  192. package/dist/node_modules/@boombot/core/compaction/types.d.ts +31 -0
  193. package/dist/node_modules/@boombot/core/compaction/types.js +7 -0
  194. package/dist/node_modules/@boombot/core/index.d.ts +9 -0
  195. package/dist/node_modules/@boombot/core/index.js +9 -0
  196. package/dist/node_modules/@boombot/core/jev/index.d.ts +2 -0
  197. package/dist/node_modules/@boombot/core/jev/index.js +2 -0
  198. package/dist/node_modules/@boombot/core/jev/jev.d.ts +30 -0
  199. package/dist/node_modules/@boombot/core/jev/jev.js +164 -0
  200. package/dist/node_modules/@boombot/core/jev/types.d.ts +22 -0
  201. package/dist/node_modules/@boombot/core/jev/types.js +1 -0
  202. package/dist/node_modules/@boombot/core/memory/consolidation/budget.d.ts +63 -0
  203. package/dist/node_modules/@boombot/core/memory/consolidation/budget.js +94 -0
  204. package/dist/node_modules/@boombot/core/memory/consolidation/consolidator.d.ts +152 -0
  205. package/dist/node_modules/@boombot/core/memory/consolidation/consolidator.js +318 -0
  206. package/dist/node_modules/@boombot/core/memory/consolidation/index.d.ts +12 -0
  207. package/dist/node_modules/@boombot/core/memory/consolidation/index.js +12 -0
  208. package/dist/node_modules/@boombot/core/memory/curation.d.ts +86 -0
  209. package/dist/node_modules/@boombot/core/memory/curation.js +120 -0
  210. package/dist/node_modules/@boombot/core/memory/errors.d.ts +12 -0
  211. package/dist/node_modules/@boombot/core/memory/errors.js +20 -0
  212. package/dist/node_modules/@boombot/core/memory/hybrid-search.d.ts +29 -0
  213. package/dist/node_modules/@boombot/core/memory/hybrid-search.js +52 -0
  214. package/dist/node_modules/@boombot/core/memory/index.d.ts +23 -0
  215. package/dist/node_modules/@boombot/core/memory/index.js +23 -0
  216. package/dist/node_modules/@boombot/core/memory/indexer.d.ts +47 -0
  217. package/dist/node_modules/@boombot/core/memory/indexer.js +138 -0
  218. package/dist/node_modules/@boombot/core/memory/lifecycle.d.ts +12 -0
  219. package/dist/node_modules/@boombot/core/memory/lifecycle.js +66 -0
  220. package/dist/node_modules/@boombot/core/memory/provider.d.ts +84 -0
  221. package/dist/node_modules/@boombot/core/memory/provider.js +117 -0
  222. package/dist/node_modules/@boombot/core/memory/recall.d.ts +7 -0
  223. package/dist/node_modules/@boombot/core/memory/recall.js +111 -0
  224. package/dist/node_modules/@boombot/core/memory/scope-resolver.d.ts +27 -0
  225. package/dist/node_modules/@boombot/core/memory/scope-resolver.js +60 -0
  226. package/dist/node_modules/@boombot/core/memory/search.d.ts +37 -0
  227. package/dist/node_modules/@boombot/core/memory/search.js +158 -0
  228. package/dist/node_modules/@boombot/core/memory/store.d.ts +60 -0
  229. package/dist/node_modules/@boombot/core/memory/store.js +289 -0
  230. package/dist/node_modules/@boombot/core/memory/types.d.ts +121 -0
  231. package/dist/node_modules/@boombot/core/memory/types.js +21 -0
  232. package/dist/node_modules/@boombot/core/memory/vector-index.d.ts +34 -0
  233. package/dist/node_modules/@boombot/core/memory/vector-index.js +65 -0
  234. package/dist/node_modules/@boombot/core/package.json +9 -0
  235. package/dist/node_modules/@boombot/core/reflection/classifier.d.ts +13 -0
  236. package/dist/node_modules/@boombot/core/reflection/classifier.js +77 -0
  237. package/dist/node_modules/@boombot/core/reflection/index.d.ts +2 -0
  238. package/dist/node_modules/@boombot/core/reflection/index.js +2 -0
  239. package/dist/node_modules/@boombot/core/reflection/types.d.ts +13 -0
  240. package/dist/node_modules/@boombot/core/reflection/types.js +4 -0
  241. package/dist/node_modules/@boombot/core/research/config.d.ts +89 -0
  242. package/dist/node_modules/@boombot/core/research/config.js +206 -0
  243. package/dist/node_modules/@boombot/core/research/crawl.d.ts +36 -0
  244. package/dist/node_modules/@boombot/core/research/crawl.js +179 -0
  245. package/dist/node_modules/@boombot/core/research/factory.d.ts +26 -0
  246. package/dist/node_modules/@boombot/core/research/factory.js +46 -0
  247. package/dist/node_modules/@boombot/core/research/http.d.ts +87 -0
  248. package/dist/node_modules/@boombot/core/research/http.js +250 -0
  249. package/dist/node_modules/@boombot/core/research/index.d.ts +17 -0
  250. package/dist/node_modules/@boombot/core/research/index.js +10 -0
  251. package/dist/node_modules/@boombot/core/research/orchestrator.d.ts +113 -0
  252. package/dist/node_modules/@boombot/core/research/orchestrator.js +252 -0
  253. package/dist/node_modules/@boombot/core/research/parse/docx.d.ts +24 -0
  254. package/dist/node_modules/@boombot/core/research/parse/docx.js +183 -0
  255. package/dist/node_modules/@boombot/core/research/parse/guards.d.ts +89 -0
  256. package/dist/node_modules/@boombot/core/research/parse/guards.js +171 -0
  257. package/dist/node_modules/@boombot/core/research/parse/index.d.ts +68 -0
  258. package/dist/node_modules/@boombot/core/research/parse/index.js +219 -0
  259. package/dist/node_modules/@boombot/core/research/parse/pdf.d.ts +38 -0
  260. package/dist/node_modules/@boombot/core/research/parse/pdf.js +567 -0
  261. package/dist/node_modules/@boombot/core/research/providers/arxiv.d.ts +9 -0
  262. package/dist/node_modules/@boombot/core/research/providers/arxiv.js +72 -0
  263. package/dist/node_modules/@boombot/core/research/providers/brave.d.ts +11 -0
  264. package/dist/node_modules/@boombot/core/research/providers/brave.js +36 -0
  265. package/dist/node_modules/@boombot/core/research/providers/duckduckgo.d.ts +9 -0
  266. package/dist/node_modules/@boombot/core/research/providers/duckduckgo.js +78 -0
  267. package/dist/node_modules/@boombot/core/research/providers/exa.d.ts +11 -0
  268. package/dist/node_modules/@boombot/core/research/providers/exa.js +34 -0
  269. package/dist/node_modules/@boombot/core/research/providers/firecrawl.d.ts +16 -0
  270. package/dist/node_modules/@boombot/core/research/providers/firecrawl.js +47 -0
  271. package/dist/node_modules/@boombot/core/research/providers/index.d.ts +17 -0
  272. package/dist/node_modules/@boombot/core/research/providers/index.js +11 -0
  273. package/dist/node_modules/@boombot/core/research/providers/jina.d.ts +13 -0
  274. package/dist/node_modules/@boombot/core/research/providers/jina.js +114 -0
  275. package/dist/node_modules/@boombot/core/research/providers/openalex.d.ts +14 -0
  276. package/dist/node_modules/@boombot/core/research/providers/openalex.js +66 -0
  277. package/dist/node_modules/@boombot/core/research/providers/semantic-scholar.d.ts +9 -0
  278. package/dist/node_modules/@boombot/core/research/providers/semantic-scholar.js +56 -0
  279. package/dist/node_modules/@boombot/core/research/providers/tavily.d.ts +11 -0
  280. package/dist/node_modules/@boombot/core/research/providers/tavily.js +35 -0
  281. package/dist/node_modules/@boombot/core/research/providers/wikipedia.d.ts +12 -0
  282. package/dist/node_modules/@boombot/core/research/providers/wikipedia.js +47 -0
  283. package/dist/node_modules/@boombot/core/research/tools.d.ts +27 -0
  284. package/dist/node_modules/@boombot/core/research/tools.js +251 -0
  285. package/dist/node_modules/@boombot/core/research/types.d.ts +133 -0
  286. package/dist/node_modules/@boombot/core/research/types.js +8 -0
  287. package/dist/node_modules/@boombot/core/research/wiring.d.ts +25 -0
  288. package/dist/node_modules/@boombot/core/research/wiring.js +51 -0
  289. package/dist/node_modules/@boombot/core/scheduler/cron.d.ts +34 -0
  290. package/dist/node_modules/@boombot/core/scheduler/cron.js +147 -0
  291. package/dist/node_modules/@boombot/core/scheduler/evaluator.d.ts +42 -0
  292. package/dist/node_modules/@boombot/core/scheduler/evaluator.js +148 -0
  293. package/dist/node_modules/@boombot/core/scheduler/index.d.ts +15 -0
  294. package/dist/node_modules/@boombot/core/scheduler/index.js +15 -0
  295. package/dist/node_modules/@boombot/core/scheduler/registry.d.ts +81 -0
  296. package/dist/node_modules/@boombot/core/scheduler/registry.js +263 -0
  297. package/dist/node_modules/@boombot/core/scheduler/runner.d.ts +59 -0
  298. package/dist/node_modules/@boombot/core/scheduler/runner.js +257 -0
  299. package/dist/node_modules/@boombot/core/scheduler/store.d.ts +150 -0
  300. package/dist/node_modules/@boombot/core/scheduler/store.js +526 -0
  301. package/dist/node_modules/@boombot/core/scheduler/timezone.d.ts +35 -0
  302. package/dist/node_modules/@boombot/core/scheduler/timezone.js +96 -0
  303. package/dist/node_modules/@boombot/core/scheduler/types.d.ts +157 -0
  304. package/dist/node_modules/@boombot/core/scheduler/types.js +89 -0
  305. package/dist/node_modules/@boombot/core/task-runner/index.d.ts +7 -0
  306. package/dist/node_modules/@boombot/core/task-runner/index.js +5 -0
  307. package/dist/node_modules/@boombot/core/task-runner/planner.d.ts +37 -0
  308. package/dist/node_modules/@boombot/core/task-runner/planner.js +154 -0
  309. package/dist/node_modules/@boombot/core/task-runner/runner.d.ts +8 -0
  310. package/dist/node_modules/@boombot/core/task-runner/runner.js +564 -0
  311. package/dist/node_modules/@boombot/core/task-runner/store.d.ts +28 -0
  312. package/dist/node_modules/@boombot/core/task-runner/store.js +126 -0
  313. package/dist/node_modules/@boombot/core/task-runner/types.d.ts +138 -0
  314. package/dist/node_modules/@boombot/core/task-runner/types.js +11 -0
  315. package/dist/node_modules/@boombot/core/workflow/dag.d.ts +16 -0
  316. package/dist/node_modules/@boombot/core/workflow/dag.js +109 -0
  317. package/dist/node_modules/@boombot/core/workflow/engine.d.ts +34 -0
  318. package/dist/node_modules/@boombot/core/workflow/engine.js +304 -0
  319. package/dist/node_modules/@boombot/core/workflow/index.d.ts +3 -0
  320. package/dist/node_modules/@boombot/core/workflow/index.js +3 -0
  321. package/dist/node_modules/@boombot/core/workflow/types.d.ts +98 -0
  322. package/dist/node_modules/@boombot/core/workflow/types.js +21 -0
  323. package/dist/node_modules/@boombot/cronjobs/handlers.d.ts +11 -0
  324. package/dist/node_modules/@boombot/cronjobs/handlers.js +174 -0
  325. package/dist/node_modules/@boombot/cronjobs/index.d.ts +2 -0
  326. package/dist/node_modules/@boombot/cronjobs/index.js +2 -0
  327. package/dist/node_modules/@boombot/cronjobs/package.json +9 -0
  328. package/dist/node_modules/@boombot/cronjobs/service.d.ts +53 -0
  329. package/dist/node_modules/@boombot/cronjobs/service.js +200 -0
  330. package/dist/node_modules/@boombot/plugins/index.d.ts +2 -0
  331. package/dist/node_modules/@boombot/plugins/index.js +2 -0
  332. package/dist/node_modules/@boombot/plugins/installer.d.ts +45 -0
  333. package/dist/node_modules/@boombot/plugins/installer.js +487 -0
  334. package/dist/node_modules/@boombot/plugins/manager.d.ts +23 -0
  335. package/dist/node_modules/@boombot/plugins/manager.js +243 -0
  336. package/dist/node_modules/@boombot/plugins/package.json +9 -0
  337. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-channels.d.ts +125 -0
  338. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-channels.js +76 -0
  339. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-plugins.d.ts +220 -0
  340. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-plugins.js +60 -0
  341. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-scheduler.d.ts +33 -0
  342. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-scheduler.js +25 -0
  343. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands.d.ts +4349 -0
  344. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands.js +563 -0
  345. package/dist/node_modules/@boombot/protocol/gateway-protocol/envelope.d.ts +32 -0
  346. package/dist/node_modules/@boombot/protocol/gateway-protocol/envelope.js +61 -0
  347. package/dist/node_modules/@boombot/protocol/gateway-protocol/errors.d.ts +13 -0
  348. package/dist/node_modules/@boombot/protocol/gateway-protocol/errors.js +16 -0
  349. package/dist/node_modules/@boombot/protocol/gateway-protocol/events.d.ts +567 -0
  350. package/dist/node_modules/@boombot/protocol/gateway-protocol/events.js +395 -0
  351. package/dist/node_modules/@boombot/protocol/gateway-protocol/index.d.ts +20 -0
  352. package/dist/node_modules/@boombot/protocol/gateway-protocol/index.js +13 -0
  353. package/dist/node_modules/@boombot/protocol/gateway-protocol/types.d.ts +91 -0
  354. package/dist/node_modules/@boombot/protocol/gateway-protocol/types.js +7 -0
  355. package/dist/node_modules/@boombot/protocol/index.d.ts +4 -0
  356. package/dist/node_modules/@boombot/protocol/index.js +3 -0
  357. package/dist/node_modules/@boombot/protocol/package.json +9 -0
  358. package/dist/node_modules/@boombot/protocol/plugin-sdk/channel.d.ts +41 -0
  359. package/dist/node_modules/@boombot/protocol/plugin-sdk/channel.js +17 -0
  360. package/dist/node_modules/@boombot/protocol/plugin-sdk/index.d.ts +10 -0
  361. package/dist/node_modules/@boombot/protocol/plugin-sdk/index.js +10 -0
  362. package/dist/node_modules/@boombot/protocol/plugin-sdk/manifest.d.ts +58 -0
  363. package/dist/node_modules/@boombot/protocol/plugin-sdk/manifest.js +116 -0
  364. package/dist/node_modules/@boombot/protocol/plugin-sdk/plugin.d.ts +25 -0
  365. package/dist/node_modules/@boombot/protocol/plugin-sdk/plugin.js +1 -0
  366. package/dist/node_modules/@boombot/protocol/providers/cipher.d.ts +7 -0
  367. package/dist/node_modules/@boombot/protocol/providers/cipher.js +52 -0
  368. package/dist/node_modules/@boombot/protocol/providers/index.d.ts +19 -0
  369. package/dist/node_modules/@boombot/protocol/providers/index.js +19 -0
  370. package/dist/node_modules/@boombot/protocol/providers/key-roulette.d.ts +79 -0
  371. package/dist/node_modules/@boombot/protocol/providers/key-roulette.js +81 -0
  372. package/dist/node_modules/@boombot/protocol/providers/probe.d.ts +29 -0
  373. package/dist/node_modules/@boombot/protocol/providers/probe.js +96 -0
  374. package/dist/node_modules/@boombot/protocol/providers/profiles/anthropic.d.ts +11 -0
  375. package/dist/node_modules/@boombot/protocol/providers/profiles/anthropic.js +11 -0
  376. package/dist/node_modules/@boombot/protocol/providers/profiles/custom.d.ts +22 -0
  377. package/dist/node_modules/@boombot/protocol/providers/profiles/custom.js +222 -0
  378. package/dist/node_modules/@boombot/protocol/providers/profiles/deepseek.d.ts +7 -0
  379. package/dist/node_modules/@boombot/protocol/providers/profiles/deepseek.js +9 -0
  380. package/dist/node_modules/@boombot/protocol/providers/profiles/google.d.ts +10 -0
  381. package/dist/node_modules/@boombot/protocol/providers/profiles/google.js +10 -0
  382. package/dist/node_modules/@boombot/protocol/providers/profiles/index.d.ts +31 -0
  383. package/dist/node_modules/@boombot/protocol/providers/profiles/index.js +56 -0
  384. package/dist/node_modules/@boombot/protocol/providers/profiles/local.d.ts +10 -0
  385. package/dist/node_modules/@boombot/protocol/providers/profiles/local.js +11 -0
  386. package/dist/node_modules/@boombot/protocol/providers/profiles/mistral.d.ts +7 -0
  387. package/dist/node_modules/@boombot/protocol/providers/profiles/mistral.js +9 -0
  388. package/dist/node_modules/@boombot/protocol/providers/profiles/openai-compatible.d.ts +10 -0
  389. package/dist/node_modules/@boombot/protocol/providers/profiles/openai-compatible.js +10 -0
  390. package/dist/node_modules/@boombot/protocol/providers/profiles/openai.d.ts +7 -0
  391. package/dist/node_modules/@boombot/protocol/providers/profiles/openai.js +11 -0
  392. package/dist/node_modules/@boombot/protocol/providers/profiles/types.d.ts +58 -0
  393. package/dist/node_modules/@boombot/protocol/providers/profiles/types.js +1 -0
  394. package/dist/node_modules/@boombot/protocol/providers/profiles/xai.d.ts +7 -0
  395. package/dist/node_modules/@boombot/protocol/providers/profiles/xai.js +10 -0
  396. package/dist/node_modules/@boombot/protocol/providers/request-profile.d.ts +102 -0
  397. package/dist/node_modules/@boombot/protocol/providers/request-profile.js +118 -0
  398. package/dist/node_modules/@boombot/protocol/providers/service.d.ts +2 -0
  399. package/dist/node_modules/@boombot/protocol/providers/service.js +418 -0
  400. package/dist/node_modules/@boombot/protocol/providers/types.d.ts +141 -0
  401. package/dist/node_modules/@boombot/protocol/providers/types.js +16 -0
  402. package/dist/node_modules/@boombot/sandbox/fs-path.d.ts +13 -0
  403. package/dist/node_modules/@boombot/sandbox/fs-path.js +79 -0
  404. package/dist/node_modules/@boombot/sandbox/index.d.ts +13 -0
  405. package/dist/node_modules/@boombot/sandbox/index.js +13 -0
  406. package/dist/node_modules/@boombot/sandbox/native.d.ts +39 -0
  407. package/dist/node_modules/@boombot/sandbox/native.js +224 -0
  408. package/dist/node_modules/@boombot/sandbox/package.json +9 -0
  409. package/dist/node_modules/@boombot/sandbox/sandbox.d.ts +92 -0
  410. package/dist/node_modules/@boombot/sandbox/sandbox.js +229 -0
  411. package/dist/node_modules/@boombot/security/harness-guard/boundary.d.ts +22 -0
  412. package/dist/node_modules/@boombot/security/harness-guard/boundary.js +105 -0
  413. package/dist/node_modules/@boombot/security/harness-guard/gates.d.ts +90 -0
  414. package/dist/node_modules/@boombot/security/harness-guard/gates.js +791 -0
  415. package/dist/node_modules/@boombot/security/harness-guard/index.d.ts +23 -0
  416. package/dist/node_modules/@boombot/security/harness-guard/index.js +23 -0
  417. package/dist/node_modules/@boombot/security/harness-guard/policy.d.ts +20 -0
  418. package/dist/node_modules/@boombot/security/harness-guard/policy.js +70 -0
  419. package/dist/node_modules/@boombot/security/harness-guard/sanitizer.d.ts +53 -0
  420. package/dist/node_modules/@boombot/security/harness-guard/sanitizer.js +145 -0
  421. package/dist/node_modules/@boombot/security/harness-guard/types.d.ts +71 -0
  422. package/dist/node_modules/@boombot/security/harness-guard/types.js +10 -0
  423. package/dist/node_modules/@boombot/security/harness-guard/verifier.d.ts +59 -0
  424. package/dist/node_modules/@boombot/security/harness-guard/verifier.js +169 -0
  425. package/dist/node_modules/@boombot/security/index.d.ts +5 -0
  426. package/dist/node_modules/@boombot/security/index.js +9 -0
  427. package/dist/node_modules/@boombot/security/package.json +9 -0
  428. package/dist/node_modules/@boombot/security/policy/approval.d.ts +117 -0
  429. package/dist/node_modules/@boombot/security/policy/approval.js +223 -0
  430. package/dist/node_modules/@boombot/security/reasoning-guard/claim-extractor.d.ts +10 -0
  431. package/dist/node_modules/@boombot/security/reasoning-guard/claim-extractor.js +124 -0
  432. package/dist/node_modules/@boombot/security/reasoning-guard/evidence-ledger.d.ts +25 -0
  433. package/dist/node_modules/@boombot/security/reasoning-guard/evidence-ledger.js +70 -0
  434. package/dist/node_modules/@boombot/security/reasoning-guard/gate.d.ts +38 -0
  435. package/dist/node_modules/@boombot/security/reasoning-guard/gate.js +61 -0
  436. package/dist/node_modules/@boombot/security/reasoning-guard/grounding-check.d.ts +12 -0
  437. package/dist/node_modules/@boombot/security/reasoning-guard/grounding-check.js +234 -0
  438. package/dist/node_modules/@boombot/security/reasoning-guard/index.d.ts +17 -0
  439. package/dist/node_modules/@boombot/security/reasoning-guard/index.js +13 -0
  440. package/dist/node_modules/@boombot/security/reasoning-guard/policy.d.ts +15 -0
  441. package/dist/node_modules/@boombot/security/reasoning-guard/policy.js +39 -0
  442. package/dist/node_modules/@boombot/security/reasoning-guard/types.d.ts +46 -0
  443. package/dist/node_modules/@boombot/security/reasoning-guard/types.js +7 -0
  444. package/dist/node_modules/@boombot/security/security/approval.d.ts +63 -0
  445. package/dist/node_modules/@boombot/security/security/approval.js +140 -0
  446. package/dist/node_modules/@boombot/security/security/audit.d.ts +39 -0
  447. package/dist/node_modules/@boombot/security/security/audit.js +70 -0
  448. package/dist/node_modules/@boombot/security/security/index.d.ts +12 -0
  449. package/dist/node_modules/@boombot/security/security/index.js +12 -0
  450. package/dist/node_modules/@boombot/security/security/trust.d.ts +79 -0
  451. package/dist/node_modules/@boombot/security/security/trust.js +87 -0
  452. package/dist/package.json +5 -0
  453. package/dist/proto/agent_core.proto +56 -0
  454. package/dist/python/agent-core-python/pyproject.toml +40 -0
  455. package/dist/python/agent-core-python/src/boombot_agent/__init__.py +5 -0
  456. package/dist/python/agent-core-python/src/boombot_agent/generated/__init__.py +0 -0
  457. package/dist/python/agent-core-python/src/boombot_agent/generated/agent_core_pb2.py +54 -0
  458. package/dist/python/agent-core-python/src/boombot_agent/generated/agent_core_pb2_grpc.py +140 -0
  459. package/dist/python/agent-core-python/src/boombot_agent/rpc.py +265 -0
  460. package/dist/python/agent-core-python/src/boombot_agent/turn.py +577 -0
  461. package/dist/python/agent-core-python/src/boombot_ml/__init__.py +12 -0
  462. package/dist/python/agent-core-python/src/boombot_ml/embeddings.py +58 -0
  463. package/dist/python/agent-core-python/src/boombot_ml/training.py +156 -0
  464. package/dist/python/agent-core-python/uv.lock +2856 -0
  465. package/dist/web/assets/ChatView-BZrSD-64.js +262 -0
  466. package/dist/web/assets/ChatView-Cahbjez6.css +1 -0
  467. package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  468. package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  469. package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  470. package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  471. package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  472. package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  473. package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  474. package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  475. package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  476. package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  477. package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  478. package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  479. package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  480. package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  481. package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  482. package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  483. package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  484. package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  485. package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  486. package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  487. package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  488. package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  489. package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  490. package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  491. package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  492. package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  493. package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  494. package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  495. package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  496. package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  497. package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  498. package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  499. package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  500. package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  501. package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  502. package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  503. package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  504. package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  505. package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  506. package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  507. package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  508. package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  509. package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  510. package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  511. package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  512. package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  513. package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  514. package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  515. package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  516. package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  517. package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  518. package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  519. package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  520. package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  521. package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  522. package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  523. package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  524. package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  525. package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  526. package/dist/web/assets/MemoryView-BJbXqqs7.css +1 -0
  527. package/dist/web/assets/MemoryView-BfMq_6CD.js +1 -0
  528. package/dist/web/assets/SchedulesView-B22QmPR-.js +1 -0
  529. package/dist/web/assets/SchedulesView-C6XK7A8a.css +1 -0
  530. package/dist/web/assets/SessionsView-C1dqdhxI.css +1 -0
  531. package/dist/web/assets/SessionsView-DS0-aH2a.js +1 -0
  532. package/dist/web/assets/TasksView-Dtasg4hd.css +1 -0
  533. package/dist/web/assets/TasksView-ey0FLsW8.js +2 -0
  534. package/dist/web/assets/Textarea-DN3Zybl1.js +1 -0
  535. package/dist/web/assets/Tooltip-Bn2cqFPz.css +1 -0
  536. package/dist/web/assets/Tooltip-MzV2Gqnc.js +1 -0
  537. package/dist/web/assets/browser-Bnt73-Ub.js +8 -0
  538. package/dist/web/assets/index-BL8bi7Yt.css +1 -0
  539. package/dist/web/assets/index-CuPR73Tn.js +73 -0
  540. package/dist/web/favicon.svg +4 -0
  541. package/dist/web/index.html +26 -0
  542. package/dist/web/logo.jpg +0 -0
  543. package/dist/web/logo.png +0 -0
  544. package/package.json +51 -4
@@ -0,0 +1,791 @@
1
+ /**
2
+ * `@boombot/security` — deterministic tool gates (layer: `'gates'`).
3
+ *
4
+ * Fail-closed static checks that run *before* a tool executes:
5
+ *
6
+ * - `tokenizeBash` / `analyzeBash` — a hand-written bash tokenizer (quotes,
7
+ * escapes, comments, operators, `$(...)`, backticks, `${...}`) plus a
8
+ * rule-based risk classifier. Deterministic: the same command always yields
9
+ * the same risk. When in doubt it escalates (fail-closed).
10
+ * - `checkHttpUrl` — outbound URL gate: protocol check, userinfo (credential
11
+ * in URL) check, and hostname allowlist with `*.example.com` wildcards.
12
+ * - `evaluateToolCall` — the entry point the verifier imports. Routes a tool
13
+ * call through deny-list / approval-list / bash-like / http-like gates.
14
+ *
15
+ * Zero dependencies. Findings are mapped to a severity and passed to `decide()`
16
+ * from `./policy.js` so `GuardMode` semantics stay in one place; clean checks
17
+ * (no finding at all) allow directly, so strict mode never denies a command
18
+ * or URL that nothing flagged. Every finding's reason names the exact
19
+ * construct that was caught.
20
+ */
21
+ import { isAbsolute, resolve, sep } from 'node:path';
22
+ import { decide } from './policy.js';
23
+ /** Layer tag attached to every verdict produced by this module. */
24
+ const LAYER = 'gates';
25
+ const RISK_ORDER = ['low', 'medium', 'high', 'critical'];
26
+ function maxRisk(a, b) {
27
+ return RISK_ORDER.indexOf(a) >= RISK_ORDER.indexOf(b) ? a : b;
28
+ }
29
+ function truncate(s, max = 160) {
30
+ return s.length > max ? `${s.slice(0, max - 3)}...` : s;
31
+ }
32
+ // ---------------------------------------------------------------------------
33
+ // tokenizeBash
34
+ // ---------------------------------------------------------------------------
35
+ /**
36
+ * Tokenize a bash command line.
37
+ *
38
+ * Handles single/double quotes (quote-aware, quotes stripped from the token
39
+ * value), backslash escapes, `#` comments (only at the start of a word,
40
+ * outside quotes), and emits operators as standalone tokens: `|`, `||`,
41
+ * `&&`, `;`, `;;`, `&`, `>`, `>>`, `<`, `<<`, `(`, `)`, `{`, `}`, `` ` ``,
42
+ * `$(`, `${`.
43
+ *
44
+ * This is a real tokenizer, not a naive whitespace split: `echo "a b" | cat`
45
+ * yields `['echo', 'a b', '|', 'cat']`, and `#` inside quotes never starts a
46
+ * comment.
47
+ */
48
+ export function tokenizeBash(command) {
49
+ const tokens = [];
50
+ let cur = '';
51
+ const push = () => {
52
+ if (cur !== '') {
53
+ tokens.push(cur);
54
+ cur = '';
55
+ }
56
+ };
57
+ const two = (i) => command.charAt(i) + command.charAt(i + 1);
58
+ let i = 0;
59
+ const n = command.length;
60
+ while (i < n) {
61
+ const c = command.charAt(i);
62
+ // whitespace separates tokens
63
+ if (c === ' ' || c === '\t' || c === '\r' || c === '\n') {
64
+ push();
65
+ i++;
66
+ continue;
67
+ }
68
+ // `#` starts a comment only at the start of a word, outside quotes
69
+ if (c === '#' && cur === '') {
70
+ while (i < n && command.charAt(i) !== '\n')
71
+ i++;
72
+ continue;
73
+ }
74
+ // single quotes: everything literal until the closing quote
75
+ if (c === "'") {
76
+ i++;
77
+ while (i < n) {
78
+ const q = command.charAt(i);
79
+ if (q === "'") {
80
+ i++;
81
+ break;
82
+ }
83
+ cur += q;
84
+ i++;
85
+ }
86
+ continue;
87
+ }
88
+ // double quotes: `\` only escapes `$`, `` ` ``, `"`, `\` and newline
89
+ if (c === '"') {
90
+ i++;
91
+ while (i < n) {
92
+ const q = command.charAt(i);
93
+ if (q === '"') {
94
+ i++;
95
+ break;
96
+ }
97
+ if (q === '\\') {
98
+ const nxt = command.charAt(i + 1);
99
+ if (nxt === '$' || nxt === '`' || nxt === '"' || nxt === '\\') {
100
+ cur += nxt;
101
+ i += 2;
102
+ }
103
+ else if (nxt === '\n') {
104
+ i += 2; // line continuation inside double quotes
105
+ }
106
+ else {
107
+ cur += '\\';
108
+ i++;
109
+ }
110
+ }
111
+ else {
112
+ cur += q;
113
+ i++;
114
+ }
115
+ }
116
+ continue;
117
+ }
118
+ // backslash escape outside quotes
119
+ if (c === '\\') {
120
+ const nxt = command.charAt(i + 1);
121
+ if (nxt === '') {
122
+ i++; // trailing backslash: drop it
123
+ continue;
124
+ }
125
+ if (nxt === '\n') {
126
+ i += 2; // line continuation
127
+ continue;
128
+ }
129
+ cur += nxt;
130
+ i += 2;
131
+ continue;
132
+ }
133
+ // two-character operators
134
+ const t2 = two(i);
135
+ if (t2 === '||' ||
136
+ t2 === '&&' ||
137
+ t2 === '>>' ||
138
+ t2 === '<<' ||
139
+ t2 === '$(' ||
140
+ t2 === '${' ||
141
+ t2 === ';;') {
142
+ push();
143
+ tokens.push(t2);
144
+ i += 2;
145
+ continue;
146
+ }
147
+ // single-character operators
148
+ if (c === '|' ||
149
+ c === '&' ||
150
+ c === ';' ||
151
+ c === '>' ||
152
+ c === '<' ||
153
+ c === '(' ||
154
+ c === ')' ||
155
+ c === '{' ||
156
+ c === '}' ||
157
+ c === '`') {
158
+ push();
159
+ tokens.push(c);
160
+ i++;
161
+ continue;
162
+ }
163
+ cur += c;
164
+ i++;
165
+ }
166
+ push();
167
+ return tokens;
168
+ }
169
+ const SHELL_NAMES = new Set(['sh', 'bash', 'zsh']);
170
+ const DOWNLOADER_NAMES = new Set(['curl', 'wget']);
171
+ const ENV_NAMES = new Set(['env', 'printenv', 'set']);
172
+ const NET_NAMES = new Set(['curl', 'wget', 'nc', 'ncat', 'ssh', 'scp']);
173
+ const NC_NAMES = new Set(['nc', 'ncat', 'netcat']);
174
+ const SYSTEM_PATH_PREFIXES = [
175
+ '/etc/',
176
+ '/usr/bin/',
177
+ '/usr/sbin/',
178
+ '/bin/',
179
+ '/sbin/',
180
+ '/boot/',
181
+ ];
182
+ const DISK_DEVICE_RE = /^\/dev\/(sd[a-z]+|hd[a-z]+|vd[a-z]+|nvme[0-9]+n[0-9]+|mmcblk[0-9]+)/;
183
+ /** Split a token stream on command separators (`;`, `&&`, `||`, `&`). */
184
+ function splitCommands(tokens) {
185
+ const cmds = [];
186
+ let cur = [];
187
+ for (const t of tokens) {
188
+ if (t === ';' || t === '&&' || t === '||' || t === '&') {
189
+ if (cur.length > 0)
190
+ cmds.push(cur);
191
+ cur = [];
192
+ }
193
+ else {
194
+ cur.push(t);
195
+ }
196
+ }
197
+ if (cur.length > 0)
198
+ cmds.push(cur);
199
+ return cmds;
200
+ }
201
+ /** Split one command into pipeline segments on `|`. */
202
+ function splitPipeline(cmd) {
203
+ const segs = [];
204
+ let cur = [];
205
+ for (const t of cmd) {
206
+ if (t === '|') {
207
+ if (cur.length > 0)
208
+ segs.push(cur);
209
+ cur = [];
210
+ }
211
+ else {
212
+ cur.push(t);
213
+ }
214
+ }
215
+ if (cur.length > 0)
216
+ segs.push(cur);
217
+ return segs;
218
+ }
219
+ /**
220
+ * Resolve the command a pipeline segment invokes: the first token, skipping
221
+ * a leading `sudo` (plus its flags) and stripping any `/path/` prefix.
222
+ */
223
+ function invokedCommand(seg) {
224
+ let i = 0;
225
+ if (seg[0] === 'sudo') {
226
+ i = 1;
227
+ while (i < seg.length) {
228
+ const t = seg[i];
229
+ if (t === undefined || !t.startsWith('-') || t === '--')
230
+ break;
231
+ if (t === '-u' || t === '--user' || t === '-g' || t === '--group')
232
+ i += 2;
233
+ else
234
+ i += 1;
235
+ }
236
+ }
237
+ const raw = seg[i];
238
+ if (raw === undefined || raw === '')
239
+ return null;
240
+ const slash = raw.lastIndexOf('/');
241
+ const name = slash >= 0 ? raw.slice(slash + 1) : raw;
242
+ if (name === '')
243
+ return null;
244
+ return { name, args: seg.slice(i + 1) };
245
+ }
246
+ /** Merge `${` `HOME` `}` token triples — and the single token `'${HOME}'`
247
+ * (from quoted `"${HOME}"`) — back into `$HOME` for analysis. */
248
+ function normalizeTokens(tokens) {
249
+ const out = [];
250
+ for (let i = 0; i < tokens.length; i++) {
251
+ const t = tokens[i];
252
+ if (t === '${' && tokens[i + 1] === 'HOME' && tokens[i + 2] === '}') {
253
+ out.push('$HOME');
254
+ i += 2;
255
+ }
256
+ else if (t === '${HOME}') {
257
+ out.push('$HOME');
258
+ }
259
+ else if (t !== undefined) {
260
+ out.push(t);
261
+ }
262
+ }
263
+ return out;
264
+ }
265
+ function isDestructiveRmTarget(t) {
266
+ return (t === '/' ||
267
+ t.startsWith('/*') ||
268
+ t === '~' ||
269
+ t.startsWith('~/') ||
270
+ /^~[A-Za-z_]/.test(t) || // ~otheruser
271
+ t === '$HOME' ||
272
+ t.startsWith('$HOME/'));
273
+ }
274
+ function isSystemPath(p) {
275
+ return SYSTEM_PATH_PREFIXES.some((pre) => p === pre.slice(0, -1) || p.startsWith(pre));
276
+ }
277
+ function checkRm(inv, raise) {
278
+ if (inv.name !== 'rm')
279
+ return;
280
+ let recursive = false;
281
+ let force = false;
282
+ const flagToks = [];
283
+ const targets = [];
284
+ let endOfFlags = false;
285
+ for (const a of inv.args) {
286
+ if (!endOfFlags && a === '--') {
287
+ endOfFlags = true;
288
+ continue;
289
+ }
290
+ if (!endOfFlags && a.startsWith('-') && a.length > 1) {
291
+ flagToks.push(a);
292
+ if (a === '--recursive')
293
+ recursive = true;
294
+ else if (a === '--force')
295
+ force = true;
296
+ else if (!a.startsWith('--')) {
297
+ if (a.includes('r') || a.includes('R'))
298
+ recursive = true;
299
+ if (a.includes('f'))
300
+ force = true;
301
+ }
302
+ }
303
+ else {
304
+ targets.push(a);
305
+ }
306
+ }
307
+ if (!recursive || !force)
308
+ return;
309
+ for (const t of targets) {
310
+ if (isDestructiveRmTarget(t)) {
311
+ raise('critical', `destructive delete: "rm ${flagToks.join(' ')} ${t}" targets a filesystem root`);
312
+ return;
313
+ }
314
+ }
315
+ }
316
+ function checkMkfs(inv, seg, raise) {
317
+ if (inv.name === 'mkfs' || inv.name.startsWith('mkfs.')) {
318
+ raise('critical', `filesystem formatter "mkfs" detected: "${truncate(seg.join(' '))}"`);
319
+ }
320
+ }
321
+ function checkDd(inv, raise) {
322
+ if (inv.name !== 'dd')
323
+ return;
324
+ const of = inv.args.find((a) => a.startsWith('of=/dev/'));
325
+ if (of !== undefined) {
326
+ raise('critical', `raw disk write via "dd": "${of}"`);
327
+ }
328
+ }
329
+ function checkNcListener(seg, raise) {
330
+ const hasNc = seg.some((t) => NC_NAMES.has(t));
331
+ const hasListen = seg.includes('-l') || seg.includes('--listen');
332
+ if (hasNc && hasListen) {
333
+ raise('medium', `network listener: "${truncate(seg.join(' '))}"`);
334
+ }
335
+ }
336
+ function checkChmod(inv, seg, raise) {
337
+ if (inv.name !== 'chmod')
338
+ return;
339
+ let recursive = false;
340
+ let mode777 = false;
341
+ let rootTarget = null;
342
+ for (const a of inv.args) {
343
+ if (a === '--recursive')
344
+ recursive = true;
345
+ else if (a.startsWith('-') && !a.startsWith('--') && /[rR]/.test(a))
346
+ recursive = true;
347
+ else if (a === '777')
348
+ mode777 = true;
349
+ else if (a === '/' || a.startsWith('/*'))
350
+ rootTarget = a;
351
+ }
352
+ if (recursive && mode777 && rootTarget !== null) {
353
+ raise('high', `recursive permission wipe: "chmod -R 777 ${rootTarget}" (${truncate(seg.join(' '))})`);
354
+ }
355
+ }
356
+ function checkFindDelete(inv, seg, raise) {
357
+ if (inv.name !== 'find')
358
+ return;
359
+ if (inv.args.includes('/') && inv.args.includes('-delete')) {
360
+ raise('medium', `mass delete via find: "${truncate(seg.join(' '))}"`);
361
+ }
362
+ }
363
+ function checkTeeSystemPath(inv, seg, raise) {
364
+ if (inv.name !== 'tee')
365
+ return;
366
+ for (const a of inv.args) {
367
+ if (a.startsWith('-'))
368
+ continue;
369
+ if (isSystemPath(a)) {
370
+ raise('high', `write to system path via "tee": "${truncate(seg.join(' '))}"`);
371
+ return;
372
+ }
373
+ }
374
+ }
375
+ function checkCopyToSystemPath(inv, seg, raise) {
376
+ if (inv.name !== 'cp' && inv.name !== 'mv' && inv.name !== 'install')
377
+ return;
378
+ const operands = inv.args.filter((a) => !a.startsWith('-'));
379
+ const dest = operands[operands.length - 1];
380
+ if (dest !== undefined && isSystemPath(dest)) {
381
+ raise('high', `write to system path via "${inv.name}": "${truncate(seg.join(' '))}"`);
382
+ }
383
+ }
384
+ function checkPipeToShell(pipeline, raise) {
385
+ let downloaderSeg = null;
386
+ for (const seg of pipeline) {
387
+ const inv = invokedCommand(seg);
388
+ const cmdName = inv === null ? seg[0] : inv.name;
389
+ if (cmdName !== undefined && SHELL_NAMES.has(cmdName) && downloaderSeg !== null) {
390
+ raise('high', `piping a remote download into a shell: "${truncate(`${downloaderSeg.join(' ')} | ... | ${seg.join(' ')}`)}"`);
391
+ return;
392
+ }
393
+ if (seg.some((t) => DOWNLOADER_NAMES.has(t)))
394
+ downloaderSeg = [...seg];
395
+ }
396
+ }
397
+ function checkEnvExfil(pipeline, raise) {
398
+ let envSeg = null;
399
+ for (const seg of pipeline) {
400
+ const inv = invokedCommand(seg);
401
+ const cmdName = inv === null ? seg[0] : inv.name;
402
+ if (cmdName === undefined)
403
+ continue;
404
+ if (envSeg === null && ENV_NAMES.has(cmdName)) {
405
+ envSeg = [...seg];
406
+ continue;
407
+ }
408
+ if (envSeg !== null && NET_NAMES.has(cmdName)) {
409
+ raise('high', `environment exfiltration: "${truncate(`${envSeg.join(' ')} | ... | ${seg.join(' ')}`)}"`);
410
+ return;
411
+ }
412
+ }
413
+ }
414
+ function checkForkBomb(raw, raise) {
415
+ const compact = raw.replace(/\s+/g, '');
416
+ // classic `:(){ :|:& };:` (whitespace variants collapse to the same shape)
417
+ if (/:\(\)\{[^}]*\|[^}]*&[^}]*\}/.test(compact)) {
418
+ raise('critical', 'fork bomb detected: ":(){ :|:& };:"');
419
+ return;
420
+ }
421
+ // named-function variant: name(){ ... name ... | ... & ... } then invoked
422
+ const m = /([A-Za-z_]\w*)\(\)\{([^}]*)\}/.exec(compact);
423
+ if (m !== null) {
424
+ const name = m[1];
425
+ const body = m[2];
426
+ const words = body.split(/[^A-Za-z0-9_]/);
427
+ const tail = compact.slice(m.index + m[0].length);
428
+ const tailWords = tail.split(/[^A-Za-z0-9_]/);
429
+ if (words.includes(name) &&
430
+ body.includes('|') &&
431
+ body.includes('&') &&
432
+ tailWords.includes(name)) {
433
+ raise('critical', `fork bomb detected: "${truncate(`${name}(){ ... }; ${name}`)}"`);
434
+ }
435
+ }
436
+ }
437
+ function checkCommandSubstitution(tokens, raise) {
438
+ let i = 0;
439
+ while (i < tokens.length) {
440
+ const t = tokens[i];
441
+ if (t === '$(') {
442
+ let depth = 1;
443
+ let j = i + 1;
444
+ while (j < tokens.length && depth > 0) {
445
+ const u = tokens[j];
446
+ if (u === '$(')
447
+ depth++;
448
+ else if (u === ')')
449
+ depth--;
450
+ j++;
451
+ }
452
+ const inside = tokens.slice(i + 1, j - 1);
453
+ if (inside.some((x) => x === 'curl' || x === 'wget')) {
454
+ raise('high', 'command substitution downloads remote content: "$(curl ...)"');
455
+ }
456
+ i = j;
457
+ }
458
+ else if (t === '`') {
459
+ let j = i + 1;
460
+ while (j < tokens.length && tokens[j] !== '`')
461
+ j++;
462
+ const inside = tokens.slice(i + 1, j);
463
+ if (inside.some((x) => x === 'curl' || x === 'wget')) {
464
+ raise('high', 'command substitution downloads remote content: "`curl ...`"');
465
+ }
466
+ i = j + 1;
467
+ }
468
+ else {
469
+ i++;
470
+ }
471
+ }
472
+ }
473
+ function checkRedirects(tokens, raise) {
474
+ for (let i = 0; i + 1 < tokens.length; i++) {
475
+ const t = tokens[i];
476
+ if (t !== '>' && t !== '>>')
477
+ continue;
478
+ const target = tokens[i + 1];
479
+ if (target === undefined)
480
+ continue;
481
+ if (DISK_DEVICE_RE.test(target)) {
482
+ raise('critical', `raw disk overwrite via redirect: "${t} ${target}"`);
483
+ }
484
+ else if (isSystemPath(target)) {
485
+ raise('high', `write to system path via redirect: "${t} ${target}"`);
486
+ }
487
+ }
488
+ }
489
+ function checkSudo(tokens, raise) {
490
+ if (tokens.includes('sudo')) {
491
+ raise('medium', 'privilege escalation via "sudo"');
492
+ }
493
+ }
494
+ function checkChmodPlusXDownloaded(tokens, raise) {
495
+ if (!tokens.some((t) => t === 'curl' || t === 'wget'))
496
+ return;
497
+ for (const cmd of splitCommands(tokens)) {
498
+ for (const seg of splitPipeline(cmd)) {
499
+ const inv = invokedCommand(seg);
500
+ if (inv !== null && inv.name === 'chmod' && inv.args.includes('+x')) {
501
+ raise('medium', `chmod +x on a downloaded file: "${truncate(seg.join(' '))}"`);
502
+ return;
503
+ }
504
+ }
505
+ }
506
+ }
507
+ /**
508
+ * Analyze a bash command and classify its risk.
509
+ *
510
+ * - `critical`: `rm -rf /` (and `/*`, `~`, `$HOME` variants, `--recursive`
511
+ * `--force` spellings), `mkfs`, `dd … of=/dev/…`, fork bombs
512
+ * (`:(){ :|:& };:` and whitespace/named variants), `> /dev/sdX`.
513
+ * - `high`: piping a download into a shell (`curl … | sh`, `… | sudo sh`),
514
+ * `$(curl …)` / backtick command substitution fetching remote content,
515
+ * env exfiltration (`env | curl …`), writes to system paths (`/etc/`,
516
+ * `/usr/bin`, …), `chmod -R 777 /`.
517
+ * - `medium`: any `sudo`, `nc -l` listeners, `chmod +x` on a downloaded
518
+ * file, `find / … -delete`.
519
+ * - `low`: everything else.
520
+ *
521
+ * Every finding appends a reason quoting the exact construct caught; the
522
+ * overall risk is the maximum over all findings (fail-closed).
523
+ */
524
+ export function analyzeBash(command) {
525
+ const reasons = [];
526
+ let risk = 'low';
527
+ const raise = (r, reason) => {
528
+ risk = maxRisk(risk, r);
529
+ reasons.push(reason);
530
+ };
531
+ const tokens = normalizeTokens(tokenizeBash(command));
532
+ if (tokens.length === 0)
533
+ return { risk: 'low', reasons: [] };
534
+ for (const cmd of splitCommands(tokens)) {
535
+ const pipeline = splitPipeline(cmd);
536
+ for (const seg of pipeline) {
537
+ const inv = invokedCommand(seg);
538
+ if (inv !== null) {
539
+ checkRm(inv, raise);
540
+ checkMkfs(inv, seg, raise);
541
+ checkDd(inv, raise);
542
+ checkChmod(inv, seg, raise);
543
+ checkFindDelete(inv, seg, raise);
544
+ checkTeeSystemPath(inv, seg, raise);
545
+ checkCopyToSystemPath(inv, seg, raise);
546
+ }
547
+ checkNcListener(seg, raise);
548
+ }
549
+ checkPipeToShell(pipeline, raise);
550
+ checkEnvExfil(pipeline, raise);
551
+ }
552
+ checkForkBomb(command, raise);
553
+ checkCommandSubstitution(tokens, raise);
554
+ checkRedirects(tokens, raise);
555
+ checkSudo(tokens, raise);
556
+ checkChmodPlusXDownloaded(tokens, raise);
557
+ return { risk, reasons };
558
+ }
559
+ // ---------------------------------------------------------------------------
560
+ // checkHttpUrl
561
+ // ---------------------------------------------------------------------------
562
+ function hostMatchesAllowlist(host, entry) {
563
+ let e = entry.trim().toLowerCase();
564
+ if (e.endsWith('.'))
565
+ e = e.slice(0, -1);
566
+ // strip an optional `:port` suffix
567
+ if (!e.startsWith('[')) {
568
+ const portMatch = /:(\d+)$/.exec(e);
569
+ if (portMatch !== null)
570
+ e = e.slice(0, e.length - portMatch[0].length);
571
+ }
572
+ if (e.startsWith('*.')) {
573
+ const base = e.slice(2);
574
+ // wildcard matches subdomains only, never the apex itself
575
+ return host.length > base.length && host.endsWith(`.${base}`);
576
+ }
577
+ return host === e;
578
+ }
579
+ /**
580
+ * Gate an outbound URL (layer: `'gates'`).
581
+ *
582
+ * - Unparseable URL → deny, severity `critical`.
583
+ * - Non-http(s) protocol → deny, severity `high`.
584
+ * - Userinfo (`user:pass@host`) → deny, severity `high` ("credential in URL").
585
+ * - `httpMode: 'off'` → allow. `'warn'` → off-list hosts still allow, but the
586
+ * reason flags them (severity `low`). `'allowlist'` → off-list hosts deny
587
+ * with severity `high`.
588
+ *
589
+ * Host matching is case-insensitive, ignores ports, and supports exact and
590
+ * `*.example.com` wildcard entries.
591
+ */
592
+ export function checkHttpUrl(rawUrl, policy) {
593
+ // guard off: allow unconditionally (types.ts: "off: every layer allows unconditionally")
594
+ if (policy.mode === 'off') {
595
+ return { decision: 'allow', reason: 'guard off', layer: LAYER };
596
+ }
597
+ let url;
598
+ try {
599
+ url = new URL(rawUrl);
600
+ }
601
+ catch {
602
+ return decide(policy, LAYER, 'critical', `unparseable URL: "${truncate(rawUrl, 120)}"`);
603
+ }
604
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
605
+ return decide(policy, LAYER, 'high', `disallowed URL protocol "${url.protocol}" (only http/https allowed): "${truncate(rawUrl, 120)}"`);
606
+ }
607
+ if (url.username !== '' || url.password !== '') {
608
+ return decide(policy, LAYER, 'high', 'credential in URL (userinfo present)');
609
+ }
610
+ // No finding from here on: httpMode 'off' and allowlist hits allow directly
611
+ // so a clean check is never denied by `decide()` in strict mode.
612
+ if (policy.httpMode === 'off') {
613
+ return { decision: 'allow', reason: 'http checks disabled by policy (httpMode=off)', layer: LAYER };
614
+ }
615
+ const host = url.hostname.toLowerCase();
616
+ const allowed = policy.httpAllowlist.some((entry) => hostMatchesAllowlist(host, entry));
617
+ if (allowed) {
618
+ return { decision: 'allow', reason: `URL host "${host}" is on the http allowlist`, layer: LAYER };
619
+ }
620
+ if (policy.httpMode === 'warn') {
621
+ // genuine finding (severity low): `decide()` allows it in warn mode with
622
+ // the reason attached, denies it in strict mode (fail-closed)
623
+ return decide(policy, LAYER, 'low', `URL host "${host}" is NOT on the http allowlist (warn mode: allowed with warning)`);
624
+ }
625
+ return decide(policy, LAYER, 'high', `URL host "${host}" is NOT on the http allowlist`);
626
+ }
627
+ // ---------------------------------------------------------------------------
628
+ // evaluateToolCall
629
+ // ---------------------------------------------------------------------------
630
+ /** Tools whose outbound HTTP is gated by name (in addition to the regex). */
631
+ const HTTP_TOOL_NAMES = new Set([
632
+ 'web-fetch',
633
+ 'research.search',
634
+ 'research.extract',
635
+ 'research.deep',
636
+ 'research.academic',
637
+ 'research.crawl',
638
+ ]);
639
+ const BASH_NAME_RE = /(\b|_)(bash|sh|shell|exec|command|run)(s?\b|_)/i;
640
+ const HTTP_NAME_RE = /fetch|https?|request/i;
641
+ /** First string-valued argument among the given names, if any. */
642
+ function stringArg(call, names) {
643
+ for (const n of names) {
644
+ const v = call.arguments[n];
645
+ if (typeof v === 'string')
646
+ return v;
647
+ }
648
+ return undefined;
649
+ }
650
+ /** Tool names whose writes are zone-gated. */
651
+ const FILE_WRITE_NAMES = new Set(['file.write', 'file.edit']);
652
+ /**
653
+ * Lexical containment check: is `target` inside any of `zones`?
654
+ *
655
+ * Absolute targets are normalized and compared directly. Relative targets
656
+ * are resolved against each zone in turn — the file tools resolve relative
657
+ * paths under their root, so a relative target that climbs out (`../..`)
658
+ * fails closed. This is an approval gate, not the security boundary itself:
659
+ * the handlers still enforce symlink-aware containment via `resolveWithin`
660
+ * at execution time.
661
+ */
662
+ function isWithinZones(target, zones) {
663
+ const trimmed = target.trim();
664
+ if (trimmed === '')
665
+ return false;
666
+ for (const zone of zones) {
667
+ const normalizedZone = resolve(zone);
668
+ if (normalizedZone === '' || !isAbsolute(normalizedZone))
669
+ continue;
670
+ const absolute = isAbsolute(trimmed) ? resolve(trimmed) : resolve(normalizedZone, trimmed);
671
+ if (absolute === normalizedZone || absolute.startsWith(normalizedZone + sep)) {
672
+ return true;
673
+ }
674
+ }
675
+ return false;
676
+ }
677
+ /**
678
+ * Zone gate for write-capable tools. Returns a `require-approval` verdict
679
+ * when the call targets a path outside every configured zone, or `undefined`
680
+ * to let evaluation continue.
681
+ *
682
+ * - `file.write` / `file.edit`: the `path` argument is checked. A missing
683
+ * or empty path fails closed (it cannot be proven in-zone).
684
+ * - `shell.exec`: an explicit `cwd` argument is checked. An absent `cwd`
685
+ * keeps the previous behavior (the handler defaults it to the workspace
686
+ * root, which the gateway puts in the zones).
687
+ * - Empty `writeZones` (the default): the gate is off entirely.
688
+ */
689
+ function checkWriteZones(call, policy) {
690
+ if (policy.writeZones.length === 0)
691
+ return undefined;
692
+ if (FILE_WRITE_NAMES.has(call.name)) {
693
+ const target = stringArg(call, ['path']);
694
+ if (target === undefined || !isWithinZones(target, policy.writeZones)) {
695
+ return {
696
+ decision: 'require-approval',
697
+ reason: `file write "${call.name}" targets a path outside the configured write zones`,
698
+ layer: LAYER,
699
+ };
700
+ }
701
+ return undefined;
702
+ }
703
+ if (call.name === 'shell.exec') {
704
+ const cwd = stringArg(call, ['cwd']);
705
+ if (cwd !== undefined && !isWithinZones(cwd, policy.writeZones)) {
706
+ return {
707
+ decision: 'require-approval',
708
+ reason: 'shell.exec cwd is outside the configured write zones',
709
+ layer: LAYER,
710
+ };
711
+ }
712
+ return undefined;
713
+ }
714
+ return undefined;
715
+ }
716
+ /**
717
+ * Evaluate one tool call against the gates layer.
718
+ *
719
+ * Order (first match wins):
720
+ * 1. guard `off` → allow unconditionally;
721
+ * 2. name exactly in `denyTools` → deny (`critical`);
722
+ * 3. name exactly in `approvalTools` → require-approval;
723
+ * 4. write-zone gate — `file.write`/`file.edit` (by `path`) and `shell.exec`
724
+ * (by explicit `cwd`) outside every configured `writeZones` →
725
+ * require-approval (fail-closed); in-zone keeps the previous behavior;
726
+ * 5. bash-like — name matches the bash regex **or** a string arg named
727
+ * `command`/`script`/`cmd` → `analyzeBash`; risk `medium`/`high`/`critical`
728
+ * goes through `decide()`, risk `low` (no findings) allows directly;
729
+ * 6. http-like — name in the known research/fetch set, name matches
730
+ * `/fetch|https?|request/i`, **or** a string arg named `url`/`href`/`link`
731
+ * → `checkHttpUrl` on that URL argument;
732
+ * 7. anything else → allow.
733
+ */
734
+ export function evaluateToolCall(call, policy) {
735
+ // guard off: allow unconditionally (types.ts: "off: every layer allows unconditionally")
736
+ if (policy.mode === 'off') {
737
+ return { decision: 'allow', reason: 'guard off', layer: LAYER };
738
+ }
739
+ if (policy.denyTools.includes(call.name)) {
740
+ return decide(policy, LAYER, 'critical', `tool "${call.name}" is on the deny list`);
741
+ }
742
+ if (policy.approvalTools.includes(call.name)) {
743
+ return { decision: 'require-approval', reason: `tool "${call.name}" requires human approval`, layer: LAYER };
744
+ }
745
+ // -- write zones ---------------------------------------------------------
746
+ const zoneVerdict = checkWriteZones(call, policy);
747
+ if (zoneVerdict !== undefined)
748
+ return zoneVerdict;
749
+ // -- bash-like -----------------------------------------------------------
750
+ const bashArg = stringArg(call, ['command', 'script', 'cmd']);
751
+ const bashByName = BASH_NAME_RE.test(call.name);
752
+ if (bashByName || bashArg !== undefined) {
753
+ const analyses = [];
754
+ if (bashArg !== undefined)
755
+ analyses.push(analyzeBash(bashArg));
756
+ if (bashByName) {
757
+ // a bash-named tool may carry the script under a different arg name
758
+ for (const v of Object.values(call.arguments)) {
759
+ if (typeof v === 'string' && v !== bashArg && v.trim() !== '') {
760
+ analyses.push(analyzeBash(v));
761
+ }
762
+ }
763
+ }
764
+ let risk = 'low';
765
+ const reasons = [];
766
+ for (const a of analyses) {
767
+ risk = maxRisk(risk, a.risk);
768
+ reasons.push(...a.reasons);
769
+ }
770
+ // No findings: allow directly so a clean command is never denied by
771
+ // `decide()` in strict mode. Findings map 1:1 onto severity.
772
+ if (risk === 'low') {
773
+ return { decision: 'allow', reason: `bash-like tool "${call.name}": no dangerous constructs detected`, layer: LAYER };
774
+ }
775
+ const severity = risk;
776
+ return decide(policy, LAYER, severity, reasons.join('; '));
777
+ }
778
+ // -- http-like -----------------------------------------------------------
779
+ // Only the url/href/link arguments are checked: a research tool carrying a
780
+ // plain-text `query` (no URL) must not be denied as an "unparseable URL".
781
+ const urlArg = stringArg(call, ['url', 'href', 'link']);
782
+ const httpByName = HTTP_TOOL_NAMES.has(call.name) || HTTP_NAME_RE.test(call.name);
783
+ if (httpByName || urlArg !== undefined) {
784
+ if (urlArg === undefined) {
785
+ return { decision: 'allow', reason: `http-like tool "${call.name}" has no URL argument to check`, layer: LAYER };
786
+ }
787
+ return checkHttpUrl(urlArg, policy);
788
+ }
789
+ // -- default --------------------------------------------------------------
790
+ return { decision: 'allow', reason: `tool "${call.name}" is not covered by any gate`, layer: LAYER };
791
+ }