@vellumai/assistant 0.9.0 → 0.10.0-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (572) hide show
  1. package/ARCHITECTURE.md +18 -34
  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 +976 -63
  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__/always-loaded-tools-guard.test.ts +2 -3
  28. package/src/__tests__/app-compiler.test.ts +15 -1
  29. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  32. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  33. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  34. package/src/__tests__/btw-routes.test.ts +6 -8
  35. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  36. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  37. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  38. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  39. package/src/__tests__/checker.test.ts +0 -3
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  41. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  44. package/src/__tests__/config-schema.test.ts +35 -0
  45. package/src/__tests__/config-watcher.test.ts +0 -18
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  47. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  48. package/src/__tests__/contacts-tools.test.ts +29 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  53. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  54. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  55. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  56. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  57. package/src/__tests__/conversation-title-service.test.ts +62 -0
  58. package/src/__tests__/credential-broker.test.ts +449 -1
  59. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  60. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  61. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  62. package/src/__tests__/credential-routes.test.ts +360 -0
  63. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  64. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  65. package/src/__tests__/disk-usage.test.ts +65 -0
  66. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  67. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  68. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  69. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  71. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  72. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  73. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  74. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  75. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  76. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  77. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  78. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  79. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  80. package/src/__tests__/identity-routes.test.ts +0 -189
  81. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  82. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  83. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  84. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  85. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  86. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  87. package/src/__tests__/llm-resolver.test.ts +70 -24
  88. package/src/__tests__/llm-schema.test.ts +1 -0
  89. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  90. package/src/__tests__/mcp-health-check.test.ts +6 -7
  91. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  92. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  93. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  94. package/src/__tests__/path-policy.test.ts +34 -0
  95. package/src/__tests__/persona-resolver.test.ts +49 -14
  96. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  97. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  98. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  99. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  100. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  101. package/src/__tests__/reaction-persistence.test.ts +150 -29
  102. package/src/__tests__/registry.test.ts +2 -7
  103. package/src/__tests__/relay-server.test.ts +285 -0
  104. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  105. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  106. package/src/__tests__/schedule-routes.test.ts +0 -30
  107. package/src/__tests__/schedule-tools.test.ts +2 -18
  108. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  109. package/src/__tests__/skill-execute-input.test.ts +51 -1
  110. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  111. package/src/__tests__/skills.test.ts +51 -0
  112. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  113. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  114. package/src/__tests__/subagent-tools.test.ts +266 -0
  115. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  116. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  117. package/src/__tests__/title-generate-hook.test.ts +100 -3
  118. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  119. package/src/__tests__/token-manager.test.ts +519 -0
  120. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  121. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  122. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  123. package/src/__tests__/tool-executor.test.ts +0 -79
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  126. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  127. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  128. package/src/__tests__/twilio-routes.test.ts +81 -1
  129. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  130. package/src/__tests__/weak-open-model.test.ts +30 -0
  131. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  132. package/src/__tests__/workspace-greetings.test.ts +152 -0
  133. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  134. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  136. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  137. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  138. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  139. package/src/agent/loop.ts +49 -29
  140. package/src/api/README.md +6 -6
  141. package/src/api/events/tool-result.ts +6 -0
  142. package/src/api/events/workflow-completed.ts +53 -0
  143. package/src/api/events/workflow-leaf-finished.ts +38 -0
  144. package/src/api/events/workflow-leaf-started.ts +35 -0
  145. package/src/api/events/workflow-progress.ts +32 -0
  146. package/src/api/events/workflow-started.ts +31 -0
  147. package/src/api/index.ts +40 -0
  148. package/src/api/responses/conversation-message.ts +28 -4
  149. package/src/api/responses/home.ts +26 -4
  150. package/src/api/responses/workflow-journal.ts +53 -0
  151. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  152. package/src/approvals/guardian-decision-primitive.ts +26 -3
  153. package/src/approvals/guardian-request-resolvers.ts +183 -80
  154. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  155. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  156. package/src/calls/call-pointer-messages.ts +10 -4
  157. package/src/calls/channel-admission-reader.ts +104 -0
  158. package/src/calls/guardian-dispatch.ts +17 -45
  159. package/src/calls/media-stream-server.ts +84 -2
  160. package/src/calls/relay-access-wait.ts +1 -1
  161. package/src/calls/relay-server.ts +66 -0
  162. package/src/calls/relay-setup-router.ts +82 -1
  163. package/src/calls/twilio-routes.ts +17 -8
  164. package/src/calls/voice-session-bridge.ts +2 -2
  165. package/src/cli/commands/clients.ts +3 -0
  166. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  167. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  168. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  169. package/src/cli/commands/memory/index.ts +30 -0
  170. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  171. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  172. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  173. package/src/cli/commands/oauth/status.test.ts +36 -0
  174. package/src/cli/commands/oauth/status.ts +23 -3
  175. package/src/cli/commands/plugins.ts +197 -4
  176. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  177. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  178. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  179. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  181. package/src/cli/lib/diff-plugin.ts +346 -0
  182. package/src/cli/lib/inspect-plugin.ts +12 -1
  183. package/src/cli/lib/install-from-github.ts +105 -17
  184. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  185. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  186. package/src/cli/lib/plugin-surfaces.ts +104 -0
  187. package/src/cli/lib/upgrade-plugin.ts +298 -10
  188. package/src/cli/program.ts +2 -6
  189. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  190. package/src/config/assistant-feature-flags.ts +22 -7
  191. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  192. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  193. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  194. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  195. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  196. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  197. package/src/config/bundled-tool-registry.ts +2 -7
  198. package/src/config/call-site-defaults.ts +15 -2
  199. package/src/config/feature-flag-registry.json +46 -31
  200. package/src/config/inference-profile-validation.ts +26 -0
  201. package/src/config/llm-resolver.ts +3 -0
  202. package/src/config/loader.ts +4 -0
  203. package/src/config/memory-v3-gate.ts +11 -0
  204. package/src/config/profile-order.ts +28 -0
  205. package/src/config/schema.ts +8 -6
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  207. package/src/config/schemas/call-site-catalog.ts +7 -0
  208. package/src/config/schemas/channels.ts +11 -0
  209. package/src/config/schemas/elevenlabs.ts +0 -1
  210. package/src/config/schemas/llm.ts +31 -0
  211. package/src/config/schemas/memory-lifecycle.ts +3 -7
  212. package/src/config/schemas/memory-v3.ts +6 -0
  213. package/src/config/schemas/platform.ts +0 -8
  214. package/src/config/schemas/services.ts +18 -0
  215. package/src/config/seed-inference-profiles.ts +109 -44
  216. package/src/config/skills.ts +21 -0
  217. package/src/config/sync-gated-profiles.ts +220 -0
  218. package/src/contacts/contact-store.ts +89 -106
  219. package/src/contacts/contacts-write.ts +5 -22
  220. package/src/contacts/types.ts +0 -1
  221. package/src/context/compactor.ts +88 -54
  222. package/src/context/strip-injections.ts +58 -10
  223. package/src/context/token-estimator.ts +1 -1
  224. package/src/credential-execution/process-manager.ts +55 -14
  225. package/src/credential-execution/prompted-credential.ts +2 -3
  226. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  227. package/src/daemon/config-watcher.ts +0 -4
  228. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  229. package/src/daemon/conversation-agent-loop.ts +114 -22
  230. package/src/daemon/conversation-history.ts +1 -1
  231. package/src/daemon/conversation-lifecycle.ts +3 -5
  232. package/src/daemon/conversation-process.ts +13 -5
  233. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  234. package/src/daemon/conversation-slash.ts +2 -23
  235. package/src/daemon/conversation-surfaces.ts +26 -0
  236. package/src/daemon/conversation-tool-setup.ts +27 -14
  237. package/src/daemon/conversation.ts +66 -14
  238. package/src/daemon/disk-pressure-policy.ts +5 -3
  239. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  240. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  241. package/src/daemon/handlers/config-a2a.ts +0 -2
  242. package/src/daemon/handlers/config-channels.ts +15 -16
  243. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  244. package/src/daemon/handlers/conversations.ts +107 -0
  245. package/src/daemon/host-browser-proxy.ts +41 -0
  246. package/src/daemon/lifecycle.ts +55 -27
  247. package/src/daemon/message-provenance.ts +2 -0
  248. package/src/daemon/message-types/contacts.ts +0 -1
  249. package/src/daemon/message-types/conversations.ts +3 -3
  250. package/src/daemon/message-types/sync.ts +0 -1
  251. package/src/daemon/message-types/web-activity.ts +7 -1
  252. package/src/daemon/message-types/workflows.ts +83 -1
  253. package/src/daemon/orphan-reaper.test.ts +0 -19
  254. package/src/daemon/orphan-reaper.ts +2 -24
  255. package/src/daemon/server.ts +0 -10
  256. package/src/daemon/tool-setup-types.ts +4 -0
  257. package/src/daemon/trust-context.ts +1 -1
  258. package/src/events/tool-audit-listener.ts +2 -2
  259. package/src/home/feed-source-enrichment.test.ts +151 -0
  260. package/src/home/feed-source-enrichment.ts +176 -0
  261. package/src/home/relationship-state.ts +2 -4
  262. package/src/instrument.ts +18 -6
  263. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  264. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  265. package/src/ipc/assistant-server.ts +37 -4
  266. package/src/ipc/gateway-flag-listener.ts +18 -2
  267. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  268. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  269. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  270. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  271. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  272. package/src/memory/auth-fallback-events-store.ts +2 -2
  273. package/src/memory/auto-analysis-enqueue.ts +3 -5
  274. package/src/memory/bookmark-crud.ts +1 -2
  275. package/src/memory/canonical-guardian-store.ts +39 -1
  276. package/src/memory/conversation-crud.ts +9 -4
  277. package/src/memory/conversation-key-store.ts +17 -2
  278. package/src/memory/conversation-title-service.ts +64 -7
  279. package/src/memory/db-init.ts +17 -17
  280. package/src/memory/embedding-backend.ts +38 -1
  281. package/src/memory/embedding-billing-breaker.ts +96 -0
  282. package/src/memory/jobs-store.ts +25 -13
  283. package/src/memory/jobs-worker.ts +54 -1
  284. package/src/memory/lifecycle-events-store.ts +2 -2
  285. package/src/memory/memory-retrospective-constants.ts +4 -4
  286. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  287. package/src/memory/memory-retrospective-job.ts +28 -227
  288. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  289. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  290. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  291. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  292. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  293. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  294. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  295. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  296. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  297. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  298. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  299. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  300. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  301. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  302. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  303. package/src/memory/migrations/index.ts +6 -0
  304. package/src/memory/migrations/run-migrations.ts +41 -0
  305. package/src/memory/migrations/validate-migration-state.ts +1 -1
  306. package/src/memory/onboarding-events-store.ts +3 -3
  307. package/src/memory/schema/contacts.ts +0 -5
  308. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  309. package/src/memory/skill-loaded-events-store.ts +2 -2
  310. package/src/memory/tool-executed-events-store.test.ts +7 -7
  311. package/src/memory/turn-trace-store.test.ts +736 -0
  312. package/src/memory/turn-trace-store.ts +364 -0
  313. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  314. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  315. package/src/memory/v2/consolidation-job.ts +2 -2
  316. package/src/memory/v2/skill-content.ts +25 -7
  317. package/src/memory/v2/skill-store.ts +7 -1
  318. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  319. package/src/memory/v3-eval/eval-packets.ts +546 -0
  320. package/src/messaging/providers/slack/adapter.ts +1 -1
  321. package/src/messaging/providers/slack/api.ts +31 -0
  322. package/src/messaging/providers/slack/send.test.ts +114 -2
  323. package/src/messaging/providers/slack/send.ts +30 -7
  324. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  325. package/src/messaging/providers/slack/withdraw.ts +161 -0
  326. package/src/notifications/AGENTS.md +2 -0
  327. package/src/notifications/access-request-copy.ts +72 -59
  328. package/src/notifications/adapters/shared.ts +29 -0
  329. package/src/notifications/adapters/slack.ts +58 -103
  330. package/src/notifications/adapters/telegram.ts +2 -20
  331. package/src/notifications/approval-card-data.ts +333 -0
  332. package/src/notifications/broadcaster.ts +16 -3
  333. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  334. package/src/notifications/copy-composer.ts +3 -3
  335. package/src/notifications/decision-engine.ts +4 -2
  336. package/src/notifications/destination-resolver.ts +4 -6
  337. package/src/notifications/guardian-question-mode.ts +10 -0
  338. package/src/notifications/home-feed-side-effect.ts +7 -16
  339. package/src/notifications/notification-utils.ts +19 -20
  340. package/src/notifications/signal.ts +79 -43
  341. package/src/notifications/types.ts +98 -121
  342. package/src/oauth/AGENTS.md +5 -24
  343. package/src/permissions/checker.test.ts +51 -0
  344. package/src/permissions/checker.ts +185 -26
  345. package/src/permissions/ipc-risk-types.ts +24 -0
  346. package/src/permissions/question-prompter.test.ts +27 -0
  347. package/src/permissions/question-prompter.ts +4 -0
  348. package/src/platform/client.test.ts +119 -0
  349. package/src/platform/client.ts +66 -0
  350. package/src/platform/consent-cache.test.ts +267 -0
  351. package/src/platform/consent-cache.ts +174 -0
  352. package/src/plugin-api/constants.ts +1 -1
  353. package/src/plugin-api/index.ts +33 -1
  354. package/src/plugin-api/model-profiles.ts +33 -0
  355. package/src/plugin-api/types.ts +50 -2
  356. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  357. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  358. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  359. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  360. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  361. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  362. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  363. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  364. package/src/plugins/defaults/advisor/config.ts +21 -0
  365. package/src/plugins/defaults/advisor/consult.ts +93 -0
  366. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  367. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  368. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  369. package/src/plugins/defaults/advisor/package.json +14 -0
  370. package/src/plugins/defaults/advisor/steering.ts +67 -0
  371. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  372. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  373. package/src/plugins/defaults/index.ts +60 -0
  374. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  375. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  376. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  377. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  378. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  379. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  380. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  381. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  382. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  384. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  385. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  386. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  387. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  388. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  389. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  390. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  391. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  392. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  393. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  394. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  395. package/src/prompts/persona-resolver.ts +14 -4
  396. package/src/prompts/templates/system-sections.ts +7 -2
  397. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  398. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  399. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  400. package/src/providers/atlascloud/client.ts +85 -0
  401. package/src/providers/fetch-provider-catalog.ts +85 -0
  402. package/src/providers/inference/adapter-factory.ts +3 -0
  403. package/src/providers/model-catalog.ts +58 -0
  404. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  405. package/src/providers/openai/chat-completions-provider.ts +7 -0
  406. package/src/providers/openai/responses-provider.ts +10 -0
  407. package/src/providers/provider-send-message.ts +11 -3
  408. package/src/providers/retry.ts +53 -12
  409. package/src/providers/search-provider-catalog.ts +10 -0
  410. package/src/providers/weak-open-model.ts +22 -0
  411. package/src/runtime/AGENTS.md +0 -1
  412. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  413. package/src/runtime/__tests__/client-health.test.ts +44 -0
  414. package/src/runtime/access-request-helper.ts +21 -53
  415. package/src/runtime/actor-trust-resolver.ts +59 -63
  416. package/src/runtime/agent-wake.ts +52 -0
  417. package/src/runtime/assistant-event-hub.ts +18 -4
  418. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  419. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  420. package/src/runtime/btw-sidechain.ts +3 -6
  421. package/src/runtime/capabilities.test.ts +120 -0
  422. package/src/runtime/capabilities.ts +197 -0
  423. package/src/runtime/channel-approval-types.ts +22 -45
  424. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  425. package/src/runtime/channel-retry-sweep.ts +1 -0
  426. package/src/runtime/channel-verification-service.ts +3 -3
  427. package/src/runtime/client-health.ts +26 -0
  428. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  429. package/src/runtime/effective-capabilities.test.ts +128 -0
  430. package/src/runtime/effective-capabilities.ts +84 -0
  431. package/src/runtime/guardian-reply-router.ts +106 -21
  432. package/src/runtime/invite-redemption-service.ts +9 -25
  433. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  434. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  435. package/src/runtime/pending-interactions.ts +15 -0
  436. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  437. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  438. package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
  439. package/src/runtime/routes/app-routes.ts +1 -1
  440. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  441. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  442. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  443. package/src/runtime/routes/btw-routes.ts +1 -27
  444. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  445. package/src/runtime/routes/client-routes.ts +10 -0
  446. package/src/runtime/routes/contact-routes.ts +31 -8
  447. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  448. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  449. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  450. package/src/runtime/routes/conversation-routes.ts +39 -14
  451. package/src/runtime/routes/credential-routes.ts +40 -16
  452. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  453. package/src/runtime/routes/events-routes.ts +1 -3
  454. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  455. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  456. package/src/runtime/routes/home-feed-routes.ts +8 -3
  457. package/src/runtime/routes/identity-routes.ts +1 -296
  458. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  459. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  460. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  461. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  462. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  463. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  464. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  465. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  466. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  467. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  468. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  469. package/src/runtime/routes/index.ts +2 -0
  470. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  471. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  472. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  473. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  474. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  475. package/src/runtime/routes/notification-routes.ts +122 -133
  476. package/src/runtime/routes/platform-routes.ts +2 -2
  477. package/src/runtime/routes/plugins-routes.ts +202 -3
  478. package/src/runtime/routes/schedule-routes.ts +0 -22
  479. package/src/runtime/routes/secret-routes.ts +10 -0
  480. package/src/runtime/routes/surface-action-routes.ts +2 -1
  481. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  482. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  483. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  484. package/src/runtime/routes/workflow-routes.ts +131 -29
  485. package/src/runtime/routes/workspace-greetings.ts +55 -0
  486. package/src/runtime/sync/resource-sync-events.ts +1 -11
  487. package/src/runtime/tool-grant-request-helper.ts +18 -16
  488. package/src/runtime/trust-context-resolver.ts +8 -5
  489. package/src/schedule/inference-profile.ts +2 -14
  490. package/src/schedule/schedule-store.ts +1 -1
  491. package/src/schedule/scheduler-types.ts +5 -1
  492. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  493. package/src/security/secret-patterns.ts +3 -0
  494. package/src/subagent/manager.ts +17 -4
  495. package/src/subagent/types.ts +6 -0
  496. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  497. package/src/telemetry/trace-collection-policy.ts +30 -0
  498. package/src/telemetry/types.ts +89 -0
  499. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  500. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  501. package/src/tools/AGENTS.md +3 -3
  502. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  503. package/src/tools/browser/browser-execution.ts +30 -19
  504. package/src/tools/document/document-tool.ts +2 -3
  505. package/src/tools/executor.ts +5 -3
  506. package/src/tools/host-terminal/host-shell.ts +5 -4
  507. package/src/tools/memory/register.ts +2 -2
  508. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  509. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  510. package/src/tools/network/web-fetch.ts +372 -1
  511. package/src/tools/network/web-search-error.ts +1 -1
  512. package/src/tools/network/web-search.ts +213 -10
  513. package/src/tools/permission-checker.ts +4 -3
  514. package/src/tools/registry.ts +20 -0
  515. package/src/tools/schedule/create.ts +7 -12
  516. package/src/tools/schedule/update.ts +4 -11
  517. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  518. package/src/tools/side-effects.ts +2 -17
  519. package/src/tools/skills/execute.ts +33 -0
  520. package/src/tools/subagent/spawn.ts +61 -12
  521. package/src/tools/terminal/shell.ts +10 -4
  522. package/src/tools/tool-approval-handler.ts +18 -13
  523. package/src/tools/tool-manifest.ts +0 -2
  524. package/src/tools/types.ts +9 -0
  525. package/src/tools/ui-surface/definitions.ts +64 -3
  526. package/src/tools/verification-control-plane-policy.ts +3 -1
  527. package/src/tools/workflows/run-workflow.test.ts +8 -18
  528. package/src/tools/workflows/run-workflow.ts +1 -0
  529. package/src/util/disk-usage.ts +78 -23
  530. package/src/util/platform.ts +10 -3
  531. package/src/watcher/telemetry.ts +2 -2
  532. package/src/workflows/capabilities.ts +2 -3
  533. package/src/workflows/engine.test.ts +175 -1
  534. package/src/workflows/engine.ts +82 -0
  535. package/src/workflows/journal-store.test.ts +70 -0
  536. package/src/workflows/journal-store.ts +18 -3
  537. package/src/workflows/run-manager.test.ts +171 -28
  538. package/src/workflows/run-manager.ts +66 -24
  539. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  540. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  541. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  542. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  543. package/src/workspace/migrations/registry.ts +8 -0
  544. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  545. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  546. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  547. package/src/__tests__/credential-vault.test.ts +0 -1706
  548. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  549. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  550. package/src/cli/commands/__tests__/task.test.ts +0 -914
  551. package/src/cli/commands/task.ts +0 -771
  552. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  553. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  554. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  555. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  556. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  557. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  558. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  559. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  560. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  561. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  562. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  563. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  564. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  565. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  566. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  567. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  568. package/src/memory/preloaded-apps.ts +0 -116
  569. package/src/notifications/tool-approval-copy.ts +0 -142
  570. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  571. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  572. package/src/tools/credentials/vault.ts +0 -712
@@ -1,1528 +0,0 @@
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
- mock,
12
- test,
13
- } from "bun:test";
14
-
15
- // ---------------------------------------------------------------------------
16
- // Mock logger
17
- // ---------------------------------------------------------------------------
18
-
19
- mock.module("../util/logger.js", () => ({
20
- getLogger: () =>
21
- new Proxy({} as Record<string, unknown>, {
22
- get: () => () => {},
23
- }),
24
- }));
25
-
26
- // ---------------------------------------------------------------------------
27
- // Use encrypted backend with a temp store path
28
- // ---------------------------------------------------------------------------
29
-
30
- import { _resetBackend } from "../security/secure-keys.js";
31
- import { setStorePathForTesting } from "./encrypted-store-test-helpers.js";
32
-
33
- const TEST_DIR = join(
34
- tmpdir(),
35
- `vellum-credvault-unit-${randomBytes(4).toString("hex")}`,
36
- );
37
- const STORE_PATH = join(TEST_DIR, "keys.enc");
38
-
39
- // ---------------------------------------------------------------------------
40
- // Mock registry to avoid double-registration
41
- // ---------------------------------------------------------------------------
42
-
43
- mock.module("../tools/registry.js", () => ({
44
- registerTool: () => {},
45
- }));
46
-
47
- // ---------------------------------------------------------------------------
48
- // Mock oauth-store to avoid SQLite dependency in unit tests
49
- // ---------------------------------------------------------------------------
50
-
51
- let disconnectOAuthProviderCalls: string[] = [];
52
-
53
- mock.module("../oauth/oauth-store.js", () => ({
54
- disconnectOAuthProvider: mock(async (provider: string) => {
55
- disconnectOAuthProviderCalls.push(provider);
56
- return "not-found" as const;
57
- }),
58
- getActiveConnection: mock(() => undefined),
59
- }));
60
-
61
- let manualConnectionStore: Record<string, string> = {};
62
- let slackChannelConfigCalls: Array<{
63
- botToken?: string;
64
- appToken?: string;
65
- userToken?: string;
66
- }> = [];
67
- let clearSlackUserTokenCalls = 0;
68
-
69
- mock.module("../oauth/manual-token-connection.js", () => ({
70
- syncManualTokenConnection: async (provider: string) => {
71
- const { credentialKey } = await import("../security/credential-key.js");
72
- const { getSecureKeyAsync } = await import("../security/secure-keys.js");
73
-
74
- if (provider === "slack_channel") {
75
- const hasBotToken = !!(await getSecureKeyAsync(
76
- credentialKey("slack_channel", "bot_token"),
77
- ));
78
- const hasAppToken = !!(await getSecureKeyAsync(
79
- credentialKey("slack_channel", "app_token"),
80
- ));
81
- if (hasBotToken && hasAppToken) {
82
- manualConnectionStore[provider] = "active";
83
- } else {
84
- delete manualConnectionStore[provider];
85
- }
86
- }
87
- },
88
- }));
89
-
90
- mock.module("../daemon/handlers/config-slack-channel.js", () => ({
91
- setSlackChannelConfig: async (
92
- botToken?: string,
93
- appToken?: string,
94
- userToken?: string,
95
- ) => {
96
- slackChannelConfigCalls.push({ botToken, appToken, userToken });
97
-
98
- const { credentialKey } = await import("../security/credential-key.js");
99
- const { getSecureKeyAsync, setSecureKeyAsync } =
100
- await import("../security/secure-keys.js");
101
- const { upsertCredentialMetadata } =
102
- await import("../tools/credentials/metadata-store.js");
103
-
104
- const hasExistingBotToken = !!(await getSecureKeyAsync(
105
- credentialKey("slack_channel", "bot_token"),
106
- ));
107
- const hasExistingAppToken = !!(await getSecureKeyAsync(
108
- credentialKey("slack_channel", "app_token"),
109
- ));
110
- const hasExistingUserToken = !!(await getSecureKeyAsync(
111
- credentialKey("slack_channel", "user_token"),
112
- ));
113
-
114
- if (appToken && !appToken.startsWith("xapp-")) {
115
- return {
116
- success: false,
117
- hasBotToken: hasExistingBotToken,
118
- hasAppToken: hasExistingAppToken,
119
- hasUserToken: hasExistingUserToken,
120
- connected: hasExistingBotToken && hasExistingAppToken,
121
- error: 'Invalid app token: must start with "xapp-"',
122
- };
123
- }
124
-
125
- if (userToken && !userToken.startsWith("xoxp-")) {
126
- return {
127
- success: false,
128
- hasBotToken: hasExistingBotToken,
129
- hasAppToken: hasExistingAppToken,
130
- hasUserToken: hasExistingUserToken,
131
- connected: hasExistingBotToken && hasExistingAppToken,
132
- error: 'Invalid user token: must start with "xoxp-"',
133
- };
134
- }
135
-
136
- if (botToken === "xoxb-invalid-token") {
137
- return {
138
- success: false,
139
- hasBotToken: hasExistingBotToken,
140
- hasAppToken: hasExistingAppToken,
141
- hasUserToken: hasExistingUserToken,
142
- connected: hasExistingBotToken && hasExistingAppToken,
143
- error: "Slack API validation failed: invalid_auth",
144
- };
145
- }
146
-
147
- if (botToken) {
148
- await setSecureKeyAsync(
149
- credentialKey("slack_channel", "bot_token"),
150
- botToken,
151
- );
152
- upsertCredentialMetadata("slack_channel", "bot_token", {});
153
- }
154
- if (appToken) {
155
- await setSecureKeyAsync(
156
- credentialKey("slack_channel", "app_token"),
157
- appToken,
158
- );
159
- upsertCredentialMetadata("slack_channel", "app_token", {});
160
- }
161
- if (userToken) {
162
- await setSecureKeyAsync(
163
- credentialKey("slack_channel", "user_token"),
164
- userToken,
165
- );
166
- upsertCredentialMetadata("slack_channel", "user_token", {});
167
- }
168
-
169
- const hasBotToken = !!(await getSecureKeyAsync(
170
- credentialKey("slack_channel", "bot_token"),
171
- ));
172
- const hasAppToken = !!(await getSecureKeyAsync(
173
- credentialKey("slack_channel", "app_token"),
174
- ));
175
- const hasUserToken = !!(await getSecureKeyAsync(
176
- credentialKey("slack_channel", "user_token"),
177
- ));
178
-
179
- if (hasBotToken && hasAppToken) {
180
- manualConnectionStore["slack_channel"] = "active";
181
- } else {
182
- delete manualConnectionStore["slack_channel"];
183
- }
184
-
185
- const warning =
186
- hasBotToken && !hasAppToken
187
- ? "Bot token stored but app token is missing - connection incomplete."
188
- : !hasBotToken && hasAppToken
189
- ? "App token stored but bot token is missing - connection incomplete."
190
- : undefined;
191
-
192
- return {
193
- success: true,
194
- hasBotToken,
195
- hasAppToken,
196
- hasUserToken,
197
- connected: hasBotToken && hasAppToken,
198
- teamName: hasBotToken ? "Test Team" : undefined,
199
- botUsername: hasBotToken ? "testbot" : undefined,
200
- warning,
201
- };
202
- },
203
- clearSlackUserToken: async () => {
204
- clearSlackUserTokenCalls++;
205
-
206
- const { credentialKey } = await import("../security/credential-key.js");
207
- const { deleteSecureKeyAsync, getSecureKeyAsync } =
208
- await import("../security/secure-keys.js");
209
- const { deleteCredentialMetadata } =
210
- await import("../tools/credentials/metadata-store.js");
211
-
212
- await deleteSecureKeyAsync(credentialKey("slack_channel", "user_token"));
213
- deleteCredentialMetadata("slack_channel", "user_token");
214
-
215
- const hasBotToken = !!(await getSecureKeyAsync(
216
- credentialKey("slack_channel", "bot_token"),
217
- ));
218
- const hasAppToken = !!(await getSecureKeyAsync(
219
- credentialKey("slack_channel", "app_token"),
220
- ));
221
-
222
- return {
223
- success: true,
224
- hasBotToken,
225
- hasAppToken,
226
- hasUserToken: false,
227
- connected:
228
- manualConnectionStore["slack_channel"] === "active" &&
229
- hasBotToken &&
230
- hasAppToken,
231
- };
232
- },
233
- }));
234
-
235
- // ---------------------------------------------------------------------------
236
- // Imports under test
237
- // ---------------------------------------------------------------------------
238
-
239
- import { credentialKey } from "../security/credential-key.js";
240
- import {
241
- getSecureKeyAsync,
242
- setSecureKeyAsync,
243
- } from "../security/secure-keys.js";
244
- import { CredentialBroker } from "../tools/credentials/broker.js";
245
- import {
246
- _setMetadataPath,
247
- upsertCredentialMetadata,
248
- } from "../tools/credentials/metadata-store.js";
249
- import { BROWSER_FILL_CAPABILITY } from "../tools/credentials/tool-policy.js";
250
- import { credentialStoreTool } from "../tools/credentials/vault.js";
251
- import type { ToolContext } from "../tools/types.js";
252
-
253
- const _ctx: ToolContext = {
254
- workingDir: "/tmp",
255
- conversationId: "test-conv",
256
- trustClass: "guardian",
257
- };
258
-
259
- beforeEach(() => {
260
- manualConnectionStore = {};
261
- slackChannelConfigCalls = [];
262
- clearSlackUserTokenCalls = 0;
263
- disconnectOAuthProviderCalls = [];
264
- });
265
-
266
- afterAll(() => {
267
- mock.restore();
268
- if (existsSync(TEST_DIR)) {
269
- rmSync(TEST_DIR, { recursive: true });
270
- }
271
- });
272
-
273
- // ---------------------------------------------------------------------------
274
- // 1. Broker — Transient (one-time) credential injection and consumption
275
- // ---------------------------------------------------------------------------
276
-
277
- describe("CredentialBroker transient credentials", () => {
278
- let broker: CredentialBroker;
279
-
280
- beforeEach(() => {
281
- if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
282
- mkdirSync(TEST_DIR, { recursive: true });
283
- setStorePathForTesting(STORE_PATH);
284
- _resetBackend();
285
- _setMetadataPath(join(TEST_DIR, "metadata.json"));
286
- broker = new CredentialBroker();
287
- });
288
-
289
- afterEach(() => {
290
- _setMetadataPath(null);
291
- setStorePathForTesting(null);
292
- _resetBackend();
293
- });
294
-
295
- test("consume returns transient value and deletes it", () => {
296
- upsertCredentialMetadata("svc", "key", { allowedTools: ["tool1"] });
297
- broker.injectTransient("svc", "key", "one-time-secret");
298
-
299
- const auth = broker.authorize({
300
- service: "svc",
301
- field: "key",
302
- toolName: "tool1",
303
- });
304
- expect(auth.authorized).toBe(true);
305
- if (!auth.authorized) return;
306
-
307
- const result = broker.consume(auth.token.tokenId);
308
- expect(result.success).toBe(true);
309
- expect(result.value).toBe("one-time-secret");
310
- expect(result.storageKey).toBe(credentialKey("svc", "key"));
311
-
312
- // Second authorize + consume should NOT have the transient value
313
- const auth2 = broker.authorize({
314
- service: "svc",
315
- field: "key",
316
- toolName: "tool1",
317
- });
318
- expect(auth2.authorized).toBe(true);
319
- if (!auth2.authorized) return;
320
- const result2 = broker.consume(auth2.token.tokenId);
321
- expect(result2.success).toBe(true);
322
- // No transient value — falls back to storage key only
323
- expect(result2.value).toBeUndefined();
324
- });
325
-
326
- test("browserFill uses transient value when available", async () => {
327
- upsertCredentialMetadata("github", "token", {
328
- allowedTools: ["browser_fill_credential"],
329
- });
330
- broker.injectTransient("github", "token", "transient-ghp-123");
331
-
332
- let filledValue: string | undefined;
333
- const result = await broker.browserFill({
334
- service: "github",
335
- field: "token",
336
- toolName: "browser_fill_credential",
337
- fill: async (v) => {
338
- filledValue = v;
339
- },
340
- });
341
-
342
- expect(result.success).toBe(true);
343
- expect(filledValue).toBe("transient-ghp-123");
344
- });
345
-
346
- test("browserFill consumes transient value — second fill falls back to stored", async () => {
347
- upsertCredentialMetadata("github", "token", {
348
- allowedTools: ["browser_fill_credential"],
349
- });
350
- await setSecureKeyAsync(credentialKey("github", "token"), "stored-value");
351
- broker.injectTransient("github", "token", "transient-value");
352
-
353
- // First fill uses transient
354
- let filled1: string | undefined;
355
- await broker.browserFill({
356
- service: "github",
357
- field: "token",
358
- toolName: "browser_fill_credential",
359
- fill: async (v) => {
360
- filled1 = v;
361
- },
362
- });
363
- expect(filled1).toBe("transient-value");
364
-
365
- // Second fill falls back to stored value
366
- let filled2: string | undefined;
367
- await broker.browserFill({
368
- service: "github",
369
- field: "token",
370
- toolName: "browser_fill_credential",
371
- fill: async (v) => {
372
- filled2 = v;
373
- },
374
- });
375
- expect(filled2).toBe("stored-value");
376
- });
377
-
378
- test("browserFill preserves transient value on fill failure", async () => {
379
- upsertCredentialMetadata("github", "token", {
380
- allowedTools: ["browser_fill_credential"],
381
- });
382
- broker.injectTransient("github", "token", "transient-preserved");
383
-
384
- // First fill fails
385
- const result1 = await broker.browserFill({
386
- service: "github",
387
- field: "token",
388
- toolName: "browser_fill_credential",
389
- fill: async () => {
390
- throw new Error("Playwright timeout");
391
- },
392
- });
393
- expect(result1.success).toBe(false);
394
-
395
- // Second fill should still have the transient value
396
- let filled: string | undefined;
397
- const result2 = await broker.browserFill({
398
- service: "github",
399
- field: "token",
400
- toolName: "browser_fill_credential",
401
- fill: async (v) => {
402
- filled = v;
403
- },
404
- });
405
- expect(result2.success).toBe(true);
406
- expect(filled).toBe("transient-preserved");
407
- });
408
-
409
- test("serverUse uses transient value when available", async () => {
410
- upsertCredentialMetadata("vercel", "api_token", {
411
- allowedTools: ["deploy"],
412
- });
413
- broker.injectTransient("vercel", "api_token", "transient-vercel-tok");
414
-
415
- const result = await broker.serverUse({
416
- service: "vercel",
417
- field: "api_token",
418
- toolName: "deploy",
419
- execute: async (v) => {
420
- expect(v).toBe("transient-vercel-tok");
421
- return "deployed";
422
- },
423
- });
424
-
425
- expect(result.success).toBe(true);
426
- expect(result.result).toBe("deployed");
427
- });
428
-
429
- test("serverUse consumes transient — subsequent call has no value without stored key", async () => {
430
- upsertCredentialMetadata("vercel", "api_token", {
431
- allowedTools: ["deploy"],
432
- });
433
- // Only transient, no stored value
434
- broker.injectTransient("vercel", "api_token", "transient-only");
435
-
436
- await broker.serverUse({
437
- service: "vercel",
438
- field: "api_token",
439
- toolName: "deploy",
440
- execute: async () => "ok",
441
- });
442
-
443
- // Second call: no transient, no stored value
444
- const result = await broker.serverUse({
445
- service: "vercel",
446
- field: "api_token",
447
- toolName: "deploy",
448
- execute: async () => {
449
- throw new Error("should not be called");
450
- },
451
- });
452
- expect(result.success).toBe(false);
453
- expect(result.reason).toContain("no stored value");
454
- });
455
-
456
- test("injectTransient replaces previous transient for same key", () => {
457
- upsertCredentialMetadata("svc", "key", { allowedTools: ["t"] });
458
- broker.injectTransient("svc", "key", "first");
459
- broker.injectTransient("svc", "key", "second");
460
-
461
- const auth = broker.authorize({
462
- service: "svc",
463
- field: "key",
464
- toolName: "t",
465
- });
466
- if (!auth.authorized) return;
467
- const result = broker.consume(auth.token.tokenId);
468
- expect(result.value).toBe("second");
469
- });
470
-
471
- test("transient value for one credential does not affect another", () => {
472
- upsertCredentialMetadata("svcA", "key", { allowedTools: ["t"] });
473
- upsertCredentialMetadata("svcB", "key", { allowedTools: ["t"] });
474
- broker.injectTransient("svcA", "key", "val-a");
475
-
476
- // svcB should not have a transient value — consume returns storageKey only
477
- const authB = broker.authorize({
478
- service: "svcB",
479
- field: "key",
480
- toolName: "t",
481
- });
482
- if (!authB.authorized) return;
483
- const resultB = broker.consume(authB.token.tokenId);
484
- expect(resultB.success).toBe(true);
485
- expect(resultB.value).toBeUndefined();
486
-
487
- // svcA should have the transient
488
- const authA = broker.authorize({
489
- service: "svcA",
490
- field: "key",
491
- toolName: "t",
492
- });
493
- if (!authA.authorized) return;
494
- const resultA = broker.consume(authA.token.tokenId);
495
- expect(resultA.value).toBe("val-a");
496
- });
497
- });
498
-
499
- // ---------------------------------------------------------------------------
500
- // 2. Vault — unknown action handling
501
- // ---------------------------------------------------------------------------
502
-
503
- describe("credential_store tool — unknown action", () => {
504
- beforeEach(() => {
505
- if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
506
- mkdirSync(TEST_DIR, { recursive: true });
507
- setStorePathForTesting(STORE_PATH);
508
- _resetBackend();
509
- _setMetadataPath(join(TEST_DIR, "metadata.json"));
510
- });
511
-
512
- afterEach(() => {
513
- _setMetadataPath(null);
514
- setStorePathForTesting(null);
515
- _resetBackend();
516
- });
517
-
518
- test("returns error for unknown action", async () => {
519
- const result = await credentialStoreTool.execute(
520
- { action: "unknown_action" },
521
- _ctx,
522
- );
523
- expect(result.isError).toBe(true);
524
- expect(result.content).toContain("unknown action");
525
- expect(result.content).toContain("unknown_action");
526
- });
527
- });
528
-
529
- // ---------------------------------------------------------------------------
530
- // 3. Vault — prompt action edge cases
531
- // ---------------------------------------------------------------------------
532
-
533
- describe("credential_store tool — prompt action", () => {
534
- beforeEach(() => {
535
- if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
536
- mkdirSync(TEST_DIR, { recursive: true });
537
- setStorePathForTesting(STORE_PATH);
538
- _resetBackend();
539
- _setMetadataPath(join(TEST_DIR, "metadata.json"));
540
- });
541
-
542
- afterEach(() => {
543
- _setMetadataPath(null);
544
- setStorePathForTesting(null);
545
- _resetBackend();
546
- });
547
-
548
- test("returns error when requestSecret is not available", async () => {
549
- const result = await credentialStoreTool.execute(
550
- { action: "prompt", service: "svc", field: "key", label: "API Key" },
551
- _ctx, // no requestSecret
552
- );
553
- expect(result.isError).toBe(true);
554
- expect(result.content).toContain("not available");
555
- });
556
-
557
- test("returns error when service is missing for prompt", async () => {
558
- const result = await credentialStoreTool.execute(
559
- { action: "prompt", field: "key" },
560
- _ctx,
561
- );
562
- expect(result.isError).toBe(true);
563
- expect(result.content).toContain("service is required");
564
- });
565
-
566
- test("returns error when field is missing for prompt", async () => {
567
- const result = await credentialStoreTool.execute(
568
- { action: "prompt", service: "svc" },
569
- _ctx,
570
- );
571
- expect(result.isError).toBe(true);
572
- expect(result.content).toContain("field is required");
573
- });
574
-
575
- test("handles user cancellation (null value)", async () => {
576
- const ctxWithPrompt: ToolContext = {
577
- ..._ctx,
578
- requestSecret: async () => ({
579
- value: null as unknown as string,
580
- delivery: "store" as const,
581
- }),
582
- };
583
- const result = await credentialStoreTool.execute(
584
- { action: "prompt", service: "svc", field: "key", label: "Test" },
585
- ctxWithPrompt,
586
- );
587
- expect(result.isError).toBe(false);
588
- expect(result.content).toContain("cancelled");
589
- });
590
-
591
- test("stores credential when user provides value via prompt", async () => {
592
- const ctxWithPrompt: ToolContext = {
593
- ..._ctx,
594
- requestSecret: async () => ({
595
- value: "prompt-secret-val",
596
- delivery: "store" as const,
597
- }),
598
- };
599
- const result = await credentialStoreTool.execute(
600
- {
601
- action: "prompt",
602
- service: "test-prompt",
603
- field: "api_key",
604
- label: "API Key",
605
- },
606
- ctxWithPrompt,
607
- );
608
- expect(result.isError).toBe(false);
609
- expect(result.content).toContain("test-prompt/api_key");
610
- expect(result.content).not.toContain("prompt-secret-val");
611
-
612
- // Verify stored
613
- expect(
614
- await getSecureKeyAsync(credentialKey("test-prompt", "api_key")),
615
- ).toBe("prompt-secret-val");
616
- });
617
-
618
- test("prompt with policy fields persists metadata", async () => {
619
- const ctxWithPrompt: ToolContext = {
620
- ..._ctx,
621
- requestSecret: async () => ({
622
- value: "prompt-val",
623
- delivery: "store" as const,
624
- }),
625
- };
626
- const result = await credentialStoreTool.execute(
627
- {
628
- action: "prompt",
629
- service: "github",
630
- field: "token",
631
- label: "GitHub Token",
632
- allowed_tools: ["browser_fill_credential"],
633
- allowed_domains: ["github.com"],
634
- usage_description: "GitHub login",
635
- },
636
- ctxWithPrompt,
637
- );
638
- expect(result.isError).toBe(false);
639
-
640
- const { getCredentialMetadata } =
641
- await import("../tools/credentials/metadata-store.js");
642
- const meta = getCredentialMetadata("github", "token");
643
- expect(meta).toBeDefined();
644
- expect(meta!.allowedTools).toEqual(["browser_fill_credential"]);
645
- expect(meta!.allowedDomains).toEqual(["github.com"]);
646
- expect(meta!.usageDescription).toBe("GitHub login");
647
- });
648
-
649
- test("chat-style slack_channel prompts create the manual connection once both tokens exist", async () => {
650
- const promptValues = ["xapp-test-token", "xoxb-test-token"];
651
- const ctxWithPrompt: ToolContext = {
652
- ..._ctx,
653
- requestSecret: async () => ({
654
- value: promptValues.shift() ?? "",
655
- delivery: "store" as const,
656
- }),
657
- };
658
-
659
- const appResult = await credentialStoreTool.execute(
660
- {
661
- action: "prompt",
662
- service: "slack_channel",
663
- field: "app_token",
664
- label: "App-Level Token",
665
- },
666
- ctxWithPrompt,
667
- );
668
- expect(appResult.isError).toBe(false);
669
- expect(manualConnectionStore["slack_channel"]).toBeUndefined();
670
- expect(slackChannelConfigCalls).toEqual([{ appToken: "xapp-test-token" }]);
671
- expect(appResult.content).toContain("connection incomplete");
672
-
673
- const botResult = await credentialStoreTool.execute(
674
- {
675
- action: "prompt",
676
- service: "slack_channel",
677
- field: "bot_token",
678
- label: "Bot User OAuth Token",
679
- },
680
- ctxWithPrompt,
681
- );
682
- expect(botResult.isError).toBe(false);
683
- expect(manualConnectionStore["slack_channel"]).toBe("active");
684
- expect(slackChannelConfigCalls).toEqual([
685
- { appToken: "xapp-test-token" },
686
- { botToken: "xoxb-test-token" },
687
- ]);
688
- expect(botResult.content).toContain(
689
- "Slack channel connected to Test Team (@testbot).",
690
- );
691
- });
692
-
693
- test("slack_channel prompt rejects transient send delivery", async () => {
694
- const ctxWithPrompt: ToolContext = {
695
- ..._ctx,
696
- requestSecret: async () => ({
697
- value: "xapp-test-token",
698
- delivery: "transient_send" as const,
699
- }),
700
- };
701
-
702
- const result = await credentialStoreTool.execute(
703
- {
704
- action: "prompt",
705
- service: "slack_channel",
706
- field: "app_token",
707
- label: "App-Level Token",
708
- },
709
- ctxWithPrompt,
710
- );
711
-
712
- expect(result.isError).toBe(true);
713
- expect(result.content).toContain("must be saved to secure storage");
714
- expect(slackChannelConfigCalls).toEqual([]);
715
- });
716
-
717
- test("slack_channel bot token prompt fails through the settings handler", async () => {
718
- const promptValues = ["xapp-test-token", "xoxb-invalid-token"];
719
- const ctxWithPrompt: ToolContext = {
720
- ..._ctx,
721
- requestSecret: async () => ({
722
- value: promptValues.shift() ?? "",
723
- delivery: "store" as const,
724
- }),
725
- };
726
-
727
- const appResult = await credentialStoreTool.execute(
728
- {
729
- action: "prompt",
730
- service: "slack_channel",
731
- field: "app_token",
732
- label: "App-Level Token",
733
- },
734
- ctxWithPrompt,
735
- );
736
- expect(appResult.isError).toBe(false);
737
-
738
- const botResult = await credentialStoreTool.execute(
739
- {
740
- action: "prompt",
741
- service: "slack_channel",
742
- field: "bot_token",
743
- label: "Bot User OAuth Token",
744
- },
745
- ctxWithPrompt,
746
- );
747
-
748
- expect(botResult.isError).toBe(true);
749
- expect(botResult.content).toContain("invalid_auth");
750
- expect(
751
- await getSecureKeyAsync(credentialKey("slack_channel", "bot_token")),
752
- ).toBeUndefined();
753
- });
754
-
755
- test("slack_channel user_token prompt routes through the settings handler", async () => {
756
- const ctxWithPrompt: ToolContext = {
757
- ..._ctx,
758
- requestSecret: async () => ({
759
- value: "xoxp-valid-user-token",
760
- delivery: "store" as const,
761
- }),
762
- };
763
-
764
- const result = await credentialStoreTool.execute(
765
- {
766
- action: "prompt",
767
- service: "slack_channel",
768
- field: "user_token",
769
- label: "User OAuth Token",
770
- },
771
- ctxWithPrompt,
772
- );
773
-
774
- expect(result.isError).toBe(false);
775
- // Routed to handler as third positional argument.
776
- expect(slackChannelConfigCalls).toEqual([
777
- {
778
- botToken: undefined,
779
- appToken: undefined,
780
- userToken: "xoxp-valid-user-token",
781
- },
782
- ]);
783
- // Stored via the handler's mock, NOT via the generic setSecureKeyAsync path.
784
- expect(
785
- await getSecureKeyAsync(credentialKey("slack_channel", "user_token")),
786
- ).toBe("xoxp-valid-user-token");
787
- });
788
-
789
- test("slack_channel user_token prompt surfaces handler rejection for malformed token", async () => {
790
- const ctxWithPrompt: ToolContext = {
791
- ..._ctx,
792
- requestSecret: async () => ({
793
- value: "abc-123",
794
- delivery: "store" as const,
795
- }),
796
- };
797
-
798
- const result = await credentialStoreTool.execute(
799
- {
800
- action: "prompt",
801
- service: "slack_channel",
802
- field: "user_token",
803
- label: "User OAuth Token",
804
- },
805
- ctxWithPrompt,
806
- );
807
-
808
- expect(result.isError).toBe(true);
809
- expect(result.content).toContain('must start with "xoxp-"');
810
- // Handler was called with malformed token.
811
- expect(slackChannelConfigCalls).toEqual([
812
- { botToken: undefined, appToken: undefined, userToken: "abc-123" },
813
- ]);
814
- // Value was NOT persisted.
815
- expect(
816
- await getSecureKeyAsync(credentialKey("slack_channel", "user_token")),
817
- ).toBeUndefined();
818
- });
819
-
820
- test("prompt rejects invalid policy input", async () => {
821
- const ctxWithPrompt: ToolContext = {
822
- ..._ctx,
823
- requestSecret: async () => ({ value: "val", delivery: "store" as const }),
824
- };
825
- const result = await credentialStoreTool.execute(
826
- {
827
- action: "prompt",
828
- service: "svc",
829
- field: "key",
830
- label: "Test",
831
- allowed_tools: "not-an-array",
832
- },
833
- ctxWithPrompt,
834
- );
835
- expect(result.isError).toBe(true);
836
- expect(result.content).toContain("allowed_tools must be an array");
837
- });
838
- });
839
-
840
- // ---------------------------------------------------------------------------
841
- // 3b. Vault — slack_channel store routing
842
- // ---------------------------------------------------------------------------
843
-
844
- describe("credential_store tool — slack_channel store routing", () => {
845
- beforeEach(() => {
846
- if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
847
- mkdirSync(TEST_DIR, { recursive: true });
848
- setStorePathForTesting(STORE_PATH);
849
- _resetBackend();
850
- _setMetadataPath(join(TEST_DIR, "metadata.json"));
851
- });
852
-
853
- afterEach(() => {
854
- _setMetadataPath(null);
855
- setStorePathForTesting(null);
856
- _resetBackend();
857
- });
858
-
859
- test("store with user_token routes to setSlackChannelConfig as third positional arg", async () => {
860
- const result = await credentialStoreTool.execute(
861
- {
862
- action: "store",
863
- service: "slack_channel",
864
- field: "user_token",
865
- value: "xoxp-valid-user-token",
866
- },
867
- _ctx,
868
- );
869
-
870
- expect(result.isError).toBe(false);
871
- // Exactly one handler call with (undefined, undefined, token).
872
- expect(slackChannelConfigCalls).toEqual([
873
- {
874
- botToken: undefined,
875
- appToken: undefined,
876
- userToken: "xoxp-valid-user-token",
877
- },
878
- ]);
879
- // Stored through the handler's mock path, not the generic setSecureKeyAsync path.
880
- expect(
881
- await getSecureKeyAsync(credentialKey("slack_channel", "user_token")),
882
- ).toBe("xoxp-valid-user-token");
883
- });
884
-
885
- test("store with user_token surfaces handler rejection for malformed token", async () => {
886
- const result = await credentialStoreTool.execute(
887
- {
888
- action: "store",
889
- service: "slack_channel",
890
- field: "user_token",
891
- value: "abc-123",
892
- },
893
- _ctx,
894
- );
895
-
896
- expect(result.isError).toBe(true);
897
- expect(result.content).toContain('must start with "xoxp-"');
898
- // Handler was called with the malformed value.
899
- expect(slackChannelConfigCalls).toEqual([
900
- { botToken: undefined, appToken: undefined, userToken: "abc-123" },
901
- ]);
902
- // Nothing was persisted.
903
- expect(
904
- await getSecureKeyAsync(credentialKey("slack_channel", "user_token")),
905
- ).toBeUndefined();
906
- });
907
-
908
- test("store with bot_token still routes via first positional arg", async () => {
909
- const result = await credentialStoreTool.execute(
910
- {
911
- action: "store",
912
- service: "slack_channel",
913
- field: "bot_token",
914
- value: "xoxb-valid-bot-token",
915
- },
916
- _ctx,
917
- );
918
-
919
- expect(result.isError).toBe(false);
920
- expect(slackChannelConfigCalls).toEqual([
921
- {
922
- botToken: "xoxb-valid-bot-token",
923
- appToken: undefined,
924
- userToken: undefined,
925
- },
926
- ]);
927
- });
928
-
929
- test("store with app_token still routes via second positional arg", async () => {
930
- const result = await credentialStoreTool.execute(
931
- {
932
- action: "store",
933
- service: "slack_channel",
934
- field: "app_token",
935
- value: "xapp-valid-app-token",
936
- },
937
- _ctx,
938
- );
939
-
940
- expect(result.isError).toBe(false);
941
- expect(slackChannelConfigCalls).toEqual([
942
- {
943
- botToken: undefined,
944
- appToken: "xapp-valid-app-token",
945
- userToken: undefined,
946
- },
947
- ]);
948
- });
949
- });
950
-
951
- // ---------------------------------------------------------------------------
952
- // 4. Vault — store action validation edge cases
953
- // ---------------------------------------------------------------------------
954
-
955
- describe("credential_store tool — store validation edge cases", () => {
956
- beforeEach(() => {
957
- if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
958
- mkdirSync(TEST_DIR, { recursive: true });
959
- setStorePathForTesting(STORE_PATH);
960
- _resetBackend();
961
- _setMetadataPath(join(TEST_DIR, "metadata.json"));
962
- });
963
-
964
- afterEach(() => {
965
- _setMetadataPath(null);
966
- setStorePathForTesting(null);
967
- _resetBackend();
968
- });
969
-
970
- test("rejects alias that is not a string", async () => {
971
- const result = await credentialStoreTool.execute(
972
- {
973
- action: "store",
974
- service: "svc",
975
- field: "key",
976
- value: "val",
977
- alias: 42,
978
- },
979
- _ctx,
980
- );
981
- expect(result.isError).toBe(true);
982
- expect(result.content).toContain("alias must be a string");
983
- });
984
-
985
- test("rejects injection_templates that is not an array", async () => {
986
- const result = await credentialStoreTool.execute(
987
- {
988
- action: "store",
989
- service: "svc",
990
- field: "key",
991
- value: "val",
992
- injection_templates: "not-an-array",
993
- },
994
- _ctx,
995
- );
996
- expect(result.isError).toBe(true);
997
- expect(result.content).toContain("injection_templates must be an array");
998
- });
999
-
1000
- test("rejects template with invalid injectionType", async () => {
1001
- const result = await credentialStoreTool.execute(
1002
- {
1003
- action: "store",
1004
- service: "svc",
1005
- field: "key",
1006
- value: "val",
1007
- injection_templates: [
1008
- { hostPattern: "*.example.com", injectionType: "cookie" },
1009
- ],
1010
- },
1011
- _ctx,
1012
- );
1013
- expect(result.isError).toBe(true);
1014
- expect(result.content).toContain(
1015
- "injectionType must be 'header' or 'query'",
1016
- );
1017
- });
1018
-
1019
- test("rejects template with empty hostPattern", async () => {
1020
- const result = await credentialStoreTool.execute(
1021
- {
1022
- action: "store",
1023
- service: "svc",
1024
- field: "key",
1025
- value: "val",
1026
- injection_templates: [
1027
- {
1028
- hostPattern: " ",
1029
- injectionType: "header",
1030
- headerName: "Authorization",
1031
- },
1032
- ],
1033
- },
1034
- _ctx,
1035
- );
1036
- expect(result.isError).toBe(true);
1037
- expect(result.content).toContain("hostPattern must be a non-empty string");
1038
- });
1039
-
1040
- test("rejects template with non-string valuePrefix", async () => {
1041
- const result = await credentialStoreTool.execute(
1042
- {
1043
- action: "store",
1044
- service: "svc",
1045
- field: "key",
1046
- value: "val",
1047
- injection_templates: [
1048
- {
1049
- hostPattern: "*.example.com",
1050
- injectionType: "header",
1051
- headerName: "Auth",
1052
- valuePrefix: 42,
1053
- },
1054
- ],
1055
- },
1056
- _ctx,
1057
- );
1058
- expect(result.isError).toBe(true);
1059
- expect(result.content).toContain("valuePrefix must be a string");
1060
- });
1061
-
1062
- test("reports multiple template errors at once", async () => {
1063
- const result = await credentialStoreTool.execute(
1064
- {
1065
- action: "store",
1066
- service: "svc",
1067
- field: "key",
1068
- value: "val",
1069
- injection_templates: [
1070
- { hostPattern: "", injectionType: "header", headerName: "X-Key" },
1071
- { hostPattern: "*.example.com", injectionType: "query" }, // missing queryParamName
1072
- ],
1073
- },
1074
- _ctx,
1075
- );
1076
- expect(result.isError).toBe(true);
1077
- expect(result.content).toContain("hostPattern");
1078
- expect(result.content).toContain("queryParamName");
1079
- });
1080
-
1081
- test("delete removes both secret and metadata", async () => {
1082
- await credentialStoreTool.execute(
1083
- {
1084
- action: "store",
1085
- service: "del-test",
1086
- field: "key",
1087
- value: "secret",
1088
- },
1089
- _ctx,
1090
- );
1091
-
1092
- // Verify stored
1093
- expect(await getSecureKeyAsync(credentialKey("del-test", "key"))).toBe(
1094
- "secret",
1095
- );
1096
- const { getCredentialMetadata } =
1097
- await import("../tools/credentials/metadata-store.js");
1098
- expect(getCredentialMetadata("del-test", "key")).toBeDefined();
1099
-
1100
- // Delete
1101
- const result = await credentialStoreTool.execute(
1102
- {
1103
- action: "delete",
1104
- service: "del-test",
1105
- field: "key",
1106
- },
1107
- _ctx,
1108
- );
1109
- expect(result.isError).toBe(false);
1110
-
1111
- // Both should be gone
1112
- expect(
1113
- await getSecureKeyAsync(credentialKey("del-test", "key")),
1114
- ).toBeUndefined();
1115
- expect(getCredentialMetadata("del-test", "key")).toBeUndefined();
1116
- });
1117
- });
1118
-
1119
- // ---------------------------------------------------------------------------
1120
- // 4b. Vault — slack_channel delete routing
1121
- // ---------------------------------------------------------------------------
1122
-
1123
- describe("credential_store tool — slack_channel delete routing", () => {
1124
- beforeEach(() => {
1125
- if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
1126
- mkdirSync(TEST_DIR, { recursive: true });
1127
- setStorePathForTesting(STORE_PATH);
1128
- _resetBackend();
1129
- _setMetadataPath(join(TEST_DIR, "metadata.json"));
1130
- });
1131
-
1132
- afterEach(() => {
1133
- _setMetadataPath(null);
1134
- setStorePathForTesting(null);
1135
- _resetBackend();
1136
- });
1137
-
1138
- test("delete with user_token leaves bot+app tokens and oauth_connection intact", async () => {
1139
- // Seed all three Slack tokens + metadata, with the manual connection active.
1140
- await setSecureKeyAsync(
1141
- credentialKey("slack_channel", "bot_token"),
1142
- "xoxb-bot",
1143
- );
1144
- await setSecureKeyAsync(
1145
- credentialKey("slack_channel", "app_token"),
1146
- "xapp-app",
1147
- );
1148
- await setSecureKeyAsync(
1149
- credentialKey("slack_channel", "user_token"),
1150
- "xoxp-user",
1151
- );
1152
- upsertCredentialMetadata("slack_channel", "bot_token", {});
1153
- upsertCredentialMetadata("slack_channel", "app_token", {});
1154
- upsertCredentialMetadata("slack_channel", "user_token", {});
1155
- manualConnectionStore["slack_channel"] = "active";
1156
-
1157
- const result = await credentialStoreTool.execute(
1158
- {
1159
- action: "delete",
1160
- service: "slack_channel",
1161
- field: "user_token",
1162
- },
1163
- _ctx,
1164
- );
1165
-
1166
- expect(result.isError).toBe(false);
1167
- // Routed through the surgical helper.
1168
- expect(clearSlackUserTokenCalls).toBe(1);
1169
- // oauth_connection row was never disconnected.
1170
- expect(disconnectOAuthProviderCalls).toEqual([]);
1171
- // user_token key + metadata removed.
1172
- expect(
1173
- await getSecureKeyAsync(credentialKey("slack_channel", "user_token")),
1174
- ).toBeUndefined();
1175
- const { getCredentialMetadata } =
1176
- await import("../tools/credentials/metadata-store.js");
1177
- expect(
1178
- getCredentialMetadata("slack_channel", "user_token"),
1179
- ).toBeUndefined();
1180
- // bot + app tokens + their metadata + manual connection still present.
1181
- expect(
1182
- await getSecureKeyAsync(credentialKey("slack_channel", "bot_token")),
1183
- ).toBe("xoxb-bot");
1184
- expect(
1185
- await getSecureKeyAsync(credentialKey("slack_channel", "app_token")),
1186
- ).toBe("xapp-app");
1187
- expect(getCredentialMetadata("slack_channel", "bot_token")).toBeDefined();
1188
- expect(getCredentialMetadata("slack_channel", "app_token")).toBeDefined();
1189
- expect(manualConnectionStore["slack_channel"]).toBe("active");
1190
- });
1191
-
1192
- test("delete with bot_token still tears down the oauth connection (regression guard)", async () => {
1193
- await setSecureKeyAsync(
1194
- credentialKey("slack_channel", "bot_token"),
1195
- "xoxb-bot",
1196
- );
1197
- await setSecureKeyAsync(
1198
- credentialKey("slack_channel", "app_token"),
1199
- "xapp-app",
1200
- );
1201
- upsertCredentialMetadata("slack_channel", "bot_token", {});
1202
- upsertCredentialMetadata("slack_channel", "app_token", {});
1203
- manualConnectionStore["slack_channel"] = "active";
1204
-
1205
- const result = await credentialStoreTool.execute(
1206
- {
1207
- action: "delete",
1208
- service: "slack_channel",
1209
- field: "bot_token",
1210
- },
1211
- _ctx,
1212
- );
1213
-
1214
- expect(result.isError).toBe(false);
1215
- // Surgical helper was not used.
1216
- expect(clearSlackUserTokenCalls).toBe(0);
1217
- // Full teardown path called disconnectOAuthProvider for the slack_channel.
1218
- expect(disconnectOAuthProviderCalls).toContain("slack_channel");
1219
- // bot_token key + metadata removed.
1220
- expect(
1221
- await getSecureKeyAsync(credentialKey("slack_channel", "bot_token")),
1222
- ).toBeUndefined();
1223
- const { getCredentialMetadata } =
1224
- await import("../tools/credentials/metadata-store.js");
1225
- expect(getCredentialMetadata("slack_channel", "bot_token")).toBeUndefined();
1226
- });
1227
-
1228
- test("delete with app_token still tears down the oauth connection (regression guard)", async () => {
1229
- await setSecureKeyAsync(
1230
- credentialKey("slack_channel", "bot_token"),
1231
- "xoxb-bot",
1232
- );
1233
- await setSecureKeyAsync(
1234
- credentialKey("slack_channel", "app_token"),
1235
- "xapp-app",
1236
- );
1237
- upsertCredentialMetadata("slack_channel", "bot_token", {});
1238
- upsertCredentialMetadata("slack_channel", "app_token", {});
1239
- manualConnectionStore["slack_channel"] = "active";
1240
-
1241
- const result = await credentialStoreTool.execute(
1242
- {
1243
- action: "delete",
1244
- service: "slack_channel",
1245
- field: "app_token",
1246
- },
1247
- _ctx,
1248
- );
1249
-
1250
- expect(result.isError).toBe(false);
1251
- expect(clearSlackUserTokenCalls).toBe(0);
1252
- expect(disconnectOAuthProviderCalls).toContain("slack_channel");
1253
- expect(
1254
- await getSecureKeyAsync(credentialKey("slack_channel", "app_token")),
1255
- ).toBeUndefined();
1256
- });
1257
- });
1258
-
1259
- // ---------------------------------------------------------------------------
1260
- // 5. Vault — tool definition schema
1261
- // ---------------------------------------------------------------------------
1262
-
1263
- describe("credential_store tool — tool definition", () => {
1264
- test("tool name and category are correct", () => {
1265
- expect(credentialStoreTool.name).toBe("credential_store");
1266
- expect(credentialStoreTool.category).toBe("credentials");
1267
- });
1268
-
1269
- test("getDefinition returns valid schema with required action", () => {
1270
- const def = credentialStoreTool;
1271
- expect(def.name).toBe("credential_store");
1272
- const schema = def.input_schema as Record<string, unknown>;
1273
- expect(schema.type).toBe("object");
1274
- expect(schema.required).toContain("action");
1275
- const props = schema.properties as Record<string, Record<string, unknown>>;
1276
- expect(props.action.enum).toEqual(["store", "list", "delete", "prompt"]);
1277
- });
1278
-
1279
- test("getDefinition includes injection_templates schema", () => {
1280
- const def = credentialStoreTool;
1281
- const schemaProps = (def.input_schema as Record<string, unknown>)
1282
- .properties as Record<string, Record<string, unknown>>;
1283
- const templates = schemaProps.injection_templates as Record<
1284
- string,
1285
- unknown
1286
- >;
1287
- expect(templates).toBeDefined();
1288
- expect(templates.type).toBe("array");
1289
- const items = (templates.items as Record<string, unknown>)
1290
- .properties as Record<string, Record<string, unknown>>;
1291
- expect(items.hostPattern).toBeDefined();
1292
- expect(items.injectionType.enum).toEqual(["header", "query"]);
1293
- });
1294
- });
1295
-
1296
- // ---------------------------------------------------------------------------
1297
- // 6. Broker — serverUseById with transient not supported
1298
- // (transient is scoped to authorize+consume and browserFill/serverUse)
1299
- // ---------------------------------------------------------------------------
1300
-
1301
- describe("CredentialBroker — serverUseById edge cases", () => {
1302
- let broker: CredentialBroker;
1303
-
1304
- beforeEach(() => {
1305
- if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
1306
- mkdirSync(TEST_DIR, { recursive: true });
1307
- setStorePathForTesting(STORE_PATH);
1308
- _resetBackend();
1309
- _setMetadataPath(join(TEST_DIR, "metadata.json"));
1310
- broker = new CredentialBroker();
1311
- });
1312
-
1313
- afterEach(() => {
1314
- _setMetadataPath(null);
1315
- setStorePathForTesting(null);
1316
- _resetBackend();
1317
- });
1318
-
1319
- test("serverUseById with multiple injection templates returns all", async () => {
1320
- const meta = upsertCredentialMetadata("multi", "api_key", {
1321
- allowedTools: ["proxy"],
1322
- injectionTemplates: [
1323
- {
1324
- hostPattern: "*.fal.ai",
1325
- injectionType: "header",
1326
- headerName: "Authorization",
1327
- valuePrefix: "Key ",
1328
- },
1329
- {
1330
- hostPattern: "gateway.fal.ai",
1331
- injectionType: "header",
1332
- headerName: "X-Fal-Key",
1333
- },
1334
- ],
1335
- });
1336
- await setSecureKeyAsync(credentialKey("multi", "api_key"), "multi-secret");
1337
-
1338
- const result = await broker.serverUseById({
1339
- credentialId: meta.credentialId,
1340
- requestingTool: "proxy",
1341
- });
1342
-
1343
- expect(result.success).toBe(true);
1344
- if (!result.success) return;
1345
- expect(result.injectionTemplates).toHaveLength(2);
1346
- expect(result.injectionTemplates[0].hostPattern).toBe("*.fal.ai");
1347
- expect(result.injectionTemplates[1].hostPattern).toBe("gateway.fal.ai");
1348
- // No secret value in result
1349
- expect(JSON.stringify(result)).not.toContain("multi-secret");
1350
- });
1351
-
1352
- test("serverUseById verifies secret exists in storage (fail-closed)", async () => {
1353
- const meta = upsertCredentialMetadata("fal", "api_key", {
1354
- allowedTools: ["proxy"],
1355
- });
1356
- // No setSecureKeyAsync — metadata exists but value doesn't
1357
-
1358
- const result = await broker.serverUseById({
1359
- credentialId: meta.credentialId,
1360
- requestingTool: "proxy",
1361
- });
1362
-
1363
- expect(result.success).toBe(false);
1364
- if (result.success) return;
1365
- expect(result.reason).toContain("no stored value");
1366
- });
1367
- });
1368
-
1369
- // ---------------------------------------------------------------------------
1370
- // 7. Broker — revokeAll clears transient values indirectly via token cleanup
1371
- // ---------------------------------------------------------------------------
1372
-
1373
- describe("CredentialBroker — revokeAll", () => {
1374
- let broker: CredentialBroker;
1375
-
1376
- beforeEach(() => {
1377
- if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
1378
- mkdirSync(TEST_DIR, { recursive: true });
1379
- setStorePathForTesting(STORE_PATH);
1380
- _resetBackend();
1381
- _setMetadataPath(join(TEST_DIR, "metadata.json"));
1382
- broker = new CredentialBroker();
1383
- });
1384
-
1385
- afterEach(() => {
1386
- _setMetadataPath(null);
1387
- setStorePathForTesting(null);
1388
- _resetBackend();
1389
- });
1390
-
1391
- test("revokeAll clears all tokens and subsequent consume fails", () => {
1392
- upsertCredentialMetadata("svc", "key", { allowedTools: ["t1", "t2"] });
1393
- const a1 = broker.authorize({
1394
- service: "svc",
1395
- field: "key",
1396
- toolName: "t1",
1397
- });
1398
- const a2 = broker.authorize({
1399
- service: "svc",
1400
- field: "key",
1401
- toolName: "t2",
1402
- });
1403
- expect(broker.activeTokenCount).toBe(2);
1404
-
1405
- broker.revokeAll();
1406
- expect(broker.activeTokenCount).toBe(0);
1407
-
1408
- if (a1.authorized) {
1409
- const r = broker.consume(a1.token.tokenId);
1410
- expect(r.success).toBe(false);
1411
- }
1412
- if (a2.authorized) {
1413
- const r = broker.consume(a2.token.tokenId);
1414
- expect(r.success).toBe(false);
1415
- }
1416
- });
1417
-
1418
- test("revokeAll on empty broker is a no-op", () => {
1419
- expect(broker.activeTokenCount).toBe(0);
1420
- broker.revokeAll();
1421
- expect(broker.activeTokenCount).toBe(0);
1422
- });
1423
- });
1424
-
1425
- // ---------------------------------------------------------------------------
1426
- // 8. Broker — canonical capability key and legacy alias
1427
- // ---------------------------------------------------------------------------
1428
-
1429
- describe("CredentialBroker — canonical capability key", () => {
1430
- let broker: CredentialBroker;
1431
-
1432
- beforeEach(() => {
1433
- if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
1434
- mkdirSync(TEST_DIR, { recursive: true });
1435
- setStorePathForTesting(STORE_PATH);
1436
- _resetBackend();
1437
- _setMetadataPath(join(TEST_DIR, "metadata.json"));
1438
- broker = new CredentialBroker();
1439
- });
1440
-
1441
- afterEach(() => {
1442
- _setMetadataPath(null);
1443
- setStorePathForTesting(null);
1444
- _resetBackend();
1445
- });
1446
-
1447
- test("authorize succeeds with canonical key when metadata has canonical key", () => {
1448
- upsertCredentialMetadata("github", "token", {
1449
- allowedTools: [BROWSER_FILL_CAPABILITY],
1450
- });
1451
-
1452
- const result = broker.authorize({
1453
- service: "github",
1454
- field: "token",
1455
- toolName: BROWSER_FILL_CAPABILITY,
1456
- });
1457
- expect(result.authorized).toBe(true);
1458
- });
1459
-
1460
- test("authorize succeeds with canonical key when metadata has legacy alias", () => {
1461
- upsertCredentialMetadata("github", "token", {
1462
- allowedTools: ["browser_fill_credential"],
1463
- });
1464
-
1465
- const result = broker.authorize({
1466
- service: "github",
1467
- field: "token",
1468
- toolName: BROWSER_FILL_CAPABILITY,
1469
- });
1470
- expect(result.authorized).toBe(true);
1471
- });
1472
-
1473
- test("authorize succeeds with legacy alias when metadata has canonical key", () => {
1474
- upsertCredentialMetadata("github", "token", {
1475
- allowedTools: [BROWSER_FILL_CAPABILITY],
1476
- });
1477
-
1478
- const result = broker.authorize({
1479
- service: "github",
1480
- field: "token",
1481
- toolName: "browser_fill_credential",
1482
- });
1483
- expect(result.authorized).toBe(true);
1484
- });
1485
-
1486
- test("serverUse with canonical key works when metadata has legacy alias", async () => {
1487
- upsertCredentialMetadata("vercel", "api_token", {
1488
- allowedTools: ["browser_fill_credential"],
1489
- });
1490
- await setSecureKeyAsync(credentialKey("vercel", "api_token"), "vercel-tok");
1491
-
1492
- const result = await broker.serverUse({
1493
- service: "vercel",
1494
- field: "api_token",
1495
- toolName: BROWSER_FILL_CAPABILITY,
1496
- execute: async (v) => v,
1497
- });
1498
-
1499
- expect(result.success).toBe(true);
1500
- expect(result.result).toBe("vercel-tok");
1501
- });
1502
-
1503
- test("non-aliased tool names are unaffected by alias resolution", () => {
1504
- upsertCredentialMetadata("svc", "key", {
1505
- allowedTools: ["custom_tool"],
1506
- });
1507
-
1508
- const result = broker.authorize({
1509
- service: "svc",
1510
- field: "key",
1511
- toolName: "custom_tool",
1512
- });
1513
- expect(result.authorized).toBe(true);
1514
- });
1515
-
1516
- test("non-aliased tool denied when only canonical key is allowed", () => {
1517
- upsertCredentialMetadata("svc", "key", {
1518
- allowedTools: [BROWSER_FILL_CAPABILITY],
1519
- });
1520
-
1521
- const result = broker.authorize({
1522
- service: "svc",
1523
- field: "key",
1524
- toolName: "unrelated_tool",
1525
- });
1526
- expect(result.authorized).toBe(false);
1527
- });
1528
- });