@vellumai/assistant 0.9.0 → 0.10.0-dev.202606192226.8ed8a6a

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 (624) hide show
  1. package/ARCHITECTURE.md +19 -35
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +1128 -119
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
  28. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  29. package/src/__tests__/app-compiler.test.ts +15 -1
  30. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  31. package/src/__tests__/approval-cascade.test.ts +6 -0
  32. package/src/__tests__/approval-routes-http.test.ts +128 -0
  33. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  34. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  35. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  36. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  37. package/src/__tests__/btw-routes.test.ts +6 -8
  38. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  39. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  40. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  41. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  42. package/src/__tests__/checker.test.ts +0 -3
  43. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  44. package/src/__tests__/compaction-events.test.ts +6 -0
  45. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  46. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  47. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  48. package/src/__tests__/config-schema.test.ts +35 -0
  49. package/src/__tests__/config-watcher.test.ts +0 -18
  50. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  51. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  52. package/src/__tests__/contacts-tools.test.ts +29 -0
  53. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  57. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
  59. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  60. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  61. package/src/__tests__/conversation-process-callsite.test.ts +6 -0
  62. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  63. package/src/__tests__/conversation-queue.test.ts +6 -0
  64. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  65. package/src/__tests__/conversation-slash-queue.test.ts +6 -0
  66. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  67. package/src/__tests__/conversation-speed-override.test.ts +6 -0
  68. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  69. package/src/__tests__/conversation-title-service.test.ts +62 -0
  70. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
  71. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  72. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  73. package/src/__tests__/credential-broker.test.ts +449 -1
  74. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  75. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  76. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  77. package/src/__tests__/credential-routes.test.ts +360 -0
  78. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  79. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  80. package/src/__tests__/disk-pressure-tools.test.ts +41 -1
  81. package/src/__tests__/disk-usage.test.ts +65 -0
  82. package/src/__tests__/document-tool-security.test.ts +9 -6
  83. package/src/__tests__/document-update-default-surface.test.ts +202 -0
  84. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  85. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  86. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  87. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  88. package/src/__tests__/gemini-count-tokens.test.ts +70 -0
  89. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  90. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  91. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  92. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  93. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  94. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  95. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  96. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  97. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  98. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  99. package/src/__tests__/identity-routes.test.ts +0 -189
  100. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  101. package/src/__tests__/injector-disk-pressure.test.ts +10 -5
  102. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  103. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  104. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  105. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  106. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  107. package/src/__tests__/llm-resolver.test.ts +70 -24
  108. package/src/__tests__/llm-schema.test.ts +1 -0
  109. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  110. package/src/__tests__/mcp-health-check.test.ts +6 -7
  111. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  112. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  113. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  114. package/src/__tests__/path-policy.test.ts +34 -0
  115. package/src/__tests__/persona-resolver.test.ts +49 -14
  116. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  118. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  119. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  120. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  121. package/src/__tests__/reaction-persistence.test.ts +150 -29
  122. package/src/__tests__/registry.test.ts +2 -7
  123. package/src/__tests__/relay-server.test.ts +285 -0
  124. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  125. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  126. package/src/__tests__/schedule-routes.test.ts +0 -30
  127. package/src/__tests__/schedule-tools.test.ts +2 -18
  128. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  129. package/src/__tests__/secret-response-routing.test.ts +35 -0
  130. package/src/__tests__/secret-routes-platform-proxy.test.ts +16 -2
  131. package/src/__tests__/skill-execute-input.test.ts +177 -1
  132. package/src/__tests__/skill-load-tool.test.ts +72 -0
  133. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  134. package/src/__tests__/skills.test.ts +51 -0
  135. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  136. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  137. package/src/__tests__/subagent-tools.test.ts +266 -0
  138. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  139. package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
  140. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  141. package/src/__tests__/title-generate-hook.test.ts +100 -3
  142. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  143. package/src/__tests__/token-manager.test.ts +519 -0
  144. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  145. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  146. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  147. package/src/__tests__/tool-executor.test.ts +0 -79
  148. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  149. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  150. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  151. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  152. package/src/__tests__/twilio-routes.test.ts +81 -1
  153. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  154. package/src/__tests__/weak-open-model.test.ts +30 -0
  155. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  156. package/src/__tests__/workspace-greetings.test.ts +152 -0
  157. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  158. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  159. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  160. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  161. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  162. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  163. package/src/agent/loop.ts +75 -29
  164. package/src/api/README.md +6 -6
  165. package/src/api/events/tool-result.ts +6 -0
  166. package/src/api/events/workflow-completed.ts +53 -0
  167. package/src/api/events/workflow-leaf-finished.ts +38 -0
  168. package/src/api/events/workflow-leaf-started.ts +35 -0
  169. package/src/api/events/workflow-progress.ts +32 -0
  170. package/src/api/events/workflow-started.ts +31 -0
  171. package/src/api/index.ts +40 -0
  172. package/src/api/responses/conversation-message.ts +28 -4
  173. package/src/api/responses/home.ts +26 -4
  174. package/src/api/responses/workflow-journal.ts +53 -0
  175. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  176. package/src/approvals/guardian-decision-primitive.ts +26 -3
  177. package/src/approvals/guardian-request-resolvers.ts +183 -80
  178. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  179. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  180. package/src/calls/call-pointer-messages.ts +10 -4
  181. package/src/calls/channel-admission-reader.ts +104 -0
  182. package/src/calls/guardian-dispatch.ts +17 -45
  183. package/src/calls/media-stream-server.ts +84 -2
  184. package/src/calls/relay-access-wait.ts +1 -1
  185. package/src/calls/relay-server.ts +66 -0
  186. package/src/calls/relay-setup-router.ts +82 -1
  187. package/src/calls/twilio-routes.ts +17 -8
  188. package/src/calls/voice-session-bridge.ts +2 -2
  189. package/src/cli/commands/clients.ts +3 -0
  190. package/src/cli/commands/db/status.ts +37 -3
  191. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  192. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  193. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  194. package/src/cli/commands/memory/index.ts +30 -0
  195. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  196. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  197. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  198. package/src/cli/commands/oauth/status.test.ts +36 -0
  199. package/src/cli/commands/oauth/status.ts +23 -3
  200. package/src/cli/commands/plugins.ts +400 -14
  201. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  202. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  203. package/src/cli/lib/__tests__/install-from-github.test.ts +31 -0
  204. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  205. package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
  206. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  207. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  208. package/src/cli/lib/diff-plugin.ts +346 -0
  209. package/src/cli/lib/inspect-plugin.ts +12 -1
  210. package/src/cli/lib/install-from-github.ts +128 -19
  211. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  212. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  213. package/src/cli/lib/plugin-marketplace.ts +11 -0
  214. package/src/cli/lib/plugin-pin-history.ts +257 -0
  215. package/src/cli/lib/plugin-surfaces.ts +104 -0
  216. package/src/cli/lib/upgrade-plugin.ts +298 -10
  217. package/src/cli/program.ts +2 -6
  218. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  219. package/src/config/assistant-feature-flags.ts +22 -7
  220. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  221. package/src/config/bundled-skills/document-editor/SKILL.md +3 -3
  222. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  223. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  224. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  225. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  226. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  227. package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
  228. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  229. package/src/config/bundled-tool-registry.ts +2 -7
  230. package/src/config/call-site-defaults.ts +15 -2
  231. package/src/config/feature-flag-registry.json +46 -31
  232. package/src/config/inference-profile-validation.ts +26 -0
  233. package/src/config/llm-resolver.ts +3 -0
  234. package/src/config/loader.ts +4 -0
  235. package/src/config/memory-v3-gate.ts +11 -0
  236. package/src/config/profile-order.ts +28 -0
  237. package/src/config/schema.ts +8 -6
  238. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  239. package/src/config/schemas/call-site-catalog.ts +7 -0
  240. package/src/config/schemas/channels.ts +11 -0
  241. package/src/config/schemas/elevenlabs.ts +0 -1
  242. package/src/config/schemas/llm.ts +31 -0
  243. package/src/config/schemas/memory-lifecycle.ts +3 -7
  244. package/src/config/schemas/memory-v3.ts +6 -0
  245. package/src/config/schemas/platform.ts +0 -8
  246. package/src/config/schemas/services.ts +18 -0
  247. package/src/config/seed-inference-profiles.ts +109 -44
  248. package/src/config/skills.ts +21 -0
  249. package/src/config/sync-gated-profiles.ts +220 -0
  250. package/src/contacts/contact-store.ts +89 -106
  251. package/src/contacts/contacts-write.ts +5 -22
  252. package/src/contacts/types.ts +0 -1
  253. package/src/context/compactor.ts +88 -54
  254. package/src/context/strip-injections.ts +58 -10
  255. package/src/context/token-estimator.ts +1 -1
  256. package/src/credential-execution/process-manager.ts +55 -14
  257. package/src/credential-execution/prompted-credential.ts +2 -3
  258. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  259. package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
  260. package/src/daemon/config-watcher.ts +0 -4
  261. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  262. package/src/daemon/conversation-agent-loop.ts +114 -22
  263. package/src/daemon/conversation-history.ts +1 -1
  264. package/src/daemon/conversation-lifecycle.ts +3 -5
  265. package/src/daemon/conversation-process.ts +13 -5
  266. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  267. package/src/daemon/conversation-slash.ts +2 -23
  268. package/src/daemon/conversation-surfaces.ts +26 -0
  269. package/src/daemon/conversation-tool-setup.ts +53 -19
  270. package/src/daemon/conversation.ts +131 -20
  271. package/src/daemon/disk-pressure-policy.ts +5 -3
  272. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  273. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  274. package/src/daemon/handlers/config-a2a.ts +0 -2
  275. package/src/daemon/handlers/config-channels.ts +15 -16
  276. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  277. package/src/daemon/handlers/conversations.ts +107 -0
  278. package/src/daemon/host-browser-proxy.ts +41 -0
  279. package/src/daemon/lifecycle.ts +55 -27
  280. package/src/daemon/memory-v2-startup.test.ts +131 -0
  281. package/src/daemon/memory-v2-startup.ts +100 -1
  282. package/src/daemon/message-provenance.ts +2 -0
  283. package/src/daemon/message-types/contacts.ts +0 -1
  284. package/src/daemon/message-types/conversations.ts +3 -3
  285. package/src/daemon/message-types/sync.ts +0 -1
  286. package/src/daemon/message-types/web-activity.ts +7 -1
  287. package/src/daemon/message-types/workflows.ts +83 -1
  288. package/src/daemon/orphan-reaper.test.ts +0 -19
  289. package/src/daemon/orphan-reaper.ts +2 -24
  290. package/src/daemon/server.ts +0 -10
  291. package/src/daemon/tool-setup-types.ts +4 -0
  292. package/src/daemon/trust-context.ts +1 -1
  293. package/src/daemon/wake-conversation-ops.ts +1 -0
  294. package/src/events/tool-audit-listener.ts +2 -2
  295. package/src/home/feed-source-enrichment.test.ts +151 -0
  296. package/src/home/feed-source-enrichment.ts +176 -0
  297. package/src/home/relationship-state.ts +2 -4
  298. package/src/instrument.ts +18 -6
  299. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  300. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  301. package/src/ipc/assistant-server.ts +37 -4
  302. package/src/ipc/gateway-flag-listener.ts +18 -2
  303. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  304. package/src/memory/__tests__/db-logs-attach.test.ts +122 -0
  305. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  306. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  307. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  308. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  309. package/src/memory/auth-fallback-events-store.ts +2 -2
  310. package/src/memory/auto-analysis-enqueue.ts +3 -5
  311. package/src/memory/bookmark-crud.ts +1 -2
  312. package/src/memory/canonical-guardian-store.ts +39 -1
  313. package/src/memory/conversation-crud.ts +9 -4
  314. package/src/memory/conversation-key-store.ts +17 -2
  315. package/src/memory/conversation-title-service.ts +64 -7
  316. package/src/memory/db-async-query.ts +20 -2
  317. package/src/memory/db-connection.ts +49 -5
  318. package/src/memory/db-init.ts +17 -17
  319. package/src/memory/embedding-backend.ts +38 -1
  320. package/src/memory/embedding-billing-breaker.ts +96 -0
  321. package/src/memory/jobs-store.ts +25 -13
  322. package/src/memory/jobs-worker.ts +54 -1
  323. package/src/memory/lifecycle-events-store.ts +2 -2
  324. package/src/memory/memory-retrospective-constants.ts +4 -4
  325. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  326. package/src/memory/memory-retrospective-job.ts +28 -227
  327. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  328. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  329. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  330. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  331. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  332. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  333. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  334. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  335. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  336. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  337. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  338. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  339. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  340. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  341. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  342. package/src/memory/migrations/index.ts +6 -0
  343. package/src/memory/migrations/run-migrations.ts +41 -0
  344. package/src/memory/migrations/validate-migration-state.ts +1 -1
  345. package/src/memory/onboarding-events-store.ts +3 -3
  346. package/src/memory/schema/contacts.ts +0 -5
  347. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  348. package/src/memory/skill-loaded-events-store.ts +2 -2
  349. package/src/memory/tool-executed-events-store.test.ts +7 -7
  350. package/src/memory/turn-trace-store.test.ts +736 -0
  351. package/src/memory/turn-trace-store.ts +364 -0
  352. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  353. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  354. package/src/memory/v2/consolidation-job.ts +2 -2
  355. package/src/memory/v2/skill-content.ts +25 -7
  356. package/src/memory/v2/skill-store.ts +7 -1
  357. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  358. package/src/memory/v3-eval/eval-packets.ts +546 -0
  359. package/src/messaging/providers/slack/adapter.ts +1 -1
  360. package/src/messaging/providers/slack/api.ts +31 -0
  361. package/src/messaging/providers/slack/send.test.ts +114 -2
  362. package/src/messaging/providers/slack/send.ts +30 -7
  363. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  364. package/src/messaging/providers/slack/withdraw.ts +161 -0
  365. package/src/notifications/AGENTS.md +2 -0
  366. package/src/notifications/access-request-copy.ts +72 -59
  367. package/src/notifications/adapters/shared.ts +29 -0
  368. package/src/notifications/adapters/slack.ts +58 -103
  369. package/src/notifications/adapters/telegram.ts +2 -20
  370. package/src/notifications/approval-card-data.ts +333 -0
  371. package/src/notifications/broadcaster.ts +16 -3
  372. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  373. package/src/notifications/copy-composer.ts +3 -3
  374. package/src/notifications/decision-engine.ts +4 -2
  375. package/src/notifications/destination-resolver.ts +4 -6
  376. package/src/notifications/guardian-question-mode.ts +10 -0
  377. package/src/notifications/home-feed-side-effect.ts +7 -16
  378. package/src/notifications/notification-utils.ts +19 -20
  379. package/src/notifications/signal.ts +79 -43
  380. package/src/notifications/types.ts +98 -121
  381. package/src/oauth/AGENTS.md +5 -24
  382. package/src/permissions/checker.test.ts +51 -0
  383. package/src/permissions/checker.ts +185 -26
  384. package/src/permissions/ipc-risk-types.ts +24 -0
  385. package/src/permissions/question-prompter.test.ts +27 -0
  386. package/src/permissions/question-prompter.ts +4 -0
  387. package/src/permissions/secret-prompter.ts +15 -1
  388. package/src/platform/client.test.ts +119 -0
  389. package/src/platform/client.ts +66 -0
  390. package/src/platform/consent-cache.test.ts +267 -0
  391. package/src/platform/consent-cache.ts +174 -0
  392. package/src/plugin-api/constants.ts +1 -1
  393. package/src/plugin-api/index.ts +33 -1
  394. package/src/plugin-api/model-profiles.ts +33 -0
  395. package/src/plugin-api/types.ts +50 -2
  396. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  397. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  398. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  399. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  400. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  401. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  402. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  403. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  404. package/src/plugins/defaults/advisor/config.ts +21 -0
  405. package/src/plugins/defaults/advisor/consult.ts +93 -0
  406. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  407. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  408. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  409. package/src/plugins/defaults/advisor/package.json +14 -0
  410. package/src/plugins/defaults/advisor/steering.ts +67 -0
  411. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  412. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  413. package/src/plugins/defaults/compaction/window-manager.ts +18 -0
  414. package/src/plugins/defaults/index.ts +60 -0
  415. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  416. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  417. package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
  418. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  419. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  420. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  421. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  422. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  423. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  424. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  425. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  426. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  427. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  428. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  429. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  430. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  431. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  432. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  433. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  434. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  435. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  436. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  437. package/src/prompts/persona-resolver.ts +14 -4
  438. package/src/prompts/templates/system-sections.ts +7 -2
  439. package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
  440. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  441. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  442. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  443. package/src/providers/anthropic/client.ts +230 -175
  444. package/src/providers/atlascloud/client.ts +85 -0
  445. package/src/providers/call-site-routing.ts +10 -0
  446. package/src/providers/fetch-provider-catalog.ts +85 -0
  447. package/src/providers/gemini/client.ts +43 -0
  448. package/src/providers/inference/adapter-factory.ts +3 -0
  449. package/src/providers/model-catalog.ts +58 -0
  450. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  451. package/src/providers/openai/chat-completions-provider.ts +7 -0
  452. package/src/providers/openai/responses-provider.ts +10 -0
  453. package/src/providers/provider-send-message.ts +11 -3
  454. package/src/providers/ratelimit.ts +9 -0
  455. package/src/providers/retry.ts +62 -12
  456. package/src/providers/search-provider-catalog.ts +10 -0
  457. package/src/providers/types.ts +16 -0
  458. package/src/providers/usage-tracking.ts +7 -0
  459. package/src/providers/weak-open-model.ts +22 -0
  460. package/src/runtime/AGENTS.md +0 -1
  461. package/src/runtime/__tests__/agent-wake.test.ts +227 -0
  462. package/src/runtime/__tests__/client-health.test.ts +44 -0
  463. package/src/runtime/access-request-helper.ts +21 -53
  464. package/src/runtime/actor-trust-resolver.ts +59 -63
  465. package/src/runtime/agent-wake.ts +71 -0
  466. package/src/runtime/assistant-event-hub.ts +18 -4
  467. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  468. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  469. package/src/runtime/btw-sidechain.ts +3 -6
  470. package/src/runtime/capabilities.test.ts +120 -0
  471. package/src/runtime/capabilities.ts +197 -0
  472. package/src/runtime/channel-approval-types.ts +22 -45
  473. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  474. package/src/runtime/channel-retry-sweep.ts +1 -0
  475. package/src/runtime/channel-verification-service.ts +3 -3
  476. package/src/runtime/client-health.ts +26 -0
  477. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  478. package/src/runtime/effective-capabilities.test.ts +128 -0
  479. package/src/runtime/effective-capabilities.ts +84 -0
  480. package/src/runtime/guardian-reply-router.ts +106 -21
  481. package/src/runtime/invite-redemption-service.ts +9 -25
  482. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +126 -0
  483. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  484. package/src/runtime/pending-interactions.ts +35 -0
  485. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  486. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  487. package/src/runtime/routes/__tests__/plugins-routes.test.ts +383 -1
  488. package/src/runtime/routes/app-routes.ts +1 -1
  489. package/src/runtime/routes/approval-routes.ts +40 -9
  490. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  491. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  492. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  493. package/src/runtime/routes/btw-routes.ts +1 -27
  494. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  495. package/src/runtime/routes/client-routes.ts +10 -0
  496. package/src/runtime/routes/contact-routes.ts +31 -8
  497. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  498. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  499. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  500. package/src/runtime/routes/conversation-routes.ts +39 -14
  501. package/src/runtime/routes/credential-routes.ts +40 -16
  502. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  503. package/src/runtime/routes/events-routes.ts +1 -3
  504. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  505. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  506. package/src/runtime/routes/home-feed-routes.ts +8 -3
  507. package/src/runtime/routes/identity-routes.ts +1 -296
  508. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  509. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  510. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  511. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  512. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  513. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  514. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  515. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  516. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  517. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  518. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  519. package/src/runtime/routes/index.ts +2 -0
  520. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  521. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  522. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  523. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  524. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  525. package/src/runtime/routes/notification-routes.ts +122 -133
  526. package/src/runtime/routes/platform-routes.ts +2 -2
  527. package/src/runtime/routes/plugins-routes.ts +366 -11
  528. package/src/runtime/routes/schedule-routes.ts +0 -22
  529. package/src/runtime/routes/secret-routes.ts +15 -0
  530. package/src/runtime/routes/surface-action-routes.ts +2 -1
  531. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  532. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  533. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  534. package/src/runtime/routes/workflow-routes.ts +131 -29
  535. package/src/runtime/routes/workspace-greetings.ts +55 -0
  536. package/src/runtime/sync/resource-sync-events.ts +1 -11
  537. package/src/runtime/tool-grant-request-helper.ts +18 -16
  538. package/src/runtime/trust-context-resolver.ts +8 -5
  539. package/src/schedule/inference-profile.ts +2 -14
  540. package/src/schedule/schedule-store.ts +1 -1
  541. package/src/schedule/scheduler-types.ts +5 -1
  542. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  543. package/src/security/secret-patterns.ts +3 -0
  544. package/src/subagent/manager.ts +17 -4
  545. package/src/subagent/types.ts +6 -0
  546. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  547. package/src/telemetry/trace-collection-policy.ts +30 -0
  548. package/src/telemetry/types.ts +89 -0
  549. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  550. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  551. package/src/tools/AGENTS.md +3 -3
  552. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  553. package/src/tools/browser/browser-execution.ts +30 -19
  554. package/src/tools/document/document-tool.ts +30 -6
  555. package/src/tools/executor.ts +5 -3
  556. package/src/tools/host-terminal/host-shell.ts +5 -4
  557. package/src/tools/memory/register.ts +2 -2
  558. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  559. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  560. package/src/tools/network/web-fetch.ts +372 -1
  561. package/src/tools/network/web-search-error.ts +1 -1
  562. package/src/tools/network/web-search.ts +213 -10
  563. package/src/tools/permission-checker.ts +4 -3
  564. package/src/tools/registry.ts +20 -0
  565. package/src/tools/schedule/create.ts +7 -12
  566. package/src/tools/schedule/update.ts +4 -11
  567. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  568. package/src/tools/side-effects.ts +2 -17
  569. package/src/tools/skills/execute.ts +99 -2
  570. package/src/tools/skills/load.ts +43 -2
  571. package/src/tools/subagent/spawn.ts +64 -15
  572. package/src/tools/terminal/shell.ts +10 -4
  573. package/src/tools/tool-approval-handler.ts +18 -13
  574. package/src/tools/tool-manifest.ts +0 -2
  575. package/src/tools/types.ts +10 -0
  576. package/src/tools/ui-surface/definitions.ts +64 -3
  577. package/src/tools/verification-control-plane-policy.ts +3 -1
  578. package/src/tools/workflows/run-workflow.test.ts +8 -18
  579. package/src/tools/workflows/run-workflow.ts +1 -0
  580. package/src/util/disk-usage.ts +78 -23
  581. package/src/util/logs-db-path.ts +22 -0
  582. package/src/util/platform.ts +10 -3
  583. package/src/watcher/telemetry.ts +2 -2
  584. package/src/workflows/capabilities.ts +2 -3
  585. package/src/workflows/engine.test.ts +175 -1
  586. package/src/workflows/engine.ts +82 -0
  587. package/src/workflows/journal-store.test.ts +70 -0
  588. package/src/workflows/journal-store.ts +18 -3
  589. package/src/workflows/run-manager.test.ts +171 -28
  590. package/src/workflows/run-manager.ts +66 -24
  591. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  592. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  593. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  594. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  595. package/src/workspace/migrations/registry.ts +8 -0
  596. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  597. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  598. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  599. package/src/__tests__/credential-vault.test.ts +0 -1706
  600. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  601. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  602. package/src/cli/commands/__tests__/task.test.ts +0 -914
  603. package/src/cli/commands/task.ts +0 -771
  604. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  605. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  606. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  607. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  608. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  609. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  610. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  611. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  612. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  613. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  614. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  615. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  616. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  617. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  618. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  619. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  620. package/src/memory/preloaded-apps.ts +0 -116
  621. package/src/notifications/tool-approval-copy.ts +0 -142
  622. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  623. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  624. package/src/tools/credentials/vault.ts +0 -712
@@ -0,0 +1,519 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { mkdirSync, readdirSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import {
6
+ afterAll,
7
+ afterEach,
8
+ beforeAll,
9
+ beforeEach,
10
+ describe,
11
+ expect,
12
+ mock,
13
+ test,
14
+ } from "bun:test";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Mock logger
18
+ // ---------------------------------------------------------------------------
19
+
20
+ mock.module("../util/logger.js", () => ({
21
+ getLogger: () =>
22
+ new Proxy({} as Record<string, unknown>, {
23
+ get: () => () => {},
24
+ }),
25
+ }));
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Use encrypted backend with a temp store path
29
+ // ---------------------------------------------------------------------------
30
+
31
+ import { _resetBackend, setSecureKeyAsync } from "../security/secure-keys.js";
32
+ import { setStorePathForTesting } from "./encrypted-store-test-helpers.js";
33
+
34
+ const TEST_DIR = join(
35
+ tmpdir(),
36
+ `vellum-token-manager-test-${randomBytes(4).toString("hex")}`,
37
+ );
38
+ const STORE_PATH = join(TEST_DIR, "keys.enc");
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Mock OAuth2 token refresh so dedup can be observed without network I/O
42
+ // ---------------------------------------------------------------------------
43
+
44
+ let mockRefreshOAuth2Token: ReturnType<
45
+ typeof mock<
46
+ (
47
+ tokenExchangeUrl: string,
48
+ clientId: string,
49
+ refreshToken: string,
50
+ clientSecret?: string,
51
+ tokenEndpointAuthMethod?: string,
52
+ ) => Promise<{ accessToken: string; expiresIn: number }>
53
+ >
54
+ >;
55
+
56
+ mock.module("../security/oauth2.js", () => {
57
+ mockRefreshOAuth2Token = mock(() =>
58
+ Promise.resolve({
59
+ accessToken: "refreshed-access-token",
60
+ expiresIn: 3600,
61
+ }),
62
+ );
63
+ return {
64
+ refreshOAuth2Token: mockRefreshOAuth2Token,
65
+ };
66
+ });
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Mock oauth-store — token-manager reads refresh config from SQLite
70
+ // ---------------------------------------------------------------------------
71
+
72
+ /** Mutable per-test map of provider connections for getConnectionByProvider */
73
+ const mockConnections = new Map<
74
+ string,
75
+ {
76
+ id: string;
77
+ provider: string;
78
+ oauthAppId: string;
79
+ expiresAt: number | null;
80
+ }
81
+ >();
82
+ const mockApps = new Map<
83
+ string,
84
+ {
85
+ id: string;
86
+ provider: string;
87
+ clientId: string;
88
+ clientSecretCredentialPath: string;
89
+ }
90
+ >();
91
+ const mockProviders = new Map<
92
+ string,
93
+ {
94
+ key: string;
95
+ tokenExchangeUrl: string;
96
+ refreshUrl?: string | null;
97
+ tokenEndpointAuthMethod?: string;
98
+ }
99
+ >();
100
+
101
+ mock.module("../oauth/oauth-store.js", () => ({
102
+ getConnectionByProvider: (service: string) => mockConnections.get(service),
103
+ getConnection: (id: string) => {
104
+ for (const conn of mockConnections.values()) {
105
+ if (conn.id === id) return conn;
106
+ }
107
+ return undefined;
108
+ },
109
+ getApp: (id: string) => mockApps.get(id),
110
+ getProvider: (key: string) => mockProviders.get(key),
111
+ updateConnection: () => {},
112
+ }));
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Import the modules under test
116
+ // ---------------------------------------------------------------------------
117
+
118
+ import {
119
+ _resetInflightRefreshes,
120
+ _resetRefreshBreakers,
121
+ withValidToken,
122
+ } from "../security/token-manager.js";
123
+ import { _setMetadataPath } from "../tools/credentials/metadata-store.js";
124
+
125
+ describe("withValidToken refresh deduplication", () => {
126
+ beforeAll(() => {
127
+ mkdirSync(TEST_DIR, { recursive: true });
128
+ });
129
+
130
+ beforeEach(() => {
131
+ _resetBackend();
132
+ for (const entry of readdirSync(TEST_DIR)) {
133
+ rmSync(join(TEST_DIR, entry), { recursive: true, force: true });
134
+ }
135
+ setStorePathForTesting(STORE_PATH);
136
+ _setMetadataPath(join(TEST_DIR, "metadata.json"));
137
+ _resetRefreshBreakers();
138
+ _resetInflightRefreshes();
139
+ mockRefreshOAuth2Token.mockClear();
140
+ // Clear mock oauth-store maps
141
+ mockConnections.clear();
142
+ mockApps.clear();
143
+ mockProviders.clear();
144
+ });
145
+
146
+ afterEach(() => {
147
+ _setMetadataPath(null);
148
+ setStorePathForTesting(null);
149
+ _resetBackend();
150
+ _resetRefreshBreakers();
151
+ _resetInflightRefreshes();
152
+ mockConnections.clear();
153
+ mockApps.clear();
154
+ mockProviders.clear();
155
+ });
156
+
157
+ afterAll(() => {
158
+ rmSync(TEST_DIR, { recursive: true, force: true });
159
+ });
160
+
161
+ /**
162
+ * Helper: set up a service with an access token, refresh token, and
163
+ * mock DB data so that token refresh can proceed through doRefresh().
164
+ *
165
+ * OAuth-specific fields (tokenExchangeUrl, clientId, expiresAt) are stored
166
+ * in the SQLite oauth-store. The mock maps simulate the DB layer.
167
+ */
168
+ async function setupService(
169
+ service: string,
170
+ opts?: { expired?: boolean; accessToken?: string },
171
+ ) {
172
+ const accessToken = opts?.accessToken ?? "old-access-token";
173
+
174
+ // Seed mock oauth-store maps so token-manager can resolve refresh config
175
+ const appId = `app-${service}`;
176
+ const connId = `conn-${service}`;
177
+
178
+ // Store access token under the oauth_connection key path that
179
+ // withValidToken reads (not the legacy credentialKey path).
180
+ await setSecureKeyAsync(
181
+ `oauth_connection/${connId}/access_token`,
182
+ accessToken,
183
+ );
184
+ mockProviders.set(service, {
185
+ key: service,
186
+ tokenExchangeUrl: "https://oauth.example.com/token",
187
+ refreshUrl: null,
188
+ });
189
+ mockApps.set(appId, {
190
+ id: appId,
191
+ provider: service,
192
+ clientId: "test-client-id",
193
+ clientSecretCredentialPath: `oauth_app/${appId}/client_secret`,
194
+ });
195
+ mockConnections.set(service, {
196
+ id: connId,
197
+ provider: service,
198
+ oauthAppId: appId,
199
+ expiresAt: opts?.expired
200
+ ? Date.now() - 60_000 // expired 1 minute ago
201
+ : Date.now() + 3600_000, // expires in 1 hour
202
+ });
203
+ // Store refresh token and client_secret in secure keys (token-manager reads them)
204
+ await setSecureKeyAsync(
205
+ `oauth_connection/${connId}/refresh_token`,
206
+ "valid-refresh-token",
207
+ );
208
+ await setSecureKeyAsync(
209
+ `oauth_app/${appId}/client_secret`,
210
+ "test-client-secret",
211
+ );
212
+ }
213
+
214
+ test("3 concurrent 401 refreshes for the same service call doRefresh exactly once", async () => {
215
+ await setupService("google");
216
+
217
+ let resolveRefresh!: (value: {
218
+ accessToken: string;
219
+ expiresIn: number;
220
+ }) => void;
221
+ const refreshPromise = new Promise<{
222
+ accessToken: string;
223
+ expiresIn: number;
224
+ }>((resolve) => {
225
+ resolveRefresh = resolve;
226
+ });
227
+
228
+ mockRefreshOAuth2Token.mockImplementation(() => refreshPromise);
229
+
230
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
231
+
232
+ const callback = async (token: string) => {
233
+ if (token === "old-access-token") throw err401;
234
+ return `result-with-${token}`;
235
+ };
236
+
237
+ // Launch 3 concurrent withValidToken calls — all will get a non-expired
238
+ // token first, call the callback, get a 401, and then try to refresh.
239
+ const p1 = withValidToken("google", callback);
240
+ const p2 = withValidToken("google", callback);
241
+ const p3 = withValidToken("google", callback);
242
+
243
+ // Let the event loop tick so all 3 calls enter the 401 retry path
244
+ await new Promise((r) => setTimeout(r, 10));
245
+
246
+ // Resolve the single refresh attempt
247
+ resolveRefresh({ accessToken: "new-token-123", expiresIn: 3600 });
248
+
249
+ const results = await Promise.all([p1, p2, p3]);
250
+
251
+ // All 3 should succeed with the refreshed token
252
+ expect(results).toEqual([
253
+ "result-with-new-token-123",
254
+ "result-with-new-token-123",
255
+ "result-with-new-token-123",
256
+ ]);
257
+
258
+ // refreshOAuth2Token should have been called exactly once
259
+ expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(1);
260
+ });
261
+
262
+ test("concurrent refreshes for different services proceed independently", async () => {
263
+ await setupService("google");
264
+ await setupService("slack");
265
+
266
+ let resolveGmail!: (value: {
267
+ accessToken: string;
268
+ expiresIn: number;
269
+ }) => void;
270
+ let resolveSlack!: (value: {
271
+ accessToken: string;
272
+ expiresIn: number;
273
+ }) => void;
274
+
275
+ const gmailPromise = new Promise<{
276
+ accessToken: string;
277
+ expiresIn: number;
278
+ }>((resolve) => {
279
+ resolveGmail = resolve;
280
+ });
281
+ const slackPromise = new Promise<{
282
+ accessToken: string;
283
+ expiresIn: number;
284
+ }>((resolve) => {
285
+ resolveSlack = resolve;
286
+ });
287
+
288
+ let refreshCallCount = 0;
289
+ mockRefreshOAuth2Token.mockImplementation(() => {
290
+ refreshCallCount++;
291
+ // Both services use the same tokenExchangeUrl in this test, so we track by
292
+ // call order to return the correct deferred promise.
293
+ if (refreshCallCount === 1) return gmailPromise;
294
+ return slackPromise;
295
+ });
296
+
297
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
298
+
299
+ const gmailCallback = async (token: string) => {
300
+ if (token === "old-access-token") throw err401;
301
+ return `gmail-${token}`;
302
+ };
303
+ const slackCallback = async (token: string) => {
304
+ if (token === "old-access-token") throw err401;
305
+ return `slack-${token}`;
306
+ };
307
+
308
+ const p1 = withValidToken("google", gmailCallback);
309
+ const p2 = withValidToken("slack", slackCallback);
310
+
311
+ await new Promise((r) => setTimeout(r, 10));
312
+
313
+ // Resolve both independently
314
+ resolveGmail({ accessToken: "gmail-new-token", expiresIn: 3600 });
315
+ resolveSlack({ accessToken: "slack-new-token", expiresIn: 3600 });
316
+
317
+ const [r1, r2] = await Promise.all([p1, p2]);
318
+
319
+ expect(r1).toBe("gmail-gmail-new-token");
320
+ expect(r2).toBe("slack-slack-new-token");
321
+
322
+ // Both services should have triggered their own refresh
323
+ expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(2);
324
+ });
325
+
326
+ test("deduplication cleans up after refresh completes, allowing subsequent refreshes", async () => {
327
+ await setupService("google");
328
+
329
+ let refreshCount = 0;
330
+ mockRefreshOAuth2Token.mockImplementation(() => {
331
+ refreshCount++;
332
+ return Promise.resolve({
333
+ accessToken: `token-${refreshCount}`,
334
+ expiresIn: 3600,
335
+ });
336
+ });
337
+
338
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
339
+
340
+ // First call triggers a refresh (old token → 401 → refresh → token-1)
341
+ const r1 = await withValidToken("google", async (token: string) => {
342
+ if (token !== "token-1") throw err401;
343
+ return token;
344
+ });
345
+ expect(r1).toBe("token-1");
346
+ expect(refreshCount).toBe(1);
347
+
348
+ // Second call also triggers a 401 to verify dedup state was cleaned up
349
+ // and a new refresh is allowed (not deduplicated with the first).
350
+ const r2 = await withValidToken("google", async (token: string) => {
351
+ if (token !== "token-2") throw err401;
352
+ return token;
353
+ });
354
+ expect(r2).toBe("token-2");
355
+ // Second refresh should have happened (not deduplicated with the first,
356
+ // since the first already completed)
357
+ expect(refreshCount).toBe(2);
358
+ });
359
+
360
+ test("deduplication propagates refresh errors to all waiting callers", async () => {
361
+ await setupService("google");
362
+
363
+ mockRefreshOAuth2Token.mockImplementation(() =>
364
+ Promise.reject(
365
+ Object.assign(
366
+ new Error("OAuth2 token refresh failed (HTTP 401: invalid_grant)"),
367
+ ),
368
+ ),
369
+ );
370
+
371
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
372
+
373
+ const callback = async (token: string) => {
374
+ if (token === "old-access-token") throw err401;
375
+ return "should-not-reach";
376
+ };
377
+
378
+ // Launch 2 concurrent calls — both should fail with the same error
379
+ const p1 = withValidToken("google", callback);
380
+ const p2 = withValidToken("google", callback);
381
+
382
+ const results = await Promise.allSettled([p1, p2]);
383
+
384
+ expect(results[0].status).toBe("rejected");
385
+ expect(results[1].status).toBe("rejected");
386
+
387
+ // Only one actual refresh attempt
388
+ expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(1);
389
+ });
390
+
391
+ // -----------------------------------------------------------------------
392
+ // refreshUrl resolution — provider.refreshUrl with fallback to tokenExchangeUrl
393
+ // -----------------------------------------------------------------------
394
+ describe("refreshUrl resolution", () => {
395
+ test("uses provider.refreshUrl when set", async () => {
396
+ await setupService("google");
397
+ mockProviders.get("google")!.refreshUrl =
398
+ "https://refresh.example.com/token";
399
+
400
+ mockRefreshOAuth2Token.mockImplementation(() =>
401
+ Promise.resolve({
402
+ accessToken: "new-token-from-refresh-url",
403
+ expiresIn: 3600,
404
+ }),
405
+ );
406
+
407
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
408
+
409
+ const callback = async (token: string) => {
410
+ if (token === "old-access-token") throw err401;
411
+ return `result-with-${token}`;
412
+ };
413
+
414
+ const result = await withValidToken("google", callback);
415
+
416
+ expect(result).toBe("result-with-new-token-from-refresh-url");
417
+ expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(1);
418
+ // Assert the refresh endpoint passed in is provider.refreshUrl, not
419
+ // the tokenExchangeUrl fallback.
420
+ expect(mockRefreshOAuth2Token.mock.calls[0]?.[0]).toBe(
421
+ "https://refresh.example.com/token",
422
+ );
423
+ });
424
+
425
+ test("falls back to provider.tokenExchangeUrl when refreshUrl is null", async () => {
426
+ // setupService sets refreshUrl: null by default — this exercises the
427
+ // fallback path explicitly.
428
+ await setupService("google");
429
+ expect(mockProviders.get("google")!.refreshUrl).toBeNull();
430
+
431
+ mockRefreshOAuth2Token.mockImplementation(() =>
432
+ Promise.resolve({
433
+ accessToken: "new-token-from-token-exchange-url",
434
+ expiresIn: 3600,
435
+ }),
436
+ );
437
+
438
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
439
+
440
+ const callback = async (token: string) => {
441
+ if (token === "old-access-token") throw err401;
442
+ return `result-with-${token}`;
443
+ };
444
+
445
+ const result = await withValidToken("google", callback);
446
+
447
+ expect(result).toBe("result-with-new-token-from-token-exchange-url");
448
+ expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(1);
449
+ // Assert the refresh endpoint falls back to tokenExchangeUrl.
450
+ expect(mockRefreshOAuth2Token.mock.calls[0]?.[0]).toBe(
451
+ "https://oauth.example.com/token",
452
+ );
453
+ });
454
+
455
+ test("falls back to provider.tokenExchangeUrl when refreshUrl is undefined", async () => {
456
+ await setupService("google");
457
+ // Delete the refreshUrl field entirely so the property is `undefined`
458
+ // rather than `null`. Both representations of "not set" must produce
459
+ // the fallback behavior.
460
+ delete mockProviders.get("google")!.refreshUrl;
461
+ expect(mockProviders.get("google")!.refreshUrl).toBeUndefined();
462
+
463
+ mockRefreshOAuth2Token.mockImplementation(() =>
464
+ Promise.resolve({
465
+ accessToken: "new-token-from-token-exchange-url",
466
+ expiresIn: 3600,
467
+ }),
468
+ );
469
+
470
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
471
+
472
+ const callback = async (token: string) => {
473
+ if (token === "old-access-token") throw err401;
474
+ return `result-with-${token}`;
475
+ };
476
+
477
+ const result = await withValidToken("google", callback);
478
+
479
+ expect(result).toBe("result-with-new-token-from-token-exchange-url");
480
+ expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(1);
481
+ // Assert the refresh endpoint falls back to tokenExchangeUrl.
482
+ expect(mockRefreshOAuth2Token.mock.calls[0]?.[0]).toBe(
483
+ "https://oauth.example.com/token",
484
+ );
485
+ });
486
+
487
+ test("falls back to provider.tokenExchangeUrl when refreshUrl is empty string", async () => {
488
+ // Platform's Python `oauth_app.refresh_url or oauth_app.token_exchange_url`
489
+ // treats an empty string as unset. We use `||` (not `??`) so empty
490
+ // strings follow the same fallback path and never resolve to an empty
491
+ // endpoint.
492
+ await setupService("google");
493
+ mockProviders.get("google")!.refreshUrl = "";
494
+
495
+ mockRefreshOAuth2Token.mockImplementation(() =>
496
+ Promise.resolve({
497
+ accessToken: "new-token-from-token-exchange-url",
498
+ expiresIn: 3600,
499
+ }),
500
+ );
501
+
502
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
503
+
504
+ const callback = async (token: string) => {
505
+ if (token === "old-access-token") throw err401;
506
+ return `result-with-${token}`;
507
+ };
508
+
509
+ const result = await withValidToken("google", callback);
510
+
511
+ expect(result).toBe("result-with-new-token-from-token-exchange-url");
512
+ expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(1);
513
+ // Assert the refresh endpoint falls back to tokenExchangeUrl — NOT "".
514
+ expect(mockRefreshOAuth2Token.mock.calls[0]?.[0]).toBe(
515
+ "https://oauth.example.com/token",
516
+ );
517
+ });
518
+ });
519
+ });
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import { buildToolApprovalSeedContentBlocks } from "../notifications/tool-approval-copy.js";
3
+ import { buildToolApprovalSeedContentBlocks } from "../notifications/approval-card-data.js";
4
4
 
5
5
  describe("buildToolApprovalSeedContentBlocks", () => {
6
6
  const toolApprovalPayload: Record<string, unknown> = {
@@ -1,11 +1,11 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
- // Toggle for the collectUsageData opt-out gate the listener consults when
3
+ // Toggle for the share_analytics opt-out gate the listener consults when
4
4
  // populating the telemetry columns.
5
- let collectUsageData = true;
5
+ let shareAnalytics = true;
6
6
 
7
- mock.module("../config/loader.js", () => ({
8
- getConfig: () => ({ collectUsageData }),
7
+ mock.module("../platform/consent-cache.js", () => ({
8
+ getCachedShareAnalytics: () => shareAnalytics,
9
9
  }));
10
10
 
11
11
  import { createToolAuditListener } from "../events/tool-audit-listener.js";
@@ -17,7 +17,7 @@ import {
17
17
 
18
18
  describe("tool audit listener", () => {
19
19
  beforeEach(() => {
20
- collectUsageData = true;
20
+ shareAnalytics = true;
21
21
  });
22
22
 
23
23
  test("records executed events with truncated output", () => {
@@ -361,8 +361,8 @@ describe("tool audit listener", () => {
361
361
  );
362
362
  });
363
363
 
364
- test("persists NULL telemetry columns when usage data collection is opted out", () => {
365
- collectUsageData = false;
364
+ test("persists NULL telemetry columns when share_analytics is opted out", () => {
365
+ shareAnalytics = false;
366
366
  const records: ToolInvocationRecord[] = [];
367
367
  const listener = createToolAuditListener((record) => records.push(record));
368
368
 
@@ -34,9 +34,6 @@ const mockConfig = {
34
34
  permissions: {
35
35
  mode: "workspace" as const,
36
36
  },
37
- // The audit listener nulls the telemetry columns when this is false; the
38
- // end-to-end listener tests below assert the opted-in sizing behavior.
39
- collectUsageData: true,
40
37
  };
41
38
 
42
39
  let checkerDecision: "allow" | "prompt" | "deny" = "allow";
@@ -56,6 +53,12 @@ mock.module("../config/loader.js", () => ({
56
53
  setNestedValue: () => {},
57
54
  }));
58
55
 
56
+ // Analytics consent is granted so the audit listener populates the telemetry
57
+ // columns; the end-to-end listener tests below assert the opted-in sizing.
58
+ mock.module("../platform/consent-cache.js", () => ({
59
+ getCachedShareAnalytics: () => true,
60
+ }));
61
+
59
62
  mock.module("../util/logger.js", () => ({
60
63
  getLogger: () =>
61
64
  new Proxy({} as Record<string, unknown>, {
@@ -587,36 +587,6 @@ describe("isSideEffectTool", () => {
587
587
  expect(isSideEffectTool("nonexistent_tool")).toBe(false);
588
588
  expect(isSideEffectTool("")).toBe(false);
589
589
  });
590
-
591
- describe("action-aware classification for mixed-action tools", () => {
592
- test("credential_store store is a side-effect", () => {
593
- expect(isSideEffectTool("credential_store", { action: "store" })).toBe(
594
- true,
595
- );
596
- });
597
-
598
- test("credential_store delete is a side-effect", () => {
599
- expect(isSideEffectTool("credential_store", { action: "delete" })).toBe(
600
- true,
601
- );
602
- });
603
-
604
- test("credential_store prompt is a side-effect", () => {
605
- expect(isSideEffectTool("credential_store", { action: "prompt" })).toBe(
606
- true,
607
- );
608
- });
609
-
610
- test("credential_store list is NOT a side-effect", () => {
611
- expect(isSideEffectTool("credential_store", { action: "list" })).toBe(
612
- false,
613
- );
614
- });
615
-
616
- test("credential_store without input is NOT a side-effect", () => {
617
- expect(isSideEffectTool("credential_store")).toBe(false);
618
- });
619
- });
620
590
  });
621
591
 
622
592
  // ---------------------------------------------------------------------------
@@ -751,10 +721,6 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
751
721
  { name: "document_create", input: { title: "doc", content: "body" } },
752
722
  { name: "document_update", input: { id: "doc-1", content: "updated" } },
753
723
  { name: "document_delete", input: { surface_id: "doc-1" } },
754
- {
755
- name: "credential_store",
756
- input: { action: "store", name: "api-key", value: "secret" },
757
- },
758
724
  ];
759
725
 
760
726
  for (const { name, input } of sideEffectTools) {
@@ -817,51 +783,6 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
817
783
  expect(promptCalled).toBe(true);
818
784
  });
819
785
 
820
- // ── Credential store action-aware (PR fix9) ──────────
821
-
822
- test("credential_store store forces prompt under forcePromptSideEffects", async () => {
823
- checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
824
-
825
- const executor = new ToolExecutor(makeTrackingPrompter());
826
- const result = await executor.execute(
827
- "credential_store",
828
- { action: "store", name: "api-key", value: "sk-secret-123" },
829
- makeContext({ forcePromptSideEffects: true }),
830
- );
831
-
832
- expect(result.isError).toBe(false);
833
- expect(promptCalled).toBe(true);
834
- });
835
-
836
- test("credential_store delete forces prompt under forcePromptSideEffects", async () => {
837
- checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
838
-
839
- const executor = new ToolExecutor(makeTrackingPrompter());
840
- const result = await executor.execute(
841
- "credential_store",
842
- { action: "delete", name: "api-key" },
843
- makeContext({ forcePromptSideEffects: true }),
844
- );
845
-
846
- expect(result.isError).toBe(false);
847
- expect(promptCalled).toBe(true);
848
- });
849
-
850
- test("credential_store list does NOT force prompt under forcePromptSideEffects", async () => {
851
- checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
852
-
853
- const executor = new ToolExecutor(makeTrackingPrompter());
854
- const result = await executor.execute(
855
- "credential_store",
856
- { action: "list" },
857
- makeContext({ forcePromptSideEffects: true }),
858
- );
859
-
860
- expect(result.isError).toBe(false);
861
- // list is read-only — must NOT trigger forced prompting
862
- expect(promptCalled).toBe(false);
863
- });
864
-
865
786
  // ── Workspace mode + forcePromptSideEffects interaction ──────────
866
787
 
867
788
  test("workspace mode allow → prompt promotion still works for side-effect tools under forcePromptSideEffects", async () => {
@@ -169,8 +169,10 @@ async function simulateNotifierPoll(params: {
169
169
  notifiedRequestIds,
170
170
  } = params;
171
171
 
172
- // Gate check: only trusted contacts with guardian route
173
- if (trustClass !== "trusted_contact" || !guardianExternalUserId) {
172
+ // Gate check: only identity-known non-guardian contacts with guardian route
173
+ const isIdentityKnownNonGuardian =
174
+ trustClass === "trusted_contact" || trustClass === "unverified_contact";
175
+ if (!isIdentityKnownNonGuardian || !guardianExternalUserId) {
174
176
  return false;
175
177
  }
176
178