@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
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Credential write path. The single sequence every "store a credential"
3
+ * caller runs.
4
+ *
5
+ * Storing a credential is not one write: the plaintext goes to the secure
6
+ * backend, the value is scrubbed from recent transcripts, the metadata record
7
+ * is upserted, and manual-token providers reconcile their connection row.
8
+ * Order matters (see {@link storeCredentialValue}), so the sequence lives here
9
+ * rather than being re-typed per entry point. The `credentials/set` route and
10
+ * the plugin-facing {@link ../../plugin-api/store-credential.storeCredential}
11
+ * both compose it.
12
+ *
13
+ * The transcript scrub and the connection sync are imported lazily, at the
14
+ * point of use. Both pull heavy graphs behind them (the scrub reaches the
15
+ * conversation database and registry; the sync reaches the OAuth store), and
16
+ * this module sits under `@vellumai/plugin-api`, so a static edge would drag
17
+ * that machinery into every plugin-api consumer for a write most of them never
18
+ * perform.
19
+ *
20
+ * Callers own their own transport-level argument validation and error mapping;
21
+ * this module throws {@link InvalidCredentialInputError} for a value it will
22
+ * not store and {@link CredentialStorageError} when the secure backend write
23
+ * fails.
24
+ */
25
+
26
+ import {
27
+ ACP_SERVICE,
28
+ assertAcpCredentialFormat,
29
+ } from "../../acp/acp-credentials.js";
30
+ import { credentialKey } from "../../security/credential-key.js";
31
+ import { normalizeSecretValue } from "../../security/secret-normalize.js";
32
+ import {
33
+ getActiveBackendName,
34
+ setSecureKeyAsync,
35
+ } from "../../security/secure-keys.js";
36
+ import { getLogger } from "../../util/logger.js";
37
+ import {
38
+ assertMetadataWritable,
39
+ upsertCredentialMetadata,
40
+ } from "./metadata-store.js";
41
+ import type { CredentialInjectionTemplate } from "./policy-types.js";
42
+
43
+ const log = getLogger("credential-store");
44
+
45
+ /** Raised for a value the store refuses before writing anything. */
46
+ export class InvalidCredentialInputError extends Error {
47
+ constructor(message: string) {
48
+ super(message);
49
+ this.name = "InvalidCredentialInputError";
50
+ }
51
+ }
52
+
53
+ /** Raised when the secure backend write itself fails. */
54
+ export class CredentialStorageError extends Error {
55
+ constructor(message: string) {
56
+ super(message);
57
+ this.name = "CredentialStorageError";
58
+ }
59
+ }
60
+
61
+ export interface StoreCredentialValueInput {
62
+ service: string;
63
+ field: string;
64
+ /** Plaintext value. Edge whitespace is trimmed before storage. */
65
+ value: string;
66
+ /** Human-friendly alias shown in credential listings. */
67
+ alias?: string;
68
+ /** What the credential is for, surfaced to the agent. */
69
+ usageDescription?: string;
70
+ allowedTools?: string[];
71
+ allowedDomains?: string[];
72
+ injectionTemplates?: CredentialInjectionTemplate[];
73
+ /**
74
+ * Skip the retroactive transcript scrub. Set it only when the plaintext
75
+ * provably never transited a conversation (a token minted by an OAuth
76
+ * refresh, say), since the scrub is a bounded but real sweep of recent
77
+ * history.
78
+ */
79
+ skipTranscriptScrub?: boolean;
80
+ }
81
+
82
+ export interface StoredCredential {
83
+ credentialId: string;
84
+ service: string;
85
+ field: string;
86
+ }
87
+
88
+ /**
89
+ * Store a credential's plaintext and reconcile everything that hangs off it.
90
+ *
91
+ * The order is load-bearing:
92
+ * 1. Validate the value (and the ACP format guard) so a rejected write leaves
93
+ * no side effects.
94
+ * 2. Fail early when credential metadata is not writable.
95
+ * 3. Write the plaintext to the secure backend.
96
+ * 4. Scrub the value from recent transcripts. This runs BEFORE the metadata
97
+ * upsert and the connection sync, because those can throw and a
98
+ * stored-but-unscrubbed secret must not depend on them succeeding. The
99
+ * credential IS stored at this point, so the scrub is best-effort hygiene
100
+ * and stays invisible to the caller.
101
+ * 5. Upsert metadata and reconcile the manual-token connection row.
102
+ *
103
+ * @throws {InvalidCredentialInputError} when the value is empty or malformed
104
+ * for its service.
105
+ * @throws {CredentialStorageError} when the secure backend rejects the write.
106
+ */
107
+ export async function storeCredentialValue(
108
+ input: StoreCredentialValueInput,
109
+ ): Promise<StoredCredential> {
110
+ const { service, field } = input;
111
+
112
+ const value = normalizeSecretValue(input.value);
113
+ if (value.length === 0) {
114
+ throw new InvalidCredentialInputError("value is required");
115
+ }
116
+
117
+ // Reject an Anthropic API key pasted into the ACP OAuth-token field (a 401
118
+ // footgun) before it reaches storage.
119
+ if (service === ACP_SERVICE) {
120
+ assertAcpCredentialFormat(field, value);
121
+ }
122
+
123
+ assertMetadataWritable();
124
+
125
+ const stored = await setSecureKeyAsync(credentialKey(service, field), value);
126
+ if (!stored) {
127
+ throw new CredentialStorageError(
128
+ `Failed to store credential in secure storage (backend: ${getActiveBackendName()})`,
129
+ );
130
+ }
131
+
132
+ // The stored plaintext may already sit in recent transcripts: the user
133
+ // message that pasted it, the persisted tool_use input, the tool result
134
+ // echoing the command. This is the scrub seam, not setSecureKeyAsync, which
135
+ // also fires on OAuth refresh rotations and MCP header writes whose values
136
+ // never transited a transcript.
137
+ if (!input.skipTranscriptScrub) {
138
+ const { isNonSecretPlatformField, scrubStoredCredentialFromTranscripts } =
139
+ await import("../../daemon/credential-transcript-scrub.js");
140
+ if (!isNonSecretPlatformField(service, field)) {
141
+ try {
142
+ const scrubbed = await scrubStoredCredentialFromTranscripts(value);
143
+ log.info(
144
+ { service, field, ...scrubbed },
145
+ "Credential stored; scrubbed value from recent transcripts",
146
+ );
147
+ } catch (err) {
148
+ log.warn(
149
+ { err, service, field },
150
+ "Credential stored, but transcript scrub failed",
151
+ );
152
+ }
153
+ }
154
+ }
155
+
156
+ const metadata = upsertCredentialMetadata(service, field, {
157
+ alias: input.alias,
158
+ usageDescription: input.usageDescription,
159
+ allowedTools: input.allowedTools,
160
+ allowedDomains: input.allowedDomains,
161
+ injectionTemplates: input.injectionTemplates,
162
+ });
163
+ const { syncManualTokenConnection } =
164
+ await import("../../oauth/manual-token-connection.js");
165
+ await syncManualTokenConnection(service);
166
+
167
+ return { credentialId: metadata.credentialId, service, field };
168
+ }
@@ -5,6 +5,8 @@
5
5
  * based on the credential's allowed tools list.
6
6
  */
7
7
 
8
+ import type { CredentialMetadata } from "./metadata-store.js";
9
+
8
10
  /**
9
11
  * Canonical capability key for browser-based credential fills.
10
12
  *
@@ -64,3 +66,68 @@ export function isToolAllowed(
64
66
  (allowed) => resolveCanonical(allowed) === canonical,
65
67
  );
66
68
  }
69
+
70
+ /**
71
+ * Remediation for a credential whose allowed_tools list is empty. Points at
72
+ * `credentials prompt` (not inline `credentials set`, which agent shells
73
+ * refuse): the secure prompt re-collects the value and sets allowed_tools.
74
+ */
75
+ const NO_TOOLS_ALLOWED_REMEDIATION =
76
+ "No tools are currently allowed - grant access via `assistant credentials prompt --service <service> --field <field> --label <label> --allowed-tools <tools>` (re-collects the value securely and sets allowed_tools).";
77
+
78
+ /** Denial reason for a tool that is not in a credential's allowed_tools list. */
79
+ export function toolNotAllowedReason(
80
+ toolName: string,
81
+ service: string,
82
+ field: string,
83
+ allowedTools: string[] | undefined,
84
+ ): string {
85
+ const tools = allowedTools ?? [];
86
+ return (
87
+ `Tool "${toolName}" is not allowed to use credential ${service}/${field}. ` +
88
+ (tools.length === 0
89
+ ? NO_TOOLS_ALLOWED_REMEDIATION
90
+ : `Allowed tools: ${tools.join(", ")}.`)
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Evaluate the policy that gates server-side use of a credential.
96
+ *
97
+ * Pure: it inspects only the metadata it is handed, performs no store reads,
98
+ * and has no side effects. Returns the denial reason, or `undefined` when the
99
+ * tool may read the credential server-side.
100
+ *
101
+ * Domain-restricted credentials are scoped to browser fills on those domains,
102
+ * so they are refused here even when the tool itself is allowed.
103
+ */
104
+ export function serverUseDenialReason(
105
+ metadata: CredentialMetadata | undefined,
106
+ toolName: string,
107
+ service: string,
108
+ field: string,
109
+ ): string | undefined {
110
+ if (!metadata) {
111
+ return `No credential found for ${service}/${field}`;
112
+ }
113
+
114
+ if (!isToolAllowed(toolName, metadata.allowedTools)) {
115
+ return toolNotAllowedReason(
116
+ toolName,
117
+ service,
118
+ field,
119
+ metadata.allowedTools,
120
+ );
121
+ }
122
+
123
+ const serverDomains = metadata.allowedDomains ?? [];
124
+ if (serverDomains.length > 0) {
125
+ return (
126
+ `Credential ${service}/${field} has domain restrictions ` +
127
+ `(${serverDomains.join(", ")}) and cannot be used server-side. ` +
128
+ "Remove domain restrictions or use a separate credential without domain policy."
129
+ );
130
+ }
131
+
132
+ return undefined;
133
+ }
@@ -1,9 +1,21 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
 
3
+ import type {
4
+ RiskAllowlistOption,
5
+ RiskDirectoryScopeOption,
6
+ RiskPatternScopeOption,
7
+ } from "@vellumai/gateway-client";
8
+
3
9
  import { getConfig } from "../config/loader.js";
10
+ import {
11
+ classifyRisk,
12
+ type RiskClassificationWithMeta,
13
+ } from "../permissions/checker.js";
4
14
  import { PermissionPrompter } from "../permissions/prompter.js";
5
- import { RiskLevel } from "../permissions/types.js";
6
- import { runInPluginContext } from "../plugins/plugin-execution-context.js";
15
+ import {
16
+ runInPluginContext,
17
+ runOutsidePluginContext,
18
+ } from "../plugins/plugin-execution-context.js";
7
19
  import { TokenExpiredError } from "../security/token-manager.js";
8
20
  import {
9
21
  recordToolError,
@@ -30,6 +42,7 @@ import { MAX_FILE_SIZE_BYTES } from "./shared/filesystem/size-guard.js";
30
42
  import { ToolApprovalHandler } from "./tool-approval-handler.js";
31
43
  import { resolveToolInvocationAlias } from "./tool-name-aliases.js";
32
44
  import { recordToolCompletion } from "./tool-profiler.js";
45
+ import { RISK_LEVEL_UNCLASSIFIED } from "./tool-types.js";
33
46
  import { type ToolContext, type ToolExecutionResult } from "./types.js";
34
47
 
35
48
  export class ToolExecutor {
@@ -60,18 +73,34 @@ export class ToolExecutor {
60
73
  ): Promise<ToolExecutionResult> {
61
74
  const startTime = Date.now();
62
75
  let decision = "allow";
63
- let riskLevel: string = RiskLevel.Low;
76
+ // The invocation's one classification, taken before the gates so every
77
+ // audit row this call can produce (gate denial, gate error, grant-consumed
78
+ // execution, permission decision) records the risk of what the model asked
79
+ // for, then handed down to `checkPermission`. A classification that does
80
+ // not complete (aborted, gateway unreachable) is recorded as such rather
81
+ // than as a level; the permission check still requires one and fails
82
+ // closed on its own.
83
+ let classification: RiskClassificationWithMeta | undefined;
84
+ try {
85
+ classification = await classifyRisk(
86
+ name,
87
+ input,
88
+ context.workingDir,
89
+ undefined,
90
+ undefined,
91
+ context.signal,
92
+ );
93
+ } catch {
94
+ // Stays undefined; the audit row records RISK_LEVEL_UNCLASSIFIED.
95
+ }
96
+ let riskLevel: string = classification?.level ?? RISK_LEVEL_UNCLASSIFIED;
64
97
  let permRiskMeta:
65
98
  | {
66
99
  riskLevel: string;
67
100
  riskReason: string;
68
- riskScopeOptions: Array<{ pattern: string; label: string }>;
69
- riskAllowlistOptions?: Array<{
70
- label: string;
71
- description: string;
72
- pattern: string;
73
- }>;
74
- riskDirectoryScopeOptions?: Array<{ scope: string; label: string }>;
101
+ riskScopeOptions: RiskPatternScopeOption[];
102
+ riskAllowlistOptions?: RiskAllowlistOption[];
103
+ riskDirectoryScopeOptions?: RiskDirectoryScopeOption[];
75
104
  isContainerized?: boolean;
76
105
  }
77
106
  | undefined;
@@ -106,6 +135,11 @@ export class ToolExecutor {
106
135
  // consumed; substitute the parsed value (with `.catch()` recoveries
107
136
  // applied) so validation and execution see the same input.
108
137
  if (gateResult.parsedInput) {
138
+ // The permission decision is about the input that runs, so a parse that
139
+ // reshaped it (a `.catch()` recovery) gets its own classification.
140
+ if (!Bun.deepEquals(input, gateResult.parsedInput)) {
141
+ classification = undefined;
142
+ }
109
143
  input = gateResult.parsedInput;
110
144
  }
111
145
 
@@ -189,6 +223,7 @@ export class ToolExecutor {
189
223
  context,
190
224
  startTime,
191
225
  computePreviewDiff,
226
+ classification,
192
227
  );
193
228
 
194
229
  riskLevel = permResult.riskLevel;
@@ -235,15 +270,18 @@ export class ToolExecutor {
235
270
  const execContext = context;
236
271
 
237
272
  // Mark the owning plugin as in context (via AsyncLocalStorage) so host
238
- // APIs the tool reaches — e.g. resolveCredential — can scope to it. The
273
+ // APIs the tool reaches, e.g. resolveCredential, can scope to it. The
239
274
  // context must be established around the `execute()` call itself so the
240
- // returned promise carries the binding across its awaits. Non-plugin
241
- // tools (default/skill/mcp/workspace) establish no context.
275
+ // returned promise carries the binding across its awaits. A non-plugin
276
+ // tool (default/skill/mcp/workspace) runs with the context explicitly
277
+ // cleared rather than merely unset: the turn may have been started by a
278
+ // plugin (a route handler calling `runConversationTurn`), and this tool
279
+ // is host code that must not inherit that plugin's identity.
242
280
  const owner = getToolOwner(name);
243
281
  const execPromise =
244
282
  owner?.kind === "plugin"
245
283
  ? runInPluginContext(owner.id, () => tool.execute(input, execContext))
246
- : tool.execute(input, execContext);
284
+ : runOutsidePluginContext(() => tool.execute(input, execContext));
247
285
 
248
286
  let execResult: ToolExecutionResult = await executeWithTimeout(
249
287
  execPromise,
@@ -295,7 +333,7 @@ export class ToolExecutor {
295
333
  safeResult.isError,
296
334
  );
297
335
 
298
- // Merge risk metadata from the classifier assessment cache onto the
336
+ // Merge the classification's risk metadata onto the
299
337
  // tool result so downstream consumers (AgentEvent → handleToolResult →
300
338
  // ToolResult SSE message) can forward it to the client.
301
339
  if (permRiskMeta) {
@@ -797,7 +797,8 @@ describe("web_search tool", () => {
797
797
  {
798
798
  title: "Keenable Result 1",
799
799
  url: "https://example.com/keenable-1",
800
- description: "First Keenable result",
800
+ description: "",
801
+ snippet: "First Keenable result",
801
802
  },
802
803
  ],
803
804
  }),
@@ -814,6 +815,45 @@ describe("web_search tool", () => {
814
815
  expect(result.content).toContain("https://example.com/keenable-1");
815
816
  });
816
817
 
818
+ test("Keenable reads the page text from snippet, capped", async () => {
819
+ seedWebSearch("your-own", "keenable");
820
+ globalThis.fetch = (async () => {
821
+ return new Response(
822
+ JSON.stringify({
823
+ results: [
824
+ {
825
+ title: "Wrapped",
826
+ url: "https://example.com/wrapped",
827
+ description: "",
828
+ snippet: "line one\n\nline two",
829
+ },
830
+ {
831
+ title: "Long",
832
+ url: "https://example.com/long",
833
+ description: "",
834
+ snippet: "word ".repeat(400),
835
+ },
836
+ {
837
+ title: "Meta only",
838
+ url: "https://example.com/meta",
839
+ description: "only a meta description",
840
+ },
841
+ ],
842
+ }),
843
+ { status: 200, headers: { "content-type": "application/json" } },
844
+ );
845
+ }) as any;
846
+
847
+ const result = await execute({ query: "test" });
848
+ expect(result.content).toContain("line one line two");
849
+ expect(result.content).toContain("only a meta description");
850
+ const longLine = String(result.content)
851
+ .split("\n")
852
+ .find((line) => line.trim().startsWith("word"));
853
+ expect(longLine?.trim().length).toBeLessThanOrEqual(500);
854
+ expect(longLine?.trim().length).toBeGreaterThan(490);
855
+ });
856
+
817
857
  test("Keenable uses the authenticated endpoint when a key is set", async () => {
818
858
  seedWebSearch("your-own", "keenable");
819
859
  mockKeenableSecureKey = "keen_test";
@@ -1,23 +1,16 @@
1
1
  import { lookup } from "node:dns/promises";
2
2
 
3
- export type ResolveHostAddresses = (hostname: string) => Promise<string[]>;
3
+ import {
4
+ looksLikeHostPortShorthand,
5
+ looksLikePathOnlyInput,
6
+ } from "@vellumai/service-contracts/url-normalization";
4
7
 
5
- export function looksLikeHostPortShorthand(value: string): boolean {
6
- if (/^\[[0-9a-fA-F:.%]+\]:\d+(?:[/?#]|$)/.test(value)) {
7
- return true;
8
- }
9
- return /^[^/?#@\s:]+:\d+(?:[/?#]|$)/.test(value);
10
- }
8
+ export type ResolveHostAddresses = (hostname: string) => Promise<string[]>;
11
9
 
12
- export function looksLikePathOnlyInput(value: string): boolean {
13
- return (
14
- value.startsWith("/") ||
15
- value.startsWith("./") ||
16
- value.startsWith("../") ||
17
- value.startsWith("?") ||
18
- value.startsWith("#")
19
- );
20
- }
10
+ // The URL input-shape predicates are defined once, beside the canonicalizer
11
+ // the gateway builds trust-rule patterns with, so what counts as a `host:port`
12
+ // or a path here is what counts there. Re-exported for existing consumers.
13
+ export { looksLikeHostPortShorthand, looksLikePathOnlyInput };
21
14
 
22
15
  export function parseUrl(input: unknown): URL | null {
23
16
  if (typeof input !== "string") {
@@ -148,7 +148,10 @@ interface FirecrawlSearchResponse {
148
148
  interface KeenableSearchResult {
149
149
  title?: string;
150
150
  url?: string;
151
+ /** The page's meta description. Empty for most pages; see keenableSnippet. */
151
152
  description?: string;
153
+ /** The page text. This is what carries content on a Keenable result. */
154
+ snippet?: string;
152
155
  published_at?: string;
153
156
  acquired_at?: string;
154
157
  }
@@ -488,6 +491,20 @@ function firecrawlTbsForFreshness(
488
491
  }
489
492
  }
490
493
 
494
+ /** Characters of page text kept per result. Keenable returns the whole page on
495
+ * every search result, an order of magnitude more than the other providers here
496
+ * return, so it is capped to keep one search from filling the context. */
497
+ const KEENABLE_SNIPPET_MAX_LENGTH = 500;
498
+
499
+ /** Keenable sends both `snippet` and `description` on every result: `snippet`
500
+ * carries the page text and `description` is the page's meta description, which
501
+ * is empty for most pages. `||` rather than `??`, since the empty description is
502
+ * present rather than nullish. */
503
+ function keenableSnippet(result: KeenableSearchResult): string {
504
+ const text = result.snippet || result.description || "";
505
+ return text.replace(/\s+/g, " ").trim().slice(0, KEENABLE_SNIPPET_MAX_LENGTH);
506
+ }
507
+
491
508
  function formatKeenableResults(
492
509
  data: KeenableSearchResponse,
493
510
  query: string,
@@ -505,8 +522,9 @@ function formatKeenableResults(
505
522
  if (r.url) {
506
523
  lines.push(` URL: ${r.url}`);
507
524
  }
508
- if (r.description) {
509
- lines.push(` ${r.description}`);
525
+ const snippet = keenableSnippet(r);
526
+ if (snippet) {
527
+ lines.push(` ${snippet}`);
510
528
  }
511
529
  if (r.published_at) {
512
530
  lines.push(` Published: ${r.published_at}`);
@@ -531,7 +549,7 @@ function buildKeenableMetadata(
531
549
  url,
532
550
  domain,
533
551
  faviconUrl: faviconUrlForDomain(domain),
534
- snippet: r.description,
552
+ snippet: keenableSnippet(r),
535
553
  };
536
554
  });
537
555
  return {