@vellumai/assistant 0.11.4 → 0.11.5-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/AGENTS.md +2 -2
  2. package/ARCHITECTURE.md +29 -4
  3. package/README.md +1 -1
  4. package/docs/architecture/memory.md +17 -5
  5. package/docs/architecture/security.md +96 -152
  6. package/docs/guardian-request-flow.md +13 -2
  7. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  11. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  15. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  16. package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
  17. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  18. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +29 -31
  19. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  20. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  21. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  22. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/openapi.yaml +193 -3
  24. package/package.json +1 -1
  25. package/src/__tests__/anthropic-provider.test.ts +10 -3
  26. package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
  27. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  28. package/src/__tests__/channel-approval-routes.test.ts +4 -0
  29. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
  30. package/src/__tests__/channel-reply-delivery.test.ts +26 -13
  31. package/src/__tests__/checker.test.ts +20 -314
  32. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  33. package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
  34. package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
  35. package/src/__tests__/config-loader-backfill.test.ts +19 -10
  36. package/src/__tests__/container-cpu-sampler.test.ts +153 -0
  37. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  38. package/src/__tests__/conversation-attachments.test.ts +0 -1
  39. package/src/__tests__/conversation-error.test.ts +17 -0
  40. package/src/__tests__/conversation-lifecycle.test.ts +20 -26
  41. package/src/__tests__/conversation-pairing.test.ts +186 -0
  42. package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
  43. package/src/__tests__/credential-broker-server-use.test.ts +24 -5
  44. package/src/__tests__/credential-routes.test.ts +22 -3
  45. package/src/__tests__/credential-security-invariants.test.ts +2 -1
  46. package/src/__tests__/daemon-credential-client.test.ts +88 -0
  47. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  48. package/src/__tests__/dm-backfill.test.ts +63 -0
  49. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  50. package/src/__tests__/edit-propagation.test.ts +107 -4
  51. package/src/__tests__/events-client-registration.test.ts +28 -0
  52. package/src/__tests__/file-write-tool.test.ts +4 -2
  53. package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
  54. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
  55. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  56. package/src/__tests__/host-proxy-interface.test.ts +11 -1
  57. package/src/__tests__/host-shell-tool.test.ts +23 -4
  58. package/src/__tests__/image-conversion.test.ts +143 -1
  59. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  60. package/src/__tests__/input-repairs.test.ts +207 -0
  61. package/src/__tests__/live-workspace-guard.test.ts +75 -0
  62. package/src/__tests__/managed-profile-guard.test.ts +4 -2
  63. package/src/__tests__/mcp-abort-signal.test.ts +1 -1
  64. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  65. package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
  66. package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
  67. package/src/__tests__/mtime-cache.test.ts +2 -0
  68. package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
  69. package/src/__tests__/openai-provider.test.ts +22 -0
  70. package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
  71. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  72. package/src/__tests__/platform.test.ts +16 -1
  73. package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
  74. package/src/__tests__/plugin-api-shim.test.ts +5 -0
  75. package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
  76. package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
  77. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  78. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  79. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  80. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
  81. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
  82. package/src/__tests__/reaction-persistence.test.ts +200 -7
  83. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  84. package/src/__tests__/resource-pressure-guard.test.ts +428 -0
  85. package/src/__tests__/resource-pressure-routes.test.ts +113 -0
  86. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  87. package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
  88. package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
  89. package/src/__tests__/skill-tool-factory.test.ts +161 -0
  90. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  91. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  92. package/src/__tests__/tool-executor.test.ts +63 -38
  93. package/src/__tests__/tool-policy.test.ts +97 -1
  94. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
  95. package/src/__tests__/user-plugin-loader.test.ts +2 -0
  96. package/src/__tests__/validate-input.test.ts +243 -7
  97. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  98. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
  99. package/src/__tests__/voice-session-bridge.test.ts +42 -0
  100. package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
  101. package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
  102. package/src/acp/acp-claude-oauth.ts +38 -21
  103. package/src/acp/prepare-agent-env.ts +146 -55
  104. package/src/agent/loop-tool-dedup.test.ts +161 -0
  105. package/src/agent/loop.ts +68 -22
  106. package/src/api/constants/app-tools.ts +34 -0
  107. package/src/api/events/resource-pressure-status-changed.ts +53 -0
  108. package/src/api/index.ts +19 -0
  109. package/src/api/responses/resource-pressure-status.ts +25 -0
  110. package/src/approvals/guardian-channel-delivery.ts +70 -6
  111. package/src/approvals/guardian-request-resolvers.ts +32 -66
  112. package/src/calls/__tests__/voice-session-bridge.test.ts +114 -0
  113. package/src/calls/voice-session-bridge.ts +60 -6
  114. package/src/channels/__tests__/message-audience.test.ts +49 -0
  115. package/src/channels/__tests__/types.test.ts +17 -3
  116. package/src/channels/message-audience.ts +40 -0
  117. package/src/channels/types.ts +16 -12
  118. package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
  119. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
  120. package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
  121. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  122. package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
  123. package/src/cli/commands/__tests__/keys.test.ts +89 -8
  124. package/src/cli/commands/__tests__/plugins.test.ts +57 -1
  125. package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
  126. package/src/cli/commands/channel-verification-sessions.ts +11 -21
  127. package/src/cli/commands/conversations.help.ts +34 -0
  128. package/src/cli/commands/conversations.ts +125 -0
  129. package/src/cli/commands/inference-providers.ts +9 -4
  130. package/src/cli/commands/inference.help.ts +9 -4
  131. package/src/cli/commands/keys.help.ts +13 -1
  132. package/src/cli/commands/keys.ts +26 -15
  133. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  134. package/src/cli/commands/memory/index.help.ts +41 -27
  135. package/src/cli/commands/memory/index.ts +2 -0
  136. package/src/cli/commands/memory/memory-v2.ts +58 -54
  137. package/src/cli/commands/memory/memory-validate.ts +18 -0
  138. package/src/cli/commands/monitoring.ts +1 -0
  139. package/src/cli/commands/plugins.help.ts +28 -31
  140. package/src/cli/commands/plugins.ts +10 -0
  141. package/src/cli/commands/skills.help.ts +13 -16
  142. package/src/cli/commands/trust.ts +3 -14
  143. package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
  144. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
  145. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
  146. package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
  147. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
  148. package/src/cli/lib/bundled-marketplace.json +13 -0
  149. package/src/cli/lib/daemon-credential-client.ts +25 -3
  150. package/src/cli/lib/install-from-github.ts +35 -24
  151. package/src/cli/lib/merge-plugin-tree.ts +22 -4
  152. package/src/cli/lib/plugin-surfaces.ts +27 -0
  153. package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
  154. package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
  155. package/src/config/default-profile-catalog.ts +5 -6
  156. package/src/config/feature-flag-registry.json +32 -32
  157. package/src/config/webhook-routing.ts +8 -0
  158. package/src/context/compactor.ts +31 -2
  159. package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
  160. package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
  161. package/src/daemon/conversation-error.ts +13 -0
  162. package/src/daemon/conversation-messaging.ts +22 -2
  163. package/src/daemon/conversation-process.ts +11 -0
  164. package/src/daemon/conversation-store.ts +10 -0
  165. package/src/daemon/conversation-surfaces.ts +21 -8
  166. package/src/daemon/conversation.ts +12 -10
  167. package/src/daemon/lifecycle.ts +6 -2
  168. package/src/daemon/message-types/conversations.ts +5 -7
  169. package/src/daemon/provider-rejection-log-fields.ts +124 -0
  170. package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
  171. package/src/daemon/resource-pressure-guard.ts +387 -0
  172. package/src/daemon/shutdown-handlers.ts +2 -0
  173. package/src/daemon/startup-error.ts +16 -0
  174. package/src/daemon/trust-context.ts +14 -10
  175. package/src/daemon/unsendable-image-notice.ts +76 -0
  176. package/src/hooks/registry.ts +65 -51
  177. package/src/ipc/__tests__/socket-path.test.ts +15 -7
  178. package/src/ipc/gateway-client.test.ts +1 -1
  179. package/src/ipc/gateway-client.ts +14 -21
  180. package/src/ipc/socket-cleanup.ts +2 -11
  181. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
  182. package/src/messaging/provider-message-metadata.ts +128 -0
  183. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
  184. package/src/messaging/providers/channel-transport.ts +90 -13
  185. package/src/messaging/providers/discord/send.ts +16 -0
  186. package/src/messaging/providers/discord/transport.ts +11 -1
  187. package/src/messaging/providers/index.ts +109 -16
  188. package/src/messaging/providers/slack/message-metadata.ts +45 -0
  189. package/src/messaging/providers/slack/render-transcript.ts +1 -4
  190. package/src/messaging/providers/slack/send.test.ts +6 -9
  191. package/src/messaging/providers/slack/send.ts +40 -28
  192. package/src/messaging/providers/slack/transport.ts +22 -26
  193. package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
  194. package/src/messaging/providers/telegram-bot/transport.ts +3 -6
  195. package/src/messaging/read-provider-metadata.test.ts +157 -0
  196. package/src/messaging/read-provider-metadata.ts +51 -0
  197. package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
  198. package/src/notifications/__tests__/edit-notification.test.ts +174 -2
  199. package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
  200. package/src/notifications/adapters/macos.ts +55 -0
  201. package/src/notifications/adapters/slack.ts +10 -5
  202. package/src/notifications/assistant-reply-producer.ts +37 -4
  203. package/src/notifications/conversation-pairing.ts +111 -21
  204. package/src/notifications/edit-notification.ts +38 -8
  205. package/src/notifications/emit-signal.ts +12 -14
  206. package/src/notifications/home-feed-side-effect.ts +218 -15
  207. package/src/notifications/types.ts +5 -0
  208. package/src/permissions/AGENTS.md +16 -0
  209. package/src/permissions/checker.test.ts +75 -122
  210. package/src/permissions/checker.ts +44 -560
  211. package/src/permissions/confirmation-guardian-request.test.ts +28 -3
  212. package/src/permissions/confirmation-guardian-request.ts +5 -1
  213. package/src/persistence/conversation-crud.ts +23 -20
  214. package/src/persistence/conversation-queries.ts +14 -1
  215. package/src/persistence/conversation-types.ts +22 -0
  216. package/src/persistence/delivery-crud.ts +122 -15
  217. package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
  218. package/src/plugin-api/conversation-turn.ts +27 -0
  219. package/src/plugin-api/credential-scope.test.ts +15 -0
  220. package/src/plugin-api/credential-scope.ts +14 -0
  221. package/src/plugin-api/index.ts +19 -1
  222. package/src/plugin-api/resolve-credential.ts +15 -10
  223. package/src/plugin-api/store-credential.ts +148 -0
  224. package/src/plugin-api/system-card.ts +36 -0
  225. package/src/plugin-api/vision-support.test.ts +82 -0
  226. package/src/plugin-api/vision-support.ts +14 -2
  227. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
  228. package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
  229. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
  230. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
  231. package/src/plugins/defaults/image-recovery/detect.ts +24 -1
  232. package/src/plugins/defaults/main.ts +15 -0
  233. package/src/plugins/defaults/memory/AGENTS.md +11 -3
  234. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  235. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
  236. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  237. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  238. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
  239. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  240. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  241. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  242. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  243. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  244. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  245. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
  246. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
  247. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  248. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  249. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  250. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  251. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  252. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  253. package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
  254. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
  255. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  256. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  257. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  258. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  259. package/src/plugins/mtime-cache.ts +1 -1
  260. package/src/plugins/pipeline.ts +14 -7
  261. package/src/plugins/plugin-execution-context.ts +35 -11
  262. package/src/plugins/plugin-tree-walk.ts +63 -4
  263. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  264. package/src/providers/anthropic/client.ts +332 -241
  265. package/src/providers/connection-resolution.ts +2 -1
  266. package/src/providers/content-blocks.ts +22 -0
  267. package/src/providers/gemini/client.ts +5 -2
  268. package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
  269. package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
  270. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
  271. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
  272. package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
  273. package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
  274. package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
  275. package/src/providers/inference/adapter-factory.ts +33 -2
  276. package/src/providers/inference/auth.ts +13 -2
  277. package/src/providers/inference/credential-usage.ts +37 -0
  278. package/src/providers/inference/missing-credential-guard.ts +110 -0
  279. package/src/providers/inference/resolve-auth.ts +7 -7
  280. package/src/providers/media-resolve.ts +176 -15
  281. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
  282. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  283. package/src/providers/openai/chat-completions-provider.ts +322 -42
  284. package/src/routes/route-host-protocol.ts +7 -0
  285. package/src/routes/worker.ts +31 -10
  286. package/src/runtime/AGENTS.md +35 -0
  287. package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
  288. package/src/runtime/__tests__/web-presence.test.ts +234 -0
  289. package/src/runtime/assistant-event-hub.ts +38 -0
  290. package/src/runtime/channel-reply-delivery.ts +54 -31
  291. package/src/runtime/channel-retry-sweep.ts +6 -4
  292. package/src/runtime/effective-capabilities.test.ts +19 -0
  293. package/src/runtime/effective-capabilities.ts +25 -0
  294. package/src/runtime/guardian-reply-router.ts +6 -2
  295. package/src/runtime/http-errors.ts +1 -0
  296. package/src/runtime/http-server.ts +20 -5
  297. package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
  298. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
  299. package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
  300. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
  301. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
  302. package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
  303. package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
  304. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  305. package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
  306. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  307. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
  308. package/src/runtime/routes/approval-routes.ts +37 -4
  309. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
  310. package/src/runtime/routes/canned-message-complete.ts +68 -29
  311. package/src/runtime/routes/client-routes.ts +113 -0
  312. package/src/runtime/routes/conversation-list-routes.ts +22 -7
  313. package/src/runtime/routes/conversation-management-routes.ts +15 -9
  314. package/src/runtime/routes/conversation-routes.ts +36 -22
  315. package/src/runtime/routes/credential-in-use.ts +85 -0
  316. package/src/runtime/routes/credential-routes.ts +55 -85
  317. package/src/runtime/routes/guardian-approval-interception.ts +22 -22
  318. package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
  319. package/src/runtime/routes/identity-routes.ts +5 -121
  320. package/src/runtime/routes/inbound-message-handler.ts +71 -27
  321. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
  322. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
  323. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
  324. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
  325. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
  326. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
  327. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
  328. package/src/runtime/routes/index.ts +2 -0
  329. package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
  330. package/src/runtime/routes/monitoring-routes.ts +9 -1
  331. package/src/runtime/routes/plugins-routes.ts +17 -5
  332. package/src/runtime/routes/resource-pressure-routes.ts +22 -0
  333. package/src/runtime/routes/secret-routes.ts +39 -8
  334. package/src/runtime/routes/settings-routes.ts +6 -6
  335. package/src/runtime/routes/surface-action-routes.ts +20 -19
  336. package/src/runtime/routes/user-route-dispatcher.ts +31 -2
  337. package/src/runtime/routes/user-route-resolution.ts +18 -1
  338. package/src/runtime/slack-reply-session.test.ts +23 -13
  339. package/src/runtime/slack-reply-session.ts +38 -55
  340. package/src/runtime/web-presence.ts +90 -0
  341. package/src/skills/validate-input.ts +177 -40
  342. package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
  343. package/src/subagent/consult-context.ts +7 -23
  344. package/src/tools/credentials/broker.ts +24 -79
  345. package/src/tools/credentials/ref-parse.ts +35 -0
  346. package/src/tools/credentials/resolve.ts +4 -10
  347. package/src/tools/credentials/store.ts +168 -0
  348. package/src/tools/credentials/tool-policy.ts +67 -0
  349. package/src/tools/executor.ts +53 -15
  350. package/src/tools/network/__tests__/web-search.test.ts +41 -1
  351. package/src/tools/network/url-safety.ts +9 -16
  352. package/src/tools/network/web-search.ts +21 -3
  353. package/src/tools/permission-checker.ts +43 -52
  354. package/src/tools/schema-transforms.ts +40 -5
  355. package/src/tools/shared/input-repairs.ts +160 -0
  356. package/src/tools/skills/skill-tool-factory.ts +18 -4
  357. package/src/tools/subagent/spawn.ts +0 -1
  358. package/src/tools/tool-approval-handler.ts +16 -10
  359. package/src/tools/tool-types.ts +26 -13
  360. package/src/tools/types.ts +6 -6
  361. package/src/util/__tests__/cgroup-memory.test.ts +3 -0
  362. package/src/util/cgroup-memory.ts +3 -0
  363. package/src/util/container-cpu-sampler.ts +250 -0
  364. package/src/util/image-conversion.ts +178 -14
  365. package/src/util/platform.ts +93 -10
  366. package/src/permissions/ipc-risk-types.ts +0 -143
  367. package/src/permissions/risk-types.ts +0 -76
@@ -110,9 +110,12 @@ function holdTheTurnOpen(): void {
110
110
  // gate it never opened.
111
111
  afterEach(() => releaseGate?.());
112
112
 
113
+ const providerCalls: { tools?: { name: string }[] }[] = [];
114
+
113
115
  const provider: Provider = {
114
116
  name: "scripted",
115
117
  async sendMessage(messages, options): Promise<ProviderResponse> {
118
+ providerCalls.push({ tools: options?.tools });
116
119
  await gate;
117
120
  return scriptedProvider.sendMessage(messages, options);
118
121
  },
@@ -169,6 +172,7 @@ function resetDb(): void {
169
172
  db.run("DELETE FROM conversation_keys");
170
173
  db.run("DELETE FROM conversations");
171
174
  broadcasts.length = 0;
175
+ providerCalls.length = 0;
172
176
  }
173
177
 
174
178
  describe("runConversationTurn persistence", () => {
@@ -399,12 +403,32 @@ describe("runConversationTurn channel binding", () => {
399
403
  expect(metadataOf(rows[0])).toMatchObject({
400
404
  userMessageChannel: "plugin",
401
405
  assistantMessageChannel: "plugin",
406
+ userMessageInterface: "plugin",
407
+ assistantMessageInterface: "plugin",
402
408
  });
403
409
  expect(rows[1].role).toBe("assistant");
404
410
  expect(metadataOf(rows[1])).toMatchObject({
405
411
  userMessageChannel: "plugin",
406
412
  assistantMessageChannel: "plugin",
413
+ userMessageInterface: "plugin",
414
+ assistantMessageInterface: "plugin",
415
+ });
416
+ });
417
+
418
+ test("puts core tools on the wire for a plugin-channel turn", async () => {
419
+ // Plugin-driven iMessage turns are non-interactive, so host tools stay
420
+ // off, but sandbox tools (bash, web_search, ...) have to remain on the
421
+ // request. Missing them is what makes the model dump `to=bash code:`
422
+ // into the SMS as prose. The create path initializes the registry
423
+ // before constructing the Conversation that snapshots it.
424
+ await runConversationTurn({
425
+ channel: CHANNEL,
426
+ content: [{ type: "text", text: "hello" }],
407
427
  });
428
+
429
+ const names = providerCalls[0]?.tools?.map((tool) => tool.name) ?? [];
430
+ expect(names.length).toBeGreaterThan(0);
431
+ expect(names).toContain("bash");
408
432
  });
409
433
 
410
434
  test("a queued turn carries its own channel rather than inheriting one", async () => {
@@ -441,6 +465,8 @@ describe("runConversationTurn channel binding", () => {
441
465
  expect(metadataOf(userRows(conversationId)[1])).toMatchObject({
442
466
  userMessageChannel: "plugin",
443
467
  assistantMessageChannel: "plugin",
468
+ userMessageInterface: "plugin",
469
+ assistantMessageInterface: "plugin",
444
470
  });
445
471
  }, 20_000);
446
472
 
@@ -466,7 +492,7 @@ describe("runConversationTurn channel binding", () => {
466
492
  userMessageChannel: "vellum",
467
493
  assistantMessageChannel: "vellum",
468
494
  });
469
- });
495
+ }, 20_000);
470
496
 
471
497
  test("keeps a known sender when a later turn omits it", async () => {
472
498
  // A plugin that knows only the chat coordinates this time is silent about
@@ -103,6 +103,7 @@ mock.module("../daemon/conversation-store.js", () => ({
103
103
  },
104
104
  }));
105
105
 
106
+ import { initializeDb } from "../persistence/db-init.js";
106
107
  import {
107
108
  getProviderRoutingSource,
108
109
  initializeProviders,
@@ -113,6 +114,12 @@ import {
113
114
  ROUTES,
114
115
  } from "../runtime/routes/secret-routes.js";
115
116
 
117
+ // A delete looks up the provider connections that resolve their auth through
118
+ // the credential, so the inference schema has to exist. No connection is
119
+ // created here: the refusal is covered by
120
+ // runtime/routes/__tests__/credential-delete-in-use.test.ts.
121
+ await initializeDb();
122
+
116
123
  const addRoute = ROUTES.find(
117
124
  (r) => r.method === "POST" && r.endpoint === "secrets",
118
125
  )!;
@@ -470,6 +470,75 @@ describe("createSkillTool — required/type/enum validation", () => {
470
470
  );
471
471
  });
472
472
 
473
+ test("repairs array shapes before validation and passes the repaired value to the executor", async () => {
474
+ const hash = computeSkillVersionHash(tempDir);
475
+ const tool = createSkillTool(
476
+ makeEntry({
477
+ executor: "echo.ts",
478
+ input_schema: {
479
+ type: "object",
480
+ properties: {
481
+ activation_hints: { type: "array", items: { type: "string" } },
482
+ avoid_when: { type: "array", items: { type: "string" } },
483
+ files: { type: "array", items: { type: "object" } },
484
+ name: { type: "string" },
485
+ },
486
+ required: ["activation_hints"],
487
+ },
488
+ }),
489
+ tempDir,
490
+ hash,
491
+ BUNDLED,
492
+ );
493
+
494
+ const result = await tool.execute(
495
+ {
496
+ activation_hints: '["user asks to deploy staging","needs a rollback"]',
497
+ avoid_when: "the repo is dirty",
498
+ files: '[{"path":"references/notes.md","content":"hi"}]',
499
+ name: "x",
500
+ },
501
+ makeContext(),
502
+ );
503
+
504
+ expect(result.isError).toBe(false);
505
+ const parsed = JSON.parse(result.content);
506
+ expect(parsed.input).toEqual({
507
+ activation_hints: ["user asks to deploy staging", "needs a rollback"],
508
+ avoid_when: ["the repo is dirty"],
509
+ files: [{ path: "references/notes.md", content: "hi" }],
510
+ name: "x",
511
+ });
512
+ });
513
+
514
+ test("rejects a truncated array with a self-correcting message", async () => {
515
+ const hash = computeSkillVersionHash(tempDir);
516
+ const tool = createSkillTool(
517
+ makeEntry({
518
+ executor: "echo.ts",
519
+ input_schema: {
520
+ type: "object",
521
+ properties: {
522
+ activation_hints: { type: "array", items: { type: "string" } },
523
+ },
524
+ },
525
+ }),
526
+ tempDir,
527
+ hash,
528
+ BUNDLED,
529
+ );
530
+
531
+ const result = await tool.execute(
532
+ { activation_hints: "[user asks to deploy staging" },
533
+ makeContext(),
534
+ );
535
+
536
+ expect(result.isError).toBe(true);
537
+ expect(result.content).toContain(
538
+ "activation_hints must be an array: pass a JSON array, not a string",
539
+ );
540
+ });
541
+
473
542
  test("passes valid input through to the executor unchanged", async () => {
474
543
  const hash = computeSkillVersionHash(tempDir);
475
544
  const tool = createSkillTool(
@@ -680,6 +749,98 @@ function subagentSpawnTool() {
680
749
  * than the resolver silently deletes the resolver's behavior, and every test
681
750
  * that calls `executeSubagentSpawn` directly still passes.
682
751
  */
752
+ describe("createSkillTool: bundled input repairs", () => {
753
+ const SCAFFOLD_SCHEMA = {
754
+ type: "object",
755
+ properties: {
756
+ skill_id: { type: "string" },
757
+ body_markdown: { type: "string" },
758
+ files: {
759
+ type: "array",
760
+ items: {
761
+ type: "object",
762
+ properties: {
763
+ path: { type: "string" },
764
+ content: { type: "string" },
765
+ copy_from: { type: "string" },
766
+ },
767
+ required: ["path"],
768
+ },
769
+ },
770
+ },
771
+ required: ["skill_id", "body_markdown"],
772
+ };
773
+
774
+ function makeScaffoldTool(bundled: boolean) {
775
+ return createSkillTool(
776
+ makeEntry({
777
+ name: "scaffold_managed_skill",
778
+ executor: "echo.ts",
779
+ input_schema: SCAFFOLD_SCHEMA,
780
+ }),
781
+ tempDir,
782
+ computeSkillVersionHash(tempDir),
783
+ bundled,
784
+ );
785
+ }
786
+
787
+ test("reads `body` as `body_markdown` and reaches the executor", async () => {
788
+ const result = await makeScaffoldTool(true).execute(
789
+ { skill_id: "deploy", body: "# Deploy\n" },
790
+ makeContext(),
791
+ );
792
+
793
+ expect(result.isError).toBe(false);
794
+ expect(JSON.parse(result.content).input).toEqual({
795
+ skill_id: "deploy",
796
+ body_markdown: "# Deploy\n",
797
+ });
798
+ });
799
+
800
+ test("reads a path-keyed files map as the declared array", async () => {
801
+ const result = await makeScaffoldTool(true).execute(
802
+ {
803
+ skill_id: "deploy",
804
+ body_markdown: "# Deploy\n",
805
+ files: { "references/notes.md": "hi" },
806
+ },
807
+ makeContext(),
808
+ );
809
+
810
+ expect(result.isError).toBe(false);
811
+ expect(JSON.parse(result.content).input.files).toEqual([
812
+ { path: "references/notes.md", content: "hi" },
813
+ ]);
814
+ });
815
+
816
+ test("accepts the injected activity field the model is shown", async () => {
817
+ const result = await makeScaffoldTool(true).execute(
818
+ {
819
+ skill_id: "deploy",
820
+ body_markdown: "# Deploy\n",
821
+ activity: "Writing the deploy skill",
822
+ },
823
+ makeContext(),
824
+ );
825
+
826
+ expect(result.isError).toBe(false);
827
+ expect(JSON.parse(result.content).input.activity).toBe(
828
+ "Writing the deploy skill",
829
+ );
830
+ });
831
+
832
+ test("a non-bundled skill reusing the name keeps its own contract", async () => {
833
+ const result = await makeScaffoldTool(false).execute(
834
+ { skill_id: "deploy", body: "# Deploy\n" },
835
+ makeContext(),
836
+ );
837
+
838
+ expect(result.isError).toBe(true);
839
+ expect(result.content).toContain("body_markdown is required");
840
+ expect(result.content).toContain('Unknown parameter "body"');
841
+ });
842
+ });
843
+
683
844
  describe("createSkillTool: subagent_spawn role validation", () => {
684
845
  const ROLE_CASES: ReadonlyArray<readonly [string, SubagentRole]> = [
685
846
  ["researcher", "researcher"],
@@ -93,7 +93,7 @@ mock.module("../skills/version-hash.js", () => ({
93
93
  // In CI there's no gateway, so we return a sensible default.
94
94
  mock.module("../ipc/gateway-client.js", () => ({
95
95
  ipcClassifyRisk: async (): Promise<
96
- import("../permissions/ipc-risk-types.js").ClassificationResult
96
+ import("@vellumai/gateway-client").ClassifyRiskIpcResponse
97
97
  > => ({
98
98
  risk: "low",
99
99
  reason: "mock",
@@ -6,9 +6,11 @@ import type { UsageAttributionSnapshot } from "../usage/attribution.js";
6
6
  let checkerDecision: "allow" | "prompt" | "deny" = "allow";
7
7
  let checkerReason = "allowed";
8
8
  let checkerRisk = "low";
9
+ let classifyThrow: Error | null = null;
9
10
  let promptDecision: "allow" | "deny" = "allow";
10
11
  let fakeToolResult: ToolExecutionResult = { content: "ok", isError: false };
11
12
  let toolThrow: Error | null = null;
13
+ let grantConsumeOk = false;
12
14
 
13
15
  // ── audit-terminal captures ───────────────────────────────
14
16
  // The executor and its permission/approval collaborators no longer emit
@@ -86,13 +88,38 @@ mock.module("../platform/consent-cache.js", () => ({
86
88
 
87
89
  mock.module("../permissions/checker.js", () => ({
88
90
  isDynamicSkillLoadInvocation: () => false,
89
- classifyRisk: async () => ({ level: checkerRisk }),
91
+ // Mirrors the real classifier's two failure modes: it throws on an aborted
92
+ // signal before doing anything, and it throws when the gateway is
93
+ // unreachable.
94
+ classifyRisk: async (
95
+ _name: string,
96
+ _input: Record<string, unknown>,
97
+ _workingDir?: string,
98
+ _preParsed?: unknown,
99
+ _manifestOverride?: unknown,
100
+ signal?: AbortSignal,
101
+ ) => {
102
+ signal?.throwIfAborted();
103
+ if (classifyThrow) {
104
+ throw classifyThrow;
105
+ }
106
+ return { level: checkerRisk };
107
+ },
90
108
  check: async () => ({ decision: checkerDecision, reason: checkerReason }),
91
- generateAllowlistOptions: () => [
92
- { label: "exact", description: "exact", pattern: "exact" },
93
- ],
94
109
  generateScopeOptions: () => [{ label: "/tmp", scope: "/tmp" }],
95
- getCachedAssessment: () => undefined,
110
+ }));
111
+
112
+ // The scoped-grant consume the gate performs for a sensitive tool invoked by
113
+ // an unknown actor. `mintGrantFromDecision` is stubbed only so the module's
114
+ // other importer links; nothing here mints.
115
+ mock.module("../approvals/approval-primitive.js", () => ({
116
+ consumeGrantForInvocation: async () =>
117
+ grantConsumeOk
118
+ ? { ok: true, grant: { id: "grant-1" } }
119
+ : { ok: false, reason: "no_match" },
120
+ mintGrantFromDecision: async () => {
121
+ throw new Error("mintGrantFromDecision should not be called");
122
+ },
96
123
  }));
97
124
 
98
125
  mock.module("../persistence/conversation-crud.js", () => ({
@@ -255,9 +282,11 @@ describe("ToolExecutor audit terminals", () => {
255
282
  checkerDecision = "allow";
256
283
  checkerReason = "allowed";
257
284
  checkerRisk = "low";
285
+ classifyThrow = null;
258
286
  promptDecision = "allow";
259
287
  fakeToolResult = { content: "ok", isError: false };
260
288
  toolThrow = null;
289
+ grantConsumeOk = false;
261
290
  executedCaptures.length = 0;
262
291
  errorCaptures.length = 0;
263
292
  deniedCaptures.length = 0;
@@ -786,4 +815,102 @@ describe("ToolExecutor audit terminals", () => {
786
815
  expect(executedCaptures).toHaveLength(1);
787
816
  expect(executedCaptures[0].toolName).toBe("file_edit");
788
817
  });
818
+
819
+ // ── audit riskLevel on rows the permission check never produces ─────────
820
+ // The classifier runs before the pre-execution gates, so a gate denial, a
821
+ // gate error, and a grant-consumed execution all record the call's real
822
+ // risk rather than a placeholder (LUM-3159).
823
+
824
+ test("a gate-denied call records the classified risk, not a placeholder", async () => {
825
+ checkerRisk = "high";
826
+ const executor = new ToolExecutor(makePrompter());
827
+
828
+ // Verification control-plane paths are guardian-only; an unknown actor is
829
+ // denied by the gate before any permission check.
830
+ const result = await executor.execute(
831
+ "bash",
832
+ { command: "curl http://host/v1/channel-verification-sessions" },
833
+ makeContext({ trustClass: "unknown" }),
834
+ );
835
+
836
+ expect(result.isError).toBe(true);
837
+ expect(deniedCaptures).toHaveLength(1);
838
+ expect(deniedCaptures[0].riskLevel).toBe("high");
839
+ expect(deniedCaptures[0].wasPrompted).toBe(false);
840
+ });
841
+
842
+ test("a gate-errored call records the classified risk", async () => {
843
+ checkerRisk = "medium";
844
+ const executor = new ToolExecutor(makePrompter());
845
+
846
+ await executor.execute("unknown_tool", { test: true }, makeContext());
847
+
848
+ expect(errorCaptures).toHaveLength(1);
849
+ expect(errorCaptures[0].errorMessage).toContain("Unknown tool");
850
+ expect(errorCaptures[0].riskLevel).toBe("medium");
851
+ });
852
+
853
+ test("a grant-consumed execution records the classified risk", async () => {
854
+ checkerRisk = "high";
855
+ grantConsumeOk = true;
856
+ const executor = new ToolExecutor(makePrompter());
857
+
858
+ // A sensitive tool from an unknown actor routes through the scoped-grant
859
+ // consume; a consumed grant skips the permission check entirely, so this
860
+ // is the one executed row whose risk the permission check never supplies.
861
+ const result = await executor.execute(
862
+ "file_write",
863
+ { path: "/tmp/project/out.txt", content: "x" },
864
+ makeContext({ trustClass: "unknown" }),
865
+ );
866
+
867
+ expect(result).toMatchObject({
868
+ isError: false,
869
+ approvalReason: "grant_scoped_consumed",
870
+ });
871
+ expect(executedCaptures).toHaveLength(1);
872
+ expect(executedCaptures[0].decision).toBe("allow");
873
+ expect(executedCaptures[0].riskLevel).toBe("high");
874
+ });
875
+
876
+ test("a call whose classification never completes records 'unclassified', not 'low'", async () => {
877
+ classifyThrow = new Error("gateway unreachable");
878
+ const executor = new ToolExecutor(makePrompter());
879
+
880
+ // Gate error path: the row says the risk was never assessed.
881
+ await executor.execute("unknown_tool", { test: true }, makeContext());
882
+ expect(errorCaptures).toHaveLength(1);
883
+ expect(errorCaptures[0].riskLevel).toBe("unclassified");
884
+
885
+ // Past the gates, the permission check still requires a classification
886
+ // and fails closed; that error row is likewise honest about the risk.
887
+ errorCaptures.length = 0;
888
+ const result = await executor.execute(
889
+ "file_read",
890
+ { path: "README.md" },
891
+ makeContext(),
892
+ );
893
+ expect(result.isError).toBe(true);
894
+ expect(executedCaptures).toHaveLength(0);
895
+ expect(errorCaptures).toHaveLength(1);
896
+ expect(errorCaptures[0].errorMessage).toContain("gateway unreachable");
897
+ expect(errorCaptures[0].riskLevel).toBe("unclassified");
898
+ });
899
+
900
+ test("a call aborted before it starts records 'unclassified' on its cancelled row", async () => {
901
+ checkerRisk = "high";
902
+ const executor = new ToolExecutor(makePrompter());
903
+ const controller = new AbortController();
904
+ controller.abort();
905
+
906
+ const result = await executor.execute(
907
+ "bash",
908
+ { command: "rm -rf /tmp" },
909
+ makeContext({ signal: controller.signal }),
910
+ );
911
+
912
+ expect(result).toEqual({ content: "Cancelled", isError: true });
913
+ expect(errorCaptures).toHaveLength(1);
914
+ expect(errorCaptures[0].riskLevel).toBe("unclassified");
915
+ });
789
916
  });