@vellumai/assistant 0.11.4 → 0.11.5-staging.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 (358) 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__/container-cpu-sampler.test.ts +153 -0
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/conversation-attachments.test.ts +0 -1
  38. package/src/__tests__/conversation-error.test.ts +17 -0
  39. package/src/__tests__/conversation-lifecycle.test.ts +20 -26
  40. package/src/__tests__/conversation-pairing.test.ts +186 -0
  41. package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
  42. package/src/__tests__/credential-broker-server-use.test.ts +24 -5
  43. package/src/__tests__/credential-routes.test.ts +22 -3
  44. package/src/__tests__/credential-security-invariants.test.ts +2 -1
  45. package/src/__tests__/daemon-credential-client.test.ts +88 -0
  46. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  47. package/src/__tests__/dm-backfill.test.ts +63 -0
  48. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  49. package/src/__tests__/edit-propagation.test.ts +107 -4
  50. package/src/__tests__/events-client-registration.test.ts +28 -0
  51. package/src/__tests__/file-write-tool.test.ts +4 -2
  52. package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
  53. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
  54. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  55. package/src/__tests__/host-proxy-interface.test.ts +11 -1
  56. package/src/__tests__/host-shell-tool.test.ts +23 -4
  57. package/src/__tests__/image-conversion.test.ts +143 -1
  58. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  59. package/src/__tests__/input-repairs.test.ts +207 -0
  60. package/src/__tests__/live-workspace-guard.test.ts +75 -0
  61. package/src/__tests__/mcp-abort-signal.test.ts +1 -1
  62. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  63. package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
  64. package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
  65. package/src/__tests__/mtime-cache.test.ts +2 -0
  66. package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
  67. package/src/__tests__/openai-provider.test.ts +22 -0
  68. package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
  69. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  70. package/src/__tests__/platform.test.ts +16 -1
  71. package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
  72. package/src/__tests__/plugin-api-shim.test.ts +5 -0
  73. package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
  74. package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
  75. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  76. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  77. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  78. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
  79. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
  80. package/src/__tests__/reaction-persistence.test.ts +200 -7
  81. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  82. package/src/__tests__/resource-pressure-guard.test.ts +428 -0
  83. package/src/__tests__/resource-pressure-routes.test.ts +113 -0
  84. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  85. package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
  86. package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
  87. package/src/__tests__/skill-tool-factory.test.ts +161 -0
  88. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  89. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  90. package/src/__tests__/tool-executor.test.ts +63 -38
  91. package/src/__tests__/tool-policy.test.ts +97 -1
  92. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
  93. package/src/__tests__/user-plugin-loader.test.ts +2 -0
  94. package/src/__tests__/validate-input.test.ts +243 -7
  95. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  96. package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
  97. package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
  98. package/src/acp/acp-claude-oauth.ts +38 -21
  99. package/src/acp/prepare-agent-env.ts +146 -55
  100. package/src/agent/loop-tool-dedup.test.ts +161 -0
  101. package/src/agent/loop.ts +68 -22
  102. package/src/api/constants/app-tools.ts +34 -0
  103. package/src/api/events/resource-pressure-status-changed.ts +53 -0
  104. package/src/api/index.ts +19 -0
  105. package/src/api/responses/resource-pressure-status.ts +25 -0
  106. package/src/approvals/guardian-channel-delivery.ts +70 -6
  107. package/src/approvals/guardian-request-resolvers.ts +32 -66
  108. package/src/channels/__tests__/message-audience.test.ts +49 -0
  109. package/src/channels/__tests__/types.test.ts +17 -3
  110. package/src/channels/message-audience.ts +40 -0
  111. package/src/channels/types.ts +16 -12
  112. package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
  113. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
  114. package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
  115. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  116. package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
  117. package/src/cli/commands/__tests__/keys.test.ts +89 -8
  118. package/src/cli/commands/__tests__/plugins.test.ts +57 -1
  119. package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
  120. package/src/cli/commands/channel-verification-sessions.ts +11 -21
  121. package/src/cli/commands/conversations.help.ts +34 -0
  122. package/src/cli/commands/conversations.ts +125 -0
  123. package/src/cli/commands/inference-providers.ts +9 -4
  124. package/src/cli/commands/inference.help.ts +9 -4
  125. package/src/cli/commands/keys.help.ts +13 -1
  126. package/src/cli/commands/keys.ts +26 -15
  127. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  128. package/src/cli/commands/memory/index.help.ts +41 -27
  129. package/src/cli/commands/memory/index.ts +2 -0
  130. package/src/cli/commands/memory/memory-v2.ts +58 -54
  131. package/src/cli/commands/memory/memory-validate.ts +18 -0
  132. package/src/cli/commands/monitoring.ts +1 -0
  133. package/src/cli/commands/plugins.help.ts +28 -31
  134. package/src/cli/commands/plugins.ts +10 -0
  135. package/src/cli/commands/skills.help.ts +13 -16
  136. package/src/cli/commands/trust.ts +3 -14
  137. package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
  138. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
  139. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
  140. package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
  141. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
  142. package/src/cli/lib/bundled-marketplace.json +13 -0
  143. package/src/cli/lib/daemon-credential-client.ts +25 -3
  144. package/src/cli/lib/install-from-github.ts +35 -24
  145. package/src/cli/lib/merge-plugin-tree.ts +22 -4
  146. package/src/cli/lib/plugin-surfaces.ts +27 -0
  147. package/src/config/feature-flag-registry.json +35 -19
  148. package/src/config/webhook-routing.ts +8 -0
  149. package/src/context/compactor.ts +31 -2
  150. package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
  151. package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
  152. package/src/daemon/conversation-error.ts +13 -0
  153. package/src/daemon/conversation-messaging.ts +22 -2
  154. package/src/daemon/conversation-process.ts +11 -0
  155. package/src/daemon/conversation-store.ts +10 -0
  156. package/src/daemon/conversation-surfaces.ts +21 -8
  157. package/src/daemon/conversation.ts +12 -10
  158. package/src/daemon/lifecycle.ts +6 -2
  159. package/src/daemon/message-types/conversations.ts +5 -7
  160. package/src/daemon/provider-rejection-log-fields.ts +124 -0
  161. package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
  162. package/src/daemon/resource-pressure-guard.ts +387 -0
  163. package/src/daemon/shutdown-handlers.ts +2 -0
  164. package/src/daemon/startup-error.ts +16 -0
  165. package/src/daemon/trust-context.ts +14 -10
  166. package/src/daemon/unsendable-image-notice.ts +76 -0
  167. package/src/hooks/registry.ts +65 -51
  168. package/src/ipc/__tests__/socket-path.test.ts +15 -7
  169. package/src/ipc/gateway-client.test.ts +1 -1
  170. package/src/ipc/gateway-client.ts +14 -21
  171. package/src/ipc/socket-cleanup.ts +2 -11
  172. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
  173. package/src/messaging/provider-message-metadata.ts +128 -0
  174. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
  175. package/src/messaging/providers/channel-transport.ts +90 -13
  176. package/src/messaging/providers/discord/send.ts +16 -0
  177. package/src/messaging/providers/discord/transport.ts +11 -1
  178. package/src/messaging/providers/index.ts +109 -16
  179. package/src/messaging/providers/slack/message-metadata.ts +45 -0
  180. package/src/messaging/providers/slack/render-transcript.ts +1 -4
  181. package/src/messaging/providers/slack/send.test.ts +6 -9
  182. package/src/messaging/providers/slack/send.ts +40 -28
  183. package/src/messaging/providers/slack/transport.ts +22 -26
  184. package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
  185. package/src/messaging/providers/telegram-bot/transport.ts +3 -6
  186. package/src/messaging/read-provider-metadata.test.ts +157 -0
  187. package/src/messaging/read-provider-metadata.ts +51 -0
  188. package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
  189. package/src/notifications/__tests__/edit-notification.test.ts +174 -2
  190. package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
  191. package/src/notifications/adapters/macos.ts +55 -0
  192. package/src/notifications/adapters/slack.ts +10 -5
  193. package/src/notifications/assistant-reply-producer.ts +37 -4
  194. package/src/notifications/conversation-pairing.ts +111 -21
  195. package/src/notifications/edit-notification.ts +38 -8
  196. package/src/notifications/emit-signal.ts +12 -14
  197. package/src/notifications/home-feed-side-effect.ts +218 -15
  198. package/src/notifications/types.ts +5 -0
  199. package/src/permissions/AGENTS.md +16 -0
  200. package/src/permissions/checker.test.ts +75 -122
  201. package/src/permissions/checker.ts +44 -560
  202. package/src/permissions/confirmation-guardian-request.test.ts +28 -3
  203. package/src/permissions/confirmation-guardian-request.ts +5 -1
  204. package/src/persistence/conversation-crud.ts +23 -20
  205. package/src/persistence/conversation-queries.ts +14 -1
  206. package/src/persistence/conversation-types.ts +22 -0
  207. package/src/persistence/delivery-crud.ts +122 -15
  208. package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
  209. package/src/plugin-api/conversation-turn.ts +27 -0
  210. package/src/plugin-api/credential-scope.test.ts +15 -0
  211. package/src/plugin-api/credential-scope.ts +14 -0
  212. package/src/plugin-api/index.ts +19 -1
  213. package/src/plugin-api/resolve-credential.ts +15 -10
  214. package/src/plugin-api/store-credential.ts +148 -0
  215. package/src/plugin-api/system-card.ts +36 -0
  216. package/src/plugin-api/vision-support.test.ts +82 -0
  217. package/src/plugin-api/vision-support.ts +14 -2
  218. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
  219. package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
  220. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
  221. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
  222. package/src/plugins/defaults/image-recovery/detect.ts +24 -1
  223. package/src/plugins/defaults/main.ts +15 -0
  224. package/src/plugins/defaults/memory/AGENTS.md +11 -3
  225. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  226. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
  227. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  228. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  229. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
  230. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  231. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  232. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  233. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  234. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  235. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  236. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
  237. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
  238. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  239. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  240. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  241. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  242. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  243. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  244. package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
  245. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
  246. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  247. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  248. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  249. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  250. package/src/plugins/mtime-cache.ts +1 -1
  251. package/src/plugins/pipeline.ts +14 -7
  252. package/src/plugins/plugin-execution-context.ts +35 -11
  253. package/src/plugins/plugin-tree-walk.ts +63 -4
  254. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  255. package/src/providers/anthropic/client.ts +332 -241
  256. package/src/providers/connection-resolution.ts +2 -1
  257. package/src/providers/content-blocks.ts +22 -0
  258. package/src/providers/gemini/client.ts +5 -2
  259. package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
  260. package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
  261. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
  262. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
  263. package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
  264. package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
  265. package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
  266. package/src/providers/inference/adapter-factory.ts +33 -2
  267. package/src/providers/inference/auth.ts +13 -2
  268. package/src/providers/inference/credential-usage.ts +37 -0
  269. package/src/providers/inference/missing-credential-guard.ts +110 -0
  270. package/src/providers/inference/resolve-auth.ts +7 -7
  271. package/src/providers/media-resolve.ts +176 -15
  272. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
  273. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  274. package/src/providers/openai/chat-completions-provider.ts +322 -42
  275. package/src/routes/route-host-protocol.ts +7 -0
  276. package/src/routes/worker.ts +31 -10
  277. package/src/runtime/AGENTS.md +35 -0
  278. package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
  279. package/src/runtime/__tests__/web-presence.test.ts +234 -0
  280. package/src/runtime/assistant-event-hub.ts +38 -0
  281. package/src/runtime/channel-reply-delivery.ts +54 -31
  282. package/src/runtime/channel-retry-sweep.ts +6 -4
  283. package/src/runtime/effective-capabilities.test.ts +19 -0
  284. package/src/runtime/effective-capabilities.ts +25 -0
  285. package/src/runtime/guardian-reply-router.ts +6 -2
  286. package/src/runtime/http-errors.ts +1 -0
  287. package/src/runtime/http-server.ts +20 -5
  288. package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
  289. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
  290. package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
  291. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
  292. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
  293. package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
  294. package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
  295. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  296. package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
  297. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  298. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
  299. package/src/runtime/routes/approval-routes.ts +37 -4
  300. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
  301. package/src/runtime/routes/canned-message-complete.ts +68 -29
  302. package/src/runtime/routes/client-routes.ts +113 -0
  303. package/src/runtime/routes/conversation-list-routes.ts +22 -7
  304. package/src/runtime/routes/conversation-management-routes.ts +15 -9
  305. package/src/runtime/routes/conversation-routes.ts +36 -22
  306. package/src/runtime/routes/credential-in-use.ts +85 -0
  307. package/src/runtime/routes/credential-routes.ts +55 -85
  308. package/src/runtime/routes/guardian-approval-interception.ts +22 -22
  309. package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
  310. package/src/runtime/routes/identity-routes.ts +5 -121
  311. package/src/runtime/routes/inbound-message-handler.ts +71 -27
  312. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
  313. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
  314. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
  315. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
  316. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
  317. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
  318. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
  319. package/src/runtime/routes/index.ts +2 -0
  320. package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
  321. package/src/runtime/routes/monitoring-routes.ts +9 -1
  322. package/src/runtime/routes/plugins-routes.ts +17 -5
  323. package/src/runtime/routes/resource-pressure-routes.ts +22 -0
  324. package/src/runtime/routes/secret-routes.ts +39 -8
  325. package/src/runtime/routes/settings-routes.ts +6 -6
  326. package/src/runtime/routes/surface-action-routes.ts +20 -19
  327. package/src/runtime/routes/user-route-dispatcher.ts +31 -2
  328. package/src/runtime/routes/user-route-resolution.ts +18 -1
  329. package/src/runtime/slack-reply-session.test.ts +23 -13
  330. package/src/runtime/slack-reply-session.ts +38 -55
  331. package/src/runtime/web-presence.ts +90 -0
  332. package/src/skills/validate-input.ts +177 -40
  333. package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
  334. package/src/subagent/consult-context.ts +7 -23
  335. package/src/tools/credentials/broker.ts +24 -79
  336. package/src/tools/credentials/ref-parse.ts +35 -0
  337. package/src/tools/credentials/resolve.ts +4 -10
  338. package/src/tools/credentials/store.ts +168 -0
  339. package/src/tools/credentials/tool-policy.ts +67 -0
  340. package/src/tools/executor.ts +53 -15
  341. package/src/tools/network/__tests__/web-search.test.ts +41 -1
  342. package/src/tools/network/url-safety.ts +9 -16
  343. package/src/tools/network/web-search.ts +21 -3
  344. package/src/tools/permission-checker.ts +43 -52
  345. package/src/tools/schema-transforms.ts +40 -5
  346. package/src/tools/shared/input-repairs.ts +160 -0
  347. package/src/tools/skills/skill-tool-factory.ts +18 -4
  348. package/src/tools/subagent/spawn.ts +0 -1
  349. package/src/tools/tool-approval-handler.ts +16 -10
  350. package/src/tools/tool-types.ts +26 -13
  351. package/src/tools/types.ts +6 -6
  352. package/src/util/__tests__/cgroup-memory.test.ts +3 -0
  353. package/src/util/cgroup-memory.ts +3 -0
  354. package/src/util/container-cpu-sampler.ts +250 -0
  355. package/src/util/image-conversion.ts +178 -14
  356. package/src/util/platform.ts +93 -10
  357. package/src/permissions/ipc-risk-types.ts +0 -143
  358. package/src/permissions/risk-types.ts +0 -76
@@ -160,6 +160,13 @@ function cachedPrefixBlock(text: string): ContentBlock {
160
160
  } as unknown as ContentBlock;
161
161
  }
162
162
 
163
+ /**
164
+ * A 1x1 PNG. The send boundary validates image bytes before serialization, so
165
+ * an image fixture has to carry a real signature to reach the wire.
166
+ */
167
+ const PNG_B64 =
168
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFAAH/q842iQAAAABJRU5ErkJggg==";
169
+
163
170
  const sampleTools: ToolDefinition[] = [
164
171
  {
165
172
  name: "file_read",
@@ -2754,7 +2761,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
2754
2761
  source: {
2755
2762
  type: "base64",
2756
2763
  media_type: "image/png",
2757
- data: "iVBOR",
2764
+ data: PNG_B64,
2758
2765
  },
2759
2766
  },
2760
2767
  { type: "text", text: "extra error detail" },
@@ -2806,7 +2813,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
2806
2813
  source: {
2807
2814
  type: "base64",
2808
2815
  media_type: "image/png",
2809
- data: "iVBOR",
2816
+ data: PNG_B64,
2810
2817
  },
2811
2818
  },
2812
2819
  ],
@@ -2853,7 +2860,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
2853
2860
  source: {
2854
2861
  type: "base64",
2855
2862
  media_type: "image/png",
2856
- data: "iVBOR",
2863
+ data: PNG_B64,
2857
2864
  },
2858
2865
  },
2859
2866
  ],
@@ -188,6 +188,46 @@ describe("approval interception trust-class gates", () => {
188
188
  );
189
189
  });
190
190
 
191
+ test("the principal-gate rejection stays visible only to the actor on Slack", async () => {
192
+ // A rejection that loses its audience is posted to the whole channel,
193
+ // telling everyone present that someone failed a guardian check. The
194
+ // audience is one object rather than a flag beside an id so it cannot be
195
+ // half-dropped, and this pins that it is actually sent.
196
+ _anchorPrincipalId = "the-real-guardian-principal";
197
+ registerPendingInteraction(
198
+ "req-guardian-mismatch-slack",
199
+ CONVERSATION_ID,
200
+ TOOL_NAME,
201
+ TOOL_INPUT,
202
+ );
203
+
204
+ await handleApprovalInterception({
205
+ conversationId: CONVERSATION_ID,
206
+ callbackData: "apr:req-guardian-mismatch-slack:approve_once",
207
+ content: "",
208
+ conversationExternalId: "C0SLACKROOM",
209
+ sourceChannel: "slack",
210
+ actorExternalId: "U0STALEGUARDIAN",
211
+ replyCallbackUrl: "https://gateway.test/deliver",
212
+ trustCtx: {
213
+ sourceChannel: "slack",
214
+ trustClass: "guardian",
215
+ requesterExternalUserId: "U0STALEGUARDIAN",
216
+ guardianExternalUserId: "U0STALEGUARDIAN",
217
+ guardianPrincipalId: "some-other-principal",
218
+ },
219
+ assistantId: ASSISTANT_ID,
220
+ });
221
+
222
+ expect(deliverSpy).toHaveBeenCalledWith(
223
+ "https://gateway.test/deliver",
224
+ expect.objectContaining({
225
+ text: "Sorry, I couldn't process that. Please try again.",
226
+ audience: { kind: "oneReader", userId: "U0STALEGUARDIAN" },
227
+ }),
228
+ );
229
+ });
230
+
191
231
  test("guardian without a resolved acting principal is rejected before any decision", async () => {
192
232
  // Address-only guardian classification (e.g. a binding row with a null
193
233
  // principal) must not authorize decisions.
@@ -99,7 +99,7 @@ mock.module("../persistence/conversation-crud.js", () => ({
99
99
  parseConversation: (row: unknown) => row,
100
100
  provenanceFromTrustContext: () => ({ source: "user" }),
101
101
  relinkAttachments: mock(() => {}),
102
- selectSlackMetaCandidateMetadata: () => null,
102
+ selectProviderMetaCandidateMetadata: () => null,
103
103
  setConversationOriginChannelIfUnset: mock(() => {}),
104
104
  setConversationOriginInterfaceIfUnset: mock(() => {}),
105
105
  setConversationInferenceProfile: mock(() => {}),
@@ -1614,6 +1614,10 @@ describe("background channel processing approval prompts", () => {
1614
1614
  | { requestId?: string }
1615
1615
  | undefined;
1616
1616
  expect(approvalMeta?.requestId).toBe("req-bg-1");
1617
+ // Telegram's chat is already one-to-one, so the prompt stays where the
1618
+ // turn is. Slack, where it does not, is covered in
1619
+ // guardian-prompt-notice-privacy.test.ts.
1620
+ expect(deliverPromptSpy.mock.calls[0]?.[1]).toBe("chat-123");
1617
1621
 
1618
1622
  deliverPromptSpy.mockRestore();
1619
1623
  });
@@ -250,7 +250,7 @@ describe("channel inbound disk pressure gate", () => {
250
250
 
251
251
  expect(body).toMatchObject({
252
252
  accepted: true,
253
- duplicate: false,
253
+ reaction: "dropped_disk_pressure",
254
254
  diskPressure: "blocked",
255
255
  reason: "trusted-contact",
256
256
  });
@@ -268,9 +268,9 @@ describe("channel inbound disk pressure gate", () => {
268
268
  eq(channelInboundEvents.externalMessageId, "slack-reaction-blocked"),
269
269
  )
270
270
  .get();
271
- expect(event?.processingStatus).toBe("processed");
272
- expect(event?.messageId).toBeNull();
273
- expect(event?.rawPayload).toBeNull();
271
+ // A blocked reaction writes nothing at all: it is dropped before the
272
+ // reacted message is resolved, so no event row is opened either.
273
+ expect(event).toBeUndefined();
274
274
  expect(db.select().from(messages).all()).toHaveLength(0);
275
275
  });
276
276
 
@@ -414,8 +414,7 @@ describe("channel inbound disk pressure gate", () => {
414
414
  chatId: "slack-channel-1",
415
415
  text: expectedRemoteBlockReply,
416
416
  assistantId: "self",
417
- ephemeral: true,
418
- user: "slack-user-1",
417
+ audience: { kind: "oneReader", userId: "slack-user-1" },
419
418
  },
420
419
  ],
421
420
  ]);
@@ -58,6 +58,19 @@ const renderedHistoryContentQueue: RenderedHistoryStub[] = [];
58
58
 
59
59
  let deliveryFailAtIndex = -1;
60
60
 
61
+ const editCalls: { callbackUrl: string; target: Record<string, unknown> }[] =
62
+ [];
63
+
64
+ mock.module("../messaging/providers/index.js", () => ({
65
+ editChannelMessage: async (
66
+ callbackUrl: string,
67
+ target: Record<string, unknown>,
68
+ ) => {
69
+ editCalls.push({ callbackUrl, target });
70
+ return { ok: true };
71
+ },
72
+ }));
73
+
61
74
  mock.module("../runtime/gateway-client.js", () => ({
62
75
  deliverChannelReply: async (
63
76
  callbackUrl: string,
@@ -782,31 +795,32 @@ describe("channel-reply-delivery", () => {
782
795
  chatId: "chat-live",
783
796
  attachments,
784
797
  assistantId: undefined,
785
- ephemeral: undefined,
786
- user: undefined,
787
- messageTs: "1700000000.000055",
798
+ audience: undefined,
788
799
  });
800
+ // Attachments post as new messages, so nothing is edited on this path.
801
+ expect(editCalls).toHaveLength(0);
789
802
  expect(seenTs).toEqual(["1700000000.000055"]);
790
803
  });
791
804
 
792
- it("passes ephemeral and user through to each delivery call", async () => {
805
+ it("carries the audience through to every delivery call", async () => {
806
+ const audience = { kind: "oneReader", userId: "U456" } as const;
793
807
  await deliverRenderedReplyViaCallback({
794
808
  callbackUrl: "http://gateway/deliver/slack",
795
809
  chatId: "C123",
796
810
  textSegments: ["Part 1.", "Part 2."],
797
811
  interSegmentDelayMs: 0,
798
- ephemeral: true,
799
- user: "U456",
812
+ audience,
800
813
  });
801
814
 
802
815
  expect(deliveryCalls).toHaveLength(2);
803
- expect(deliveryCalls[0].payload.ephemeral).toBe(true);
804
- expect(deliveryCalls[0].payload.user).toBe("U456");
805
- expect(deliveryCalls[1].payload.ephemeral).toBe(true);
806
- expect(deliveryCalls[1].payload.user).toBe("U456");
816
+ // Every segment, not just the first: a reply restricted to one reader
817
+ // that loses the restriction partway becomes a public one.
818
+ for (const call of deliveryCalls) {
819
+ expect(call.payload.audience).toEqual(audience);
820
+ }
807
821
  });
808
822
 
809
- it("does not include ephemeral fields when not set", async () => {
823
+ it("leaves the audience unset when the reply is for the room", async () => {
810
824
  await deliverRenderedReplyViaCallback({
811
825
  callbackUrl: "http://gateway/deliver/slack",
812
826
  chatId: "C123",
@@ -815,8 +829,7 @@ describe("channel-reply-delivery", () => {
815
829
  });
816
830
 
817
831
  expect(deliveryCalls).toHaveLength(1);
818
- expect(deliveryCalls[0].payload.ephemeral).toBeUndefined();
819
- expect(deliveryCalls[0].payload.user).toBeUndefined();
832
+ expect(deliveryCalls[0].payload.audience).toBeUndefined();
820
833
  });
821
834
 
822
835
  it("suppresses delivery when the only text segment is <no_response/>", async () => {
@@ -39,6 +39,7 @@ mock.module("../util/logger.js", () => ({
39
39
 
40
40
  import {
41
41
  installIpcMock,
42
+ lastIpcParams,
42
43
  mockIpcResponse,
43
44
  } from "./helpers/gateway-classify-mock.js";
44
45
  installIpcMock();
@@ -53,7 +54,8 @@ function mockRisk(
53
54
  mockIpcResponse("classify_risk", {
54
55
  risk,
55
56
  reason: "test fixture",
56
- matchType: "shell",
57
+ matchType: "registry",
58
+ scopeOptions: [],
57
59
  ...extras,
58
60
  });
59
61
  }
@@ -82,8 +84,6 @@ import * as envRegistry from "../config/env-registry.js";
82
84
  import {
83
85
  check,
84
86
  classifyRisk,
85
- clearRiskCache,
86
- generateAllowlistOptions,
87
87
  generateScopeOptions,
88
88
  SCOPE_AWARE_TOOLS,
89
89
  } from "../permissions/checker.js";
@@ -180,8 +180,6 @@ describe("Permission Checker", () => {
180
180
  mockIpcResponse("get_global_thresholds", DEFAULT_GATEWAY_THRESHOLDS);
181
181
  // Clear the gateway threshold cache so each test gets a fresh threshold read
182
182
  _clearGlobalCacheForTesting();
183
- // Reset trust-store state and risk classification cache between tests
184
- clearRiskCache();
185
183
  // Reset guardian persona mock so each test opts in explicitly
186
184
  mockGuardianPersonaPath = null;
187
185
  loggerWarnCalls.length = 0;
@@ -447,268 +445,6 @@ describe("Permission Checker", () => {
447
445
  });
448
446
  });
449
447
 
450
- // ── generateAllowlistOptions ───────────────────────────────────
451
-
452
- describe("generateAllowlistOptions", () => {
453
- test("file_write: generates prefixed file, ancestor directory wildcards, and tool wildcard", async () => {
454
- const options = await generateAllowlistOptions("file_write", {
455
- path: "/home/user/project/file.ts",
456
- });
457
- expect(options).toHaveLength(5);
458
- // Patterns are prefixed with tool name to match check()'s "tool:path" format
459
- expect(options[0].pattern).toBe("file_write:/home/user/project/file.ts");
460
- expect(options[1].pattern).toBe("file_write:/home/user/project/**");
461
- expect(options[2].pattern).toBe("file_write:/home/user/**");
462
- expect(options[3].pattern).toBe("file_write:/home/**");
463
- expect(options[4].pattern).toBe("file_write:*");
464
- // Labels stay user-friendly
465
- expect(options[0].label).toBe("/home/user/project/file.ts");
466
- expect(options[1].label).toBe("/home/user/project/**");
467
- });
468
-
469
- test("file_read: generates prefixed file, directory, and tool wildcard", async () => {
470
- const options = await generateAllowlistOptions("file_read", {
471
- path: "/tmp/data.json",
472
- });
473
- expect(options).toHaveLength(3);
474
- expect(options[0].pattern).toBe("file_read:/tmp/data.json");
475
- expect(options[1].pattern).toBe("file_read:/tmp/**");
476
- expect(options[2].pattern).toBe("file_read:*");
477
- });
478
-
479
- test("host_file_read: generates prefixed file, directory, and tool wildcard", async () => {
480
- const options = await generateAllowlistOptions("host_file_read", {
481
- path: "/etc/hosts",
482
- });
483
- expect(options).toHaveLength(3);
484
- expect(options[0].pattern).toBe("host_file_read:/etc/hosts");
485
- expect(options[1].pattern).toBe("host_file_read:/etc/**");
486
- expect(options[2].pattern).toBe("host_file_read:*");
487
- });
488
-
489
- test("host_file_write with file_path key", async () => {
490
- const options = await generateAllowlistOptions("host_file_write", {
491
- file_path: "/tmp/out.txt",
492
- });
493
- expect(options[0].pattern).toBe("host_file_write:/tmp/out.txt");
494
- expect(options[1].pattern).toBe("host_file_write:/tmp/**");
495
- expect(options[2].pattern).toBe("host_file_write:*");
496
- });
497
-
498
- test("file_write with file_path key", async () => {
499
- const options = await generateAllowlistOptions("file_write", {
500
- file_path: "/tmp/out.txt",
501
- });
502
- expect(options[0].pattern).toBe("file_write:/tmp/out.txt");
503
- });
504
-
505
- test("unknown tool returns wildcard", async () => {
506
- const options = await generateAllowlistOptions("other_tool", {
507
- foo: "bar",
508
- });
509
- expect(options).toHaveLength(1);
510
- expect(options[0].pattern).toBe("*");
511
- });
512
-
513
- test("web_fetch: generates exact url, origin wildcard, and tool wildcard", async () => {
514
- const options = await generateAllowlistOptions("web_fetch", {
515
- url: "https://example.com/docs/page",
516
- });
517
- expect(options).toHaveLength(3);
518
- expect(options[0].pattern).toBe(
519
- "web_fetch:https://example.com/docs/page",
520
- );
521
- expect(options[1].pattern).toBe("web_fetch:https://example.com/*");
522
- expect(options[2].pattern).toBe("**");
523
- });
524
-
525
- test("web_fetch: strips fragments when generating allowlist options", async () => {
526
- const options = await generateAllowlistOptions("web_fetch", {
527
- url: "https://example.com/docs/page#section-1",
528
- });
529
- expect(options).toHaveLength(3);
530
- expect(options[0].pattern).toBe(
531
- "web_fetch:https://example.com/docs/page",
532
- );
533
- expect(options[1].pattern).toBe("web_fetch:https://example.com/*");
534
- expect(options[2].pattern).toBe("**");
535
- });
536
-
537
- test("web_fetch: strips trailing-dot hostnames when generating allowlist options", async () => {
538
- const options = await generateAllowlistOptions("web_fetch", {
539
- url: "https://example.com./docs/page",
540
- });
541
- expect(options).toHaveLength(3);
542
- expect(options[0].pattern).toBe(
543
- "web_fetch:https://example.com/docs/page",
544
- );
545
- expect(options[1].pattern).toBe("web_fetch:https://example.com/*");
546
- expect(options[2].pattern).toBe("**");
547
- });
548
-
549
- test("web_fetch: strips userinfo when generating allowlist options", async () => {
550
- const username = "demo";
551
- const credential = ["c", "r", "e", "d", "1", "2", "3"].join("");
552
- const credentialedUrl = new URL("https://example.com/docs/page");
553
- credentialedUrl.username = username;
554
- credentialedUrl.password = credential;
555
- const options = await generateAllowlistOptions("web_fetch", {
556
- url: credentialedUrl.href,
557
- });
558
- expect(options).toHaveLength(3);
559
- expect(options[0].pattern).toBe(
560
- "web_fetch:https://example.com/docs/page",
561
- );
562
- expect(options[1].pattern).toBe("web_fetch:https://example.com/*");
563
- expect(options[2].pattern).toBe("**");
564
- expect(options[0].pattern).not.toContain("demo:cred123@");
565
- });
566
-
567
- test("web_fetch: normalizes scheme-less host:port for allowlist options", async () => {
568
- const options = await generateAllowlistOptions("web_fetch", {
569
- url: "example.com:8443/docs/page",
570
- });
571
- expect(options).toHaveLength(3);
572
- expect(options[0].pattern).toBe(
573
- "web_fetch:https://example.com:8443/docs/page",
574
- );
575
- expect(options[1].pattern).toBe("web_fetch:https://example.com:8443/*");
576
- expect(options[2].pattern).toBe("**");
577
- });
578
-
579
- test("web_fetch: does not coerce path-only urls to https hostnames in allowlist options", async () => {
580
- const options = await generateAllowlistOptions("web_fetch", {
581
- url: "/docs/getting-started",
582
- });
583
- expect(options).toHaveLength(2);
584
- expect(options[0].pattern).toBe("web_fetch:/docs/getting-started");
585
- expect(options[1].pattern).toBe("**");
586
- });
587
-
588
- test("scaffold_managed_skill: generates per-skill and wildcard options", async () => {
589
- const options = await generateAllowlistOptions("scaffold_managed_skill", {
590
- skill_id: "my-tool",
591
- });
592
- expect(options).toHaveLength(2);
593
- expect(options[0].label).toBe("my-tool");
594
- expect(options[0].pattern).toBe("scaffold_managed_skill:my-tool");
595
- expect(options[0].description).toBe("This skill only");
596
- expect(options[1].label).toBe("scaffold_managed_skill:*");
597
- expect(options[1].pattern).toBe("scaffold_managed_skill:*");
598
- expect(options[1].description).toBe("All managed skill scaffolds");
599
- });
600
-
601
- test("delete_managed_skill: generates per-skill and wildcard options", async () => {
602
- const options = await generateAllowlistOptions("delete_managed_skill", {
603
- skill_id: "doomed",
604
- });
605
- expect(options).toHaveLength(2);
606
- expect(options[0].pattern).toBe("delete_managed_skill:doomed");
607
- expect(options[1].pattern).toBe("delete_managed_skill:*");
608
- expect(options[1].description).toBe("All managed skill deletes");
609
- });
610
-
611
- test("scaffold_managed_skill with empty skill_id: only wildcard option", async () => {
612
- const options = await generateAllowlistOptions("scaffold_managed_skill", {
613
- skill_id: "",
614
- });
615
- expect(options).toHaveLength(1);
616
- expect(options[0].pattern).toBe("scaffold_managed_skill:*");
617
- });
618
-
619
- test("web_fetch: escapes minimatch metacharacters in generated exact and origin patterns", async () => {
620
- const options = await generateAllowlistOptions("web_fetch", {
621
- url: "https://[2001:db8::1]/search?q=test",
622
- });
623
- expect(options).toHaveLength(3);
624
- expect(options[0].label).toBe("https://[2001:db8::1]/search?q=test");
625
- expect(options[0].pattern).toBe(
626
- "web_fetch:https://\\[2001:db8::1\\]/search\\?q=test",
627
- );
628
- expect(options[1].pattern).toBe("web_fetch:https://\\[2001:db8::1\\]/*");
629
- expect(options[2].pattern).toBe("**");
630
- });
631
-
632
- // ── network_request allowlist options ─────────────────────────
633
-
634
- test("network_request: generates exact url, origin wildcard, and tool wildcard", async () => {
635
- const options = await generateAllowlistOptions("network_request", {
636
- url: "https://api.example.com/v1/data",
637
- });
638
- expect(options).toHaveLength(3);
639
- expect(options[0].pattern).toBe(
640
- "network_request:https://api.example.com/v1/data",
641
- );
642
- expect(options[1].pattern).toBe(
643
- "network_request:https://api.example.com/*",
644
- );
645
- expect(options[2].pattern).toBe("**");
646
- expect(options[2].label).toBe("network_request:*");
647
- expect(options[2].description).toBe("All network requests");
648
- });
649
-
650
- test("network_request: origin wildcard uses friendly hostname", async () => {
651
- const options = await generateAllowlistOptions("network_request", {
652
- url: "https://www.example.com/path",
653
- });
654
- expect(options[1].description).toBe("Any page on example.com");
655
- });
656
-
657
- test("network_request: normalizes scheme-less host:port input", async () => {
658
- const options = await generateAllowlistOptions("network_request", {
659
- url: "api.example.com:8443/v1/data",
660
- });
661
- expect(options).toHaveLength(3);
662
- expect(options[0].pattern).toBe(
663
- "network_request:https://api.example.com:8443/v1/data",
664
- );
665
- expect(options[1].pattern).toBe(
666
- "network_request:https://api.example.com:8443/*",
667
- );
668
- expect(options[2].pattern).toBe("**");
669
- });
670
-
671
- test("network_request: strips fragments and userinfo", async () => {
672
- const username = "demo";
673
- const credential = ["c", "r", "e", "d", "1", "2", "3"].join("");
674
- const credentialedUrl = new URL(
675
- "https://api.example.com/v1/data#section",
676
- );
677
- credentialedUrl.username = username;
678
- credentialedUrl.password = credential;
679
- const options = await generateAllowlistOptions("network_request", {
680
- url: credentialedUrl.href,
681
- });
682
- expect(options).toHaveLength(3);
683
- expect(options[0].pattern).toBe(
684
- "network_request:https://api.example.com/v1/data",
685
- );
686
- expect(options[0].pattern).not.toContain("demo:cred123@");
687
- expect(options[0].pattern).not.toContain("#section");
688
- });
689
-
690
- test("network_request: escapes minimatch metacharacters", async () => {
691
- const options = await generateAllowlistOptions("network_request", {
692
- url: "https://[2001:db8::1]/api?key=val",
693
- });
694
- expect(options).toHaveLength(3);
695
- expect(options[0].pattern).toBe(
696
- "network_request:https://\\[2001:db8::1\\]/api\\?key=val",
697
- );
698
- expect(options[1].pattern).toBe(
699
- "network_request:https://\\[2001:db8::1\\]/*",
700
- );
701
- });
702
-
703
- test("network_request: empty url produces only tool wildcard", async () => {
704
- const options = await generateAllowlistOptions("network_request", {
705
- url: "",
706
- });
707
- expect(options).toHaveLength(1);
708
- expect(options[0].pattern).toBe("**");
709
- });
710
- });
711
-
712
448
  // ── generateScopeOptions ───────────────────────────────────────
713
449
 
714
450
  describe("generateScopeOptions", () => {
@@ -1287,65 +1023,39 @@ describe("Permission Checker", () => {
1287
1023
  // pattern (skillId@hash) and an any-version pattern (bare skillId).
1288
1024
  // Input-supplied version_hash is always ignored to prevent spoofing.
1289
1025
 
1290
- describe("hash-aware skill_load permission candidates (PR 33)", () => {
1026
+ describe("skill metadata sent to the gateway", () => {
1291
1027
  function ensureSkillsDir(): void {
1292
1028
  mkdirSync(join(checkerTestDir, "skills"), { recursive: true });
1293
1029
  }
1294
1030
 
1295
- test("allowlist options only include version-specific option when hash is available", async () => {
1296
- ensureSkillsDir();
1297
- writeSkill("test-opts-skill", "Test Options Skill");
1298
-
1299
- const options = await generateAllowlistOptions("skill_load", {
1300
- skill: "test-opts-skill",
1301
- });
1302
-
1303
- // Should have only the version-specific option
1304
- expect(options).toHaveLength(1);
1305
- expect(options[0].pattern).toMatch(/^skill_load:test-opts-skill@v1:/);
1306
- expect(options[0].description).toBe("This exact version");
1307
- });
1308
-
1309
- test("allowlist options ignore input version_hash and use disk-computed hash (regression)", async () => {
1031
+ test("the version hash comes from disk, never from the model's input", async () => {
1032
+ // The gateway builds the version-pinned trust-rule option from the hash
1033
+ // the daemon sends; taking it from the invocation would let a caller pin
1034
+ // a rule to a version that was never on disk.
1310
1035
  ensureSkillsDir();
1311
1036
  writeSkill("test-opts-explicit", "Test Opts Explicit");
1312
1037
 
1313
- // Even when a version_hash is supplied in the input, allowlist
1314
- // options must use the disk-computed hash, not the input value.
1315
- const options = await generateAllowlistOptions("skill_load", {
1038
+ await classifyRisk("skill_load", {
1316
1039
  skill: "test-opts-explicit",
1317
1040
  version_hash: "v1:customhash123",
1318
1041
  });
1319
1042
 
1320
- expect(options).toHaveLength(1);
1321
- // Should be the disk-computed hash, NOT the input hash
1322
- expect(options[0].pattern).toMatch(/^skill_load:test-opts-explicit@v1:/);
1323
- expect(options[0].pattern).not.toBe(
1324
- "skill_load:test-opts-explicit@v1:customhash123",
1325
- );
1326
- expect(options[0].description).toBe("This exact version");
1043
+ const metadata = lastIpcParams("classify_risk")?.skillMetadata as
1044
+ | { skillId: string; versionHash: string }
1045
+ | undefined;
1046
+ expect(metadata?.skillId).toBe("test-opts-explicit");
1047
+ expect(metadata?.versionHash).toMatch(/^v1:/);
1048
+ expect(metadata?.versionHash).not.toBe("v1:customhash123");
1327
1049
  });
1328
1050
 
1329
- test("allowlist options for unresolvable skill fall back to raw selector", async () => {
1051
+ test("an unresolvable selector is sent with no metadata", async () => {
1330
1052
  ensureSkillsDir();
1331
1053
 
1332
- const options = await generateAllowlistOptions("skill_load", {
1333
- skill: "no-such-skill",
1334
- });
1335
-
1336
- // Should have only the raw selector
1337
- expect(options).toHaveLength(1);
1338
- expect(options[0].pattern).toBe("skill_load:no-such-skill");
1339
- expect(options[0].description).toBe("This skill");
1340
- });
1341
-
1342
- test("allowlist options for empty skill selector only has wildcard", async () => {
1343
- const options = await generateAllowlistOptions("skill_load", {
1344
- skill: "",
1345
- });
1054
+ await classifyRisk("skill_load", { skill: "no-such-skill" });
1346
1055
 
1347
- expect(options).toHaveLength(1);
1348
- expect(options[0].pattern).toBe("skill_load:*");
1056
+ const params = lastIpcParams("classify_risk");
1057
+ expect(params?.skill).toBe("no-such-skill");
1058
+ expect(params?.skillMetadata).toBeUndefined();
1349
1059
  });
1350
1060
  });
1351
1061
 
@@ -1514,7 +1224,6 @@ describe("bash network_mode=proxied — risk capped at medium", () => {
1514
1224
  mockRisk("low");
1515
1225
  mockIpcResponse("get_global_thresholds", DEFAULT_GATEWAY_THRESHOLDS);
1516
1226
  _clearGlobalCacheForTesting();
1517
- clearRiskCache();
1518
1227
  });
1519
1228
 
1520
1229
  test("proxied bash follows risk-based policy (medium risk → prompt outside container)", async () => {
@@ -1562,7 +1271,6 @@ describe("credentialed proxied bash — high risk escalation", () => {
1562
1271
  mockRisk("low");
1563
1272
  mockIpcResponse("get_global_thresholds", DEFAULT_GATEWAY_THRESHOLDS);
1564
1273
  _clearGlobalCacheForTesting();
1565
- clearRiskCache();
1566
1274
  });
1567
1275
 
1568
1276
  test("proxied bash with credential_ids sends credentialRefCount in IPC params", async () => {
@@ -1652,7 +1360,6 @@ describe("workspace mode — auto-allow workspace-scoped operations", () => {
1652
1360
  mockRisk("low");
1653
1361
  mockIpcResponse("get_global_thresholds", DEFAULT_GATEWAY_THRESHOLDS);
1654
1362
  _clearGlobalCacheForTesting();
1655
- clearRiskCache();
1656
1363
  try {
1657
1364
  rmSync(join(checkerTestDir, "protected", "trust.json"));
1658
1365
  } catch {
@@ -1796,7 +1503,6 @@ describe("integration regressions (PR 11)", () => {
1796
1503
  mockRisk("low");
1797
1504
  mockIpcResponse("get_global_thresholds", DEFAULT_GATEWAY_THRESHOLDS);
1798
1505
  _clearGlobalCacheForTesting();
1799
- clearRiskCache();
1800
1506
  // Delete the trust file to prevent stale default rules from prior tests
1801
1507
  try {
1802
1508
  rmSync(join(checkerTestDir, "protected", "trust.json"));
@@ -45,6 +45,10 @@ mock.module("../plugins/defaults/memory/substrate/skill-store.js", () => ({
45
45
  seedV2SkillEntries: async () => {
46
46
  seedCallCount += 1;
47
47
  },
48
+ // The validate route builds the page index, which reads the seeded
49
+ // skill catalog; an empty catalog keeps that walk inert here.
50
+ listSkillEntries: () => [],
51
+ SKILL_SLUG_PREFIX: "skills/",
48
52
  }));
49
53
 
50
54
  // ---------------------------------------------------------------------------
@@ -254,7 +258,7 @@ describe("all memory v2 routes — MEMORY_V2_DISABLED gate", () => {
254
258
  const result = (await route!.handler({ body: {} })) as {
255
259
  pageCount: number;
256
260
  edgeCount: number;
257
- missingEdgeEndpoints: unknown[];
261
+ danglingLinks: unknown[];
258
262
  oversizedPages: unknown[];
259
263
  parseFailures: unknown[];
260
264
  };
@@ -263,7 +267,7 @@ describe("all memory v2 routes — MEMORY_V2_DISABLED gate", () => {
263
267
  // returned at all (no MEMORY_V2_DISABLED throw).
264
268
  expect(result.pageCount).toBe(0);
265
269
  expect(result.edgeCount).toBe(0);
266
- expect(result.missingEdgeEndpoints).toEqual([]);
270
+ expect(result.danglingLinks).toEqual([]);
267
271
  expect(result.oversizedPages).toEqual([]);
268
272
  expect(result.parseFailures).toEqual([]);
269
273
  });