@vellumai/assistant 0.4.49 → 0.4.51

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 (353) hide show
  1. package/ARCHITECTURE.md +24 -33
  2. package/README.md +3 -3
  3. package/docs/architecture/integrations.md +2 -2
  4. package/docs/architecture/keychain-broker.md +6 -6
  5. package/docs/architecture/memory.md +180 -119
  6. package/knip.json +32 -0
  7. package/package.json +3 -2
  8. package/src/__tests__/agent-loop.test.ts +3 -1
  9. package/src/__tests__/anthropic-provider.test.ts +114 -23
  10. package/src/__tests__/approval-cascade.test.ts +1 -15
  11. package/src/__tests__/approval-routes-http.test.ts +2 -0
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  13. package/src/__tests__/btw-routes.test.ts +61 -5
  14. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  15. package/src/__tests__/checker.test.ts +13 -0
  16. package/src/__tests__/config-schema.test.ts +1 -68
  17. package/src/__tests__/config-watcher.test.ts +8 -0
  18. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  19. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  20. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  21. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  22. package/src/__tests__/credential-security-invariants.test.ts +8 -7
  23. package/src/__tests__/credential-vault-unit.test.ts +23 -18
  24. package/src/__tests__/credential-vault.test.ts +30 -18
  25. package/src/__tests__/credentials-cli.test.ts +257 -82
  26. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  27. package/src/__tests__/date-context.test.ts +93 -77
  28. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  29. package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
  30. package/src/__tests__/history-repair.test.ts +245 -0
  31. package/src/__tests__/host-cu-proxy.test.ts +165 -3
  32. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  33. package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
  34. package/src/__tests__/integration-status.test.ts +31 -30
  35. package/src/__tests__/invite-redemption-service.test.ts +166 -13
  36. package/src/__tests__/invite-routes-http.test.ts +166 -5
  37. package/src/__tests__/keychain-broker-client.test.ts +4 -4
  38. package/src/__tests__/list-messages-attachments.test.ts +193 -0
  39. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  40. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  41. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  42. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  43. package/src/__tests__/memory-regressions.test.ts +477 -2841
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  45. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  46. package/src/__tests__/mime-builder.test.ts +28 -0
  47. package/src/__tests__/native-web-search.test.ts +1 -0
  48. package/src/__tests__/oauth-cli.test.ts +824 -31
  49. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  50. package/src/__tests__/oauth-store.test.ts +363 -17
  51. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  52. package/src/__tests__/registry.test.ts +0 -1
  53. package/src/__tests__/relay-server.test.ts +55 -1
  54. package/src/__tests__/schedule-tools.test.ts +32 -0
  55. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  56. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  57. package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
  58. package/src/__tests__/secure-keys.test.ts +78 -18
  59. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  60. package/src/__tests__/server-history-render.test.ts +2 -2
  61. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  62. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  63. package/src/__tests__/session-agent-loop.test.ts +19 -15
  64. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  65. package/src/__tests__/session-error.test.ts +124 -2
  66. package/src/__tests__/session-history-web-search.test.ts +918 -0
  67. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  68. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  69. package/src/__tests__/session-queue.test.ts +37 -27
  70. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  71. package/src/__tests__/session-slash-known.test.ts +1 -15
  72. package/src/__tests__/session-slash-queue.test.ts +1 -15
  73. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  74. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  75. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  76. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  77. package/src/__tests__/skills-install-extract.test.ts +93 -0
  78. package/src/__tests__/skills.test.ts +2 -2
  79. package/src/__tests__/skillssh-registry.test.ts +451 -0
  80. package/src/__tests__/slack-channel-config.test.ts +10 -8
  81. package/src/__tests__/trust-store.test.ts +15 -0
  82. package/src/__tests__/twilio-config.test.ts +11 -10
  83. package/src/__tests__/twilio-provider.test.ts +9 -4
  84. package/src/__tests__/voice-invite-redemption.test.ts +85 -5
  85. package/src/agent/ax-tree-compaction.test.ts +51 -0
  86. package/src/agent/loop.ts +39 -12
  87. package/src/approvals/AGENTS.md +1 -1
  88. package/src/approvals/guardian-request-resolvers.ts +14 -2
  89. package/src/bundler/compiler-tools.ts +66 -2
  90. package/src/calls/call-domain.ts +134 -3
  91. package/src/calls/call-store.ts +6 -0
  92. package/src/calls/relay-server.ts +44 -6
  93. package/src/calls/relay-setup-router.ts +17 -1
  94. package/src/calls/twilio-config.ts +5 -4
  95. package/src/calls/twilio-provider.ts +14 -9
  96. package/src/calls/twilio-rest.ts +10 -7
  97. package/src/calls/types.ts +3 -1
  98. package/src/cli/commands/config.ts +14 -9
  99. package/src/cli/commands/contacts.ts +3 -0
  100. package/src/cli/commands/credentials.ts +170 -174
  101. package/src/cli/commands/doctor.ts +11 -8
  102. package/src/cli/commands/keys.ts +9 -9
  103. package/src/cli/commands/mcp.ts +46 -59
  104. package/src/cli/commands/memory.ts +16 -165
  105. package/src/cli/commands/oauth/apps.ts +68 -10
  106. package/src/cli/commands/oauth/connections.ts +475 -105
  107. package/src/cli/commands/oauth/index.ts +3 -3
  108. package/src/cli/commands/oauth/providers.ts +18 -4
  109. package/src/cli/commands/sessions.ts +5 -2
  110. package/src/cli/commands/skills.ts +173 -1
  111. package/src/cli/http-client.ts +0 -20
  112. package/src/cli/main-screen.tsx +2 -2
  113. package/src/cli/program.ts +5 -6
  114. package/src/cli.ts +20 -22
  115. package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
  116. package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
  117. package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +35 -11
  119. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  120. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  121. package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
  135. package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
  136. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
  137. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
  138. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
  139. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
  140. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
  141. package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
  142. package/src/config/bundled-skills/messaging/SKILL.md +1 -1
  143. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  144. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  145. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
  146. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
  148. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
  149. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
  150. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
  151. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
  152. package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
  153. package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
  154. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
  155. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
  156. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
  157. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
  158. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
  159. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
  160. package/src/config/bundled-tool-registry.ts +2 -5
  161. package/src/config/loader.ts +6 -42
  162. package/src/config/schema.ts +1 -12
  163. package/src/config/schemas/memory-lifecycle.ts +0 -9
  164. package/src/config/schemas/memory-processing.ts +0 -180
  165. package/src/config/schemas/memory-retrieval.ts +32 -104
  166. package/src/config/schemas/memory.ts +0 -10
  167. package/src/config/types.ts +0 -4
  168. package/src/contacts/contact-store.ts +39 -2
  169. package/src/contacts/contacts-write.ts +9 -0
  170. package/src/context/window-manager.ts +4 -1
  171. package/src/daemon/config-watcher.ts +55 -2
  172. package/src/daemon/daemon-control.ts +1 -1
  173. package/src/daemon/date-context.ts +114 -31
  174. package/src/daemon/handlers/config-ingress.ts +2 -2
  175. package/src/daemon/handlers/config-slack-channel.ts +59 -39
  176. package/src/daemon/handlers/config-telegram.ts +23 -14
  177. package/src/daemon/handlers/session-history.ts +1 -358
  178. package/src/daemon/handlers/sessions.ts +18 -13
  179. package/src/daemon/handlers/shared.ts +3 -17
  180. package/src/daemon/handlers/skills.ts +20 -1
  181. package/src/daemon/history-repair.ts +72 -8
  182. package/src/daemon/host-cu-proxy.ts +55 -26
  183. package/src/daemon/lifecycle.ts +39 -4
  184. package/src/daemon/mcp-reload-service.ts +2 -2
  185. package/src/daemon/message-types/computer-use.ts +1 -12
  186. package/src/daemon/message-types/memory.ts +4 -16
  187. package/src/daemon/message-types/messages.ts +1 -0
  188. package/src/daemon/message-types/sessions.ts +4 -42
  189. package/src/daemon/server.ts +6 -1
  190. package/src/daemon/session-agent-loop-handlers.ts +38 -0
  191. package/src/daemon/session-agent-loop.ts +334 -48
  192. package/src/daemon/session-error.ts +89 -6
  193. package/src/daemon/session-history.ts +17 -7
  194. package/src/daemon/session-media-retry.ts +6 -2
  195. package/src/daemon/session-memory.ts +69 -149
  196. package/src/daemon/session-process.ts +10 -1
  197. package/src/daemon/session-runtime-assembly.ts +49 -19
  198. package/src/daemon/session-slash.ts +3 -5
  199. package/src/daemon/session-surfaces.ts +4 -1
  200. package/src/daemon/session-tool-setup.ts +7 -1
  201. package/src/daemon/session.ts +12 -2
  202. package/src/email/providers/index.ts +2 -2
  203. package/src/instrument.ts +61 -1
  204. package/src/media/avatar-router.ts +1 -1
  205. package/src/memory/admin.ts +2 -191
  206. package/src/memory/canonical-guardian-store.ts +38 -2
  207. package/src/memory/conversation-crud.ts +0 -33
  208. package/src/memory/conversation-queries.ts +25 -83
  209. package/src/memory/db-init.ts +32 -0
  210. package/src/memory/embedding-backend.ts +84 -8
  211. package/src/memory/embedding-types.ts +9 -1
  212. package/src/memory/indexer.ts +7 -46
  213. package/src/memory/invite-store.ts +19 -0
  214. package/src/memory/items-extractor.ts +274 -76
  215. package/src/memory/job-handlers/backfill.ts +2 -127
  216. package/src/memory/job-handlers/cleanup.ts +2 -16
  217. package/src/memory/job-handlers/extraction.ts +2 -138
  218. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  219. package/src/memory/job-handlers/summarization.ts +3 -148
  220. package/src/memory/job-utils.ts +21 -59
  221. package/src/memory/jobs-store.ts +1 -159
  222. package/src/memory/jobs-worker.ts +9 -52
  223. package/src/memory/migrations/104-core-indexes.ts +3 -3
  224. package/src/memory/migrations/149-oauth-tables.ts +2 -0
  225. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  226. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  227. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  228. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  229. package/src/memory/migrations/154-drop-fts.ts +20 -0
  230. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  231. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  232. package/src/memory/migrations/157-invite-contact-id.ts +104 -0
  233. package/src/memory/migrations/index.ts +8 -0
  234. package/src/memory/migrations/registry.ts +6 -0
  235. package/src/memory/qdrant-client.ts +148 -51
  236. package/src/memory/raw-query.ts +1 -1
  237. package/src/memory/retriever.test.ts +294 -273
  238. package/src/memory/retriever.ts +421 -645
  239. package/src/memory/schema/calls.ts +2 -0
  240. package/src/memory/schema/contacts.ts +1 -0
  241. package/src/memory/schema/memory-core.ts +3 -48
  242. package/src/memory/schema/oauth.ts +2 -0
  243. package/src/memory/search/formatting.ts +263 -176
  244. package/src/memory/search/lexical.ts +1 -254
  245. package/src/memory/search/ranking.ts +0 -455
  246. package/src/memory/search/semantic.ts +100 -14
  247. package/src/memory/search/staleness.ts +47 -0
  248. package/src/memory/search/tier-classifier.ts +21 -0
  249. package/src/memory/search/types.ts +15 -77
  250. package/src/memory/task-memory-cleanup.ts +4 -6
  251. package/src/messaging/provider.ts +1 -1
  252. package/src/messaging/providers/gmail/adapter.ts +1 -1
  253. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  254. package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
  255. package/src/messaging/providers/whatsapp/adapter.ts +13 -9
  256. package/src/messaging/registry.ts +9 -5
  257. package/src/oauth/byo-connection.test.ts +40 -25
  258. package/src/oauth/connect-orchestrator.ts +4 -10
  259. package/src/oauth/connection-resolver.ts +20 -6
  260. package/src/oauth/manual-token-connection.ts +5 -5
  261. package/src/oauth/oauth-store.ts +183 -31
  262. package/src/oauth/platform-connection.test.ts +1 -1
  263. package/src/oauth/provider-behaviors.ts +503 -4
  264. package/src/oauth/seed-providers.ts +214 -8
  265. package/src/oauth/token-persistence.ts +31 -16
  266. package/src/permissions/defaults.ts +1 -0
  267. package/src/permissions/trust-store.ts +23 -1
  268. package/src/playbooks/playbook-compiler.ts +1 -1
  269. package/src/prompts/system-prompt.ts +18 -2
  270. package/src/providers/anthropic/client.ts +56 -126
  271. package/src/providers/types.ts +7 -1
  272. package/src/runtime/AGENTS.md +9 -0
  273. package/src/runtime/auth/route-policy.ts +6 -3
  274. package/src/runtime/channel-readiness-service.ts +48 -40
  275. package/src/runtime/guardian-reply-router.ts +24 -22
  276. package/src/runtime/http-server.ts +2 -2
  277. package/src/runtime/http-types.ts +2 -0
  278. package/src/runtime/invite-redemption-service.ts +72 -12
  279. package/src/runtime/invite-service.ts +43 -0
  280. package/src/runtime/middleware/twilio-validation.ts +1 -1
  281. package/src/runtime/pending-interactions.ts +2 -2
  282. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  283. package/src/runtime/routes/btw-routes.ts +10 -5
  284. package/src/runtime/routes/conversation-routes.ts +56 -11
  285. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  286. package/src/runtime/routes/integrations/slack/channel.ts +2 -2
  287. package/src/runtime/routes/integrations/telegram.ts +2 -2
  288. package/src/runtime/routes/integrations/twilio.ts +17 -17
  289. package/src/runtime/routes/invite-routes.ts +29 -4
  290. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  291. package/src/runtime/routes/memory-item-routes.ts +503 -0
  292. package/src/runtime/routes/secret-routes.ts +17 -0
  293. package/src/runtime/routes/session-management-routes.ts +3 -3
  294. package/src/runtime/routes/settings-routes.ts +3 -3
  295. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  296. package/src/runtime/routes/workspace-routes.ts +9 -4
  297. package/src/runtime/routes/workspace-utils.ts +8 -2
  298. package/src/schedule/integration-status.ts +26 -19
  299. package/src/security/keychain-broker-client.ts +17 -4
  300. package/src/security/oauth2.ts +6 -7
  301. package/src/security/secure-keys.ts +44 -19
  302. package/src/security/token-manager.ts +46 -39
  303. package/src/services/vercel-deploy.ts +0 -24
  304. package/src/signals/confirm.ts +78 -0
  305. package/src/signals/mcp-reload.ts +18 -0
  306. package/src/skills/catalog-install.ts +74 -18
  307. package/src/skills/skillssh-registry.ts +503 -0
  308. package/src/tools/assets/search.ts +5 -1
  309. package/src/tools/computer-use/definitions.ts +0 -10
  310. package/src/tools/computer-use/registry.ts +1 -1
  311. package/src/tools/credentials/vault.ts +22 -7
  312. package/src/tools/memory/definitions.ts +4 -13
  313. package/src/tools/memory/handlers.test.ts +83 -103
  314. package/src/tools/memory/handlers.ts +50 -85
  315. package/src/tools/network/script-proxy/session-manager.ts +8 -8
  316. package/src/tools/schedule/create.ts +10 -3
  317. package/src/tools/schedule/update.ts +8 -1
  318. package/src/tools/skills/load.ts +25 -2
  319. package/src/watcher/provider-types.ts +1 -1
  320. package/src/watcher/providers/github.ts +1 -1
  321. package/src/watcher/providers/gmail.ts +3 -3
  322. package/src/watcher/providers/google-calendar.ts +3 -3
  323. package/src/watcher/providers/linear.ts +1 -1
  324. package/src/__tests__/clarification-resolver.test.ts +0 -193
  325. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  326. package/src/__tests__/conflict-policy.test.ts +0 -269
  327. package/src/__tests__/conflict-store.test.ts +0 -372
  328. package/src/__tests__/contradiction-checker.test.ts +0 -361
  329. package/src/__tests__/entity-extractor.test.ts +0 -211
  330. package/src/__tests__/entity-search.test.ts +0 -1117
  331. package/src/__tests__/profile-compiler.test.ts +0 -392
  332. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  333. package/src/__tests__/session-profile-injection.test.ts +0 -557
  334. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  335. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  336. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  337. package/src/daemon/session-conflict-gate.ts +0 -167
  338. package/src/daemon/session-dynamic-profile.ts +0 -77
  339. package/src/memory/clarification-resolver.ts +0 -417
  340. package/src/memory/conflict-intent.ts +0 -205
  341. package/src/memory/conflict-policy.ts +0 -127
  342. package/src/memory/conflict-store.ts +0 -410
  343. package/src/memory/contradiction-checker.ts +0 -508
  344. package/src/memory/entity-extractor.ts +0 -535
  345. package/src/memory/format-recall.ts +0 -47
  346. package/src/memory/fts-reconciler.ts +0 -165
  347. package/src/memory/job-handlers/conflict.ts +0 -200
  348. package/src/memory/profile-compiler.ts +0 -195
  349. package/src/memory/recall-cache.ts +0 -117
  350. package/src/memory/search/entity.ts +0 -535
  351. package/src/memory/search/query-expansion.test.ts +0 -70
  352. package/src/memory/search/query-expansion.ts +0 -118
  353. package/src/runtime/routes/mcp-routes.ts +0 -20
@@ -1,3 +1,6 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
1
4
  import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
2
5
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
6
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
@@ -11,7 +14,7 @@ import {
11
14
  deleteMcpOAuthCredentials,
12
15
  McpOAuthProvider,
13
16
  } from "../../mcp/mcp-oauth-provider.js";
14
- import { httpSend } from "../http-client.js";
17
+ import { getWorkspaceDir } from "../../util/platform.js";
15
18
  import { log } from "../logger.js";
16
19
 
17
20
  export const HEALTH_CHECK_TIMEOUT_MS = 10_000;
@@ -51,6 +54,21 @@ export async function checkServerHealth(
51
54
  }
52
55
  }
53
56
 
57
+ /**
58
+ * Write a signal file so the daemon's ConfigWatcher triggers an MCP reload.
59
+ * Used by `mcp reload`, `mcp auth`, and any operation that needs the daemon
60
+ * to reconnect MCP servers.
61
+ */
62
+ function signalMcpReload(): void {
63
+ try {
64
+ const signalsDir = join(getWorkspaceDir(), "signals");
65
+ mkdirSync(signalsDir, { recursive: true });
66
+ writeFileSync(join(signalsDir, "mcp-reload"), "");
67
+ } catch {
68
+ // Best-effort — the daemon may not be running or the directory may not exist.
69
+ }
70
+ }
71
+
54
72
  export function registerMcpCommand(program: Command): void {
55
73
  const mcp = program
56
74
  .command("mcp")
@@ -67,8 +85,8 @@ server uses one of three transport types:
67
85
  sse Remote server using Server-Sent Events
68
86
  streamable-http Remote server using Streamable HTTP transport
69
87
 
70
- After changing MCP server configuration, run 'vellum mcp reload' to apply
71
- changes without restarting the assistant.
88
+ MCP server configuration changes are detected automatically by the running
89
+ assistant. You can also run 'vellum mcp reload' to trigger a manual reload.
72
90
 
73
91
  Examples:
74
92
  $ assistant mcp list
@@ -251,61 +269,20 @@ Examples:
251
269
  .addHelpText(
252
270
  "after",
253
271
  `
254
- Sends a message to the running assistant to disconnect and reconnect all MCP
255
- servers using the current configuration from disk. Active sessions pick up
256
- new tools on their next turn automatically. The assistant must be running.
272
+ Signals the running assistant to disconnect and reconnect all MCP servers
273
+ using the current configuration from disk. Active sessions pick up new tools
274
+ on their next turn automatically. The assistant must be running.
257
275
 
258
276
  Examples:
259
277
  $ vellum mcp reload
260
278
  $ vellum mcp reload # after editing config.json to add a new server
261
279
  $ vellum mcp reload # after running "vellum mcp auth <server>"`,
262
280
  )
263
- .action(async () => {
264
- log.info("Sending reload request to assistant...");
265
- try {
266
- const res = await httpSend("/v1/mcp/reload", { method: "POST" });
267
- const response = (await res.json()) as {
268
- success: boolean;
269
- serverCount?: number;
270
- toolCount?: number;
271
- servers?: {
272
- id: string;
273
- connected: boolean;
274
- disabled?: boolean;
275
- toolCount: number;
276
- tools: string[];
277
- }[];
278
- error?: string;
279
- };
280
- if (response.success) {
281
- log.info(
282
- `MCP servers reloaded: ${response.serverCount} server(s), ${response.toolCount} tool(s)\n`,
283
- );
284
- if (response.servers && response.servers.length > 0) {
285
- for (const server of response.servers) {
286
- const status = server.disabled
287
- ? "⊘ Disabled"
288
- : server.connected
289
- ? "\u2713 Connected"
290
- : "\u2717 Not connected";
291
- log.info(` ${server.id}`);
292
- log.info(` Status: ${status}`);
293
- log.info(
294
- ` Tools: ${server.toolCount > 0 ? server.tools.join(", ") : "(none)"}`,
295
- );
296
- log.info("");
297
- }
298
- }
299
- } else {
300
- log.error(`Failed to reload: ${response.error}`);
301
- process.exitCode = 1;
302
- }
303
- } catch (err) {
304
- log.error(
305
- `Failed to send reload request: ${err instanceof Error ? err.message : err}`,
306
- );
307
- process.exitCode = 1;
308
- }
281
+ .action(() => {
282
+ signalMcpReload();
283
+ log.info(
284
+ "MCP reload signal sent. The running assistant will reconnect servers shortly.",
285
+ );
309
286
  });
310
287
 
311
288
  mcp
@@ -422,7 +399,10 @@ Examples:
422
399
 
423
400
  saveRawConfig(raw);
424
401
  log.info(`Added MCP server "${name}" (${opts.transportType})`);
425
- log.info("Run 'vellum mcp reload' to apply changes.");
402
+ log.info(
403
+ "The running assistant will pick up this change automatically. " +
404
+ "Or run 'vellum mcp reload' to apply now.",
405
+ );
426
406
  },
427
407
  );
428
408
 
@@ -444,8 +424,8 @@ OAuth flow. If the server already has valid cached tokens, the command succeeds
444
424
  immediately without opening a browser. Tokens are cached locally for future use
445
425
  by the assistant.
446
426
 
447
- After successful authentication, run 'vellum mcp reload' to apply changes
448
- without restarting the assistant.
427
+ After successful authentication, the running assistant detects the change
428
+ automatically. You can also run 'vellum mcp reload' to apply immediately.
449
429
 
450
430
  Examples:
451
431
  $ assistant mcp auth my-server
@@ -603,7 +583,11 @@ Examples:
603
583
  provider.stopCallbackServer();
604
584
 
605
585
  log.info(`Authentication successful for "${name}".`);
606
- log.info("Run 'vellum mcp reload' to apply changes.");
586
+ log.info(
587
+ "The running assistant will pick up this change automatically. " +
588
+ "Or run 'vellum mcp reload' to apply now.",
589
+ );
590
+ signalMcpReload();
607
591
  process.exit(0);
608
592
  });
609
593
 
@@ -621,8 +605,8 @@ any stored OAuth credentials (tokens, client info, discovery metadata) for
621
605
  sse/streamable-http servers. If no OAuth credentials exist, the cleanup is
622
606
  silently skipped.
623
607
 
624
- After removal, run 'vellum mcp reload' to apply changes without restarting
625
- the assistant.
608
+ After removal, the running assistant detects the change automatically. You
609
+ can also run 'vellum mcp reload' to apply immediately.
626
610
 
627
611
  Examples:
628
612
  $ assistant mcp remove my-server
@@ -655,6 +639,9 @@ Examples:
655
639
  delete servers[name];
656
640
  saveRawConfig(raw);
657
641
  log.info(`Removed MCP server "${name}".`);
658
- log.info("Run 'vellum mcp reload' to apply changes.");
642
+ log.info(
643
+ "The running assistant will pick up this change automatically. " +
644
+ "Or run 'vellum mcp reload' to apply now.",
645
+ );
659
646
  });
660
647
  }
@@ -1,21 +1,16 @@
1
1
  import type { Command } from "commander";
2
2
 
3
3
  import {
4
- dismissPendingConflicts,
5
4
  getMemorySystemStatus,
6
5
  queryMemory,
7
6
  requestMemoryBackfill,
8
7
  requestMemoryCleanup,
9
8
  requestMemoryRebuildIndex,
10
9
  } from "../../memory/admin.js";
11
- import { listPendingConflictDetails } from "../../memory/conflict-store.js";
12
10
  import { listConversations } from "../../memory/conversation-queries.js";
13
- import { rawGet } from "../../memory/db.js";
14
11
  import { initializeDb } from "../db.js";
15
12
  import { log } from "../logger.js";
16
13
 
17
- const SHORT_HASH_LENGTH = 8;
18
-
19
14
  export function registerMemoryCommand(program: Command): void {
20
15
  const memory = program
21
16
  .command("memory")
@@ -24,23 +19,20 @@ export function registerMemoryCommand(program: Command): void {
24
19
  memory.addHelpText(
25
20
  "after",
26
21
  `
27
- The memory subsystem indexes conversation segments into full-text search (FTS)
28
- and vector embeddings for semantic recall. When the assistant encounters new
29
- information that contradicts a stored fact, a conflict is created and held in
30
- "pending_clarification" status until explicitly dismissed or resolved.
22
+ The memory subsystem indexes conversation segments using hybrid search (dense
23
+ and sparse vector embeddings) for semantic recall, with tier classification
24
+ to prioritize high-value memories.
31
25
 
32
26
  Key concepts:
33
27
  segments Chunks of conversation text extracted for indexing
34
28
  items Distilled facts/statements derived from segments
35
29
  summaries Compressed representations of conversation history
36
30
  embeddings Vector representations used for semantic similarity search
37
- conflicts Pairs of contradictory statements awaiting resolution
38
31
 
39
32
  Examples:
40
33
  $ assistant memory status
41
34
  $ assistant memory query "What is the project deadline?"
42
- $ assistant memory backfill
43
- $ assistant memory dismiss-conflicts --all`,
35
+ $ assistant memory backfill`,
44
36
  );
45
37
 
46
38
  memory
@@ -59,10 +51,7 @@ Fields shown:
59
51
  items Total distilled fact items stored
60
52
  summaries Total compressed conversation summaries
61
53
  embeddings Total vector embeddings computed
62
- pending conflicts Conflicts awaiting user resolution
63
- resolved conflicts Conflicts that have been dismissed or resolved
64
- oldest pending age How long the oldest unresolved conflict has been waiting
65
- cleanup backlogs Number of resolved conflicts and superseded items pending cleanup
54
+ cleanup backlogs Number of superseded items pending cleanup
66
55
  cleanup throughput Number of cleanup operations completed in the last 24 hours
67
56
  jobs Status of background jobs (backfill, cleanup, rebuild-index)
68
57
 
@@ -84,29 +73,9 @@ Examples:
84
73
  log.info(`Items: ${status.counts.items.toLocaleString()}`);
85
74
  log.info(`Summaries: ${status.counts.summaries.toLocaleString()}`);
86
75
  log.info(`Embeddings: ${status.counts.embeddings.toLocaleString()}`);
87
- log.info(
88
- `Pending conflicts: ${status.conflicts.pending.toLocaleString()}`,
89
- );
90
- log.info(
91
- `Resolved conflicts: ${status.conflicts.resolved.toLocaleString()}`,
92
- );
93
- if (status.conflicts.oldestPendingAgeMs != null) {
94
- const oldestMinutes = Math.floor(
95
- status.conflicts.oldestPendingAgeMs / 60_000,
96
- );
97
- log.info(`Oldest pending conflict age: ${oldestMinutes} min`);
98
- } else {
99
- log.info("Oldest pending conflict age: n/a");
100
- }
101
- log.info(
102
- `Cleanup backlog (resolved conflicts): ${status.cleanup.resolvedBacklog.toLocaleString()}`,
103
- );
104
76
  log.info(
105
77
  `Cleanup backlog (superseded items): ${status.cleanup.supersededBacklog.toLocaleString()}`,
106
78
  );
107
- log.info(
108
- `Cleanup throughput 24h (resolved conflicts): ${status.cleanup.resolvedCompleted24h.toLocaleString()}`,
109
- );
110
79
  log.info(
111
80
  `Cleanup throughput 24h (superseded items): ${status.cleanup.supersededCompleted24h.toLocaleString()}`,
112
81
  );
@@ -123,8 +92,8 @@ Examples:
123
92
  .addHelpText(
124
93
  "after",
125
94
  `
126
- Queues a background job to index unprocessed conversation segments into FTS
127
- and vector embeddings. The job resumes from where the last backfill left off,
95
+ Queues a background job to index unprocessed conversation segments into
96
+ vector embeddings. The job resumes from where the last backfill left off,
128
97
  processing only new or unindexed segments.
129
98
 
130
99
  The --force flag restarts the backfill from the very beginning, reprocessing
@@ -144,7 +113,7 @@ Examples:
144
113
  memory
145
114
  .command("cleanup")
146
115
  .description(
147
- "Queue cleanup jobs for resolved conflicts and stale superseded items",
116
+ "Queue cleanup jobs for stale superseded items",
148
117
  )
149
118
  .option(
150
119
  "--retention-ms <ms>",
@@ -153,11 +122,8 @@ Examples:
153
122
  .addHelpText(
154
123
  "after",
155
124
  `
156
- Queues two background cleanup jobs:
157
- 1. Resolved conflicts cleanup removes conflict records that have been
158
- dismissed or resolved past the retention threshold.
159
- 2. Stale superseded items cleanup — removes memory items that have been
160
- superseded by newer, corrected facts past the retention threshold.
125
+ Queues a background cleanup job to remove memory items that have been
126
+ superseded by newer, corrected facts past the retention threshold.
161
127
 
162
128
  The optional --retention-ms flag sets the minimum age (in milliseconds) a
163
129
  record must have before it is eligible for cleanup. If omitted, the system
@@ -175,9 +141,6 @@ Examples:
175
141
  const jobs = requestMemoryCleanup(
176
142
  Number.isFinite(retentionMs) ? retentionMs : undefined,
177
143
  );
178
- log.info(
179
- `Queued cleanup_resolved_conflicts job: ${jobs.resolvedConflictsJobId}`,
180
- );
181
144
  log.info(
182
145
  `Queued cleanup_stale_superseded_items job: ${jobs.staleSupersededItemsJobId}`,
183
146
  );
@@ -195,8 +158,8 @@ Examples:
195
158
  Arguments:
196
159
  text The recall query string used to search memory (e.g. "What is the
197
160
  project deadline?"). Matched against indexed segments using the full
198
- recall pipeline: lexical (FTS), semantic (vector similarity), recency
199
- (time-weighted), and entity (named entity extraction).
161
+ recall pipeline: semantic (dense + sparse vector similarity) and recency
162
+ (time-weighted).
200
163
 
201
164
  Runs the complete memory recall pipeline and displays hit counts for each
202
165
  retrieval strategy, the total injected token count, query latency, and the
@@ -221,10 +184,8 @@ Examples:
221
184
  if (result.degraded) {
222
185
  log.info(`Memory degraded: ${result.reason ?? "unknown reason"}`);
223
186
  }
224
- log.info(`Lexical hits: ${result.lexicalHits}`);
225
187
  log.info(`Semantic hits: ${result.semanticHits}`);
226
188
  log.info(`Recency hits: ${result.recencyHits}`);
227
- log.info(`Entity hits: ${result.entityHits}`);
228
189
  log.info(`Injected tokens: ${result.injectedTokens}`);
229
190
  log.info(`Latency: ${result.latencyMs}ms`);
230
191
  if (result.injectedText.length > 0) {
@@ -237,13 +198,13 @@ Examples:
237
198
 
238
199
  memory
239
200
  .command("rebuild-index")
240
- .description("Queue a memory FTS+embedding index rebuild job")
201
+ .description("Queue a memory embedding index rebuild job")
241
202
  .addHelpText(
242
203
  "after",
243
204
  `
244
- Queues a background job that performs a full rebuild of both the FTS (full-text
245
- search) index and the vector embedding index. All existing index data is
246
- dropped and reconstructed from the source memory items.
205
+ Queues a background job that performs a full rebuild of the vector embedding
206
+ index. All existing index data is dropped and reconstructed from the source
207
+ memory items.
247
208
 
248
209
  This is useful after schema changes, embedding model upgrades, or if index
249
210
  corruption is suspected. The rebuild runs asynchronously; use "assistant memory
@@ -258,114 +219,4 @@ Examples:
258
219
  const jobId = requestMemoryRebuildIndex();
259
220
  log.info(`Queued rebuild-index job: ${jobId}`);
260
221
  });
261
-
262
- memory
263
- .command("dismiss-conflicts")
264
- .description("Dismiss pending memory conflicts (all or matching a pattern)")
265
- .option("-a, --all", "Dismiss all pending conflicts")
266
- .option(
267
- "-p, --pattern <regex>",
268
- "Dismiss conflicts where either statement matches this regex",
269
- )
270
- .option("-s, --scope <id>", 'Memory scope (default: "default")')
271
- .option("--dry-run", "Show what would be dismissed without making changes")
272
- .addHelpText(
273
- "after",
274
- `
275
- Two modes of operation:
276
- --all Dismiss every pending conflict in the scope
277
- --pattern <regex> Dismiss only conflicts where either the existing or
278
- candidate statement matches the given regex (case-insensitive)
279
-
280
- At least one of --all or --pattern must be provided. If both are given,
281
- --all takes priority and all pending conflicts are dismissed.
282
-
283
- The --scope flag targets a specific memory scope. Defaults to "default" if
284
- omitted. The --dry-run flag previews which conflicts would be dismissed
285
- without actually modifying any records.
286
-
287
- Examples:
288
- $ assistant memory dismiss-conflicts --all
289
- $ assistant memory dismiss-conflicts --pattern "project deadline" --dry-run
290
- $ assistant memory dismiss-conflicts --pattern "^preferred\\b" --scope work`,
291
- )
292
- .action(
293
- (opts: {
294
- all?: boolean;
295
- pattern?: string;
296
- scope?: string;
297
- dryRun?: boolean;
298
- }) => {
299
- if (!opts.all && !opts.pattern) {
300
- log.info("At least one of --all or --pattern must be provided.");
301
- log.info("Use --dry-run to preview without making changes.");
302
- return;
303
- }
304
-
305
- initializeDb();
306
-
307
- const pattern = opts.pattern
308
- ? new RegExp(opts.pattern, "i")
309
- : undefined;
310
-
311
- if (opts.dryRun) {
312
- const scopeId = opts.scope ?? "default";
313
- const totalPending =
314
- rawGet<{ c: number }>(
315
- `SELECT COUNT(*) AS c FROM memory_item_conflicts WHERE scope_id = ? AND status = 'pending_clarification'`,
316
- scopeId,
317
- )?.c ?? 0;
318
-
319
- // Show a sample of conflicts (can't paginate without dismissing)
320
- const sample = listPendingConflictDetails(scopeId, 1000);
321
- let matchCount = 0;
322
- for (const conflict of sample) {
323
- const matches =
324
- opts.all ||
325
- (pattern &&
326
- (pattern.test(conflict.existingStatement) ||
327
- pattern.test(conflict.candidateStatement)));
328
- if (!matches) continue;
329
- matchCount++;
330
- log.info(
331
- ` [${conflict.id.slice(0, SHORT_HASH_LENGTH)}] "${
332
- conflict.existingStatement
333
- }" vs "${conflict.candidateStatement}"`,
334
- );
335
- }
336
-
337
- if (opts.all) {
338
- // --all matches everything, so matchCount is just the sample size
339
- log.info(
340
- `\nDry run: ${totalPending} of ${totalPending} pending conflicts would be dismissed.`,
341
- );
342
- } else {
343
- const moreNote =
344
- totalPending > sample.length
345
- ? ` (showing first ${sample.length} of ${totalPending})`
346
- : "";
347
- log.info(
348
- `\nDry run: ${matchCount} of ${totalPending} pending conflicts would be dismissed.${moreNote}`,
349
- );
350
- }
351
- return;
352
- }
353
-
354
- const result = dismissPendingConflicts({
355
- all: opts.all,
356
- pattern,
357
- scopeId: opts.scope,
358
- });
359
- for (const detail of result.details) {
360
- log.info(
361
- ` Dismissed [${detail.id.slice(0, SHORT_HASH_LENGTH)}]: "${
362
- detail.existingStatement
363
- }" vs "${detail.candidateStatement}"`,
364
- );
365
- }
366
- log.info(
367
- `\nDismissed ${result.dismissed} conflicts. ${result.remaining} pending conflicts remain.`,
368
- );
369
- },
370
- );
371
222
  }
@@ -8,6 +8,8 @@ import {
8
8
  listApps,
9
9
  upsertApp,
10
10
  } from "../../../oauth/oauth-store.js";
11
+ import { credentialKey } from "../../../security/credential-key.js";
12
+ import { getCredentialMetadata } from "../../../tools/credentials/metadata-store.js";
11
13
  import { getCliLogger } from "../../logger.js";
12
14
  import { shouldOutputJson, writeOutput } from "../../output.js";
13
15
 
@@ -45,8 +47,8 @@ client_secret linked to a provider. Each provider can have multiple apps
45
47
  Examples:
46
48
  $ assistant oauth apps list
47
49
  $ assistant oauth apps get --id <uuid>
48
- $ assistant oauth apps get --provider integration:gmail
49
- $ assistant oauth apps upsert --provider integration:gmail --client-id abc123
50
+ $ assistant oauth apps get --provider integration:google
51
+ $ assistant oauth apps upsert --provider integration:google --client-id abc123
50
52
  $ assistant oauth apps delete <id>`,
51
53
  );
52
54
 
@@ -96,7 +98,7 @@ Examples:
96
98
  "Look up an OAuth app by ID, provider + client-id, or provider",
97
99
  )
98
100
  .option("--id <id>", "App ID (UUID)")
99
- .option("--provider <key>", "Provider key (e.g. integration:gmail)")
101
+ .option("--provider <key>", "Provider key (e.g. integration:google)")
100
102
  .option("--client-id <id>", "OAuth client ID (requires --provider)")
101
103
  .addHelpText(
102
104
  "after",
@@ -107,10 +109,10 @@ Three lookup modes are supported:
107
109
  $ assistant oauth apps get --id <uuid>
108
110
 
109
111
  2. By provider + client ID (exact match):
110
- $ assistant oauth apps get --provider integration:gmail --client-id abc123
112
+ $ assistant oauth apps get --provider integration:google --client-id abc123
111
113
 
112
114
  3. By provider only (returns the most recently created app):
113
- $ assistant oauth apps get --provider integration:gmail
115
+ $ assistant oauth apps get --provider integration:google
114
116
 
115
117
  At least --id or --provider must be specified.`,
116
118
  )
@@ -159,12 +161,19 @@ At least --id or --provider must be specified.`,
159
161
  apps
160
162
  .command("upsert")
161
163
  .description("Create or return an existing OAuth app registration")
162
- .requiredOption("--provider <key>", "Provider key (e.g. integration:gmail)")
164
+ .requiredOption(
165
+ "--provider <key>",
166
+ "Provider key (e.g. integration:google)",
167
+ )
163
168
  .requiredOption("--client-id <id>", "OAuth client ID")
164
169
  .option(
165
170
  "--client-secret <secret>",
166
171
  "OAuth client secret (stored in secure keychain)",
167
172
  )
173
+ .option(
174
+ "--client-secret-credential-path <path>",
175
+ "Path to an existing client secret in the credential store (mutually exclusive with --client-secret)",
176
+ )
168
177
  .addHelpText(
169
178
  "after",
170
179
  `
@@ -175,21 +184,70 @@ stored in the secure system keychain — not in the database.
175
184
  When an existing app is matched and a --client-secret is provided, the stored
176
185
  secret is updated. The app row itself is returned as-is.
177
186
 
187
+ You can supply the client secret directly via --client-secret, or reference an
188
+ existing credential in the store via --client-secret-credential-path. These two
189
+ options are mutually exclusive — providing both is an error.
190
+
191
+ The --client-secret-credential-path accepts two formats:
192
+ 1. Full credential path: "credential/integration:google/client_secret"
193
+ 2. Short name (service:field): "integration:google:client_secret"
194
+ Resolved via the metadata store by splitting on the last colon.
195
+
178
196
  Examples:
179
- $ assistant oauth apps upsert --provider integration:gmail --client-id abc123
197
+ $ assistant oauth apps upsert --provider integration:google --client-id abc123
180
198
  $ assistant oauth apps upsert --provider integration:slack --client-id def456 --client-secret s3cret
181
- $ assistant oauth apps upsert --provider integration:gmail --client-id abc123 --json`,
199
+ $ assistant oauth apps upsert --provider integration:slack --client-id def456 --client-secret-credential-path "credential/integration:slack/client_secret"
200
+ $ assistant oauth apps upsert --provider integration:slack --client-id def456 --client-secret-credential-path "integration:slack:client_secret"
201
+ $ assistant oauth apps upsert --provider integration:google --client-id abc123 --json`,
182
202
  )
183
203
  .action(
184
204
  async (
185
- opts: { provider: string; clientId: string; clientSecret?: string },
205
+ opts: {
206
+ provider: string;
207
+ clientId: string;
208
+ clientSecret?: string;
209
+ clientSecretCredentialPath?: string;
210
+ },
186
211
  cmd: Command,
187
212
  ) => {
188
213
  try {
214
+ if (opts.clientSecret && opts.clientSecretCredentialPath) {
215
+ writeOutput(cmd, {
216
+ ok: false,
217
+ error:
218
+ "Cannot provide both --client-secret and --client-secret-credential-path",
219
+ });
220
+ process.exitCode = 1;
221
+ return;
222
+ }
223
+
224
+ let resolvedCredentialPath = opts.clientSecretCredentialPath;
225
+ if (
226
+ resolvedCredentialPath &&
227
+ !resolvedCredentialPath.startsWith("credential/")
228
+ ) {
229
+ // Attempt to interpret as a credential key — split on the LAST colon to get service/field
230
+ const lastColon = resolvedCredentialPath.lastIndexOf(":");
231
+ if (lastColon > 0) {
232
+ const asService = resolvedCredentialPath.slice(0, lastColon);
233
+ const asField = resolvedCredentialPath.slice(lastColon + 1);
234
+ // If a credential exists in metadata with these coordinates, resolve it
235
+ const meta = getCredentialMetadata(asService, asField);
236
+ if (meta) {
237
+ resolvedCredentialPath = credentialKey(asService, asField);
238
+ }
239
+ }
240
+ }
241
+
242
+ const clientSecretOpts = opts.clientSecret
243
+ ? { clientSecretValue: opts.clientSecret }
244
+ : resolvedCredentialPath
245
+ ? { clientSecretCredentialPath: resolvedCredentialPath }
246
+ : undefined;
189
247
  const row = await upsertApp(
190
248
  opts.provider,
191
249
  opts.clientId,
192
- opts.clientSecret,
250
+ clientSecretOpts,
193
251
  );
194
252
 
195
253
  if (!shouldOutputJson(cmd)) {