@vellumai/assistant 0.11.4 → 0.11.5-dev.202608220019.a778cf0

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 (457) hide show
  1. package/AGENTS.md +6 -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__/ingress.test.ts +118 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
  15. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  19. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  20. package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
  21. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  22. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +30 -32
  23. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  24. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
  25. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  26. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  27. package/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
  28. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  29. package/openapi.yaml +487 -11
  30. package/package.json +1 -1
  31. package/scripts/sync-web-search-catalog.ts +6 -0
  32. package/src/__tests__/anthropic-provider.test.ts +10 -3
  33. package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
  34. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  35. package/src/__tests__/channel-approval-routes.test.ts +4 -0
  36. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
  37. package/src/__tests__/channel-readiness-service.test.ts +126 -0
  38. package/src/__tests__/channel-reply-delivery.test.ts +30 -17
  39. package/src/__tests__/checker.test.ts +20 -314
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  41. package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
  42. package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +19 -10
  44. package/src/__tests__/container-cpu-sampler.test.ts +153 -0
  45. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  46. package/src/__tests__/conversation-attachments.test.ts +0 -1
  47. package/src/__tests__/conversation-delete-watch-timeline.test.ts +231 -0
  48. package/src/__tests__/conversation-error.test.ts +17 -0
  49. package/src/__tests__/conversation-lifecycle.test.ts +20 -26
  50. package/src/__tests__/conversation-pairing.test.ts +186 -0
  51. package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
  52. package/src/__tests__/credential-broker-server-use.test.ts +24 -5
  53. package/src/__tests__/credential-routes.test.ts +22 -3
  54. package/src/__tests__/credential-security-invariants.test.ts +2 -1
  55. package/src/__tests__/daemon-credential-client.test.ts +88 -0
  56. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  57. package/src/__tests__/dm-backfill.test.ts +63 -0
  58. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  59. package/src/__tests__/edit-propagation.test.ts +107 -4
  60. package/src/__tests__/events-client-registration.test.ts +28 -0
  61. package/src/__tests__/file-write-tool.test.ts +4 -2
  62. package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
  63. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
  64. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  65. package/src/__tests__/host-proxy-interface.test.ts +11 -1
  66. package/src/__tests__/host-shell-tool.test.ts +23 -4
  67. package/src/__tests__/image-conversion.test.ts +143 -1
  68. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  69. package/src/__tests__/input-repairs.test.ts +207 -0
  70. package/src/__tests__/live-workspace-guard.test.ts +75 -0
  71. package/src/__tests__/managed-profile-guard.test.ts +4 -2
  72. package/src/__tests__/mcp-abort-signal.test.ts +1 -1
  73. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  74. package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
  75. package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
  76. package/src/__tests__/mtime-cache.test.ts +2 -0
  77. package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
  78. package/src/__tests__/openai-provider.test.ts +40 -0
  79. package/src/__tests__/openai-responses-provider.test.ts +18 -0
  80. package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
  81. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  82. package/src/__tests__/platform.test.ts +16 -1
  83. package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
  84. package/src/__tests__/plugin-api-shim.test.ts +5 -0
  85. package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
  86. package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
  87. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  88. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  89. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  90. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
  91. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
  92. package/src/__tests__/reaction-persistence.test.ts +200 -7
  93. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  94. package/src/__tests__/resource-pressure-guard.test.ts +428 -0
  95. package/src/__tests__/resource-pressure-routes.test.ts +113 -0
  96. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  97. package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
  98. package/src/__tests__/sanitize-config-for-transfer.test.ts +28 -0
  99. package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
  100. package/src/__tests__/settings-routes.test.ts +85 -3
  101. package/src/__tests__/skill-tool-factory.test.ts +161 -0
  102. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  103. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  104. package/src/__tests__/tool-executor.test.ts +63 -38
  105. package/src/__tests__/tool-policy.test.ts +97 -1
  106. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
  107. package/src/__tests__/user-plugin-loader.test.ts +2 -0
  108. package/src/__tests__/validate-input.test.ts +243 -7
  109. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  110. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
  111. package/src/__tests__/voice-session-bridge.test.ts +42 -0
  112. package/src/__tests__/web-search-catalog-parity.test.ts +8 -0
  113. package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
  114. package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
  115. package/src/acp/acp-claude-oauth.ts +38 -21
  116. package/src/acp/prepare-agent-env.ts +146 -55
  117. package/src/agent/loop-tool-dedup.test.ts +161 -0
  118. package/src/agent/loop.ts +68 -22
  119. package/src/api/constants/app-tools.ts +34 -0
  120. package/src/api/constants/profile-config-validation.ts +60 -0
  121. package/src/api/events/resource-pressure-status-changed.ts +53 -0
  122. package/src/api/events/tool-result.ts +6 -1
  123. package/src/api/events/watch-retro-completed.ts +52 -0
  124. package/src/api/index.ts +30 -0
  125. package/src/api/responses/resource-pressure-status.ts +25 -0
  126. package/src/approvals/guardian-channel-delivery.ts +70 -6
  127. package/src/approvals/guardian-request-resolvers.ts +32 -66
  128. package/src/calls/__tests__/voice-session-bridge.test.ts +114 -0
  129. package/src/calls/voice-session-bridge.ts +60 -6
  130. package/src/channels/__tests__/message-audience.test.ts +49 -0
  131. package/src/channels/__tests__/types.test.ts +17 -3
  132. package/src/channels/message-audience.ts +40 -0
  133. package/src/channels/types.ts +16 -12
  134. package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
  135. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
  136. package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
  137. package/src/cli/commands/__tests__/conversations-slack.test.ts +2 -1
  138. package/src/cli/commands/__tests__/inference-profiles.test.ts +16 -4
  139. package/src/cli/commands/__tests__/inference-providers.test.ts +135 -2
  140. package/src/cli/commands/__tests__/keys.test.ts +89 -8
  141. package/src/cli/commands/__tests__/plugins.test.ts +57 -1
  142. package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
  143. package/src/cli/commands/channel-verification-sessions.ts +11 -21
  144. package/src/cli/commands/conversations.help.ts +34 -0
  145. package/src/cli/commands/conversations.ts +125 -0
  146. package/src/cli/commands/inference-profiles.ts +56 -3
  147. package/src/cli/commands/inference-providers.ts +37 -6
  148. package/src/cli/commands/inference.help.ts +9 -4
  149. package/src/cli/commands/keys.help.ts +13 -1
  150. package/src/cli/commands/keys.ts +26 -15
  151. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  152. package/src/cli/commands/memory/index.help.ts +41 -27
  153. package/src/cli/commands/memory/index.ts +2 -0
  154. package/src/cli/commands/memory/memory-v2.ts +58 -54
  155. package/src/cli/commands/memory/memory-validate.ts +18 -0
  156. package/src/cli/commands/monitoring.ts +1 -0
  157. package/src/cli/commands/oauth/request.test.ts +93 -0
  158. package/src/cli/commands/oauth/request.ts +23 -9
  159. package/src/cli/commands/plugins.help.ts +28 -31
  160. package/src/cli/commands/plugins.ts +10 -0
  161. package/src/cli/commands/skills.help.ts +13 -16
  162. package/src/cli/commands/trust.ts +3 -14
  163. package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
  164. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
  165. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
  166. package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
  167. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
  168. package/src/cli/lib/bundled-marketplace.json +26 -0
  169. package/src/cli/lib/daemon-credential-client.ts +25 -3
  170. package/src/cli/lib/install-from-github.ts +35 -24
  171. package/src/cli/lib/merge-plugin-tree.ts +22 -4
  172. package/src/cli/lib/open-browser.test.ts +67 -0
  173. package/src/cli/lib/open-browser.ts +24 -5
  174. package/src/cli/lib/plugin-surfaces.ts +27 -0
  175. package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
  176. package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
  177. package/src/config/__tests__/profile-materialization.test.ts +26 -0
  178. package/src/config/default-profile-catalog.ts +5 -6
  179. package/src/config/feature-flag-registry.json +36 -28
  180. package/src/config/profile-materialization.ts +29 -0
  181. package/src/config/sanitize-for-transfer.ts +16 -0
  182. package/src/config/schemas/llm.ts +7 -0
  183. package/src/config/schemas/services.ts +6 -0
  184. package/src/config/webhook-routing.ts +8 -0
  185. package/src/context/compactor.ts +31 -2
  186. package/src/context/outbound-sanitize.ts +6 -0
  187. package/src/daemon/__tests__/lifecycle-watch-timeline-sweep.test.ts +98 -0
  188. package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
  189. package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
  190. package/src/daemon/conversation-error.ts +19 -2
  191. package/src/daemon/conversation-messaging.ts +22 -2
  192. package/src/daemon/conversation-process.ts +11 -0
  193. package/src/daemon/conversation-store.ts +10 -0
  194. package/src/daemon/conversation-surfaces.ts +21 -8
  195. package/src/daemon/conversation.ts +12 -10
  196. package/src/daemon/daemon-control.ts +1 -0
  197. package/src/daemon/handlers/__tests__/config-ingress-tunnel-records.test.ts +208 -0
  198. package/src/daemon/handlers/config-ingress.ts +115 -5
  199. package/src/daemon/lifecycle.ts +30 -2
  200. package/src/daemon/message-types/conversations.ts +5 -7
  201. package/src/daemon/message-types/web-activity.ts +3 -2
  202. package/src/daemon/provider-rejection-log-fields.ts +124 -0
  203. package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
  204. package/src/daemon/resource-pressure-guard.ts +387 -0
  205. package/src/daemon/shutdown-handlers.ts +2 -0
  206. package/src/daemon/startup-error.ts +16 -0
  207. package/src/daemon/trust-context.ts +14 -10
  208. package/src/daemon/unsendable-image-notice.ts +76 -0
  209. package/src/hooks/registry.ts +65 -51
  210. package/src/inbound/__tests__/tunnel-probe.test.ts +448 -0
  211. package/src/inbound/tunnel-probe.ts +261 -0
  212. package/src/ipc/__tests__/socket-path.test.ts +15 -7
  213. package/src/ipc/gateway-client.test.ts +1 -1
  214. package/src/ipc/gateway-client.ts +14 -21
  215. package/src/ipc/socket-cleanup.ts +2 -11
  216. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
  217. package/src/messaging/provider-message-metadata.ts +128 -0
  218. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +138 -73
  219. package/src/messaging/providers/channel-transport.ts +100 -16
  220. package/src/messaging/providers/discord/send.ts +16 -0
  221. package/src/messaging/providers/discord/transport.ts +23 -1
  222. package/src/messaging/providers/index.ts +84 -16
  223. package/src/messaging/providers/slack/message-metadata.ts +45 -0
  224. package/src/messaging/providers/slack/render-transcript.ts +1 -4
  225. package/src/messaging/providers/slack/send.test.ts +88 -34
  226. package/src/messaging/providers/slack/send.ts +99 -50
  227. package/src/messaging/providers/slack/transport.ts +35 -37
  228. package/src/messaging/providers/telegram-bot/send.test.ts +88 -18
  229. package/src/messaging/providers/telegram-bot/send.ts +36 -0
  230. package/src/messaging/providers/telegram-bot/transport.ts +22 -8
  231. package/src/messaging/read-provider-metadata.test.ts +157 -0
  232. package/src/messaging/read-provider-metadata.ts +51 -0
  233. package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
  234. package/src/notifications/__tests__/edit-notification.test.ts +174 -2
  235. package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
  236. package/src/notifications/adapters/macos.ts +55 -0
  237. package/src/notifications/adapters/slack.ts +10 -5
  238. package/src/notifications/assistant-reply-producer.ts +37 -4
  239. package/src/notifications/conversation-pairing.ts +111 -21
  240. package/src/notifications/edit-notification.ts +38 -8
  241. package/src/notifications/emit-signal.ts +12 -14
  242. package/src/notifications/home-feed-side-effect.ts +218 -15
  243. package/src/notifications/types.ts +5 -0
  244. package/src/permissions/AGENTS.md +16 -0
  245. package/src/permissions/checker.test.ts +75 -122
  246. package/src/permissions/checker.ts +44 -560
  247. package/src/permissions/confirmation-guardian-request.test.ts +28 -3
  248. package/src/permissions/confirmation-guardian-request.ts +5 -1
  249. package/src/persistence/conversation-crud.ts +69 -20
  250. package/src/persistence/conversation-queries.ts +14 -1
  251. package/src/persistence/conversation-types.ts +22 -0
  252. package/src/persistence/db-async-query.ts +2 -1
  253. package/src/persistence/db-maintenance.ts +15 -0
  254. package/src/persistence/delivery-crud.ts +122 -15
  255. package/src/persistence/embeddings/qdrant-manager.ts +1 -0
  256. package/src/persistence/migrations/367-create-watch-timeline-entries.ts +46 -0
  257. package/src/persistence/migrations/368-watch-timeline-screenshot-blob.ts +33 -0
  258. package/src/persistence/migrations/__tests__/367-create-watch-timeline-entries.test.ts +98 -0
  259. package/src/persistence/migrations/__tests__/368-watch-timeline-screenshot-blob.test.ts +98 -0
  260. package/src/persistence/schema/index.ts +1 -0
  261. package/src/persistence/schema/watch.ts +29 -0
  262. package/src/persistence/steps.ts +4 -0
  263. package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
  264. package/src/plugin-api/conversation-turn.ts +27 -0
  265. package/src/plugin-api/credential-scope.test.ts +15 -0
  266. package/src/plugin-api/credential-scope.ts +14 -0
  267. package/src/plugin-api/index.ts +19 -1
  268. package/src/plugin-api/resolve-credential.ts +15 -10
  269. package/src/plugin-api/store-credential.ts +148 -0
  270. package/src/plugin-api/system-card.ts +36 -0
  271. package/src/plugin-api/vision-support.test.ts +82 -0
  272. package/src/plugin-api/vision-support.ts +14 -2
  273. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
  274. package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
  275. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
  276. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
  277. package/src/plugins/defaults/image-recovery/detect.ts +24 -1
  278. package/src/plugins/defaults/main.ts +15 -0
  279. package/src/plugins/defaults/memory/AGENTS.md +11 -3
  280. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  281. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
  282. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  283. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  284. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
  285. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  286. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  287. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  288. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  289. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  290. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  291. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
  292. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
  293. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  294. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  295. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  296. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  297. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  298. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  299. package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
  300. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
  301. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  302. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  303. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  304. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  305. package/src/plugins/mtime-cache.ts +1 -1
  306. package/src/plugins/pipeline.ts +14 -7
  307. package/src/plugins/plugin-execution-context.ts +35 -11
  308. package/src/plugins/plugin-tree-walk.ts +63 -4
  309. package/src/providers/__tests__/retry-network-error.test.ts +84 -0
  310. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  311. package/src/providers/anthropic/client.ts +332 -241
  312. package/src/providers/connection-resolution.ts +25 -2
  313. package/src/providers/content-blocks.ts +22 -0
  314. package/src/providers/fetch-provider-catalog.ts +19 -0
  315. package/src/providers/gemini/client.ts +5 -2
  316. package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
  317. package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
  318. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
  319. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
  320. package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
  321. package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
  322. package/src/providers/inference/__tests__/endpoint-probe.test.ts +92 -0
  323. package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
  324. package/src/providers/inference/__tests__/profile-config-validation.test.ts +39 -0
  325. package/src/providers/inference/__tests__/profile-probe-classify.test.ts +66 -0
  326. package/src/providers/inference/adapter-factory.ts +33 -2
  327. package/src/providers/inference/auth.ts +13 -2
  328. package/src/providers/inference/credential-usage.ts +37 -0
  329. package/src/providers/inference/endpoint-probe.ts +115 -0
  330. package/src/providers/inference/missing-credential-guard.ts +110 -0
  331. package/src/providers/inference/profile-probe.ts +256 -0
  332. package/src/providers/inference/resolve-auth.ts +7 -7
  333. package/src/providers/media-resolve.ts +176 -15
  334. package/src/providers/model-catalog.ts +10 -0
  335. package/src/providers/openai/__tests__/api-error-normalization.test.ts +17 -1
  336. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
  337. package/src/providers/openai/__tests__/connection-error-wrap.test.ts +44 -0
  338. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  339. package/src/providers/openai/api-error-normalization.ts +16 -2
  340. package/src/providers/openai/chat-completions-provider.ts +326 -44
  341. package/src/providers/openai/responses-provider.ts +5 -2
  342. package/src/providers/provider-send-message.ts +11 -0
  343. package/src/providers/retry.ts +6 -0
  344. package/src/providers/search-provider-catalog.ts +20 -0
  345. package/src/routes/route-host-protocol.ts +7 -0
  346. package/src/routes/worker.ts +31 -10
  347. package/src/runtime/AGENTS.md +36 -0
  348. package/src/runtime/__tests__/host-observe.test.ts +302 -0
  349. package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
  350. package/src/runtime/__tests__/web-presence.test.ts +234 -0
  351. package/src/runtime/assistant-event-hub.ts +38 -0
  352. package/src/runtime/channel-readiness-service.ts +39 -13
  353. package/src/runtime/channel-readiness-types.ts +49 -2
  354. package/src/runtime/channel-reply-delivery.ts +55 -32
  355. package/src/runtime/channel-retry-sweep.ts +6 -4
  356. package/src/runtime/effective-capabilities.test.ts +19 -0
  357. package/src/runtime/effective-capabilities.ts +25 -0
  358. package/src/runtime/guardian-reply-router.ts +6 -2
  359. package/src/runtime/host-observe.ts +246 -0
  360. package/src/runtime/http-errors.ts +1 -0
  361. package/src/runtime/http-server.ts +185 -6
  362. package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
  363. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
  364. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +80 -0
  365. package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
  366. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +118 -0
  367. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +72 -1
  368. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +508 -0
  369. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
  370. package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
  371. package/src/runtime/routes/__tests__/plugins-routes.test.ts +103 -71
  372. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  373. package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
  374. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  375. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
  376. package/src/runtime/routes/__tests__/watch-routes-guardian-cache.test.ts +139 -0
  377. package/src/runtime/routes/__tests__/watch-routes.test.ts +598 -0
  378. package/src/runtime/routes/approval-routes.ts +37 -4
  379. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
  380. package/src/runtime/routes/canned-message-complete.ts +68 -29
  381. package/src/runtime/routes/channel-readiness-routes.ts +14 -2
  382. package/src/runtime/routes/client-routes.ts +113 -0
  383. package/src/runtime/routes/conversation-list-routes.ts +22 -7
  384. package/src/runtime/routes/conversation-management-routes.ts +15 -9
  385. package/src/runtime/routes/conversation-query-routes.ts +10 -0
  386. package/src/runtime/routes/conversation-routes.ts +36 -22
  387. package/src/runtime/routes/credential-in-use.ts +85 -0
  388. package/src/runtime/routes/credential-routes.ts +55 -85
  389. package/src/runtime/routes/guardian-approval-interception.ts +44 -53
  390. package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
  391. package/src/runtime/routes/host-cu-routes.ts +18 -0
  392. package/src/runtime/routes/identity-routes.ts +5 -121
  393. package/src/runtime/routes/inbound-message-handler.ts +74 -30
  394. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
  395. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +182 -297
  396. package/src/runtime/routes/inbound-stages/background-dispatch.ts +173 -386
  397. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
  398. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
  399. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
  400. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
  401. package/src/runtime/routes/index.ts +4 -0
  402. package/src/runtime/routes/inference-profiles-routes.ts +232 -31
  403. package/src/runtime/routes/inference-provider-connection-routes.ts +33 -11
  404. package/src/runtime/routes/ingress-status-routes.ts +180 -0
  405. package/src/runtime/routes/monitoring-routes.ts +9 -1
  406. package/src/runtime/routes/oauth-commands-routes.ts +1 -1
  407. package/src/runtime/routes/plugins-routes.ts +28 -32
  408. package/src/runtime/routes/resource-pressure-routes.ts +22 -0
  409. package/src/runtime/routes/secret-routes.ts +39 -8
  410. package/src/runtime/routes/settings-routes.ts +15 -12
  411. package/src/runtime/routes/surface-action-routes.ts +20 -19
  412. package/src/runtime/routes/user-route-dispatcher.ts +31 -2
  413. package/src/runtime/routes/user-route-resolution.ts +18 -1
  414. package/src/runtime/routes/watch-routes.ts +807 -0
  415. package/src/runtime/slack-reply-session.test.ts +23 -13
  416. package/src/runtime/slack-reply-session.ts +38 -55
  417. package/src/runtime/web-presence.ts +90 -0
  418. package/src/security/__tests__/untrusted-content.test.ts +42 -0
  419. package/src/security/untrusted-content.ts +28 -9
  420. package/src/skills/validate-input.ts +177 -40
  421. package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
  422. package/src/subagent/consult-context.ts +7 -23
  423. package/src/tools/credentials/broker.ts +24 -79
  424. package/src/tools/credentials/ref-parse.ts +35 -0
  425. package/src/tools/credentials/resolve.ts +4 -10
  426. package/src/tools/credentials/store.ts +168 -0
  427. package/src/tools/credentials/tool-policy.ts +67 -0
  428. package/src/tools/executor.ts +53 -15
  429. package/src/tools/network/__tests__/firecrawl-compat.test.ts +77 -0
  430. package/src/tools/network/__tests__/web-fetch-fastcrw.test.ts +169 -0
  431. package/src/tools/network/__tests__/web-search.test.ts +138 -3
  432. package/src/tools/network/firecrawl-compat.ts +90 -0
  433. package/src/tools/network/url-safety.ts +9 -16
  434. package/src/tools/network/web-fetch.ts +142 -62
  435. package/src/tools/network/web-search.ts +162 -58
  436. package/src/tools/permission-checker.ts +43 -52
  437. package/src/tools/schema-transforms.ts +40 -5
  438. package/src/tools/shared/input-repairs.ts +160 -0
  439. package/src/tools/skills/skill-tool-factory.ts +18 -4
  440. package/src/tools/subagent/spawn.ts +0 -1
  441. package/src/tools/tool-approval-handler.ts +16 -10
  442. package/src/tools/tool-types.ts +26 -13
  443. package/src/tools/types.ts +6 -6
  444. package/src/util/__tests__/cgroup-memory.test.ts +3 -0
  445. package/src/util/cgroup-memory.ts +3 -0
  446. package/src/util/container-cpu-sampler.ts +250 -0
  447. package/src/util/image-conversion.ts +178 -14
  448. package/src/util/platform.ts +93 -10
  449. package/src/util/worker-process.ts +1 -0
  450. package/src/watch/__tests__/watch-retro.test.ts +665 -0
  451. package/src/watch/__tests__/watch-session-manager.test.ts +566 -0
  452. package/src/watch/__tests__/watch-timeline.test.ts +670 -0
  453. package/src/watch/watch-retro.ts +480 -0
  454. package/src/watch/watch-session-manager.ts +575 -0
  455. package/src/watch/watch-timeline.ts +848 -0
  456. package/src/permissions/ipc-risk-types.ts +0 -143
  457. package/src/permissions/risk-types.ts +0 -76
@@ -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
  });
@@ -201,6 +201,22 @@ describe("client OS per-row evidence marker", () => {
201
201
  expect(isMacOriginatedUserMessage(lastUserMetadata())).toBe(true);
202
202
  });
203
203
 
204
+ // Mirrors the Mac-originated case above: a plain browser tab reports
205
+ // `client.os: "web"`, while the Electron desktop renderer resolves its own
206
+ // host OS first and persists `macos`/`windows` instead.
207
+ test("marks an OS this row's own transport reported (web)", async () => {
208
+ const ctx = createWebTurnContext("web");
209
+ await persistQueuedMessageBody(ctx, {
210
+ content: "hello",
211
+ requestId: "req-transport-os-web",
212
+ requestClientOs: "web",
213
+ });
214
+
215
+ expect(lastUserMetadata().client).toEqual({ os: "web" });
216
+ expect(lastUserMetadata().clientOsFromRequest).toBe(true);
217
+ expect(isMacOriginatedUserMessage(lastUserMetadata())).toBe(false);
218
+ });
219
+
204
220
  test("marks an OS this row's own request headers reported", async () => {
205
221
  const ctx = createWebTurnContext(undefined);
206
222
  await persistQueuedMessageBody(ctx, {
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Compaction must never bake invalid image bytes into the rebuilt history.
3
+ *
4
+ * Retained images are re-hydrated from the attachment store and re-optimized
5
+ * at compaction time. If that pipeline yields bytes that no longer parse as an
6
+ * image (e.g. a corrupt conversion-cache entry), embedding them would make the
7
+ * provider reject EVERY subsequent turn with a 400: the conversation wedges
8
+ * until the block is manually removed. `buildRetainedImageBlocks` drops such
9
+ * payloads instead.
10
+ */
11
+ import { beforeEach, describe, expect, test } from "bun:test";
12
+
13
+ import {
14
+ buildRetainedImageBlocks,
15
+ collectImageManifest,
16
+ } from "../context/compactor.js";
17
+ import { attachInlineAttachmentToMessage } from "../persistence/attachments-store.js";
18
+ import {
19
+ addMessage,
20
+ createConversation,
21
+ } from "../persistence/conversation-crud.js";
22
+ import { getDb } from "../persistence/db-connection.js";
23
+ import { initializeDb } from "../persistence/db-init.js";
24
+
25
+ await initializeDb();
26
+
27
+ // 1x1 transparent PNG.
28
+ const PNG_1X1_BASE64 =
29
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
30
+
31
+ // Declared image/png, but the bytes are not any known image format:
32
+ // the stand-in for a corrupted payload surviving in the pipeline.
33
+ const GARBAGE_BASE64 = Buffer.from("definitely not an image").toString(
34
+ "base64",
35
+ );
36
+
37
+ function resetTables(): void {
38
+ const db = getDb();
39
+ db.run("DELETE FROM message_attachments");
40
+ db.run("DELETE FROM attachments");
41
+ db.run("DELETE FROM messages");
42
+ db.run("DELETE FROM conversations");
43
+ }
44
+
45
+ async function addImageMessage(
46
+ conversationId: string,
47
+ filename: string,
48
+ dataBase64: string,
49
+ ): Promise<void> {
50
+ const inserted = await addMessage(
51
+ conversationId,
52
+ "user",
53
+ JSON.stringify([{ type: "text", text: filename }]),
54
+ {
55
+ metadata: { provenanceTrustClass: "guardian" },
56
+ skipIndexing: true,
57
+ },
58
+ );
59
+ await attachInlineAttachmentToMessage(
60
+ inserted.id,
61
+ 0,
62
+ filename,
63
+ "image/png",
64
+ dataBase64,
65
+ );
66
+ }
67
+
68
+ describe("buildRetainedImageBlocks corrupt-bytes gate", () => {
69
+ beforeEach(resetTables);
70
+
71
+ test("valid image bytes are retained", async () => {
72
+ const conv = createConversation();
73
+ await addImageMessage(conv.id, "good.png", PNG_1X1_BASE64);
74
+ const manifest = collectImageManifest(conv.id, "guardian");
75
+
76
+ const { blocks, resolved, missing } = await buildRetainedImageBlocks(
77
+ ["good.png"],
78
+ manifest,
79
+ );
80
+
81
+ expect(resolved).toEqual(["good.png"]);
82
+ expect(missing).toEqual([]);
83
+ expect(blocks).toHaveLength(1);
84
+ expect(blocks[0].source.type).toBe("base64");
85
+ });
86
+
87
+ test("bytes that do not parse as an image are dropped, not embedded", async () => {
88
+ const conv = createConversation();
89
+ await addImageMessage(conv.id, "corrupt.png", GARBAGE_BASE64);
90
+ await addImageMessage(conv.id, "good.png", PNG_1X1_BASE64);
91
+ const manifest = collectImageManifest(conv.id, "guardian");
92
+
93
+ const { blocks, resolved, missing } = await buildRetainedImageBlocks(
94
+ ["corrupt.png", "good.png"],
95
+ manifest,
96
+ );
97
+
98
+ // The corrupt payload is dropped (not listed as manifest-missing, since
99
+ // it resolved fine; its bytes were the problem) and the valid one
100
+ // survives.
101
+ expect(resolved).toEqual(["good.png"]);
102
+ expect(missing).toEqual([]);
103
+ expect(blocks).toHaveLength(1);
104
+ });
105
+
106
+ test("a truncated JPEG (valid SOI header, no EOI) is dropped", async () => {
107
+ // A JPEG torn mid-write keeps its SOI header, so a format sniff alone
108
+ // accepts it; the gate must also require an EOI marker.
109
+ const tornJpeg = Buffer.concat([
110
+ Buffer.from([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10]),
111
+ Buffer.from("JFIF\0"),
112
+ Buffer.alloc(64),
113
+ ]).toString("base64");
114
+ const conv = createConversation();
115
+ await addImageMessage(conv.id, "torn.jpg", tornJpeg);
116
+ const manifest = collectImageManifest(conv.id, "guardian");
117
+
118
+ const { blocks, resolved, missing } = await buildRetainedImageBlocks(
119
+ ["torn.jpg"],
120
+ manifest,
121
+ );
122
+
123
+ expect(resolved).toEqual([]);
124
+ expect(missing).toEqual([]);
125
+ expect(blocks).toHaveLength(0);
126
+ });
127
+
128
+ test("unresolvable filenames still land in missing", async () => {
129
+ const conv = createConversation();
130
+ await addImageMessage(conv.id, "good.png", PNG_1X1_BASE64);
131
+ const manifest = collectImageManifest(conv.id, "guardian");
132
+
133
+ const { blocks, resolved, missing } = await buildRetainedImageBlocks(
134
+ ["never-uploaded.png", "good.png"],
135
+ manifest,
136
+ );
137
+
138
+ expect(missing).toEqual(["never-uploaded.png"]);
139
+ expect(resolved).toEqual(["good.png"]);
140
+ expect(blocks).toHaveLength(1);
141
+ });
142
+ });