@vellumai/assistant 0.11.4 → 0.11.5-staging.2

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 (367) hide show
  1. package/AGENTS.md +2 -2
  2. package/ARCHITECTURE.md +29 -4
  3. package/README.md +1 -1
  4. package/docs/architecture/memory.md +17 -5
  5. package/docs/architecture/security.md +96 -152
  6. package/docs/guardian-request-flow.md +13 -2
  7. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  11. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  15. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  16. package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
  17. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  18. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +29 -31
  19. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  20. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  21. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  22. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/openapi.yaml +193 -3
  24. package/package.json +1 -1
  25. package/src/__tests__/anthropic-provider.test.ts +10 -3
  26. package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
  27. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  28. package/src/__tests__/channel-approval-routes.test.ts +4 -0
  29. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
  30. package/src/__tests__/channel-reply-delivery.test.ts +26 -13
  31. package/src/__tests__/checker.test.ts +20 -314
  32. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  33. package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
  34. package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
  35. package/src/__tests__/config-loader-backfill.test.ts +19 -10
  36. package/src/__tests__/container-cpu-sampler.test.ts +153 -0
  37. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  38. package/src/__tests__/conversation-attachments.test.ts +0 -1
  39. package/src/__tests__/conversation-error.test.ts +17 -0
  40. package/src/__tests__/conversation-lifecycle.test.ts +20 -26
  41. package/src/__tests__/conversation-pairing.test.ts +186 -0
  42. package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
  43. package/src/__tests__/credential-broker-server-use.test.ts +24 -5
  44. package/src/__tests__/credential-routes.test.ts +22 -3
  45. package/src/__tests__/credential-security-invariants.test.ts +2 -1
  46. package/src/__tests__/daemon-credential-client.test.ts +88 -0
  47. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  48. package/src/__tests__/dm-backfill.test.ts +63 -0
  49. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  50. package/src/__tests__/edit-propagation.test.ts +107 -4
  51. package/src/__tests__/events-client-registration.test.ts +28 -0
  52. package/src/__tests__/file-write-tool.test.ts +4 -2
  53. package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
  54. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
  55. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  56. package/src/__tests__/host-proxy-interface.test.ts +11 -1
  57. package/src/__tests__/host-shell-tool.test.ts +23 -4
  58. package/src/__tests__/image-conversion.test.ts +143 -1
  59. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  60. package/src/__tests__/input-repairs.test.ts +207 -0
  61. package/src/__tests__/live-workspace-guard.test.ts +75 -0
  62. package/src/__tests__/managed-profile-guard.test.ts +4 -2
  63. package/src/__tests__/mcp-abort-signal.test.ts +1 -1
  64. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  65. package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
  66. package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
  67. package/src/__tests__/mtime-cache.test.ts +2 -0
  68. package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
  69. package/src/__tests__/openai-provider.test.ts +22 -0
  70. package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
  71. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  72. package/src/__tests__/platform.test.ts +16 -1
  73. package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
  74. package/src/__tests__/plugin-api-shim.test.ts +5 -0
  75. package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
  76. package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
  77. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  78. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  79. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  80. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
  81. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
  82. package/src/__tests__/reaction-persistence.test.ts +200 -7
  83. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  84. package/src/__tests__/resource-pressure-guard.test.ts +428 -0
  85. package/src/__tests__/resource-pressure-routes.test.ts +113 -0
  86. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  87. package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
  88. package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
  89. package/src/__tests__/skill-tool-factory.test.ts +161 -0
  90. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  91. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  92. package/src/__tests__/tool-executor.test.ts +63 -38
  93. package/src/__tests__/tool-policy.test.ts +97 -1
  94. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
  95. package/src/__tests__/user-plugin-loader.test.ts +2 -0
  96. package/src/__tests__/validate-input.test.ts +243 -7
  97. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  98. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
  99. package/src/__tests__/voice-session-bridge.test.ts +42 -0
  100. package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
  101. package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
  102. package/src/acp/acp-claude-oauth.ts +38 -21
  103. package/src/acp/prepare-agent-env.ts +146 -55
  104. package/src/agent/loop-tool-dedup.test.ts +161 -0
  105. package/src/agent/loop.ts +68 -22
  106. package/src/api/constants/app-tools.ts +34 -0
  107. package/src/api/events/resource-pressure-status-changed.ts +53 -0
  108. package/src/api/index.ts +19 -0
  109. package/src/api/responses/resource-pressure-status.ts +25 -0
  110. package/src/approvals/guardian-channel-delivery.ts +70 -6
  111. package/src/approvals/guardian-request-resolvers.ts +32 -66
  112. package/src/calls/__tests__/voice-session-bridge.test.ts +114 -0
  113. package/src/calls/voice-session-bridge.ts +60 -6
  114. package/src/channels/__tests__/message-audience.test.ts +49 -0
  115. package/src/channels/__tests__/types.test.ts +17 -3
  116. package/src/channels/message-audience.ts +40 -0
  117. package/src/channels/types.ts +16 -12
  118. package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
  119. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
  120. package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
  121. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  122. package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
  123. package/src/cli/commands/__tests__/keys.test.ts +89 -8
  124. package/src/cli/commands/__tests__/plugins.test.ts +57 -1
  125. package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
  126. package/src/cli/commands/channel-verification-sessions.ts +11 -21
  127. package/src/cli/commands/conversations.help.ts +34 -0
  128. package/src/cli/commands/conversations.ts +125 -0
  129. package/src/cli/commands/inference-providers.ts +9 -4
  130. package/src/cli/commands/inference.help.ts +9 -4
  131. package/src/cli/commands/keys.help.ts +13 -1
  132. package/src/cli/commands/keys.ts +26 -15
  133. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  134. package/src/cli/commands/memory/index.help.ts +41 -27
  135. package/src/cli/commands/memory/index.ts +2 -0
  136. package/src/cli/commands/memory/memory-v2.ts +58 -54
  137. package/src/cli/commands/memory/memory-validate.ts +18 -0
  138. package/src/cli/commands/monitoring.ts +1 -0
  139. package/src/cli/commands/plugins.help.ts +28 -31
  140. package/src/cli/commands/plugins.ts +10 -0
  141. package/src/cli/commands/skills.help.ts +13 -16
  142. package/src/cli/commands/trust.ts +3 -14
  143. package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
  144. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
  145. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
  146. package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
  147. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
  148. package/src/cli/lib/bundled-marketplace.json +13 -0
  149. package/src/cli/lib/daemon-credential-client.ts +25 -3
  150. package/src/cli/lib/install-from-github.ts +35 -24
  151. package/src/cli/lib/merge-plugin-tree.ts +22 -4
  152. package/src/cli/lib/plugin-surfaces.ts +27 -0
  153. package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
  154. package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
  155. package/src/config/default-profile-catalog.ts +5 -6
  156. package/src/config/feature-flag-registry.json +32 -32
  157. package/src/config/webhook-routing.ts +8 -0
  158. package/src/context/compactor.ts +31 -2
  159. package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
  160. package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
  161. package/src/daemon/conversation-error.ts +13 -0
  162. package/src/daemon/conversation-messaging.ts +22 -2
  163. package/src/daemon/conversation-process.ts +11 -0
  164. package/src/daemon/conversation-store.ts +10 -0
  165. package/src/daemon/conversation-surfaces.ts +21 -8
  166. package/src/daemon/conversation.ts +12 -10
  167. package/src/daemon/lifecycle.ts +6 -2
  168. package/src/daemon/message-types/conversations.ts +5 -7
  169. package/src/daemon/provider-rejection-log-fields.ts +124 -0
  170. package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
  171. package/src/daemon/resource-pressure-guard.ts +387 -0
  172. package/src/daemon/shutdown-handlers.ts +2 -0
  173. package/src/daemon/startup-error.ts +16 -0
  174. package/src/daemon/trust-context.ts +14 -10
  175. package/src/daemon/unsendable-image-notice.ts +76 -0
  176. package/src/hooks/registry.ts +65 -51
  177. package/src/ipc/__tests__/socket-path.test.ts +15 -7
  178. package/src/ipc/gateway-client.test.ts +1 -1
  179. package/src/ipc/gateway-client.ts +14 -21
  180. package/src/ipc/socket-cleanup.ts +2 -11
  181. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
  182. package/src/messaging/provider-message-metadata.ts +128 -0
  183. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
  184. package/src/messaging/providers/channel-transport.ts +90 -13
  185. package/src/messaging/providers/discord/send.ts +16 -0
  186. package/src/messaging/providers/discord/transport.ts +11 -1
  187. package/src/messaging/providers/index.ts +109 -16
  188. package/src/messaging/providers/slack/message-metadata.ts +45 -0
  189. package/src/messaging/providers/slack/render-transcript.ts +1 -4
  190. package/src/messaging/providers/slack/send.test.ts +6 -9
  191. package/src/messaging/providers/slack/send.ts +40 -28
  192. package/src/messaging/providers/slack/transport.ts +22 -26
  193. package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
  194. package/src/messaging/providers/telegram-bot/transport.ts +3 -6
  195. package/src/messaging/read-provider-metadata.test.ts +157 -0
  196. package/src/messaging/read-provider-metadata.ts +51 -0
  197. package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
  198. package/src/notifications/__tests__/edit-notification.test.ts +174 -2
  199. package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
  200. package/src/notifications/adapters/macos.ts +55 -0
  201. package/src/notifications/adapters/slack.ts +10 -5
  202. package/src/notifications/assistant-reply-producer.ts +37 -4
  203. package/src/notifications/conversation-pairing.ts +111 -21
  204. package/src/notifications/edit-notification.ts +38 -8
  205. package/src/notifications/emit-signal.ts +12 -14
  206. package/src/notifications/home-feed-side-effect.ts +218 -15
  207. package/src/notifications/types.ts +5 -0
  208. package/src/permissions/AGENTS.md +16 -0
  209. package/src/permissions/checker.test.ts +75 -122
  210. package/src/permissions/checker.ts +44 -560
  211. package/src/permissions/confirmation-guardian-request.test.ts +28 -3
  212. package/src/permissions/confirmation-guardian-request.ts +5 -1
  213. package/src/persistence/conversation-crud.ts +23 -20
  214. package/src/persistence/conversation-queries.ts +14 -1
  215. package/src/persistence/conversation-types.ts +22 -0
  216. package/src/persistence/delivery-crud.ts +122 -15
  217. package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
  218. package/src/plugin-api/conversation-turn.ts +27 -0
  219. package/src/plugin-api/credential-scope.test.ts +15 -0
  220. package/src/plugin-api/credential-scope.ts +14 -0
  221. package/src/plugin-api/index.ts +19 -1
  222. package/src/plugin-api/resolve-credential.ts +15 -10
  223. package/src/plugin-api/store-credential.ts +148 -0
  224. package/src/plugin-api/system-card.ts +36 -0
  225. package/src/plugin-api/vision-support.test.ts +82 -0
  226. package/src/plugin-api/vision-support.ts +14 -2
  227. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
  228. package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
  229. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
  230. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
  231. package/src/plugins/defaults/image-recovery/detect.ts +24 -1
  232. package/src/plugins/defaults/main.ts +15 -0
  233. package/src/plugins/defaults/memory/AGENTS.md +11 -3
  234. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  235. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
  236. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  237. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  238. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
  239. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  240. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  241. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  242. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  243. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  244. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  245. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
  246. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
  247. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  248. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  249. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  250. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  251. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  252. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  253. package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
  254. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
  255. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  256. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  257. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  258. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  259. package/src/plugins/mtime-cache.ts +1 -1
  260. package/src/plugins/pipeline.ts +14 -7
  261. package/src/plugins/plugin-execution-context.ts +35 -11
  262. package/src/plugins/plugin-tree-walk.ts +63 -4
  263. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  264. package/src/providers/anthropic/client.ts +332 -241
  265. package/src/providers/connection-resolution.ts +2 -1
  266. package/src/providers/content-blocks.ts +22 -0
  267. package/src/providers/gemini/client.ts +5 -2
  268. package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
  269. package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
  270. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
  271. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
  272. package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
  273. package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
  274. package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
  275. package/src/providers/inference/adapter-factory.ts +33 -2
  276. package/src/providers/inference/auth.ts +13 -2
  277. package/src/providers/inference/credential-usage.ts +37 -0
  278. package/src/providers/inference/missing-credential-guard.ts +110 -0
  279. package/src/providers/inference/resolve-auth.ts +7 -7
  280. package/src/providers/media-resolve.ts +176 -15
  281. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
  282. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  283. package/src/providers/openai/chat-completions-provider.ts +322 -42
  284. package/src/routes/route-host-protocol.ts +7 -0
  285. package/src/routes/worker.ts +31 -10
  286. package/src/runtime/AGENTS.md +35 -0
  287. package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
  288. package/src/runtime/__tests__/web-presence.test.ts +234 -0
  289. package/src/runtime/assistant-event-hub.ts +38 -0
  290. package/src/runtime/channel-reply-delivery.ts +54 -31
  291. package/src/runtime/channel-retry-sweep.ts +6 -4
  292. package/src/runtime/effective-capabilities.test.ts +19 -0
  293. package/src/runtime/effective-capabilities.ts +25 -0
  294. package/src/runtime/guardian-reply-router.ts +6 -2
  295. package/src/runtime/http-errors.ts +1 -0
  296. package/src/runtime/http-server.ts +20 -5
  297. package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
  298. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
  299. package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
  300. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
  301. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
  302. package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
  303. package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
  304. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  305. package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
  306. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  307. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
  308. package/src/runtime/routes/approval-routes.ts +37 -4
  309. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
  310. package/src/runtime/routes/canned-message-complete.ts +68 -29
  311. package/src/runtime/routes/client-routes.ts +113 -0
  312. package/src/runtime/routes/conversation-list-routes.ts +22 -7
  313. package/src/runtime/routes/conversation-management-routes.ts +15 -9
  314. package/src/runtime/routes/conversation-routes.ts +36 -22
  315. package/src/runtime/routes/credential-in-use.ts +85 -0
  316. package/src/runtime/routes/credential-routes.ts +55 -85
  317. package/src/runtime/routes/guardian-approval-interception.ts +22 -22
  318. package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
  319. package/src/runtime/routes/identity-routes.ts +5 -121
  320. package/src/runtime/routes/inbound-message-handler.ts +71 -27
  321. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
  322. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
  323. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
  324. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
  325. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
  326. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
  327. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
  328. package/src/runtime/routes/index.ts +2 -0
  329. package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
  330. package/src/runtime/routes/monitoring-routes.ts +9 -1
  331. package/src/runtime/routes/plugins-routes.ts +17 -5
  332. package/src/runtime/routes/resource-pressure-routes.ts +22 -0
  333. package/src/runtime/routes/secret-routes.ts +39 -8
  334. package/src/runtime/routes/settings-routes.ts +6 -6
  335. package/src/runtime/routes/surface-action-routes.ts +20 -19
  336. package/src/runtime/routes/user-route-dispatcher.ts +31 -2
  337. package/src/runtime/routes/user-route-resolution.ts +18 -1
  338. package/src/runtime/slack-reply-session.test.ts +23 -13
  339. package/src/runtime/slack-reply-session.ts +38 -55
  340. package/src/runtime/web-presence.ts +90 -0
  341. package/src/skills/validate-input.ts +177 -40
  342. package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
  343. package/src/subagent/consult-context.ts +7 -23
  344. package/src/tools/credentials/broker.ts +24 -79
  345. package/src/tools/credentials/ref-parse.ts +35 -0
  346. package/src/tools/credentials/resolve.ts +4 -10
  347. package/src/tools/credentials/store.ts +168 -0
  348. package/src/tools/credentials/tool-policy.ts +67 -0
  349. package/src/tools/executor.ts +53 -15
  350. package/src/tools/network/__tests__/web-search.test.ts +41 -1
  351. package/src/tools/network/url-safety.ts +9 -16
  352. package/src/tools/network/web-search.ts +21 -3
  353. package/src/tools/permission-checker.ts +43 -52
  354. package/src/tools/schema-transforms.ts +40 -5
  355. package/src/tools/shared/input-repairs.ts +160 -0
  356. package/src/tools/skills/skill-tool-factory.ts +18 -4
  357. package/src/tools/subagent/spawn.ts +0 -1
  358. package/src/tools/tool-approval-handler.ts +16 -10
  359. package/src/tools/tool-types.ts +26 -13
  360. package/src/tools/types.ts +6 -6
  361. package/src/util/__tests__/cgroup-memory.test.ts +3 -0
  362. package/src/util/cgroup-memory.ts +3 -0
  363. package/src/util/container-cpu-sampler.ts +250 -0
  364. package/src/util/image-conversion.ts +178 -14
  365. package/src/util/platform.ts +93 -10
  366. package/src/permissions/ipc-risk-types.ts +0 -143
  367. package/src/permissions/risk-types.ts +0 -76
@@ -1,4 +1,3 @@
1
- import { createHash } from "node:crypto";
2
1
  import { homedir } from "node:os";
3
2
  import { dirname, join, resolve } from "node:path";
4
3
 
@@ -19,10 +18,6 @@ import { getSkillRoots } from "../skills/path-classifier.js";
19
18
  import { computeTransitiveSkillVersionHash } from "../skills/transitive-version-hash.js";
20
19
  import { computeSkillVersionHash } from "../skills/version-hash.js";
21
20
  import type { ManifestOverride } from "../tools/execution-target.js";
22
- import {
23
- looksLikeHostPortShorthand,
24
- looksLikePathOnlyInput,
25
- } from "../tools/network/url-safety.js";
26
21
  import { getTool, getToolOwner, resolveTool } from "../tools/registry.js";
27
22
  import { resolveRealPath } from "../tools/shared/filesystem/path-policy.js";
28
23
  import type { Tool } from "../tools/types.js";
@@ -46,9 +41,7 @@ import {
46
41
  getAutoApproveThreshold,
47
42
  refreshAutoApproveThreshold,
48
43
  } from "./gateway-threshold-reader.js";
49
- import type { RiskAssessment } from "./risk-types.js";
50
44
  import {
51
- type AllowlistOption,
52
45
  type PermissionCheckResult,
53
46
  type PolicyContext,
54
47
  RiskLevel,
@@ -60,114 +53,19 @@ import {
60
53
  resolveSandboxBase,
61
54
  } from "./workspace-policy.js";
62
55
 
63
- // ── Risk classification cache ────────────────────────────────────────────────
64
- // classifyRisk() is called on every permission check and delegates to the
65
- // gateway via IPC. Cache results keyed on
66
- // (toolName, inputHash, workingDir, manifestOverride).
67
- // Invalidated when trust rules change since risk classification for file tools
68
- // depends on skill source path checks which reference config, but the core
69
- // risk logic is input-deterministic.
70
- /** The result of classifyRisk(): a risk level with an optional human-readable reason. */
71
- export interface RiskClassification {
72
- level: RiskLevel;
73
- /** Human-readable explanation of why this risk level was assigned. */
74
- reason?: string;
75
- }
76
-
77
56
  /**
78
- * Extended risk classification that includes gateway-provided metadata
79
- * used by check() for command candidate building and sandbox auto-approve.
57
+ * One gateway classification as the daemon carries it: the `classify_risk`
58
+ * response with `risk` mapped onto the daemon's {@link RiskLevel} as `level`.
59
+ * Produced once per tool invocation by {@link classifyRisk} and handed down
60
+ * through `checkPermission` and {@link check}; the daemon keeps no memo of
61
+ * it, so a trust-rule, config, or skill change is reflected on the next call.
80
62
  */
81
- interface RiskClassificationWithMeta extends RiskClassification {
82
- /** Command candidates from the gateway for trust rule matching (bash tools). */
83
- commandCandidates?: string[];
84
- /** Action keys from the gateway for trust rule matching (bash tools). */
85
- actionKeys?: string[];
86
- /** Whether the command qualifies for sandbox auto-approve (bash tools). */
87
- sandboxAutoApprove?: boolean;
88
- /**
89
- * Lexically-resolved path args from the gateway for bash sandbox
90
- * auto-approve. Stored in the cache so the symlink escape check can be
91
- * re-run on cache hits (symlink targets may change between calls).
92
- */
93
- sandboxPathArgs?: string[];
94
- /** Allowlist options from the gateway for generateAllowlistOptions(). */
95
- allowlistOptions?: AllowlistOption[];
96
- /** Resolved filesystem path arguments for directory-scoped rule matching. */
97
- resolvedPaths?: string[];
98
- }
99
-
100
- const RISK_CACHE_MAX = 256;
101
- const riskCache = new Map<string, RiskClassificationWithMeta>();
102
-
103
- // ── Assessment cache ─────────────────────────────────────────────────────────
104
- // Stores the full ClassificationResult from the gateway so that
105
- // generateAllowlistOptions() can read gateway-produced allowlistOptions
106
- // without re-classifying. Keyed on (toolName, inputHash) — a simpler key
107
- // than the full risk cache since generateAllowlistOptions() does not receive
108
- // workingDir or manifestOverride. Cleared alongside the risk cache.
109
- const assessmentCache = new Map<string, RiskAssessment>();
110
-
111
- function assessmentCacheKey(
112
- toolName: string,
113
- input: Record<string, unknown>,
114
- ): string {
115
- const { reason: _reason, activity: _activity, ...cacheableInput } = input;
116
- const inputJson = JSON.stringify(cacheableInput);
117
- const hash = createHash("sha256").update(inputJson).digest("hex");
118
- return `${toolName}\0${hash}`;
119
- }
120
-
121
- function riskCacheKey(
122
- toolName: string,
123
- input: Record<string, unknown>,
124
- workingDir?: string,
125
- manifestOverride?: ManifestOverride,
126
- fsStateKey?: string,
127
- ): string {
128
- // Strip `reason` and `activity` before computing the cache key — they are
129
- // cosmetic status text that varies per invocation even for identical tool
130
- // operations, causing unnecessary cache misses.
131
- const { reason: _reason, activity: _activity, ...cacheableInput } = input;
132
- const inputJson = JSON.stringify(cacheableInput);
133
- const hash = createHash("sha256")
134
- .update(inputJson)
135
- .update("\0")
136
- .update(workingDir ?? "")
137
- .update("\0")
138
- .update(manifestOverride ? JSON.stringify(manifestOverride) : "")
139
- // For file tools, fold in the symlink-resolved target path(s). File risk
140
- // depends on filesystem state (a symlink can be retargeted under a
141
- // protected dir between calls), so the same raw input must miss the cache
142
- // when its canonicalized target changes.
143
- .update("\0")
144
- .update(fsStateKey ?? "")
145
- .digest("hex");
146
- return `${toolName}\0${hash}`;
147
- }
148
-
149
- /**
150
- * Compute the filesystem-state component of the risk cache key for file tools:
151
- * the symlink-resolved target path(s). Returns `undefined` for non-file tools
152
- * (whose risk does not depend on filesystem state).
153
- */
154
- function fileToolFsStateKey(
155
- toolName: string,
156
- input: Record<string, unknown>,
157
- workingDir?: string,
158
- ): string | undefined {
159
- if (!FILE_TOOL_NAMES.has(toolName)) {
160
- return undefined;
161
- }
162
- const resolved = resolveFileToolPaths(toolName, input, workingDir);
163
- return `${resolved.resolvedPath ?? ""}\0${resolved.resolvedTransferDestPath ?? ""}\0${resolved.resolvedWorkingDir ?? ""}`;
164
- }
165
-
166
- /** Clear the risk classification cache. Called when trust rules change. Exported for test setup. */
167
- export function clearRiskCache(): void {
168
- riskCache.clear();
169
- assessmentCache.clear();
170
- }
63
+ export type RiskClassificationWithMeta = Omit<
64
+ ClassifyRiskIpcResponse,
65
+ "risk"
66
+ > & {
67
+ level: RiskLevel;
68
+ };
171
69
 
172
70
  // ── Approval policy singleton ────────────────────────────────────────────────
173
71
  const defaultApprovalPolicy = new DefaultApprovalPolicy();
@@ -347,81 +245,20 @@ function computeTransitiveHashSafe(skillId: string): string | undefined {
347
245
  }
348
246
  }
349
247
 
350
- function canonicalizeWebFetchUrl(parsed: URL): URL {
351
- parsed.hash = "";
352
- parsed.username = "";
353
- parsed.password = "";
354
-
355
- try {
356
- // Normalize equivalent escaped paths (for example, "/%70rivate" -> "/private")
357
- // so path-scoped trust rules cannot be bypassed via percent-encoding.
358
- parsed.pathname = decodeURI(parsed.pathname);
359
- } catch {
360
- // Keep URL parser canonical form when decoding fails.
361
- }
362
-
363
- if (parsed.hostname.endsWith(".")) {
364
- parsed.hostname = parsed.hostname.replace(/\.+$/, "");
365
- }
366
-
367
- return parsed;
368
- }
369
-
370
- function normalizeWebFetchUrl(rawUrl: string): URL | null {
371
- const trimmed = rawUrl.trim();
372
- if (!trimmed) {
373
- return null;
374
- }
375
-
376
- if (looksLikeHostPortShorthand(trimmed)) {
377
- try {
378
- return canonicalizeWebFetchUrl(new URL(`https://${trimmed}`));
379
- } catch {
380
- return null;
381
- }
382
- }
383
-
384
- try {
385
- const parsed = new URL(trimmed);
386
- if (parsed.protocol === "http:" || parsed.protocol === "https:") {
387
- return canonicalizeWebFetchUrl(parsed);
388
- }
389
- return null;
390
- } catch {
391
- // Fall through.
392
- }
393
-
394
- if (looksLikePathOnlyInput(trimmed)) {
395
- return null;
396
- }
397
-
398
- if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) {
399
- return null;
400
- }
401
-
402
- try {
403
- return canonicalizeWebFetchUrl(new URL(`https://${trimmed}`));
404
- } catch {
405
- return null;
406
- }
407
- }
408
-
409
- function escapeMinimatchLiteral(value: string): string {
410
- return value.replace(/([\\*?[\]{}()!+@|])/g, "\\$1");
411
- }
412
-
413
248
  // ── IPC param builders ───────────────────────────────────────────────────────
414
- // Build the ClassifyRiskParams for each tool family. These resolve
249
+ // Build the classify_risk request for each tool family. These resolve
415
250
  // assistant-local context (file paths, skill metadata, etc.) before
416
251
  // forwarding to the gateway.
417
252
 
418
253
  import type {
419
- ClassifyRiskParams,
420
- FileContext,
421
- SkillMetadata,
422
- } from "./ipc-risk-types.js";
423
-
424
- function buildFileContext(): FileContext {
254
+ ClassifyRiskFileContext,
255
+ ClassifyRiskIpcParams,
256
+ ClassifyRiskIpcResponse,
257
+ ClassifyRiskSkillMetadata,
258
+ RiskLevelValue,
259
+ } from "@vellumai/gateway-client";
260
+
261
+ function buildFileContext(): ClassifyRiskFileContext {
425
262
  const config = getConfig();
426
263
  // Canonicalize the protected directories via realpath so that a symlinked
427
264
  // component anywhere in their path still prefix-matches the canonicalized
@@ -479,16 +316,6 @@ function resolveClassificationPath(
479
316
  return resolveRealPath(base);
480
317
  }
481
318
 
482
- const FILE_TOOL_NAMES = new Set([
483
- "file_read",
484
- "file_write",
485
- "file_edit",
486
- "host_file_read",
487
- "host_file_write",
488
- "host_file_edit",
489
- "host_file_transfer",
490
- ]);
491
-
492
319
  interface FileToolResolution {
493
320
  filePath: string;
494
321
  effectiveWorkingDir: string;
@@ -508,10 +335,8 @@ interface FileToolResolution {
508
335
 
509
336
  /**
510
337
  * Resolve the security-sensitive path(s) of a file tool invocation, including
511
- * symlink canonicalization. Shared by the IPC param builder and the risk cache
512
- * key so both observe the same filesystem state — file risk now depends on
513
- * symlink targets, so the cache must key on the canonicalized path, not just
514
- * the raw tool input.
338
+ * symlink canonicalization, for the IPC params: file risk depends on the
339
+ * symlink target, not the raw tool input.
515
340
  */
516
341
  function resolveFileToolPaths(
517
342
  toolName: string,
@@ -568,7 +393,9 @@ function resolveFileToolPaths(
568
393
  };
569
394
  }
570
395
 
571
- function resolveSkillMetadata(selector: string): SkillMetadata | undefined {
396
+ function resolveSkillMetadata(
397
+ selector: string,
398
+ ): ClassifyRiskSkillMetadata | undefined {
572
399
  const resolved = resolveSkillIdAndHash(selector);
573
400
  if (!resolved) {
574
401
  return undefined;
@@ -593,7 +420,7 @@ function buildClassifyRiskParams(
593
420
  input: Record<string, unknown>,
594
421
  workingDir?: string,
595
422
  manifestOverride?: ManifestOverride,
596
- ): ClassifyRiskParams {
423
+ ): ClassifyRiskIpcParams {
597
424
  // ── Bash/host_bash ──
598
425
  if (toolName === "bash" || toolName === "host_bash") {
599
426
  // Count credential references attached to this invocation.
@@ -683,7 +510,7 @@ function buildClassifyRiskParams(
683
510
  // instead of hardcoding medium for unknown tools. When the tool is not in the
684
511
  // registry but a manifestOverride provides a risk, use that instead.
685
512
  const tool = getTool(toolName);
686
- let registryDefaultRisk: string | undefined;
513
+ let registryDefaultRisk: RiskLevelValue | undefined;
687
514
  if (tool) {
688
515
  registryDefaultRisk =
689
516
  tool.defaultRiskLevel === RiskLevel.Low
@@ -719,11 +546,9 @@ function riskStringToLevel(risk: string): RiskLevel {
719
546
  * with symlink resolution. The gateway's lexical check cannot follow
720
547
  * symlinks (no filesystem access), so the daemon resolves each path arg
721
548
  * through {@link isPathWithinWorkspaceRoot} (which uses realpathSync) and
722
- * revokes auto-approve if any escapes the workspace boundary.
723
- *
724
- * Called both on fresh gateway results and on cache hits, because symlink
725
- * targets can change between invocations — a path that was safe on the
726
- * first call may escape on the second if the symlink was retargeted.
549
+ * revokes auto-approve if any escapes the workspace boundary. Runs on every
550
+ * classification, so a symlink retargeted between two invocations of the
551
+ * same command is caught on the second.
727
552
  */
728
553
  function applyBashSymlinkEscapeCheck(
729
554
  result: RiskClassificationWithMeta,
@@ -755,31 +580,6 @@ export async function classifyRisk(
755
580
  ): Promise<RiskClassificationWithMeta> {
756
581
  signal?.throwIfAborted();
757
582
 
758
- // Check cache first.
759
- const cacheKey = riskCacheKey(
760
- toolName,
761
- input,
762
- workingDir,
763
- manifestOverride,
764
- fileToolFsStateKey(toolName, input, workingDir),
765
- );
766
- const cached = riskCache.get(cacheKey);
767
- if (cached !== undefined) {
768
- // LRU refresh
769
- riskCache.delete(cacheKey);
770
- riskCache.set(cacheKey, cached);
771
- // Re-run the symlink escape check on cache hits: symlink targets can
772
- // change between invocations, so a path that was safe when cached may
773
- // now escape. Return a shallow copy so the cache entry is not mutated.
774
- if (cached.sandboxPathArgs && cached.sandboxPathArgs.length > 0) {
775
- const fresh = { ...cached };
776
- applyBashSymlinkEscapeCheck(fresh, cached.sandboxPathArgs);
777
- return fresh;
778
- }
779
- return cached;
780
- }
781
-
782
- // ── Delegate to gateway via IPC ────────────────────────────────────────────
783
583
  const ipcParams = buildClassifyRiskParams(
784
584
  toolName,
785
585
  input,
@@ -797,15 +597,10 @@ export async function classifyRisk(
797
597
  );
798
598
  }
799
599
 
600
+ const { risk, ...carried } = gatewayResult;
800
601
  const result: RiskClassificationWithMeta = {
801
- level: riskStringToLevel(gatewayResult.risk),
802
- reason: gatewayResult.reason,
803
- commandCandidates: gatewayResult.commandCandidates,
804
- actionKeys: gatewayResult.actionKeys,
805
- sandboxAutoApprove: gatewayResult.sandboxAutoApprove,
806
- sandboxPathArgs: gatewayResult.sandboxPathArgs,
807
- allowlistOptions: gatewayResult.allowlistOptions,
808
- resolvedPaths: gatewayResult.resolvedPaths,
602
+ ...carried,
603
+ level: riskStringToLevel(risk),
809
604
  };
810
605
 
811
606
  // ── Symlink escape check for bash sandbox auto-approve ───────────────
@@ -815,41 +610,8 @@ export async function classifyRisk(
815
610
  // `ln -s /etc /workspace/escape`) would pass the lexical check and
816
611
  // be auto-approved. Resolve the gateway-provided path args through
817
612
  // symlinks here and revoke auto-approve if any escapes the workspace.
818
- // The check is also re-run on cache hits (see above) because symlink
819
- // targets can change between invocations.
820
613
  applyBashSymlinkEscapeCheck(result, gatewayResult.sandboxPathArgs);
821
614
 
822
- // Cache the result.
823
- if (riskCache.size >= RISK_CACHE_MAX) {
824
- const oldest = riskCache.keys().next().value;
825
- if (oldest !== undefined) {
826
- riskCache.delete(oldest);
827
- }
828
- }
829
- riskCache.set(cacheKey, result);
830
-
831
- // Store a RiskAssessment-shaped entry in the assessment cache so that
832
- // generateAllowlistOptions() can retrieve gateway-produced allowlistOptions
833
- // and permission-checker.ts can populate riskScopeOptions for the Rule
834
- // Editor Modal via cachedAssessment.scopeOptions.
835
- const assessment: RiskAssessment = {
836
- riskLevel: gatewayResult.risk === "unknown" ? "medium" : gatewayResult.risk,
837
- reason: gatewayResult.reason,
838
- scopeOptions: gatewayResult.scopeOptions ?? [],
839
- matchType: gatewayResult.matchType ?? "unknown",
840
- allowlistOptions: gatewayResult.allowlistOptions,
841
- directoryScopeOptions: gatewayResult.directoryScopeOptions,
842
- resolvedPaths: gatewayResult.resolvedPaths,
843
- };
844
- const aKey = assessmentCacheKey(toolName, input);
845
- if (assessmentCache.size >= RISK_CACHE_MAX) {
846
- const oldest = assessmentCache.keys().next().value;
847
- if (oldest !== undefined) {
848
- assessmentCache.delete(oldest);
849
- }
850
- }
851
- assessmentCache.set(aKey, assessment);
852
-
853
615
  return result;
854
616
  }
855
617
 
@@ -899,6 +661,14 @@ function isRetrospectiveSkillAuthoringGrant(
899
661
  return false;
900
662
  }
901
663
 
664
+ /**
665
+ * Decide allow / prompt / deny for one tool invocation.
666
+ *
667
+ * `classification` is the invocation's gateway classification when the caller
668
+ * already holds it (the executor classifies once, before its gates, and hands
669
+ * it down through `checkPermission`); a caller without one gets a fresh
670
+ * classification of the same input here.
671
+ */
902
672
  export async function check(
903
673
  toolName: string,
904
674
  input: Record<string, unknown>,
@@ -906,6 +676,7 @@ export async function check(
906
676
  policyContext?: PolicyContext,
907
677
  manifestOverride?: ManifestOverride,
908
678
  signal?: AbortSignal,
679
+ classification?: RiskClassificationWithMeta,
909
680
  ): Promise<PermissionCheckResult> {
910
681
  signal?.throwIfAborted();
911
682
 
@@ -917,7 +688,7 @@ export async function check(
917
688
  };
918
689
  }
919
690
 
920
- const classification = await classifyRisk(
691
+ classification ??= await classifyRisk(
921
692
  toolName,
922
693
  input,
923
694
  workingDir,
@@ -926,24 +697,7 @@ export async function check(
926
697
  signal,
927
698
  );
928
699
 
929
- const { level: classifiedRisk, reason: riskReason } = classification;
930
-
931
- // Inline-command ("dynamic") skill loads execute embedded shell at load time
932
- // via child_process.spawn, outside the tool-approval pipeline that the
933
- // auto-approve threshold governs. Treat an uncovered one as High so the
934
- // standard threshold decides it like any other high-risk action: it runs at
935
- // Full access (autoApproveUpTo "high") and prompts below it. A covering user
936
- // trust rule arrives as matchType
937
- // "user_rule" with the risk already lowered (the escape hatch), so leave it
938
- // untouched. The gateway classifier is authoritative and also returns High;
939
- // this local elevation is defense-in-depth for the gateway-unreachable or
940
- // under-classified path. The separate non-interactive denial (no human to
941
- // approve embedded shell) lives in tools/permission-checker.ts.
942
- const risk =
943
- isDynamicSkillLoadInvocation(toolName, input) &&
944
- getCachedAssessment(toolName, input)?.matchType !== "user_rule"
945
- ? RiskLevel.High
946
- : classifiedRisk;
700
+ const { level: risk, reason: riskReason } = classification;
947
701
 
948
702
  // Use gateway-provided sandboxAutoApprove instead of evaluating locally.
949
703
  const hasSandboxAutoApprove = classification.sandboxAutoApprove ?? false;
@@ -1021,276 +775,6 @@ export async function check(
1021
775
  };
1022
776
  }
1023
777
 
1024
- const TOOL_DISPLAY_NAMES: Record<string, string> = {
1025
- file_read: "file reads",
1026
- file_write: "file writes",
1027
- file_edit: "file edits",
1028
- host_file_read: "host file reads",
1029
- host_file_write: "host file writes",
1030
- host_file_edit: "host file edits",
1031
- host_file_transfer: "host file transfers",
1032
- web_fetch: "URL fetches",
1033
- network_request: "network requests",
1034
- };
1035
-
1036
- function friendlyBasename(filePath: string): string {
1037
- const parts = filePath.split("/");
1038
- return parts[parts.length - 1] || filePath;
1039
- }
1040
-
1041
- function friendlyHostname(url: URL): string {
1042
- return url.hostname.replace(/^www\./, "");
1043
- }
1044
-
1045
- // ── Per-tool allowlist option strategies ─────────────────────────────────────
1046
- // Each strategy receives the tool name and raw input and returns allowlist
1047
- // options. Adding support for a new tool type means adding a function here
1048
- // and registering it in ALLOWLIST_STRATEGIES below.
1049
-
1050
- type AllowlistStrategy = (
1051
- toolName: string,
1052
- input: Record<string, unknown>,
1053
- ) => Promise<AllowlistOption[]> | AllowlistOption[];
1054
-
1055
- function fileAllowlistStrategy(
1056
- toolName: string,
1057
- input: Record<string, unknown>,
1058
- ): AllowlistOption[] {
1059
- let filePath: string;
1060
- if (toolName === "host_file_transfer") {
1061
- // Use the host-side path: source_path for to_sandbox, dest_path for to_host.
1062
- const direction = (input.direction as string) ?? "";
1063
- filePath =
1064
- direction === "to_sandbox"
1065
- ? ((input.source_path as string) ?? "")
1066
- : ((input.dest_path as string) ?? "");
1067
- } else {
1068
- filePath =
1069
- (input.path as string) ??
1070
- (input.file_path as string) ??
1071
- (input.dest_path as string) ??
1072
- (input.source_path as string) ??
1073
- "";
1074
- }
1075
- const toolLabel = TOOL_DISPLAY_NAMES[toolName] ?? toolName;
1076
- const options: AllowlistOption[] = [];
1077
-
1078
- // Patterns must match the "tool:path" format used by check()
1079
- options.push({
1080
- label: filePath,
1081
- description: `This file only`,
1082
- pattern: `${toolName}:${filePath}`,
1083
- });
1084
-
1085
- // Ancestor directory wildcards — walk up from immediate parent, stop at home dir or /
1086
- const home = homedir();
1087
- let dir = dirname(filePath);
1088
- const maxLevels = 3;
1089
- let levels = 0;
1090
- while (dir && dir !== "/" && dir !== "." && levels < maxLevels) {
1091
- const dirName = friendlyBasename(dir);
1092
- options.push({
1093
- label: `${dir}/**`,
1094
- description: `Anything in ${dirName}/`,
1095
- pattern: `${toolName}:${dir}/**`,
1096
- });
1097
- if (dir === home) {
1098
- break;
1099
- }
1100
- const parent = dirname(dir);
1101
- if (parent === dir) {
1102
- break;
1103
- }
1104
- dir = parent;
1105
- levels++;
1106
- }
1107
-
1108
- options.push({
1109
- label: `${toolName}:*`,
1110
- description: `All ${toolLabel}`,
1111
- pattern: `${toolName}:*`,
1112
- });
1113
- return options;
1114
- }
1115
-
1116
- function urlAllowlistStrategy(
1117
- toolName: string,
1118
- input: Record<string, unknown>,
1119
- ): AllowlistOption[] {
1120
- const rawUrl = getStringField(input, "url").trim();
1121
- const normalized = normalizeWebFetchUrl(rawUrl);
1122
- const exact = normalized?.href ?? rawUrl;
1123
-
1124
- const options: AllowlistOption[] = [];
1125
- if (exact) {
1126
- options.push({
1127
- label: exact,
1128
- description: "This exact URL",
1129
- pattern: `${toolName}:${escapeMinimatchLiteral(exact)}`,
1130
- });
1131
- }
1132
- if (normalized) {
1133
- const host = friendlyHostname(normalized);
1134
- options.push({
1135
- label: `${normalized.origin}/*`,
1136
- description: `Any page on ${host}`,
1137
- pattern: `${toolName}:${escapeMinimatchLiteral(normalized.origin)}/*`,
1138
- });
1139
- }
1140
- const toolLabel = TOOL_DISPLAY_NAMES[toolName] ?? toolName;
1141
- // Use standalone "**" globstar — minimatch only treats ** as globstar when
1142
- // it is its own path segment, so "${toolName}:*" would fail to match URL
1143
- // candidates containing "/". The tool field is already filtered separately.
1144
- options.push({
1145
- label: `${toolName}:*`,
1146
- description: `All ${toolLabel}`,
1147
- pattern: `**`,
1148
- });
1149
-
1150
- const seen = new Set<string>();
1151
- return options.filter((o) => {
1152
- if (seen.has(o.pattern)) {
1153
- return false;
1154
- }
1155
- seen.add(o.pattern);
1156
- return true;
1157
- });
1158
- }
1159
-
1160
- function managedSkillAllowlistStrategy(
1161
- toolName: string,
1162
- input: Record<string, unknown>,
1163
- ): AllowlistOption[] {
1164
- const skillId = getStringField(input, "skill_id").trim();
1165
- const toolLabel =
1166
- toolName === "scaffold_managed_skill" ? "scaffold" : "delete";
1167
- const options: AllowlistOption[] = [];
1168
- if (skillId) {
1169
- options.push({
1170
- label: skillId,
1171
- description: `This skill only`,
1172
- pattern: `${toolName}:${skillId}`,
1173
- });
1174
- }
1175
- options.push({
1176
- label: `${toolName}:*`,
1177
- description: `All managed skill ${toolLabel}s`,
1178
- pattern: `${toolName}:*`,
1179
- });
1180
- return options;
1181
- }
1182
-
1183
- function skillLoadAllowlistStrategy(
1184
- _toolName: string,
1185
- input: Record<string, unknown>,
1186
- ): AllowlistOption[] {
1187
- const rawSelector = getStringField(input, "skill").trim();
1188
-
1189
- if (rawSelector) {
1190
- const resolved = resolveSkillIdAndHash(rawSelector);
1191
-
1192
- if (resolved && hasInlineExpansions(resolved.id)) {
1193
- const transitiveHash = computeTransitiveHashSafe(resolved.id);
1194
- const options: AllowlistOption[] = [];
1195
- if (transitiveHash) {
1196
- options.push({
1197
- label: `${resolved.id}@${transitiveHash}`,
1198
- description: "This exact version (pinned)",
1199
- pattern: `skill_load_dynamic:${resolved.id}@${transitiveHash}`,
1200
- });
1201
- }
1202
- options.push({
1203
- label: resolved.id,
1204
- description: "This skill (any version)",
1205
- pattern: `skill_load_dynamic:${resolved.id}`,
1206
- });
1207
- return options;
1208
- }
1209
-
1210
- if (resolved && resolved.versionHash) {
1211
- return [
1212
- {
1213
- label: `${resolved.id}@${resolved.versionHash}`,
1214
- description: "This exact version",
1215
- pattern: `skill_load:${resolved.id}@${resolved.versionHash}`,
1216
- },
1217
- ];
1218
- }
1219
- return [
1220
- {
1221
- label: rawSelector,
1222
- description: "This skill",
1223
- pattern: `skill_load:${rawSelector}`,
1224
- },
1225
- ];
1226
- }
1227
-
1228
- return [
1229
- {
1230
- label: "skill_load:*",
1231
- description: "All skill loads",
1232
- pattern: "skill_load:*",
1233
- },
1234
- ];
1235
- }
1236
-
1237
- const ALLOWLIST_STRATEGIES: Record<string, AllowlistStrategy> = {
1238
- file_read: fileAllowlistStrategy,
1239
- file_write: fileAllowlistStrategy,
1240
- file_edit: fileAllowlistStrategy,
1241
- host_file_read: fileAllowlistStrategy,
1242
- host_file_write: fileAllowlistStrategy,
1243
- host_file_edit: fileAllowlistStrategy,
1244
- host_file_transfer: fileAllowlistStrategy,
1245
- web_fetch: urlAllowlistStrategy,
1246
- network_request: urlAllowlistStrategy,
1247
- scaffold_managed_skill: managedSkillAllowlistStrategy,
1248
- delete_managed_skill: managedSkillAllowlistStrategy,
1249
- skill_load: skillLoadAllowlistStrategy,
1250
- };
1251
-
1252
- export async function generateAllowlistOptions(
1253
- toolName: string,
1254
- input: Record<string, unknown>,
1255
- signal?: AbortSignal,
1256
- ): Promise<AllowlistOption[]> {
1257
- signal?.throwIfAborted();
1258
-
1259
- // Use gateway-produced allowlist options from the assessment cache.
1260
- // For bash/host_bash tools, these are always provided by the gateway.
1261
- // For other tools that have classifier-produced options, use those too.
1262
- const aKey = assessmentCacheKey(toolName, input);
1263
- const cachedAssessment = assessmentCache.get(aKey);
1264
- if (
1265
- cachedAssessment?.allowlistOptions &&
1266
- cachedAssessment.allowlistOptions.length > 0
1267
- ) {
1268
- return cachedAssessment.allowlistOptions;
1269
- }
1270
-
1271
- // Fall back to the per-tool strategy function for non-bash tools
1272
- // or when no cached assessment exists.
1273
- if (Object.hasOwn(ALLOWLIST_STRATEGIES, toolName)) {
1274
- return ALLOWLIST_STRATEGIES[toolName](toolName, input);
1275
- }
1276
-
1277
- return [{ label: "*", description: "Everything", pattern: "*" }];
1278
- }
1279
-
1280
- /**
1281
- * Retrieve a cached RiskAssessment for a given tool invocation.
1282
- * Returns `undefined` when no classifier-backed assessment exists
1283
- * (e.g. MCP tools, unknown tools that fall through to registry defaults).
1284
- */
1285
- export function getCachedAssessment(
1286
- toolName: string,
1287
- input: Record<string, unknown>,
1288
- ): RiskAssessment | undefined {
1289
- return assessmentCache.get(assessmentCacheKey(toolName, input));
1290
- }
1291
-
1292
- // Directory-based scope only applies to filesystem and shell tools.
1293
- // All other tools auto-use "everywhere" (the client handles this).
1294
778
  export const SCOPE_AWARE_TOOLS = new Set([
1295
779
  "bash",
1296
780
  "host_bash",