@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2

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 (596) hide show
  1. package/AGENTS.md +9 -3
  2. package/ARCHITECTURE.md +32 -10
  3. package/docs/architecture/memory.md +43 -5
  4. package/docs/architecture/security.md +96 -152
  5. package/docs/architecture/turn-actor.md +70 -0
  6. package/docs/browser-use-architecture-phase2.md +128 -56
  7. package/docs/flux-turn-detection-spike.md +248 -0
  8. package/docs/guardian-request-flow.md +35 -0
  9. package/docs/stt-provider-onboarding.md +3 -1
  10. package/knip.json +3 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  24. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  25. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  26. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  27. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  28. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  29. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  30. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  31. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  32. package/openapi.yaml +289 -113
  33. package/package.json +1 -1
  34. package/scripts/voice-ttft-spike.ts +3 -3
  35. package/scripts/write-plugin-api-shim.ts +10 -0
  36. package/src/__tests__/app-compiler.test.ts +38 -3
  37. package/src/__tests__/app-control-flow.test.ts +1 -0
  38. package/src/__tests__/approval-routes-http.test.ts +2 -2
  39. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  40. package/src/__tests__/attachments-store.test.ts +21 -12
  41. package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
  42. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  43. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  44. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  45. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  46. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  47. package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
  48. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  49. package/src/__tests__/checker.test.ts +20 -314
  50. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  51. package/src/__tests__/compaction-events.test.ts +8 -10
  52. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  53. package/src/__tests__/config-schema.test.ts +25 -10
  54. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  55. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  56. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  57. package/src/__tests__/conversation-agent-loop.test.ts +32 -21
  58. package/src/__tests__/conversation-attachments.test.ts +0 -1
  59. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  61. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  63. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  64. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  65. package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
  66. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  67. package/src/__tests__/conversation-queue.test.ts +154 -4
  68. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  69. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  71. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  75. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  82. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  83. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  85. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  86. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  87. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  88. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  89. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  90. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  91. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  92. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  93. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  94. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  95. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  96. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  97. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  98. package/src/__tests__/document-sync-tags.test.ts +0 -75
  99. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  100. package/src/__tests__/edit-propagation.test.ts +107 -11
  101. package/src/__tests__/file-ops-service.test.ts +163 -30
  102. package/src/__tests__/filesystem-tools.test.ts +23 -24
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  104. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  105. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  106. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  107. package/src/__tests__/host-file-read-tool.test.ts +16 -19
  108. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  109. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  110. package/src/__tests__/injector-chain.test.ts +63 -41
  111. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  112. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  113. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  114. package/src/__tests__/llm-schema.test.ts +5 -2
  115. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  116. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  117. package/src/__tests__/media-generate-image.test.ts +131 -21
  118. package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
  119. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  120. package/src/__tests__/mtime-cache.test.ts +3 -1
  121. package/src/__tests__/non-member-access-request.test.ts +0 -20
  122. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  123. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  124. package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
  125. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  126. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  127. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  128. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  129. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  130. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
  131. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  132. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  133. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  134. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  135. package/src/__tests__/qdrant-manager.test.ts +14 -1
  136. package/src/__tests__/reaction-persistence.test.ts +200 -7
  137. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  138. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  139. package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
  140. package/src/__tests__/run-due-schedules.test.ts +21 -0
  141. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  142. package/src/__tests__/schedule-routes.test.ts +23 -0
  143. package/src/__tests__/schedule-store.test.ts +17 -0
  144. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  145. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  146. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  147. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  148. package/src/__tests__/skills.test.ts +32 -0
  149. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  150. package/src/__tests__/starter-task-flow.test.ts +5 -4
  151. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  152. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  153. package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
  154. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  155. package/src/__tests__/subagent-tools.test.ts +81 -101
  156. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  157. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  158. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  159. package/src/__tests__/tool-executor.test.ts +68 -39
  160. package/src/__tests__/turn-events-store.test.ts +43 -0
  161. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  162. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  163. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  164. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  165. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  166. package/src/__tests__/user-plugin-loader.test.ts +3 -1
  167. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  168. package/src/__tests__/visible-app-context.test.ts +16 -9
  169. package/src/__tests__/voice-config-update.test.ts +40 -0
  170. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  171. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  172. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  173. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  174. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  175. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  176. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  177. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  178. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  179. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  180. package/src/acp/__tests__/auth-required.test.ts +161 -0
  181. package/src/acp/acp-claude-oauth.ts +19 -2
  182. package/src/acp/agent-process.test.ts +100 -0
  183. package/src/acp/agent-process.ts +29 -26
  184. package/src/acp/auth-required.ts +102 -0
  185. package/src/acp/session-manager.test.ts +119 -0
  186. package/src/acp/session-manager.ts +76 -3
  187. package/src/api/events/acp-auth-required.ts +55 -0
  188. package/src/api/events/host-file.ts +2 -2
  189. package/src/api/index.ts +7 -0
  190. package/src/api/surfaces.ts +12 -3
  191. package/src/apps/app-store.ts +3 -0
  192. package/src/bundler/package-resolver.ts +2 -30
  193. package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
  194. package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
  195. package/src/calls/call-controller.ts +19 -3
  196. package/src/calls/call-setup-flow.ts +0 -1
  197. package/src/calls/media-stream-stt-session.ts +15 -0
  198. package/src/calls/voice-session-bridge.ts +115 -41
  199. package/src/calls/voice-triage-escalate.ts +105 -2
  200. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  201. package/src/channels/config.ts +13 -0
  202. package/src/channels/plugin-channel-declarations.ts +108 -0
  203. package/src/channels/types.ts +30 -0
  204. package/src/cli/AGENTS.md +5 -2
  205. package/src/cli/bundled-modules.ts +29 -0
  206. package/src/cli/commands/credentials.help.ts +2 -2
  207. package/src/cli/commands/db/repair.ts +4 -8
  208. package/src/cli/commands/domain.ts +6 -3
  209. package/src/cli/commands/email.ts +6 -3
  210. package/src/cli/commands/inference-providers.ts +1 -1
  211. package/src/cli/commands/keys.ts +8 -3
  212. package/src/cli/commands/mcp.help.ts +13 -4
  213. package/src/cli/commands/mcp.ts +9 -0
  214. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  215. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  216. package/src/cli/commands/memory/index.help.ts +84 -28
  217. package/src/cli/commands/memory/index.ts +2 -0
  218. package/src/cli/commands/memory/memory-v2.ts +58 -54
  219. package/src/cli/commands/memory/memory-v3.ts +64 -0
  220. package/src/cli/commands/memory/memory-validate.ts +18 -0
  221. package/src/cli/commands/plugins.ts +85 -36
  222. package/src/cli/commands/schedules.ts +35 -1
  223. package/src/cli/commands/stt.help.ts +27 -2
  224. package/src/cli/commands/trust.ts +3 -14
  225. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  226. package/src/cli/lib/bundled-marketplace.json +14 -1
  227. package/src/cli/lib/upgrade-plugin.ts +42 -0
  228. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  229. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  230. package/src/config/__tests__/default-provider.test.ts +6 -1
  231. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  232. package/src/config/assistant-feature-flags.ts +36 -15
  233. package/src/config/balanced-model-experiment.ts +35 -0
  234. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  235. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  236. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  237. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  238. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  240. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  241. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  242. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  243. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  244. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  245. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  246. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  247. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  248. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  249. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  250. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  251. package/src/config/call-site-defaults.ts +11 -5
  252. package/src/config/default-profile-catalog.ts +179 -16
  253. package/src/config/default-profile-names.ts +4 -1
  254. package/src/config/default-provider-resolution.ts +4 -0
  255. package/src/config/feature-flag-registry.json +12 -12
  256. package/src/config/llm-context-resolution.ts +11 -3
  257. package/src/config/llm-resolver.ts +28 -1
  258. package/src/config/profile-materialization.ts +70 -22
  259. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  260. package/src/config/schemas/call-site-catalog.ts +4 -4
  261. package/src/config/schemas/live-voice.ts +57 -23
  262. package/src/config/schemas/llm.ts +59 -32
  263. package/src/config/schemas/mcp.ts +23 -0
  264. package/src/config/schemas/plugin-updates.ts +6 -2
  265. package/src/config/schemas/stt.ts +1 -0
  266. package/src/config/skills.ts +9 -2
  267. package/src/context/outbound-sanitize.ts +96 -1
  268. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  269. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  270. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  271. package/src/daemon/conversation-agent-loop.ts +31 -19
  272. package/src/daemon/conversation-messaging.ts +5 -1
  273. package/src/daemon/conversation-notifiers.ts +20 -10
  274. package/src/daemon/conversation-process.ts +20 -6
  275. package/src/daemon/conversation-runtime-assembly.ts +12 -6
  276. package/src/daemon/conversation-store.ts +14 -4
  277. package/src/daemon/conversation-surfaces.ts +75 -26
  278. package/src/daemon/conversation-tool-setup.ts +3 -6
  279. package/src/daemon/conversation.ts +154 -51
  280. package/src/daemon/doordash-steps.ts +2 -2
  281. package/src/daemon/lifecycle.ts +14 -1
  282. package/src/daemon/mcp-reload-service.ts +36 -6
  283. package/src/daemon/process-message.ts +13 -26
  284. package/src/daemon/providers-setup.ts +6 -3
  285. package/src/daemon/trust-context-types.ts +29 -0
  286. package/src/daemon/trust-context.ts +12 -7
  287. package/src/daemon/wake-conversation-ops.ts +3 -2
  288. package/src/daemon/windows-compiled-entry.ts +4 -0
  289. package/src/documents/document-store.ts +143 -240
  290. package/src/hooks/hook-loader.ts +3 -3
  291. package/src/hooks/registry.ts +103 -45
  292. package/src/hooks/types.ts +5 -0
  293. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  294. package/src/inbound/oauth-callback-url.ts +61 -0
  295. package/src/ipc/gateway-client.test.ts +1 -1
  296. package/src/ipc/gateway-client.ts +14 -21
  297. package/src/ipc/gateway-flag-listener.ts +17 -3
  298. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  299. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  300. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
  301. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  302. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  303. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  304. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  305. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  306. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  307. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  308. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  309. package/src/live-voice/live-voice-archive.ts +2 -0
  310. package/src/live-voice/live-voice-manager.ts +16 -3
  311. package/src/live-voice/live-voice-metrics.ts +57 -32
  312. package/src/live-voice/live-voice-photo.ts +1 -2
  313. package/src/live-voice/live-voice-session.ts +598 -323
  314. package/src/live-voice/progress-narration.ts +277 -0
  315. package/src/live-voice/protocol.ts +21 -1
  316. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  317. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  318. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  319. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  320. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  321. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  322. package/src/mcp/client.ts +16 -11
  323. package/src/mcp/effective-config.ts +113 -0
  324. package/src/mcp/manager.ts +11 -6
  325. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  326. package/src/mcp/mcp-oauth-provider.ts +205 -240
  327. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  328. package/src/monitoring/control.ts +1 -0
  329. package/src/monitoring/db-integrity-sample.ts +4 -5
  330. package/src/monitoring/plugin-auto-update.ts +128 -24
  331. package/src/notifications/AGENTS.md +2 -0
  332. package/src/notifications/approval-card-data.ts +33 -0
  333. package/src/notifications/signal.ts +1 -0
  334. package/src/permissions/AGENTS.md +16 -0
  335. package/src/permissions/checker.test.ts +75 -122
  336. package/src/permissions/checker.ts +44 -560
  337. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  338. package/src/permissions/confirmation-guardian-request.ts +2 -2
  339. package/src/permissions/prompter.ts +1 -5
  340. package/src/permissions/question-guardian-request.test.ts +14 -6
  341. package/src/permissions/question-guardian-request.ts +1 -2
  342. package/src/persistence/attachments-store.ts +8 -1
  343. package/src/persistence/bookmark-crud.ts +3 -7
  344. package/src/persistence/conversation-attention-store.ts +16 -45
  345. package/src/persistence/conversation-crud.ts +33 -4
  346. package/src/persistence/conversation-lineage.ts +9 -0
  347. package/src/persistence/conversation-queries.ts +182 -52
  348. package/src/persistence/delivery-crud.ts +150 -36
  349. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  350. package/src/persistence/external-conversation-store.ts +32 -4
  351. package/src/persistence/llm-request-log-store.ts +4 -10
  352. package/src/persistence/llm-usage-store.ts +8 -3
  353. package/src/persistence/message-reads.test.ts +197 -0
  354. package/src/persistence/message-reads.ts +211 -0
  355. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  356. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  357. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  358. package/src/persistence/real-user-turn-filter.ts +27 -3
  359. package/src/persistence/schema/documents.ts +4 -4
  360. package/src/persistence/steps.ts +9 -0
  361. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  362. package/src/plugin-api/constants.ts +8 -0
  363. package/src/plugin-api/conversation-turn.ts +195 -5
  364. package/src/plugin-api/index.ts +38 -6
  365. package/src/plugin-api/resolve-credential.ts +3 -2
  366. package/src/plugin-api/store-credential.ts +146 -0
  367. package/src/plugin-api/vision-support.test.ts +1 -1
  368. package/src/plugin-api/webhook-url.ts +13 -11
  369. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  370. package/src/plugins/defaults/main.ts +21 -7
  371. package/src/plugins/defaults/memory/AGENTS.md +15 -3
  372. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  373. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  374. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  375. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  376. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  377. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  378. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  379. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  380. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  381. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  382. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  383. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  384. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
  385. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  386. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  387. package/src/plugins/defaults/memory/indexer.ts +3 -1
  388. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  389. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  390. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  391. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  392. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  393. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
  394. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  395. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  396. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  397. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  398. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  399. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  400. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
  401. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
  402. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  403. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  404. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  405. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  406. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  407. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  408. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  409. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
  410. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  411. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  412. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  413. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  414. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  415. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  416. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  417. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  418. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  419. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  420. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  421. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  422. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  423. package/src/plugins/defaults/memory/worker.ts +6 -3
  424. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  425. package/src/plugins/external-plugin-loader.ts +47 -0
  426. package/src/plugins/mcp-servers.ts +361 -0
  427. package/src/plugins/mtime-cache.ts +37 -46
  428. package/src/plugins/pipeline.ts +14 -7
  429. package/src/plugins/plugin-execution-context.ts +35 -11
  430. package/src/plugins/worker-plugin-surface.ts +33 -0
  431. package/src/prompts/templates/system-sections.ts +0 -7
  432. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  433. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  434. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  435. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  436. package/src/providers/__tests__/retry-callsite.test.ts +25 -2
  437. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  438. package/src/providers/anthropic/client.ts +332 -241
  439. package/src/providers/call-site-routing.ts +30 -3
  440. package/src/providers/connection-resolution.ts +194 -11
  441. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
  442. package/src/providers/inference/adapter-factory.ts +3 -0
  443. package/src/providers/inference/auth.ts +6 -0
  444. package/src/providers/inference/connection-availability.ts +24 -2
  445. package/src/providers/inference/connections.ts +2 -0
  446. package/src/providers/model-catalog.ts +3 -3
  447. package/src/providers/model-intents.ts +28 -8
  448. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
  449. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  450. package/src/providers/openai/chat-completions-provider.ts +133 -36
  451. package/src/providers/openai/codex-models.ts +2 -1
  452. package/src/providers/provider-send-message.ts +32 -3
  453. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  454. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
  455. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  456. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  457. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  458. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
  459. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  460. package/src/providers/speech-to-text/resolve.ts +25 -2
  461. package/src/routes/control.ts +1 -0
  462. package/src/routes/route-host-client.ts +1 -0
  463. package/src/routes/route-host-protocol.ts +7 -0
  464. package/src/routes/worker.ts +48 -15
  465. package/src/runtime/AGENTS.md +16 -17
  466. package/src/runtime/access-request-helper.ts +9 -12
  467. package/src/runtime/agent-wake.ts +18 -15
  468. package/src/runtime/effective-capabilities.test.ts +43 -0
  469. package/src/runtime/effective-capabilities.ts +27 -0
  470. package/src/runtime/guardian-reply-router.ts +6 -2
  471. package/src/runtime/pre-first-message-gate.ts +4 -0
  472. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  473. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
  474. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  475. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  476. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  477. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  478. package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
  479. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  480. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  481. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  482. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  483. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
  484. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  485. package/src/runtime/routes/channel-route-shared.ts +0 -6
  486. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  487. package/src/runtime/routes/conversation-list-routes.ts +181 -23
  488. package/src/runtime/routes/conversation-management-routes.ts +17 -12
  489. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  490. package/src/runtime/routes/conversation-routes.ts +43 -29
  491. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  492. package/src/runtime/routes/credential-routes.ts +26 -82
  493. package/src/runtime/routes/default-provider-routes.ts +15 -0
  494. package/src/runtime/routes/documents-routes.ts +3 -222
  495. package/src/runtime/routes/inbound-message-handler.ts +21 -45
  496. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  497. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  498. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  499. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  500. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  501. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
  502. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  503. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
  504. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
  505. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
  506. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  507. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  508. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  509. package/src/runtime/routes/log-export-routes.ts +3 -0
  510. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  511. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  512. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  513. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  514. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  515. package/src/runtime/routes/plugins-routes.ts +38 -8
  516. package/src/runtime/routes/schedule-routes.ts +94 -7
  517. package/src/runtime/routes/settings-routes.ts +6 -6
  518. package/src/runtime/routes/stt-routes.ts +31 -25
  519. package/src/runtime/routes/surface-action-routes.ts +20 -19
  520. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  521. package/src/runtime/routes/user-route-dispatcher.ts +69 -15
  522. package/src/runtime/routes/user-route-import.ts +108 -0
  523. package/src/runtime/routes/user-route-resolution.ts +18 -1
  524. package/src/runtime/routes/workspace-routes.ts +0 -9
  525. package/src/runtime/routes/workspace-utils.ts +3 -13
  526. package/src/runtime/services/conversation-serializer.ts +7 -2
  527. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  528. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  529. package/src/schedule/plugin-schedule-availability.ts +58 -0
  530. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  531. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  532. package/src/schedule/schedule-store.ts +5 -1
  533. package/src/schedule/scheduler.ts +9 -4
  534. package/src/schedule/worker-control.ts +1 -0
  535. package/src/schedule/worker.ts +6 -0
  536. package/src/security/oauth2.ts +6 -22
  537. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  538. package/src/stt/__tests__/types.test.ts +94 -0
  539. package/src/stt/daemon-batch-transcriber.ts +10 -0
  540. package/src/stt/stt-stream-session.ts +8 -4
  541. package/src/stt/types.ts +103 -0
  542. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  543. package/src/subagent/consult-context.ts +11 -11
  544. package/src/subagent/consult-prompt.ts +26 -35
  545. package/src/subagent/manager.ts +21 -40
  546. package/src/subagent/notify.ts +7 -1
  547. package/src/subagent/types.ts +15 -12
  548. package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
  549. package/src/tools/acp/spawn.test.ts +97 -0
  550. package/src/tools/acp/spawn.ts +38 -4
  551. package/src/tools/credentials/ref-parse.ts +35 -0
  552. package/src/tools/credentials/resolve.ts +4 -10
  553. package/src/tools/credentials/store.ts +168 -0
  554. package/src/tools/document/document-tool.ts +12 -3
  555. package/src/tools/executor.ts +53 -15
  556. package/src/tools/filesystem/read.ts +27 -10
  557. package/src/tools/host-filesystem/read.ts +27 -15
  558. package/src/tools/network/url-safety.ts +9 -16
  559. package/src/tools/permission-checker.ts +43 -52
  560. package/src/tools/registry.ts +2 -1
  561. package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
  562. package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
  563. package/src/tools/shared/filesystem/types.ts +5 -5
  564. package/src/tools/skills/scaffold-managed.ts +25 -7
  565. package/src/tools/subagent/spawn.ts +28 -88
  566. package/src/tools/tool-types.ts +26 -13
  567. package/src/tools/types.ts +6 -6
  568. package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
  569. package/src/tools/workflows/run-workflow.ts +1 -2
  570. package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
  571. package/src/tts/reasoning-tag-filter.ts +89 -0
  572. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  573. package/src/util/logger.ts +16 -0
  574. package/src/util/think-tag-stream.ts +95 -0
  575. package/src/util/worker-process.ts +37 -4
  576. package/src/windows-compiled-cli.ts +32 -0
  577. package/src/windows-compiled-entry.ts +4 -0
  578. package/src/windows-compiled-logger.ts +6 -0
  579. package/src/windows-compiled-worker-entry.ts +29 -0
  580. package/src/workspace/byok-default-profile-ensure.ts +76 -24
  581. package/src/workspace/custom-profile-ensure.ts +4 -24
  582. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  583. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  584. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  585. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  586. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  587. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  588. package/src/workspace/migrations/registry.ts +10 -0
  589. package/src/workspace/provider-commit-message-generator.ts +7 -5
  590. package/src/__tests__/document-workspace-file.test.ts +0 -467
  591. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  592. package/src/live-voice/front-decision.ts +0 -476
  593. package/src/permissions/ipc-risk-types.ts +0 -143
  594. package/src/permissions/risk-types.ts +0 -76
  595. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  596. package/src/subagent/consult-transcript.ts +0 -90
@@ -0,0 +1,134 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join, relative, sep } from "node:path";
3
+ import { describe, expect, test } from "bun:test";
4
+
5
+ import { Glob } from "bun";
6
+
7
+ /**
8
+ * Ratchet over direct reads of the `messages` table outside `persistence/`.
9
+ *
10
+ * Every row in `messages` is conversation content in some state of
11
+ * completeness, and each read encodes a visibility decision (completed
12
+ * history vs. any row). `persistence/` owns that contract: named accessors in
13
+ * `message-reads.ts` and the history readers in `conversation-crud.ts` state
14
+ * their decision in their name; everything else should go through them. A
15
+ * direct reader that re-decides visibility on its own is how the contract
16
+ * fails silently, so a new direct read is a reviewed exception, not a
17
+ * default.
18
+ *
19
+ * The BASELINE lists every file outside `persistence/` that reads the table
20
+ * directly today. Each entry has been audited and carries its visibility
21
+ * decision as a comment at the read site. The guard fails when a file outside
22
+ * the baseline gains a direct read (route it through an accessor, or audit it
23
+ * and baseline it with a site comment explaining the decision in your PR),
24
+ * and when a baseline entry goes stale (remove it, so coverage cannot rot).
25
+ *
26
+ * Detection is deliberately shape-based (from/join of `messages` in drizzle
27
+ * builders, `FROM messages` / `JOIN messages` in raw SQL): this is a ratchet
28
+ * against sprawl, not a proof, and matching the read shapes keeps it free of
29
+ * false positives from unrelated identifiers.
30
+ *
31
+ * Regenerate after an intentional change with:
32
+ * UPDATE_MESSAGES_READ_BASELINE=1 bun test src/__tests__/messages-read-boundary-guard.test.ts
33
+ * and paste the printed list here, explaining the new read in your PR.
34
+ */
35
+
36
+ const SRC_REL = "src";
37
+ const SRC_ABS = join(process.cwd(), SRC_REL);
38
+
39
+ /**
40
+ * Audited direct readers outside persistence/, as file -> count of matching
41
+ * read sites. Counts, not just filenames: a new read added inside an
42
+ * already-baselined file must fail the ratchet too, since each site carries
43
+ * its own visibility decision. A same-file refactor that replaces one read
44
+ * with another at equal count passes; that is accepted ratchet granularity
45
+ * (the diff still shows the site to its reviewer).
46
+ */
47
+ const BASELINE: Readonly<Record<string, number>> = {
48
+ "src/apps/app-store.ts": 1,
49
+ "src/daemon/credential-transcript-scrub.ts": 1,
50
+ "src/live-voice/live-voice-archive.ts": 1,
51
+ "src/monitoring/recovery/inflight-content.ts": 2,
52
+ "src/plugins/defaults/memory/context-search/sources/conversations.ts": 1,
53
+ "src/plugins/defaults/memory/graph/image-ref-utils.ts": 1,
54
+ "src/plugins/defaults/memory/indexer.ts": 2,
55
+ "src/plugins/defaults/memory/memory-retrospective-accounting.ts": 4,
56
+ "src/plugins/defaults/memory/substrate/sweep-job.ts": 1,
57
+ "src/plugins/defaults/memory/v1/graph/extraction.ts": 3,
58
+ "src/plugins/defaults/memory/v1/job-handlers/backfill.ts": 1,
59
+ "src/plugins/defaults/memory/v1/job-handlers/embedding.ts": 1,
60
+ "src/plugins/defaults/memory/v1/job-handlers/index-maintenance.ts": 1,
61
+ "src/plugins/defaults/memory/v2/harness/oracle.ts": 1,
62
+ "src/plugins/defaults/memory/v2/harness/replay-input.ts": 1,
63
+ "src/plugins/defaults/memory/v3-eval/eval-packets.ts": 2,
64
+ "src/plugins/defaults/memory/v3/prune.ts": 1,
65
+ "src/plugins/defaults/memory/v3/selection-log-store.ts": 1,
66
+ "src/runtime/pre-first-message-gate.ts": 1,
67
+ "src/runtime/routes/log-export-routes.ts": 1,
68
+ "src/runtime/routes/surface-conversation-resolver.ts": 2,
69
+ "src/telemetry/turn-events-store.ts": 2,
70
+ "src/telemetry/turn-trace-store.ts": 2,
71
+ "src/workspace/migrations/rebuild-conversation-disk-view.ts": 1,
72
+ };
73
+
74
+ const DRIZZLE_READ =
75
+ /\.(?:from|innerJoin|leftJoin|rightJoin|fullJoin)\(\s*messages\s*[,)]/g;
76
+ const RAW_SQL_READ = /(?:FROM|JOIN)\s+messages\b/g;
77
+
78
+ function isExempt(relPath: string): boolean {
79
+ const parts = relPath.split(sep);
80
+ return (
81
+ parts[1] === "persistence" ||
82
+ parts.includes("__tests__") ||
83
+ relPath.endsWith(".test.ts")
84
+ );
85
+ }
86
+
87
+ async function scanDirectReaders(): Promise<Map<string, number>> {
88
+ const glob = new Glob("**/*.ts");
89
+ const found = new Map<string, number>();
90
+ for await (const file of glob.scan({ cwd: SRC_ABS, absolute: true })) {
91
+ const relPath = join(SRC_REL, relative(SRC_ABS, file));
92
+ if (isExempt(relPath)) {
93
+ continue;
94
+ }
95
+ const source = readFileSync(file, "utf-8");
96
+ const count =
97
+ [...source.matchAll(DRIZZLE_READ)].length +
98
+ [...source.matchAll(RAW_SQL_READ)].length;
99
+ if (count > 0) {
100
+ found.set(relPath, count);
101
+ }
102
+ }
103
+ return found;
104
+ }
105
+
106
+ describe("messages read boundary", () => {
107
+ test("no new direct messages reads beyond the audited baseline", async () => {
108
+ const found = await scanDirectReaders();
109
+
110
+ if (process.env.UPDATE_MESSAGES_READ_BASELINE === "1") {
111
+ console.log(
112
+ JSON.stringify(Object.fromEntries([...found].sort()), null, 2),
113
+ );
114
+ }
115
+
116
+ const grown = [...found]
117
+ .filter(([file, count]) => count > (BASELINE[file] ?? 0))
118
+ .map(([file, count]) => `${file}: ${BASELINE[file] ?? 0} -> ${count}`);
119
+ expect(
120
+ grown,
121
+ `New direct reads of the messages table outside persistence/ (route ` +
122
+ `through an accessor in persistence/message-reads.ts, or audit the ` +
123
+ `read, state its visibility decision at the site, and baseline it):`,
124
+ ).toEqual([]);
125
+
126
+ const shrunk = Object.entries(BASELINE)
127
+ .filter(([file, count]) => (found.get(file) ?? 0) < count)
128
+ .map(([file, count]) => `${file}: ${count} -> ${found.get(file) ?? 0}`);
129
+ expect(
130
+ shrunk,
131
+ "Stale baseline entries (fewer direct reads than baselined; ratchet the count down so coverage cannot rot):",
132
+ ).toEqual([]);
133
+ });
134
+ });
@@ -30,14 +30,15 @@ import {
30
30
  } from "bun:test";
31
31
 
32
32
  import { _inspectHookCacheForTests } from "../hooks/hook-loader.js";
33
+ import { getUserHooksFor } from "../hooks/registry.js";
33
34
  import {
34
35
  _inspectToolCacheForTests,
35
36
  getCachedUserTools,
36
- getUserHooksFor,
37
37
  populateCacheAtBoot,
38
38
  reconcilePluginSourcesNow,
39
39
  resetPluginCacheForTests,
40
40
  } from "../plugins/mtime-cache.js";
41
+ import { resetPluginRegistryForTests } from "../plugins/registry.js";
41
42
  import { getSourceVersionsPath } from "../plugins/source-versions.js";
42
43
  import {
43
44
  __resetRegistryForTesting,
@@ -157,6 +158,7 @@ beforeEach(() => {
157
158
  // The registry pulls plugin tools via loadPluginTools(); clear its side
158
159
  // (tools + pulled fingerprints) so registrations never leak across tests.
159
160
  __resetRegistryForTesting();
161
+ resetPluginRegistryForTests();
160
162
  });
161
163
 
162
164
  afterAll(() => {
@@ -501,7 +501,6 @@ describe("access-request-helper unit tests", () => {
501
501
 
502
502
  test("notifyGuardianOfAccessRequest returns no_sender_id when actorExternalId is absent", async () => {
503
503
  const result = await notifyGuardianOfAccessRequest({
504
- canonicalAssistantId: "self",
505
504
  sourceChannel: "telegram",
506
505
  conversationExternalId: "chat-123",
507
506
  actorExternalId: undefined,
@@ -522,7 +521,6 @@ describe("access-request-helper unit tests", () => {
522
521
 
523
522
  test("notifyGuardianOfAccessRequest creates request with self-healed principal when no binding exists", async () => {
524
523
  const result = await notifyGuardianOfAccessRequest({
525
- canonicalAssistantId: "self",
526
524
  sourceChannel: "telegram",
527
525
  conversationExternalId: "chat-123",
528
526
  actorExternalId: "unknown-user",
@@ -565,7 +563,6 @@ describe("access-request-helper unit tests", () => {
565
563
  });
566
564
 
567
565
  const result = await notifyGuardianOfAccessRequest({
568
- canonicalAssistantId: "self",
569
566
  sourceChannel: "telegram",
570
567
  conversationExternalId: "tg-chat",
571
568
  actorExternalId: "unknown-tg-user",
@@ -619,7 +616,6 @@ describe("access-request-helper unit tests", () => {
619
616
  });
620
617
 
621
618
  const result = await notifyGuardianOfAccessRequest({
622
- canonicalAssistantId: "self",
623
619
  sourceChannel: "telegram",
624
620
  conversationExternalId: "chat-123",
625
621
  actorExternalId: "unknown-user",
@@ -660,7 +656,6 @@ describe("access-request-helper unit tests", () => {
660
656
  });
661
657
 
662
658
  const result = await notifyGuardianOfAccessRequest({
663
- canonicalAssistantId: "self",
664
659
  sourceChannel: "telegram",
665
660
  conversationExternalId: "chat-123",
666
661
  actorExternalId: "unknown-user",
@@ -688,7 +683,6 @@ describe("access-request-helper unit tests", () => {
688
683
  test("notifyGuardianOfAccessRequest resolves the vellum anchor from the gateway delivery", async () => {
689
684
  // Only the vellum anchor (seeded in resetState) is served by the gateway.
690
685
  const result = await notifyGuardianOfAccessRequest({
691
- canonicalAssistantId: "self",
692
686
  sourceChannel: "telegram",
693
687
  conversationExternalId: "chat-123",
694
688
  actorExternalId: "unknown-user",
@@ -722,7 +716,6 @@ describe("access-request-helper unit tests", () => {
722
716
 
723
717
  await expect(
724
718
  notifyGuardianOfAccessRequest({
725
- canonicalAssistantId: "self",
726
719
  sourceChannel: "telegram",
727
720
  conversationExternalId: "chat-123",
728
721
  actorExternalId: "unknown-user",
@@ -739,7 +732,6 @@ describe("access-request-helper unit tests", () => {
739
732
 
740
733
  test("notifyGuardianOfAccessRequest for voice channel includes actorDisplayName in contextPayload", async () => {
741
734
  const result = await notifyGuardianOfAccessRequest({
742
- canonicalAssistantId: "self",
743
735
  sourceChannel: "phone",
744
736
  conversationExternalId: "+15559998888",
745
737
  actorExternalId: "+15559998888",
@@ -770,7 +762,6 @@ describe("access-request-helper unit tests", () => {
770
762
 
771
763
  test("notifyGuardianOfAccessRequest includes requestCode in contextPayload", async () => {
772
764
  const result = await notifyGuardianOfAccessRequest({
773
- canonicalAssistantId: "self",
774
765
  sourceChannel: "telegram",
775
766
  conversationExternalId: "chat-123",
776
767
  actorExternalId: "unknown-user",
@@ -794,7 +785,6 @@ describe("access-request-helper unit tests", () => {
794
785
  // is created and the status is forwarded to the card payload. (A `revoked`
795
786
  // status would instead suppress — covered by the keep-out tests.)
796
787
  const result = await notifyGuardianOfAccessRequest({
797
- canonicalAssistantId: "self",
798
788
  sourceChannel: "telegram",
799
789
  conversationExternalId: "chat-123",
800
790
  actorExternalId: "pending-user",
@@ -834,7 +824,6 @@ describe("access-request-helper unit tests", () => {
834
824
  };
835
825
 
836
826
  const result = await notifyGuardianOfAccessRequest({
837
- canonicalAssistantId: "self",
838
827
  sourceChannel: "phone",
839
828
  conversationExternalId: "+15556667777",
840
829
  actorExternalId: "+15556667777",
@@ -897,7 +886,6 @@ describe("access-request-helper unit tests", () => {
897
886
  };
898
887
 
899
888
  const result = await notifyGuardianOfAccessRequest({
900
- canonicalAssistantId: "self",
901
889
  sourceChannel: "telegram",
902
890
  conversationExternalId: "chat-123",
903
891
  actorExternalId: "unknown-user",
@@ -929,7 +917,6 @@ describe("access-request-helper unit tests", () => {
929
917
  // request row. A revoked/blocked contact is deliberately kept out — no new
930
918
  // prompt, signal, or pending request.
931
919
  const result = await notifyGuardianOfAccessRequest({
932
- canonicalAssistantId: "self",
933
920
  sourceChannel: "telegram",
934
921
  conversationExternalId: "chat-kept-out",
935
922
  actorExternalId: "revoked-user",
@@ -969,7 +956,6 @@ describe("access-request-helper unit tests", () => {
969
956
  });
970
957
 
971
958
  const result = await notifyGuardianOfAccessRequest({
972
- canonicalAssistantId: "self",
973
959
  sourceChannel: "telegram",
974
960
  conversationExternalId: "chat-parked",
975
961
  actorExternalId: "parked-user",
@@ -1006,7 +992,6 @@ describe("access-request-helper unit tests", () => {
1006
992
  });
1007
993
 
1008
994
  const result = await notifyGuardianOfAccessRequest({
1009
- canonicalAssistantId: "self",
1010
995
  sourceChannel: "telegram",
1011
996
  conversationExternalId: "chat-approved",
1012
997
  actorExternalId: "approved-user",
@@ -1047,7 +1032,6 @@ describe("access-request-helper unit tests", () => {
1047
1032
  bridgeState.requests.get(requestId)!.updatedAt = staleUpdatedAt;
1048
1033
 
1049
1034
  const result = await notifyGuardianOfAccessRequest({
1050
- canonicalAssistantId: "self",
1051
1035
  sourceChannel: "telegram",
1052
1036
  conversationExternalId: "chat-stale",
1053
1037
  actorExternalId: "stale-user",
@@ -1082,7 +1066,6 @@ describe("access-request-helper unit tests", () => {
1082
1066
  });
1083
1067
 
1084
1068
  const result = await notifyGuardianOfAccessRequest({
1085
- canonicalAssistantId: "self",
1086
1069
  sourceChannel: "telegram",
1087
1070
  conversationExternalId: "chat-flip",
1088
1071
  actorExternalId: "flip-user",
@@ -1101,7 +1084,6 @@ describe("access-request-helper unit tests", () => {
1101
1084
  // Suppression is scoped to the sender via their own contact status, so a
1102
1085
  // kept-out contact never bleeds into a different sender's prompt.
1103
1086
  const keptOut = await notifyGuardianOfAccessRequest({
1104
- canonicalAssistantId: "self",
1105
1087
  sourceChannel: "telegram",
1106
1088
  conversationExternalId: "chat-kept",
1107
1089
  actorExternalId: "revoked-user",
@@ -1112,7 +1094,6 @@ describe("access-request-helper unit tests", () => {
1112
1094
 
1113
1095
  // A different sender with no keep-out still gets a fresh prompt.
1114
1096
  const result = await notifyGuardianOfAccessRequest({
1115
- canonicalAssistantId: "self",
1116
1097
  sourceChannel: "telegram",
1117
1098
  conversationExternalId: "chat-fresh",
1118
1099
  actorExternalId: "fresh-user",
@@ -1141,7 +1122,6 @@ describe("maybeNotifyGuardianOfAdmittedContact", () => {
1141
1122
  });
1142
1123
 
1143
1124
  const baseParams = {
1144
- canonicalAssistantId: "self",
1145
1125
  sourceChannel: "telegram" as const,
1146
1126
  conversationExternalId: "chat-123",
1147
1127
  actorExternalId: "user-unknown-456",
@@ -103,7 +103,10 @@ mock.module("../persistence/conversation-crud.js", () => ({
103
103
  },
104
104
  // The handler treats provenance as a flat spread; returning {} keeps the
105
105
  // metadata snapshot focused on the fields under test.
106
- provenanceFromTrustContext: () => ({}),
106
+ // Mirrors the real mapping so provenance assertions observe what production
107
+ // stamps rather than a placeholder.
108
+ provenanceFromTrustContext: (ctx?: { trustClass?: string }) =>
109
+ ctx ? { provenanceTrustClass: ctx.trustClass } : {},
107
110
  reserveMessage: mock(
108
111
  async (
109
112
  conversationId: string,
@@ -187,6 +190,8 @@ function makeDeps(
187
190
  requesterTimezoneLabel?: string;
188
191
  clientTimezone?: string;
189
192
  sourceThreadId?: string;
193
+ /** Turn-local trust; when set, the live slot below diverges from it. */
194
+ currentTurnTrustContext?: Record<string, unknown>;
190
195
  } = {},
191
196
  ): EventHandlerDeps {
192
197
  const assistantMessageChannel = overrides.assistantMessageChannel ?? "slack";
@@ -195,6 +200,7 @@ function makeDeps(
195
200
  conversationId,
196
201
  provider: { name: "anthropic" },
197
202
  currentTurnSurfaces: [],
203
+ currentTurnTrustContext: overrides.currentTurnTrustContext,
198
204
  trustContext: {
199
205
  sourceChannel: assistantMessageChannel,
200
206
  trustClass: "guardian",
@@ -439,6 +445,39 @@ describe("outbound assistant Slack metadata persistence", () => {
439
445
  expect(slackMeta.channelTs).toBeUndefined();
440
446
  });
441
447
 
448
+ test("envelope resolves from the turn's actor when the slot has moved", async () => {
449
+ // Provenance class and Slack routing must come from ONE actor: the turn's.
450
+ // A queued Slack turn runs while the guardian has since written the live
451
+ // slot; the assistant row must carry the sender's class AND the sender's
452
+ // channel/thread, not a mixed envelope.
453
+ const conversationId = "conv-slack-moved-slot";
454
+ const deps = makeDeps(conversationId, {
455
+ assistantMessageChannel: "slack",
456
+ // Live slot: the guardian, on a different chat, no thread.
457
+ requesterChatId: "C-GUARDIAN",
458
+ currentTurnTrustContext: {
459
+ sourceChannel: "slack",
460
+ trustClass: "trusted_contact",
461
+ requesterChatId: "C-CONTACT",
462
+ sourceThreadId: "1723300000.000100",
463
+ },
464
+ });
465
+ await handleLlmCallStarted(state, deps);
466
+ await handleMessageComplete(
467
+ state,
468
+ deps,
469
+ makeMessageCompleteEvent("reply to the contact"),
470
+ );
471
+
472
+ const persisted = lastAssistantPersisted();
473
+ expect(persisted.metadata?.provenanceTrustClass).toBe("trusted_contact");
474
+ const slackMeta = JSON.parse(
475
+ persisted.metadata?.slackMeta as string,
476
+ ) as Record<string, unknown>;
477
+ expect(slackMeta.channelId).toBe("C-CONTACT");
478
+ expect(slackMeta.threadTs).toBe("1723300000.000100");
479
+ });
480
+
442
481
  test("ignores a non-ts sourceThreadId", async () => {
443
482
  const conversationId = "conv-slack-bad-thread";
444
483
  const channelId = "C789BAD";
@@ -44,12 +44,8 @@ mock.module("../permissions/checker.js", () => ({
44
44
  }
45
45
  return { decision: "allow", reason: "allowed" };
46
46
  },
47
- generateAllowlistOptions: () => [
48
- { label: "exact", description: "exact", pattern: "exact" },
49
- ],
50
47
  generateScopeOptions: () =>
51
48
  scopeOptionsOverride ?? [{ label: "/tmp", scope: "/tmp" }],
52
- getCachedAssessment: () => undefined,
53
49
  }));
54
50
 
55
51
  // Mock every export so downstream test files that dynamically import modules
@@ -0,0 +1,261 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { existsSync, mkdirSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import {
6
+ afterAll,
7
+ afterEach,
8
+ beforeEach,
9
+ describe,
10
+ expect,
11
+ spyOn,
12
+ test,
13
+ } from "bun:test";
14
+
15
+ import { credentialKey } from "@vellumai/credential-storage";
16
+
17
+ import * as scrub from "../daemon/credential-transcript-scrub.js";
18
+ import * as manualToken from "../oauth/manual-token-connection.js";
19
+ import { resolveCredential } from "../plugin-api/resolve-credential.js";
20
+ import {
21
+ CredentialStoreError,
22
+ storeCredential,
23
+ } from "../plugin-api/store-credential.js";
24
+ import { runInPluginContext } from "../plugins/plugin-execution-context.js";
25
+ import * as secureKeys from "../security/secure-keys.js";
26
+ import {
27
+ _setMetadataPath,
28
+ getCredentialMetadata,
29
+ } from "../tools/credentials/metadata-store.js";
30
+ import { storeCredentialValue } from "../tools/credentials/store.js";
31
+
32
+ // Real metadata store backed by a temp file. A mock.module on metadata-store
33
+ // or secure-keys would replace the whole module namespace, so only the secure
34
+ // backend access, the transcript scrub, and the manual-token connection sync
35
+ // are intercepted, via restorable spies.
36
+
37
+ const TEST_DIR = join(
38
+ tmpdir(),
39
+ `vellum-plugin-storecred-${randomBytes(4).toString("hex")}`,
40
+ );
41
+ const META_PATH = join(TEST_DIR, "metadata.json");
42
+
43
+ /** The plugin every scoped case runs as: it owns the `acme` field. */
44
+ const PLUGIN = "acme";
45
+
46
+ /** Run `fn` as the plugin would: inside its execution context. */
47
+ function asPlugin<T>(fn: () => T): T {
48
+ return runInPluginContext(PLUGIN, fn);
49
+ }
50
+
51
+ let secureStore: Map<string, string>;
52
+ let writeSucceeds: boolean;
53
+ let setSpy: ReturnType<typeof spyOn>;
54
+ let getSpy: ReturnType<typeof spyOn>;
55
+ let scrubSpy: ReturnType<typeof spyOn>;
56
+ let syncSpy: ReturnType<typeof spyOn>;
57
+
58
+ beforeEach(() => {
59
+ if (existsSync(TEST_DIR)) {
60
+ rmSync(TEST_DIR, { recursive: true });
61
+ }
62
+ mkdirSync(TEST_DIR, { recursive: true });
63
+ _setMetadataPath(META_PATH);
64
+
65
+ secureStore = new Map();
66
+ writeSucceeds = true;
67
+ setSpy = spyOn(secureKeys, "setSecureKeyAsync").mockImplementation(
68
+ async (key: string, value: string) => {
69
+ if (!writeSucceeds) {
70
+ return false;
71
+ }
72
+ secureStore.set(key, value);
73
+ return true;
74
+ },
75
+ );
76
+ getSpy = spyOn(secureKeys, "getSecureKeyResultAsync").mockImplementation(
77
+ async (key: string) => ({
78
+ value: secureStore.get(key),
79
+ unreachable: false,
80
+ }),
81
+ );
82
+ scrubSpy = spyOn(
83
+ scrub,
84
+ "scrubStoredCredentialFromTranscripts",
85
+ ).mockImplementation(async () => ({
86
+ dbMessagesScrubbed: 0,
87
+ residentMessagesScrubbed: 0,
88
+ }));
89
+ syncSpy = spyOn(manualToken, "syncManualTokenConnection").mockImplementation(
90
+ async () => {},
91
+ );
92
+ });
93
+
94
+ afterEach(() => {
95
+ setSpy.mockRestore();
96
+ getSpy.mockRestore();
97
+ scrubSpy.mockRestore();
98
+ syncSpy.mockRestore();
99
+ });
100
+
101
+ afterAll(() => {
102
+ _setMetadataPath(null);
103
+ if (existsSync(TEST_DIR)) {
104
+ rmSync(TEST_DIR, { recursive: true });
105
+ }
106
+ });
107
+
108
+ describe("storeCredential", () => {
109
+ test("creates a credential from a service/field ref", async () => {
110
+ const stored = await asPlugin(() =>
111
+ storeCredential("openai/acme", "sk-secret"),
112
+ );
113
+
114
+ expect(stored.service).toBe("openai");
115
+ expect(stored.field).toBe(PLUGIN);
116
+ expect(stored.credentialId).toBeTruthy();
117
+ expect(secureStore.get(credentialKey("openai", "acme"))).toBe("sk-secret");
118
+ expect(getCredentialMetadata("openai", "acme")?.credentialId).toBe(
119
+ stored.credentialId,
120
+ );
121
+ });
122
+
123
+ test("stores a value that resolveCredential reads back", async () => {
124
+ await asPlugin(() => storeCredential("openai/acme", "sk-secret"));
125
+ await expect(
126
+ asPlugin(() => resolveCredential("openai/acme")),
127
+ ).resolves.toBe("sk-secret");
128
+ });
129
+
130
+ test("trims edge whitespace from the value", async () => {
131
+ await asPlugin(() => storeCredential("openai/acme", " sk-secret\n"));
132
+ expect(secureStore.get(credentialKey("openai", "acme"))).toBe("sk-secret");
133
+ });
134
+
135
+ test("records label and description on the credential metadata", async () => {
136
+ await asPlugin(() =>
137
+ storeCredential("openai/acme", "sk-secret", {
138
+ label: "Acme key",
139
+ description: "Used by the acme plugin",
140
+ }),
141
+ );
142
+
143
+ const metadata = getCredentialMetadata("openai", "acme");
144
+ expect(metadata?.alias).toBe("Acme key");
145
+ expect(metadata?.usageDescription).toBe("Used by the acme plugin");
146
+ });
147
+
148
+ test("replaces the value of an existing credential named by UUID", async () => {
149
+ const first = await asPlugin(() =>
150
+ storeCredential("openai/acme", "sk-old"),
151
+ );
152
+ const second = await asPlugin(() =>
153
+ storeCredential(first.credentialId, "sk-new"),
154
+ );
155
+
156
+ expect(second).toEqual(first);
157
+ expect(secureStore.get(credentialKey("openai", "acme"))).toBe("sk-new");
158
+ });
159
+
160
+ test("rejects a ref that names no credential and is not service/field", async () => {
161
+ await expect(
162
+ asPlugin(() => storeCredential("openai", "sk-secret")),
163
+ ).rejects.toBeInstanceOf(CredentialStoreError);
164
+ expect(setSpy).not.toHaveBeenCalled();
165
+ });
166
+
167
+ test("rejects an empty value", async () => {
168
+ await expect(
169
+ asPlugin(() => storeCredential("openai/acme", " ")),
170
+ ).rejects.toThrow(/value is required/);
171
+ expect(setSpy).not.toHaveBeenCalled();
172
+ });
173
+
174
+ test("surfaces a failed secure-backend write", async () => {
175
+ writeSucceeds = false;
176
+ await expect(
177
+ asPlugin(() => storeCredential("openai/acme", "sk-secret")),
178
+ ).rejects.toThrow(CredentialStoreError);
179
+ expect(getCredentialMetadata("openai", "acme")).toBeUndefined();
180
+ });
181
+
182
+ test("scrubs the stored value from recent transcripts", async () => {
183
+ await asPlugin(() => storeCredential("openai/acme", "sk-secret"));
184
+ expect(scrubSpy).toHaveBeenCalledWith("sk-secret");
185
+ });
186
+
187
+ test("skips the transcript scrub when asked", async () => {
188
+ await asPlugin(() =>
189
+ storeCredential("openai/acme", "sk-secret", {
190
+ skipTranscriptScrub: true,
191
+ }),
192
+ );
193
+ expect(scrubSpy).not.toHaveBeenCalled();
194
+ });
195
+
196
+ test("reconciles the manual-token connection for the service", async () => {
197
+ await asPlugin(() => storeCredential("openai/acme", "sk-secret"));
198
+ expect(syncSpy).toHaveBeenCalledWith("openai");
199
+ });
200
+
201
+ describe("plugin scoping", () => {
202
+ test("fails closed with no plugin in context", async () => {
203
+ // A plugin's module body is evaluated outside any context. An unscoped
204
+ // branch there would let top-level code overwrite the user's own
205
+ // credentials, so the write is refused instead.
206
+ await expect(storeCredential("openai/acme", "sk-secret")).rejects.toThrow(
207
+ /requires an active plugin execution context/,
208
+ );
209
+ expect(setSpy).not.toHaveBeenCalled();
210
+ expect(getCredentialMetadata("openai", "acme")).toBeUndefined();
211
+ });
212
+
213
+ test("blocks a plugin from storing a credential whose field differs from its name", async () => {
214
+ await expect(
215
+ asPlugin(() => storeCredential("openai/api_key", "sk-secret")),
216
+ ).rejects.toThrow(/out of scope/);
217
+ });
218
+
219
+ test("does not write the secure backend when out of scope", async () => {
220
+ await expect(
221
+ asPlugin(() => storeCredential("openai/api_key", "sk-secret")),
222
+ ).rejects.toThrow(CredentialStoreError);
223
+ expect(setSpy).not.toHaveBeenCalled();
224
+ expect(getCredentialMetadata("openai", "api_key")).toBeUndefined();
225
+ });
226
+
227
+ test("blocks a plugin from overwriting another credential by UUID", async () => {
228
+ // Seeded through the host write path, the way the user's own credential
229
+ // is created (`credentials set`), not through the plugin API.
230
+ const existing = await storeCredentialValue({
231
+ service: "openai",
232
+ field: "api_key",
233
+ value: "sk-user",
234
+ skipTranscriptScrub: true,
235
+ });
236
+ setSpy.mockClear();
237
+
238
+ await expect(
239
+ asPlugin(() => storeCredential(existing.credentialId, "sk-hijacked")),
240
+ ).rejects.toThrow(/out of scope/);
241
+ expect(setSpy).not.toHaveBeenCalled();
242
+ expect(secureStore.get(credentialKey("openai", "api_key"))).toBe(
243
+ "sk-user",
244
+ );
245
+ });
246
+
247
+ test("scoping applies by field only, across any service", async () => {
248
+ await asPlugin(async () => {
249
+ await storeCredential("stripe/acme", "stripe-secret");
250
+ await storeCredential("openai/acme", "openai-secret");
251
+ });
252
+
253
+ expect(secureStore.get(credentialKey("stripe", "acme"))).toBe(
254
+ "stripe-secret",
255
+ );
256
+ expect(secureStore.get(credentialKey("openai", "acme"))).toBe(
257
+ "openai-secret",
258
+ );
259
+ });
260
+ });
261
+ });