@vellumai/assistant 0.8.0 → 0.8.1

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 (692) hide show
  1. package/AGENTS.md +11 -0
  2. package/Dockerfile +5 -4
  3. package/README.md +2 -2
  4. package/docker-entrypoint.sh +16 -0
  5. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
  6. package/eslint-rules/cli-no-daemon-internals.js +283 -0
  7. package/eslint.config.mjs +12 -0
  8. package/knip.json +2 -1
  9. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
  10. package/openapi.yaml +4847 -1698
  11. package/package.json +3 -1
  12. package/scripts/generate-openapi.ts +52 -4
  13. package/scripts/sync-llm-catalog.ts +165 -0
  14. package/scripts/sync-web-search-catalog.ts +107 -0
  15. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
  17. package/src/__tests__/anthropic-provider.test.ts +92 -2
  18. package/src/__tests__/app-control-flow.test.ts +7 -0
  19. package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
  20. package/src/__tests__/avatar-identity-sync.test.ts +87 -0
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
  22. package/src/__tests__/btw-routes.test.ts +1 -0
  23. package/src/__tests__/call-site-routing-provider.test.ts +172 -45
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
  25. package/src/__tests__/channel-policy.test.ts +12 -0
  26. package/src/__tests__/checker.test.ts +89 -0
  27. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
  28. package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
  29. package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
  30. package/src/__tests__/config-loader-backfill.test.ts +526 -102
  31. package/src/__tests__/config-loader-corrupt.test.ts +68 -0
  32. package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
  33. package/src/__tests__/config-schema-cmd.test.ts +63 -29
  34. package/src/__tests__/config-schema.test.ts +14 -3
  35. package/src/__tests__/config-set-platform-guard.test.ts +75 -152
  36. package/src/__tests__/config-set-route.test.ts +198 -0
  37. package/src/__tests__/config-watcher.test.ts +6 -0
  38. package/src/__tests__/contacts-tools.test.ts +51 -199
  39. package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
  40. package/src/__tests__/context-search-agent-runner.test.ts +22 -138
  41. package/src/__tests__/context-search-conversations-source.test.ts +42 -16
  42. package/src/__tests__/context-search-fanout.test.ts +20 -157
  43. package/src/__tests__/context-search-memory-v2-source.test.ts +3 -3
  44. package/src/__tests__/context-search-types.test.ts +7 -2
  45. package/src/__tests__/context-window-manager.test.ts +389 -1
  46. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  47. package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
  48. package/src/__tests__/conversation-error.test.ts +38 -0
  49. package/src/__tests__/conversation-fork-crud.test.ts +241 -1
  50. package/src/__tests__/conversation-inference-profile-route.test.ts +14 -14
  51. package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
  52. package/src/__tests__/conversation-lifecycle.test.ts +124 -0
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
  54. package/src/__tests__/conversation-process-callsite.test.ts +21 -1
  55. package/src/__tests__/conversation-runtime-assembly.test.ts +4 -4
  56. package/src/__tests__/conversation-slash-commands.test.ts +194 -2
  57. package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
  58. package/src/__tests__/credential-security-invariants.test.ts +5 -6
  59. package/src/__tests__/daemon-credential-client.test.ts +56 -1
  60. package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
  61. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
  62. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
  63. package/src/__tests__/db-proxy-transaction.test.ts +206 -0
  64. package/src/__tests__/external-plugin-loader.test.ts +458 -0
  65. package/src/__tests__/filing-service.test.ts +23 -3
  66. package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
  67. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  68. package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
  69. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -8
  70. package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
  71. package/src/__tests__/heartbeat-service.test.ts +50 -233
  72. package/src/__tests__/history-repair.test.ts +89 -0
  73. package/src/__tests__/host-app-control-proxy.test.ts +109 -1
  74. package/src/__tests__/host-app-control-routes.test.ts +247 -1
  75. package/src/__tests__/host-browser-proxy.test.ts +416 -20
  76. package/src/__tests__/host-browser-routes.test.ts +325 -33
  77. package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
  78. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
  79. package/src/__tests__/inference-profile-reaper.test.ts +154 -0
  80. package/src/__tests__/inference-profile-session-handler.test.ts +398 -0
  81. package/src/__tests__/inference-profile-session-ipc.test.ts +236 -0
  82. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
  83. package/src/__tests__/install-skill-routing.test.ts +2 -2
  84. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +15 -0
  85. package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
  86. package/src/__tests__/llm-catalog-parity.test.ts +146 -0
  87. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +188 -0
  88. package/src/__tests__/llm-request-log-source-factory.test.ts +124 -0
  89. package/src/__tests__/llm-resolver.test.ts +46 -0
  90. package/src/__tests__/managed-profile-guard.test.ts +131 -2
  91. package/src/__tests__/mcp-auth-routes.test.ts +1 -0
  92. package/src/__tests__/mcp-cli.test.ts +182 -220
  93. package/src/__tests__/mcp-health-check.test.ts +56 -27
  94. package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
  95. package/src/__tests__/message-complete-display-id.test.ts +175 -0
  96. package/src/__tests__/notification-platform-adapter.test.ts +229 -0
  97. package/src/__tests__/oauth-cli.test.ts +38 -2009
  98. package/src/__tests__/oauth-commands-routes.test.ts +711 -0
  99. package/src/__tests__/oauth-connect-routes.test.ts +174 -11
  100. package/src/__tests__/oauth-providers-routes.test.ts +14 -10
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +33 -12
  102. package/src/__tests__/openai-responses-provider.test.ts +17 -0
  103. package/src/__tests__/plugin-bootstrap.test.ts +31 -2
  104. package/src/__tests__/plugin-route-contribution.test.ts +31 -3
  105. package/src/__tests__/plugin-tool-contribution.test.ts +31 -3
  106. package/src/__tests__/plugin-types.test.ts +13 -11
  107. package/src/__tests__/process-message-background-slack.test.ts +46 -0
  108. package/src/__tests__/profile-entry-status.test.ts +43 -0
  109. package/src/__tests__/provider-managed-proxy-integration.test.ts +12 -4
  110. package/src/__tests__/provider-registry-ollama.test.ts +12 -4
  111. package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
  112. package/src/__tests__/relay-server.test.ts +118 -0
  113. package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
  114. package/src/__tests__/schedule-retry.test.ts +56 -4
  115. package/src/__tests__/schedule-routes.test.ts +104 -0
  116. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
  117. package/src/__tests__/scheduler-recurrence.test.ts +87 -34
  118. package/src/__tests__/scheduler-reuse-conversation.test.ts +161 -5
  119. package/src/__tests__/scheduler-wake.test.ts +0 -63
  120. package/src/__tests__/secret-allowlist.test.ts +1 -0
  121. package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
  122. package/src/__tests__/shell-credential-ref.test.ts +95 -3
  123. package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
  124. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  125. package/src/__tests__/skill-load-tool.test.ts +2 -4
  126. package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
  127. package/src/__tests__/suggestion-routes.test.ts +3 -3
  128. package/src/__tests__/sync-message-contract.test.ts +63 -0
  129. package/src/__tests__/task-scheduler.test.ts +88 -23
  130. package/src/__tests__/update-bulletin-job.test.ts +96 -193
  131. package/src/__tests__/usage-cli.test.ts +11 -73
  132. package/src/__tests__/user-plugin-loader.test.ts +145 -0
  133. package/src/__tests__/vercel-config.test.ts +168 -0
  134. package/src/__tests__/web-search-catalog-parity.test.ts +86 -0
  135. package/src/__tests__/web-search.test.ts +303 -2
  136. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
  137. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +58 -0
  138. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
  139. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +191 -0
  140. package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
  141. package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
  142. package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
  143. package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
  144. package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
  145. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
  146. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
  147. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
  148. package/src/acp/__tests__/helpers/which-stub.ts +4 -2
  149. package/src/acp/resolve-agent.test.ts +25 -0
  150. package/src/acp/resolve-agent.ts +13 -2
  151. package/src/acp/session-manager.ts +14 -0
  152. package/src/approvals/guardian-request-resolvers.ts +32 -87
  153. package/src/calls/relay-server.ts +35 -0
  154. package/src/calls/relay-setup-router.ts +36 -0
  155. package/src/calls/types.ts +1 -0
  156. package/src/calls/voice-session-bridge.ts +23 -4
  157. package/src/channels/config.ts +14 -1
  158. package/src/channels/types.ts +1 -0
  159. package/src/cli/AGENTS.md +164 -4
  160. package/src/cli/__tests__/notifications.test.ts +54 -0
  161. package/src/cli/commands/__tests__/avatar.test.ts +540 -0
  162. package/src/cli/commands/__tests__/backup.test.ts +236 -776
  163. package/src/cli/commands/__tests__/cache.test.ts +1 -1
  164. package/src/cli/commands/__tests__/changelog.test.ts +593 -0
  165. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
  166. package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
  167. package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
  168. package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
  169. package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
  170. package/src/cli/commands/__tests__/email-core.test.ts +579 -0
  171. package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
  172. package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
  173. package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
  174. package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
  175. package/src/cli/commands/__tests__/skills.test.ts +563 -0
  176. package/src/cli/commands/__tests__/status.test.ts +249 -0
  177. package/src/cli/commands/__tests__/stt.test.ts +320 -0
  178. package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
  179. package/src/cli/commands/__tests__/tts.test.ts +321 -0
  180. package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
  181. package/src/cli/commands/attachment.ts +8 -3
  182. package/src/cli/commands/audit.ts +95 -64
  183. package/src/cli/commands/auth.ts +61 -58
  184. package/src/cli/commands/avatar.ts +276 -390
  185. package/src/cli/commands/backup.ts +409 -505
  186. package/src/cli/commands/bash.ts +9 -5
  187. package/src/cli/commands/browser.ts +28 -9
  188. package/src/cli/commands/cache.ts +9 -4
  189. package/src/cli/commands/changelog.ts +414 -0
  190. package/src/cli/commands/channel-verification-sessions.ts +238 -317
  191. package/src/cli/commands/clients.ts +8 -3
  192. package/src/cli/commands/completions.ts +9 -9
  193. package/src/cli/commands/config.ts +102 -72
  194. package/src/cli/commands/contacts.ts +575 -696
  195. package/src/cli/commands/conversations-defer.ts +17 -69
  196. package/src/cli/commands/conversations-import.ts +90 -253
  197. package/src/cli/commands/conversations.ts +346 -436
  198. package/src/cli/commands/credential-execution.ts +9 -6
  199. package/src/cli/commands/credentials.ts +456 -736
  200. package/src/cli/commands/domain.ts +128 -206
  201. package/src/cli/commands/email.ts +606 -794
  202. package/src/cli/commands/gateway.ts +8 -1
  203. package/src/cli/commands/image-generation.ts +157 -205
  204. package/src/cli/commands/inference-providers.ts +352 -0
  205. package/src/cli/commands/inference-session.ts +415 -0
  206. package/src/cli/commands/inference.ts +87 -65
  207. package/src/cli/commands/keys.ts +8 -3
  208. package/src/cli/commands/mcp.ts +103 -287
  209. package/src/cli/commands/memory-v2.ts +162 -516
  210. package/src/cli/commands/notifications.ts +33 -7
  211. package/src/cli/commands/oauth/apps.ts +292 -261
  212. package/src/cli/commands/oauth/connect.ts +176 -297
  213. package/src/cli/commands/oauth/disconnect.ts +16 -215
  214. package/src/cli/commands/oauth/index.ts +49 -45
  215. package/src/cli/commands/oauth/mode.ts +43 -199
  216. package/src/cli/commands/oauth/ping.ts +17 -125
  217. package/src/cli/commands/oauth/providers.ts +732 -921
  218. package/src/cli/commands/oauth/request.ts +60 -350
  219. package/src/cli/commands/oauth/shared.ts +11 -121
  220. package/src/cli/commands/oauth/status.ts +31 -121
  221. package/src/cli/commands/oauth/token.ts +13 -55
  222. package/src/cli/commands/pending.ts +19 -10
  223. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
  224. package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
  225. package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
  226. package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
  227. package/src/cli/commands/platform/connect.ts +16 -80
  228. package/src/cli/commands/platform/disconnect.ts +14 -112
  229. package/src/cli/commands/platform/index.ts +177 -246
  230. package/src/cli/commands/routes.ts +153 -336
  231. package/src/cli/commands/sequence.ts +316 -360
  232. package/src/cli/commands/skills.ts +449 -671
  233. package/src/cli/commands/status.ts +58 -37
  234. package/src/cli/commands/stt.ts +94 -262
  235. package/src/cli/commands/task.ts +14 -40
  236. package/src/cli/commands/trust.ts +8 -3
  237. package/src/cli/commands/tts.ts +162 -167
  238. package/src/cli/commands/ui.ts +35 -42
  239. package/src/cli/commands/usage.ts +188 -126
  240. package/src/cli/commands/watchers.ts +8 -3
  241. package/src/cli/commands/webhooks.ts +99 -193
  242. package/src/cli/lib/__tests__/register-command.test.ts +85 -0
  243. package/src/cli/lib/daemon-credential-client.ts +4 -5
  244. package/src/cli/lib/nested-value.ts +44 -0
  245. package/src/cli/lib/open-browser.ts +36 -0
  246. package/src/cli/lib/register-command.ts +19 -0
  247. package/src/cli/lib/time-ago.ts +34 -0
  248. package/src/cli/program.ts +2 -4
  249. package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
  250. package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
  251. package/src/cli/utils/conversation-id.ts +30 -0
  252. package/src/cli/utils/parse-duration.ts +41 -0
  253. package/src/config/acp-defaults.test.ts +5 -1
  254. package/src/config/acp-defaults.ts +11 -4
  255. package/src/config/bundled-skills/acp/TOOLS.json +2 -2
  256. package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +12 -45
  258. package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
  259. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
  260. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
  261. package/src/config/bundled-tool-registry.ts +0 -2
  262. package/src/config/feature-flag-registry.json +16 -0
  263. package/src/config/llm-resolver.ts +16 -1
  264. package/src/config/loader.ts +76 -14
  265. package/src/config/raw-config-utils.ts +2 -30
  266. package/src/config/schema.ts +4 -0
  267. package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
  268. package/src/config/schemas/call-site-catalog.ts +29 -7
  269. package/src/config/schemas/llm-request-logs.ts +57 -0
  270. package/src/config/schemas/llm.ts +52 -2
  271. package/src/config/schemas/memory-retrospective.ts +48 -0
  272. package/src/config/schemas/memory-v2.ts +32 -1
  273. package/src/config/schemas/memory.ts +4 -0
  274. package/src/config/schemas/services.ts +15 -12
  275. package/src/config/seed-inference-profiles.ts +195 -134
  276. package/src/contacts/contact-store.ts +0 -61
  277. package/src/context/window-manager.ts +191 -5
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +79 -0
  279. package/src/daemon/__tests__/conversation-tool-setup.test.ts +109 -4
  280. package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
  281. package/src/daemon/approval-generators.ts +23 -29
  282. package/src/daemon/config-watcher.ts +2 -0
  283. package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
  284. package/src/daemon/conversation-agent-loop.ts +127 -97
  285. package/src/daemon/conversation-error.ts +21 -0
  286. package/src/daemon/conversation-lifecycle.ts +46 -5
  287. package/src/daemon/conversation-process.ts +36 -19
  288. package/src/daemon/conversation-runtime-assembly.ts +14 -5
  289. package/src/daemon/conversation-slash.ts +175 -23
  290. package/src/daemon/conversation-store.ts +17 -10
  291. package/src/daemon/conversation-surfaces.ts +76 -12
  292. package/src/daemon/conversation-tool-setup.ts +24 -14
  293. package/src/daemon/conversation.ts +48 -9
  294. package/src/daemon/external-plugins-bootstrap.ts +18 -8
  295. package/src/daemon/guardian-action-generators.ts +7 -22
  296. package/src/daemon/handlers/config-model.ts +8 -126
  297. package/src/daemon/handlers/config-slack-channel.ts +10 -7
  298. package/src/daemon/handlers/config-vercel.ts +3 -1
  299. package/src/daemon/handlers/skills.ts +84 -5
  300. package/src/daemon/history-repair.ts +33 -6
  301. package/src/daemon/host-app-control-proxy.ts +44 -19
  302. package/src/daemon/host-bash-proxy.ts +85 -158
  303. package/src/daemon/host-browser-proxy.ts +96 -35
  304. package/src/daemon/host-proxy-base.ts +13 -1
  305. package/src/daemon/host-proxy-preactivation.ts +25 -1
  306. package/src/daemon/identity-helpers.ts +19 -0
  307. package/src/daemon/lifecycle.ts +42 -43
  308. package/src/daemon/meet-host-supervisor.ts +15 -15
  309. package/src/daemon/memory-v2-startup.ts +9 -2
  310. package/src/daemon/message-protocol.ts +6 -0
  311. package/src/daemon/message-types/bookmarks.ts +18 -0
  312. package/src/daemon/message-types/conversations.ts +12 -9
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/sync.ts +60 -0
  315. package/src/daemon/pkb-reminder-builder.test.ts +54 -13
  316. package/src/daemon/pkb-reminder-builder.ts +21 -7
  317. package/src/daemon/process-message.ts +56 -23
  318. package/src/daemon/server.ts +23 -18
  319. package/src/daemon/shutdown-handlers.ts +0 -2
  320. package/src/daemon/tool-setup-types.ts +9 -0
  321. package/src/daemon/tool-side-effects.ts +6 -4
  322. package/src/daemon/wake-target-adapter.ts +11 -0
  323. package/src/export/transcript-formatter.ts +61 -2
  324. package/src/filing/filing-service.ts +40 -53
  325. package/src/heartbeat/__tests__/heartbeat-service.test.ts +359 -0
  326. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  327. package/src/heartbeat/heartbeat-service.ts +148 -127
  328. package/src/home/__tests__/feed-types.test.ts +63 -131
  329. package/src/home/__tests__/feed-writer.test.ts +77 -278
  330. package/src/home/__tests__/post-connect-feed.test.ts +9 -12
  331. package/src/home/feed-types.ts +19 -73
  332. package/src/home/feed-writer.ts +25 -156
  333. package/src/home/post-connect-feed.ts +1 -3
  334. package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
  335. package/src/ipc/__tests__/email-ipc.test.ts +506 -0
  336. package/src/ipc/__tests__/exit-helper.test.ts +104 -0
  337. package/src/ipc/__tests__/streaming-client.test.ts +237 -0
  338. package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
  339. package/src/ipc/assistant-server.ts +55 -6
  340. package/src/ipc/cli-client.ts +370 -50
  341. package/src/ipc/routes/db-proxy-transaction.ts +151 -0
  342. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
  343. package/src/ipc/skill-routes/events.ts +30 -3
  344. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
  345. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
  346. package/src/live-voice/live-voice-session-manager.ts +11 -4
  347. package/src/live-voice/live-voice-session.ts +14 -6
  348. package/src/memory/__tests__/bookmark-crud.test.ts +258 -0
  349. package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
  350. package/src/memory/__tests__/conversation-types.test.ts +36 -0
  351. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
  352. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
  353. package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
  354. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +213 -0
  355. package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
  356. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
  357. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
  358. package/src/memory/bookmark-crud.ts +179 -0
  359. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
  360. package/src/memory/context-search/agent-protocol.ts +5 -1
  361. package/src/memory/context-search/agent-runner.ts +60 -85
  362. package/src/memory/context-search/limits.ts +1 -4
  363. package/src/memory/context-search/search.ts +23 -113
  364. package/src/memory/context-search/sources/conversations.ts +18 -6
  365. package/src/memory/context-search/sources/memory-v2.ts +39 -14
  366. package/src/memory/context-search/sources/memory.ts +7 -0
  367. package/src/memory/context-search/sources/workspace.ts +13 -10
  368. package/src/memory/context-search/types.ts +1 -1
  369. package/src/memory/conversation-bootstrap.ts +11 -0
  370. package/src/memory/conversation-crud.ts +312 -10
  371. package/src/memory/conversation-queries.ts +9 -5
  372. package/src/memory/conversation-title-service.ts +1 -0
  373. package/src/memory/conversation-types.ts +16 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +2 -1
  376. package/src/memory/embedding-runtime-manager.ts +1 -2
  377. package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
  378. package/src/memory/graph/conversation-graph-memory.ts +76 -5
  379. package/src/memory/graph/extraction.ts +4 -0
  380. package/src/memory/graph/graph-memory-state-store.ts +16 -3
  381. package/src/memory/graph/tool-handlers.ts +17 -7
  382. package/src/memory/graph/tools.ts +44 -5
  383. package/src/memory/indexer.ts +17 -0
  384. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +13 -15
  385. package/src/memory/jobs/embed-concept-page.ts +45 -9
  386. package/src/memory/jobs-store.ts +51 -1
  387. package/src/memory/jobs-worker.ts +52 -3
  388. package/src/memory/llm-request-log-source-clickhouse.ts +317 -0
  389. package/src/memory/llm-request-log-source-local.ts +26 -0
  390. package/src/memory/llm-request-log-source.ts +97 -0
  391. package/src/memory/llm-request-log-store.ts +1 -1
  392. package/src/memory/memory-retrospective-constants.ts +13 -0
  393. package/src/memory/memory-retrospective-enqueue.ts +114 -0
  394. package/src/memory/memory-retrospective-job.ts +351 -0
  395. package/src/memory/memory-retrospective-startup-cleanup.ts +108 -0
  396. package/src/memory/memory-retrospective-state.ts +162 -0
  397. package/src/memory/memory-retrospective-trigger-check.ts +91 -0
  398. package/src/memory/memory-v2-activation-log-store.ts +49 -5
  399. package/src/memory/memory-v2-concept-frequency.ts +4 -0
  400. package/src/memory/message-content.ts +38 -1
  401. package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
  402. package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
  403. package/src/memory/migrations/229-delete-private-conversations.test.ts +70 -1
  404. package/src/memory/migrations/229-delete-private-conversations.ts +12 -0
  405. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
  406. package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
  407. package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
  408. package/src/memory/migrations/242-message-bookmarks.ts +38 -0
  409. package/src/memory/migrations/243-provider-connections.ts +68 -0
  410. package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
  411. package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
  412. package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
  413. package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
  414. package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
  415. package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
  416. package/src/memory/migrations/index.ts +7 -0
  417. package/src/memory/published-pages-store.ts +16 -0
  418. package/src/memory/schema/bookmarks.ts +38 -0
  419. package/src/memory/schema/conversations.ts +2 -0
  420. package/src/memory/schema/index.ts +2 -0
  421. package/src/memory/schema/inference.ts +29 -0
  422. package/src/memory/schema/memory-core.ts +9 -0
  423. package/src/memory/search/semantic.ts +1 -4
  424. package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
  425. package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
  426. package/src/memory/v2/__tests__/activation.test.ts +11 -4
  427. package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
  428. package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
  429. package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
  430. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
  431. package/src/memory/v2/__tests__/injection.test.ts +628 -10
  432. package/src/memory/v2/__tests__/migration.test.ts +7 -3
  433. package/src/memory/v2/__tests__/page-index.test.ts +277 -0
  434. package/src/memory/v2/__tests__/page-store.test.ts +14 -1
  435. package/src/memory/v2/__tests__/prompts-router.test.ts +257 -0
  436. package/src/memory/v2/__tests__/qdrant.test.ts +72 -0
  437. package/src/memory/v2/__tests__/reranker.test.ts +4 -4
  438. package/src/memory/v2/__tests__/router.test.ts +516 -0
  439. package/src/memory/v2/__tests__/sim.test.ts +45 -1
  440. package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
  441. package/src/memory/v2/__tests__/static-context.test.ts +7 -22
  442. package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
  443. package/src/memory/v2/activation-store.ts +34 -5
  444. package/src/memory/v2/activation.ts +40 -27
  445. package/src/memory/v2/backfill-jobs.ts +17 -84
  446. package/src/memory/v2/consolidation-job.ts +85 -78
  447. package/src/memory/v2/frontmatter-sweep.ts +91 -0
  448. package/src/memory/v2/injection.ts +440 -109
  449. package/src/memory/v2/migration.ts +117 -20
  450. package/src/memory/v2/page-index.ts +191 -0
  451. package/src/memory/v2/page-store.ts +3 -0
  452. package/src/memory/v2/prompts/consolidation.ts +9 -7
  453. package/src/memory/v2/prompts/router.ts +192 -0
  454. package/src/memory/v2/qdrant.ts +100 -87
  455. package/src/memory/v2/reranker.ts +14 -7
  456. package/src/memory/v2/router.ts +322 -0
  457. package/src/memory/v2/sim.ts +25 -12
  458. package/src/memory/v2/skill-store.ts +118 -29
  459. package/src/memory/v2/static-context.ts +16 -9
  460. package/src/memory/v2/sweep-job.ts +122 -96
  461. package/src/memory/v2/types.ts +10 -6
  462. package/src/memory/validation.ts +13 -0
  463. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
  464. package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
  465. package/src/notifications/__tests__/signal-registry.test.ts +17 -0
  466. package/src/notifications/adapters/platform.ts +171 -0
  467. package/src/notifications/conversation-pairing.ts +2 -2
  468. package/src/notifications/copy-composer.ts +15 -0
  469. package/src/notifications/destination-resolver.ts +21 -0
  470. package/src/notifications/emit-signal.ts +28 -1
  471. package/src/notifications/home-feed-side-effect.ts +111 -0
  472. package/src/notifications/signal.ts +5 -0
  473. package/src/permissions/checker.ts +12 -0
  474. package/src/permissions/ipc-risk-types.ts +2 -0
  475. package/src/plugin-api/index.ts +13 -0
  476. package/src/plugin-api/package.json +12 -0
  477. package/src/plugin-api/types.ts +62 -0
  478. package/src/plugins/defaults/injectors.ts +19 -3
  479. package/src/plugins/external-plugin-loader.ts +294 -0
  480. package/src/plugins/types.ts +46 -30
  481. package/src/plugins/user-loader.ts +64 -41
  482. package/src/proactive-artifact/job.test.ts +12 -4
  483. package/src/proactive-artifact/job.ts +4 -0
  484. package/src/proactive-artifact/trigger-state.test.ts +9 -0
  485. package/src/proactive-artifact/trigger-state.ts +4 -0
  486. package/src/prompts/__tests__/system-prompt.test.ts +105 -0
  487. package/src/prompts/system-prompt.ts +22 -1
  488. package/src/prompts/update-bulletin-job.ts +61 -73
  489. package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
  490. package/src/providers/__tests__/inference.test.ts +288 -0
  491. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  492. package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
  493. package/src/providers/__tests__/retry-callsite.test.ts +14 -32
  494. package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
  495. package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
  496. package/src/providers/anthropic/client.ts +95 -26
  497. package/src/providers/call-site-routing.ts +94 -16
  498. package/src/providers/connection-resolution.ts +163 -0
  499. package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
  500. package/src/providers/inference/adapter-factory.ts +173 -0
  501. package/src/providers/inference/auth.ts +112 -0
  502. package/src/providers/inference/backfill.ts +196 -0
  503. package/src/providers/inference/connections.ts +356 -0
  504. package/src/providers/inference/resolve-auth.ts +65 -0
  505. package/src/providers/model-catalog.ts +104 -6
  506. package/src/providers/openai/responses-provider.ts +4 -2
  507. package/src/providers/provider-env-vars.ts +17 -7
  508. package/src/providers/provider-secret-catalog.ts +49 -30
  509. package/src/providers/provider-send-message.ts +41 -20
  510. package/src/providers/registry.ts +143 -159
  511. package/src/providers/retry.ts +18 -10
  512. package/src/providers/search-provider-catalog.ts +121 -0
  513. package/src/runtime/AGENTS.md +18 -5
  514. package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
  515. package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
  516. package/src/runtime/actor-trust-resolver.ts +32 -10
  517. package/src/runtime/agent-wake.ts +35 -6
  518. package/src/runtime/assistant-event-hub.ts +3 -85
  519. package/src/runtime/auth/route-policy.ts +303 -8
  520. package/src/runtime/auth/same-actor.ts +2 -0
  521. package/src/runtime/background-job-runner.ts +339 -0
  522. package/src/runtime/btw-sidechain.ts +1 -0
  523. package/src/runtime/http-router.ts +36 -1
  524. package/src/runtime/http-server.ts +31 -5
  525. package/src/runtime/http-types.ts +2 -0
  526. package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
  527. package/src/runtime/middleware/request-logger.ts +62 -1
  528. package/src/runtime/pre-first-message-gate.ts +83 -0
  529. package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
  530. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +251 -0
  531. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
  532. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +315 -0
  533. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +189 -0
  534. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
  535. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
  536. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
  537. package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
  538. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
  539. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  540. package/src/runtime/routes/acp-routes.ts +10 -8
  541. package/src/runtime/routes/app-management-routes.ts +228 -3
  542. package/src/runtime/routes/approval-routes.ts +0 -18
  543. package/src/runtime/routes/audit-routes.ts +43 -0
  544. package/src/runtime/routes/auth-routes.ts +72 -0
  545. package/src/runtime/routes/avatar-routes.ts +273 -20
  546. package/src/runtime/routes/backup-routes.ts +406 -2
  547. package/src/runtime/routes/bookmark-routes.ts +154 -0
  548. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  549. package/src/runtime/routes/contact-routes.ts +0 -160
  550. package/src/runtime/routes/conversation-cli-routes.ts +192 -0
  551. package/src/runtime/routes/conversation-management-routes.ts +30 -43
  552. package/src/runtime/routes/conversation-query-routes.ts +334 -86
  553. package/src/runtime/routes/conversation-routes.ts +31 -10
  554. package/src/runtime/routes/conversations-import-routes.ts +229 -0
  555. package/src/runtime/routes/credential-routes.ts +540 -0
  556. package/src/runtime/routes/debug-routes.ts +2 -2
  557. package/src/runtime/routes/document-pdf-renderer.ts +5 -1
  558. package/src/runtime/routes/domain-routes.ts +167 -0
  559. package/src/runtime/routes/email-routes.ts +603 -0
  560. package/src/runtime/routes/errors.ts +2 -2
  561. package/src/runtime/routes/events-routes.ts +192 -0
  562. package/src/runtime/routes/home-feed-routes.ts +6 -78
  563. package/src/runtime/routes/host-app-control-routes.ts +44 -2
  564. package/src/runtime/routes/host-browser-routes.ts +103 -22
  565. package/src/runtime/routes/http-adapter.ts +2 -0
  566. package/src/runtime/routes/identity-routes.ts +5 -0
  567. package/src/runtime/routes/image-generation-routes.ts +99 -0
  568. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +137 -1
  569. package/src/runtime/routes/inbound-stages/background-dispatch.ts +87 -7
  570. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
  571. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
  572. package/src/runtime/routes/index.ts +36 -0
  573. package/src/runtime/routes/inference-profile-session-handler.ts +312 -0
  574. package/src/runtime/routes/inference-profile-session-reaper.ts +98 -0
  575. package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
  576. package/src/runtime/routes/inference-provider-connection-routes.ts +317 -0
  577. package/src/runtime/routes/inference-send-routes.ts +115 -0
  578. package/src/runtime/routes/integrations/twilio.ts +1 -0
  579. package/src/runtime/routes/mcp-auth-routes.ts +283 -9
  580. package/src/runtime/routes/memory-v2-routes.ts +13 -398
  581. package/src/runtime/routes/notification-routes.ts +2 -0
  582. package/src/runtime/routes/oauth-apps.ts +112 -7
  583. package/src/runtime/routes/oauth-commands-routes.ts +1007 -0
  584. package/src/runtime/routes/oauth-connect-routes.ts +67 -5
  585. package/src/runtime/routes/oauth-providers.ts +298 -8
  586. package/src/runtime/routes/platform-routes.ts +336 -0
  587. package/src/runtime/routes/playground/inject-failures.ts +2 -1
  588. package/src/runtime/routes/playground/reset-circuit.ts +2 -1
  589. package/src/runtime/routes/playground/state.ts +2 -1
  590. package/src/runtime/routes/publish-routes.ts +221 -0
  591. package/src/runtime/routes/schedule-routes.ts +82 -0
  592. package/src/runtime/routes/sequence-routes.ts +291 -0
  593. package/src/runtime/routes/settings-routes.ts +2 -10
  594. package/src/runtime/routes/skills-routes.ts +31 -1
  595. package/src/runtime/routes/stt-routes.ts +240 -3
  596. package/src/runtime/routes/surface-action-routes.ts +43 -7
  597. package/src/runtime/routes/tts-routes.ts +67 -0
  598. package/src/runtime/routes/types.ts +32 -0
  599. package/src/runtime/routes/user-routes-cli.ts +243 -0
  600. package/src/runtime/routes/webhook-routes.ts +165 -0
  601. package/src/runtime/sync/resource-sync-events.ts +25 -0
  602. package/src/runtime/sync/sync-publisher.test.ts +105 -0
  603. package/src/runtime/sync/sync-publisher.ts +21 -0
  604. package/src/schedule/scheduler.ts +200 -123
  605. package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
  606. package/src/security/secret-patterns.ts +3 -0
  607. package/src/sequence/engine.ts +38 -40
  608. package/src/subagent/manager.ts +20 -15
  609. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
  610. package/src/tools/browser/browser-execution.ts +15 -4
  611. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
  612. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
  613. package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
  614. package/src/tools/browser/cdp-client/factory.ts +66 -5
  615. package/src/tools/browser/runtime-check.ts +77 -0
  616. package/src/tools/memory/register.test.ts +3 -3
  617. package/src/tools/memory/register.ts +9 -1
  618. package/src/tools/network/__tests__/web-search.test.ts +156 -0
  619. package/src/tools/network/web-search.ts +280 -37
  620. package/src/tools/permission-checker.ts +13 -5
  621. package/src/tools/subagent/spawn.ts +3 -3
  622. package/src/tools/terminal/shell.ts +44 -0
  623. package/src/usage/attribution.ts +3 -2
  624. package/src/util/pricing.ts +86 -160
  625. package/src/watcher/__tests__/engine.test.ts +301 -0
  626. package/src/watcher/constants.ts +7 -0
  627. package/src/watcher/engine.ts +90 -90
  628. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
  629. package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
  630. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +28 -4
  631. package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
  632. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +104 -0
  633. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +93 -0
  634. package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
  635. package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
  636. package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
  637. package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
  638. package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
  639. package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
  640. package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
  641. package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
  642. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
  643. package/src/workspace/migrations/registry.ts +22 -0
  644. package/src/workspace/migrations/runner.ts +13 -2
  645. package/src/workspace/migrations/types.ts +13 -3
  646. package/src/workspace/provider-commit-message-generator.ts +3 -2
  647. package/src/__tests__/context-search-pkb-source.test.ts +0 -498
  648. package/src/__tests__/credentials-cli.test.ts +0 -1225
  649. package/src/__tests__/memory-admin-recall.test.ts +0 -213
  650. package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
  651. package/src/cli/commands/__tests__/email-download.test.ts +0 -260
  652. package/src/cli/commands/__tests__/email-list.test.ts +0 -216
  653. package/src/cli/commands/__tests__/email-register.test.ts +0 -186
  654. package/src/cli/commands/__tests__/email-send.test.ts +0 -416
  655. package/src/cli/commands/__tests__/email-status.test.ts +0 -185
  656. package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
  657. package/src/cli/commands/__tests__/routes.test.ts +0 -562
  658. package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
  659. package/src/cli/commands/autonomy.ts +0 -365
  660. package/src/cli/commands/memory.ts +0 -424
  661. package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
  662. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
  663. package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
  664. package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
  665. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
  666. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
  667. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
  668. package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
  669. package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
  670. package/src/cli/lib/daemon-avatar-client.ts +0 -37
  671. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
  672. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
  673. package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
  674. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
  675. package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
  676. package/src/home/__tests__/emit-feed-event.test.ts +0 -169
  677. package/src/home/__tests__/feed-population-integration.test.ts +0 -312
  678. package/src/home/__tests__/feed-scheduler.test.ts +0 -222
  679. package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
  680. package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
  681. package/src/home/__tests__/rollup-producer.test.ts +0 -507
  682. package/src/home/assistant-feed-authoring.ts +0 -135
  683. package/src/home/emit-feed-event.ts +0 -169
  684. package/src/home/feed-scheduler.ts +0 -281
  685. package/src/home/platform-gmail-digest.ts +0 -163
  686. package/src/home/rewrite-command-preview.ts +0 -66
  687. package/src/home/rewrite-feed-title.ts +0 -58
  688. package/src/home/rollup-producer.ts +0 -426
  689. package/src/memory/admin.ts +0 -326
  690. package/src/memory/context-search/sources/pkb.ts +0 -476
  691. package/src/memory/graph/compaction.ts +0 -299
  692. /package/src/cli/{commands → lib}/cache-fs.ts +0 -0
@@ -0,0 +1,1007 @@
1
+ /**
2
+ * Route handlers for OAuth CLI command operations: disconnect, mode, status,
3
+ * ping, token, and request.
4
+ *
5
+ * These routes back the thin IPC wrappers in assistant/src/cli/commands/oauth/.
6
+ */
7
+
8
+ import { readFileSync } from "node:fs";
9
+
10
+ import { getConfig, loadRawConfig, saveRawConfig, setNestedValue } from "../../config/loader.js";
11
+ import {
12
+ getServiceMode,
13
+ type Services,
14
+ ServicesSchema,
15
+ } from "../../config/schemas/services.js";
16
+ import type { OAuthConnectionRequest } from "../../oauth/connection.js";
17
+ import {
18
+ resolveOAuthConnection,
19
+ type ResolveOAuthConnectionOptions,
20
+ } from "../../oauth/connection-resolver.js";
21
+ import {
22
+ disconnectOAuthProvider,
23
+ getActiveConnection,
24
+ getAppByProviderAndClientId,
25
+ getConnection,
26
+ getProvider,
27
+ listActiveConnectionsByProvider,
28
+ listConnections,
29
+ } from "../../oauth/oauth-store.js";
30
+ import { VellumPlatformClient } from "../../platform/client.js";
31
+ import { withValidToken } from "../../security/token-manager.js";
32
+ import { getLogger } from "../../util/logger.js";
33
+ import { BadRequestError, InternalError, NotFoundError } from "./errors.js";
34
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
35
+
36
+ const log = getLogger("oauth-commands-routes");
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Shared helpers
40
+ // ---------------------------------------------------------------------------
41
+
42
+ interface PlatformConnectionEntry {
43
+ id: string;
44
+ account_label?: string;
45
+ scopes_granted?: string[];
46
+ status?: string;
47
+ }
48
+
49
+ function getManagedServiceConfigKey(provider: string): string | null {
50
+ const providerRow = getProvider(provider);
51
+ const managedKey = providerRow?.managedServiceConfigKey;
52
+ if (!managedKey || !(managedKey in ServicesSchema.shape)) return null;
53
+ return managedKey;
54
+ }
55
+
56
+ function isManagedMode(provider: string): boolean {
57
+ const managedKey = getManagedServiceConfigKey(provider);
58
+ if (!managedKey) return false;
59
+ try {
60
+ const services: Services = getConfig().services;
61
+ return getServiceMode(services, managedKey as keyof Services) === "managed";
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ async function requirePlatformClient(): Promise<VellumPlatformClient> {
68
+ const client = await VellumPlatformClient.create();
69
+ if (!client) {
70
+ throw new BadRequestError(
71
+ "Not connected to Vellum platform. Run `vellum platform connect` to connect first.",
72
+ );
73
+ }
74
+ if (!client.platformAssistantId) {
75
+ throw new BadRequestError(
76
+ "Connected to Vellum platform but no assistant ID is configured. Ensure the assistant is registered on the platform.",
77
+ );
78
+ }
79
+ return client;
80
+ }
81
+
82
+ async function fetchActiveConnections(
83
+ client: VellumPlatformClient,
84
+ provider: string,
85
+ ): Promise<PlatformConnectionEntry[]> {
86
+ const params = new URLSearchParams();
87
+ params.set("provider", provider);
88
+ params.set("status", "ACTIVE");
89
+
90
+ const path = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/connections/?${params.toString()}`;
91
+ const response = await client.fetch(path);
92
+
93
+ if (!response.ok) {
94
+ const hint =
95
+ response.status === 401 || response.status === 403
96
+ ? `. Your platform session may have expired. Run \`vellum platform connect\` to reconnect.`
97
+ : "";
98
+ throw new InternalError(`Platform returned HTTP ${response.status}${hint}`);
99
+ }
100
+
101
+ const body = (await response.json()) as unknown;
102
+ return (
103
+ Array.isArray(body)
104
+ ? body
105
+ : ((body as Record<string, unknown>).results ?? [])
106
+ ) as PlatformConnectionEntry[];
107
+ }
108
+
109
+ /**
110
+ * Best-effort helper to count active platform connections for a provider.
111
+ * Returns 0 if the platform client cannot be created or the fetch fails.
112
+ */
113
+ async function countManagedConnections(provider: string): Promise<number> {
114
+ try {
115
+ const client = await VellumPlatformClient.create();
116
+ if (!client || !client.platformAssistantId) return 0;
117
+ const entries = await fetchActiveConnections(client, provider);
118
+ return entries.length;
119
+ } catch {
120
+ return 0;
121
+ }
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Disconnect handler
126
+ // ---------------------------------------------------------------------------
127
+
128
+ async function handleDisconnect({ body = {} }: RouteHandlerArgs) {
129
+ const b = body as {
130
+ provider: string;
131
+ account?: string;
132
+ connection_id?: string;
133
+ };
134
+
135
+ if (!b.provider) throw new BadRequestError("provider is required");
136
+
137
+ const providerRow = getProvider(b.provider);
138
+ if (!providerRow) {
139
+ throw new NotFoundError(
140
+ `Unknown provider "${b.provider}". Run 'assistant oauth providers list' to see available providers.`,
141
+ );
142
+ }
143
+
144
+ if (b.account && b.connection_id) {
145
+ throw new BadRequestError(
146
+ `Cannot specify both account and connection_id. Use one or the other.`,
147
+ );
148
+ }
149
+
150
+ const managed = isManagedMode(b.provider);
151
+
152
+ if (managed) {
153
+ const client = await requirePlatformClient();
154
+ const entries = await fetchActiveConnections(client, b.provider);
155
+
156
+ let connectionId: string | undefined;
157
+ let accountLabel: string | undefined;
158
+
159
+ if (b.account) {
160
+ const matching = entries.filter((c) => c.account_label === b.account);
161
+ if (matching.length === 0) {
162
+ throw new NotFoundError(
163
+ `No active connection found for "${b.provider}" with account "${b.account}".`,
164
+ );
165
+ }
166
+ connectionId = matching[0].id;
167
+ accountLabel = matching[0].account_label;
168
+ } else if (b.connection_id) {
169
+ const match = entries.find((c) => c.id === b.connection_id);
170
+ if (!match) {
171
+ throw new NotFoundError(
172
+ `Connection "${b.connection_id}" is not an active ${b.provider} connection.`,
173
+ );
174
+ }
175
+ connectionId = match.id;
176
+ accountLabel = match.account_label;
177
+ } else {
178
+ if (entries.length === 0) {
179
+ throw new NotFoundError(`No active connections found for "${b.provider}".`);
180
+ }
181
+ if (entries.length > 1) {
182
+ throw new BadRequestError(
183
+ `Multiple active connections for "${b.provider}". Specify which one to disconnect with account or connection_id. ` +
184
+ `Run 'assistant oauth status ${b.provider}' to see connected accounts and IDs.`,
185
+ );
186
+ }
187
+ connectionId = entries[0].id;
188
+ accountLabel = entries[0].account_label;
189
+ }
190
+
191
+ const disconnectPath = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/connections/${encodeURIComponent(connectionId!)}/disconnect/`;
192
+ const disconnectResponse = await client.fetch(disconnectPath, {
193
+ method: "POST",
194
+ headers: { "Content-Type": "application/json" },
195
+ });
196
+
197
+ if (!disconnectResponse.ok) {
198
+ const errorText = await disconnectResponse.text().catch(() => "");
199
+ throw new InternalError(
200
+ `Platform returned HTTP ${disconnectResponse.status}${errorText ? `: ${errorText}` : ""}`,
201
+ );
202
+ }
203
+
204
+ const result: Record<string, unknown> = {
205
+ ok: true,
206
+ provider: b.provider,
207
+ connectionId,
208
+ };
209
+ if (accountLabel) result.account = accountLabel;
210
+ return result;
211
+ }
212
+
213
+ // BYO path
214
+ let connectionId: string | undefined;
215
+ let accountLabel: string | undefined;
216
+
217
+ if (b.account) {
218
+ const conn = getActiveConnection(b.provider, { account: b.account });
219
+ if (!conn) {
220
+ throw new NotFoundError(
221
+ `No active connection found for "${b.provider}" with account "${b.account}".`,
222
+ );
223
+ }
224
+ connectionId = conn.id;
225
+ accountLabel = conn.accountInfo ?? undefined;
226
+ } else if (b.connection_id) {
227
+ const conn = getConnection(b.connection_id);
228
+ if (!conn || conn.provider !== b.provider) {
229
+ throw new NotFoundError(
230
+ `Connection "${b.connection_id}" is not an active ${b.provider} connection.`,
231
+ );
232
+ }
233
+ connectionId = conn.id;
234
+ accountLabel = conn.accountInfo ?? undefined;
235
+ } else {
236
+ const active = listActiveConnectionsByProvider(b.provider);
237
+ if (active.length === 0) {
238
+ throw new NotFoundError(`No active connections found for "${b.provider}".`);
239
+ }
240
+ if (active.length > 1) {
241
+ throw new BadRequestError(
242
+ `Multiple active connections for "${b.provider}". Specify which one to disconnect with account or connection_id. ` +
243
+ `Run 'assistant oauth status ${b.provider}' to see connected accounts and IDs.`,
244
+ );
245
+ }
246
+ connectionId = active[0].id;
247
+ accountLabel = active[0].accountInfo ?? undefined;
248
+ }
249
+
250
+ const oauthResult = await disconnectOAuthProvider(
251
+ b.provider,
252
+ undefined,
253
+ connectionId,
254
+ );
255
+ if (oauthResult === "error") {
256
+ throw new InternalError(
257
+ `Failed to disconnect OAuth provider "${b.provider}" — please try again.`,
258
+ );
259
+ }
260
+
261
+ const result: Record<string, unknown> = {
262
+ ok: true,
263
+ provider: b.provider,
264
+ connectionId,
265
+ };
266
+ if (accountLabel) result.account = accountLabel;
267
+ return result;
268
+ }
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // Mode handlers
272
+ // ---------------------------------------------------------------------------
273
+
274
+ function handleModeGet({ queryParams = {} }: RouteHandlerArgs) {
275
+ const provider = queryParams.provider;
276
+ if (!provider) throw new BadRequestError("provider query param is required");
277
+
278
+ const providerRow = getProvider(provider);
279
+ if (!providerRow) {
280
+ throw new NotFoundError(
281
+ `Unknown provider "${provider}". Run 'assistant oauth providers list' to see available providers.`,
282
+ );
283
+ }
284
+
285
+ const managedKey = getManagedServiceConfigKey(provider);
286
+ if (managedKey === null) {
287
+ return {
288
+ ok: true,
289
+ provider,
290
+ mode: "your-own",
291
+ managedModeSupported: false,
292
+ };
293
+ }
294
+
295
+ const services: Services = getConfig().services;
296
+ const currentMode = getServiceMode(services, managedKey as keyof Services);
297
+
298
+ return {
299
+ ok: true,
300
+ provider,
301
+ mode: currentMode,
302
+ managedModeSupported: true,
303
+ };
304
+ }
305
+
306
+ async function handleModeSet({ body = {} }: RouteHandlerArgs) {
307
+ const b = body as { provider: string; mode: string };
308
+ if (!b.provider) throw new BadRequestError("provider is required");
309
+ if (!b.mode) throw new BadRequestError("mode is required");
310
+
311
+ const providerRow = getProvider(b.provider);
312
+ if (!providerRow) {
313
+ throw new NotFoundError(
314
+ `Unknown provider "${b.provider}". Run 'assistant oauth providers list' to see available providers.`,
315
+ );
316
+ }
317
+
318
+ if (b.mode !== "managed" && b.mode !== "your-own") {
319
+ throw new BadRequestError(
320
+ `Invalid mode "${b.mode}". Valid values are "managed" or "your-own".`,
321
+ );
322
+ }
323
+
324
+ const managedKey = getManagedServiceConfigKey(b.provider);
325
+
326
+ if (managedKey === null) {
327
+ if (b.mode === "your-own") {
328
+ return {
329
+ ok: true,
330
+ provider: b.provider,
331
+ mode: "your-own",
332
+ changed: false,
333
+ managedModeSupported: false,
334
+ };
335
+ }
336
+ throw new BadRequestError(
337
+ `Managed mode is not available for ${b.provider}. Only providers with platform-managed OAuth support can be switched to managed mode.`,
338
+ );
339
+ }
340
+
341
+ // Require platform connection when switching to managed mode
342
+ if (b.mode === "managed") {
343
+ const client = await VellumPlatformClient.create();
344
+ if (!client) {
345
+ throw new BadRequestError(
346
+ "Not connected to Vellum platform. Run `vellum platform connect` to connect first.",
347
+ );
348
+ }
349
+ }
350
+
351
+ const services: Services = getConfig().services;
352
+ const currentMode = getServiceMode(services, managedKey as keyof Services);
353
+
354
+ if (currentMode === b.mode) {
355
+ return {
356
+ ok: true,
357
+ provider: b.provider,
358
+ mode: b.mode,
359
+ changed: false,
360
+ managedModeSupported: true,
361
+ };
362
+ }
363
+
364
+ const raw = loadRawConfig();
365
+ setNestedValue(raw, `services.${managedKey}.mode`, b.mode);
366
+ saveRawConfig(raw);
367
+
368
+ // Best-effort check for active connections on old and new modes
369
+ let oldModeConnections = 0;
370
+ let newModeConnections = 0;
371
+ if (currentMode === "managed") {
372
+ oldModeConnections = await countManagedConnections(b.provider);
373
+ newModeConnections = listActiveConnectionsByProvider(b.provider).length;
374
+ } else {
375
+ oldModeConnections = listActiveConnectionsByProvider(b.provider).length;
376
+ newModeConnections = await countManagedConnections(b.provider);
377
+ }
378
+
379
+ let hint: string | undefined;
380
+ if (oldModeConnections > 0 && newModeConnections === 0) {
381
+ hint = `No active connections in ${b.mode} mode. Run 'assistant oauth connect ${b.provider}' to connect.`;
382
+ }
383
+
384
+ const result: Record<string, unknown> = {
385
+ ok: true,
386
+ provider: b.provider,
387
+ mode: b.mode,
388
+ changed: true,
389
+ managedModeSupported: true,
390
+ };
391
+ if (hint) result.hint = hint;
392
+ return result;
393
+ }
394
+
395
+ // ---------------------------------------------------------------------------
396
+ // Status handler
397
+ // ---------------------------------------------------------------------------
398
+
399
+ async function handleStatus({ queryParams = {} }: RouteHandlerArgs) {
400
+ const provider = queryParams.provider;
401
+ if (!provider) throw new BadRequestError("provider query param is required");
402
+
403
+ const providerRow = getProvider(provider);
404
+ if (!providerRow) {
405
+ throw new NotFoundError(
406
+ `Unknown provider "${provider}". Run 'assistant oauth providers list' to see available providers.`,
407
+ );
408
+ }
409
+
410
+ const managed = isManagedMode(provider);
411
+
412
+ if (managed) {
413
+ const client = await requirePlatformClient();
414
+ const rawEntries = await fetchActiveConnections(client, provider);
415
+
416
+ const connections = rawEntries.map((c) => ({
417
+ id: c.id,
418
+ account: c.account_label ?? null,
419
+ grantedScopes: c.scopes_granted ?? [],
420
+ status: c.status ?? "ACTIVE",
421
+ }));
422
+
423
+ return {
424
+ ok: true,
425
+ provider,
426
+ mode: "managed",
427
+ connections,
428
+ };
429
+ }
430
+
431
+ // BYO path
432
+ const allConnections = listConnections(provider);
433
+ const activeRows = allConnections.filter((r) => r.status === "active");
434
+
435
+ const connections = activeRows.map((r) => {
436
+ let grantedScopes: string[] = [];
437
+ try {
438
+ grantedScopes = r.grantedScopes ? JSON.parse(r.grantedScopes) : [];
439
+ } catch {
440
+ // Malformed JSON — default to empty
441
+ }
442
+
443
+ return {
444
+ id: r.id,
445
+ account: r.accountInfo ?? null,
446
+ grantedScopes,
447
+ expiresAt: r.expiresAt ? new Date(r.expiresAt).toISOString() : null,
448
+ hasRefreshToken: r.hasRefreshToken === 1,
449
+ status: r.status,
450
+ };
451
+ });
452
+
453
+ return {
454
+ ok: true,
455
+ provider,
456
+ mode: "byo",
457
+ connections,
458
+ };
459
+ }
460
+
461
+ // ---------------------------------------------------------------------------
462
+ // Ping handler
463
+ // ---------------------------------------------------------------------------
464
+
465
+ async function handlePing({ body = {} }: RouteHandlerArgs) {
466
+ const b = body as {
467
+ provider: string;
468
+ account?: string;
469
+ client_id?: string;
470
+ };
471
+
472
+ if (!b.provider) throw new BadRequestError("provider is required");
473
+
474
+ const providerRow = getProvider(b.provider);
475
+ if (!providerRow) {
476
+ throw new NotFoundError(
477
+ `Unknown provider "${b.provider}". Run 'assistant oauth providers list' to see available providers.`,
478
+ );
479
+ }
480
+
481
+ if (!providerRow.pingUrl) {
482
+ throw new BadRequestError(
483
+ `No ping URL configured for "${b.provider}". Register one with 'assistant oauth providers register --ping-url <url>'.`,
484
+ );
485
+ }
486
+
487
+ const pingUrl = providerRow.pingUrl as string;
488
+ const parsed = new URL(pingUrl);
489
+ const baseUrl = `${parsed.protocol}//${parsed.host}`;
490
+ const path = parsed.pathname;
491
+
492
+ const query: Record<string, string> = {};
493
+ for (const [key, value] of parsed.searchParams) {
494
+ query[key] = value;
495
+ }
496
+
497
+ const resolveOptions: ResolveOAuthConnectionOptions = {};
498
+ if (b.account) resolveOptions.account = b.account;
499
+ if (b.client_id) resolveOptions.clientId = b.client_id;
500
+
501
+ const connection = await resolveOAuthConnection(b.provider, resolveOptions);
502
+
503
+ const method = (providerRow.pingMethod as string | null) ?? "GET";
504
+
505
+ const pingHeaders: Record<string, string> = providerRow.pingHeaders
506
+ ? JSON.parse(providerRow.pingHeaders as string)
507
+ : {};
508
+
509
+ const pingBody: unknown = providerRow.pingBody
510
+ ? JSON.parse(providerRow.pingBody as string)
511
+ : undefined;
512
+
513
+ const response = await connection.request({
514
+ method,
515
+ path,
516
+ baseUrl,
517
+ ...(Object.keys(query).length > 0 ? { query } : {}),
518
+ ...(Object.keys(pingHeaders).length > 0 ? { headers: pingHeaders } : {}),
519
+ ...(pingBody !== undefined ? { body: pingBody } : {}),
520
+ });
521
+
522
+ if (response.status >= 200 && response.status < 300) {
523
+ return { ok: true, provider: b.provider, status: response.status };
524
+ }
525
+
526
+ const payload: Record<string, unknown> = {
527
+ ok: false,
528
+ provider: b.provider,
529
+ status: response.status,
530
+ error: `Ping failed with HTTP ${response.status}`,
531
+ };
532
+
533
+ if (response.status === 401 || response.status === 403) {
534
+ payload.hint =
535
+ `Run 'assistant oauth status ${b.provider}' to check connection health. ` +
536
+ `To reconnect, run 'assistant oauth connect --help'.`;
537
+ }
538
+
539
+ return payload;
540
+ }
541
+
542
+ // ---------------------------------------------------------------------------
543
+ // Token handler
544
+ // ---------------------------------------------------------------------------
545
+
546
+ async function handleToken({ body = {} }: RouteHandlerArgs) {
547
+ const b = body as {
548
+ provider: string;
549
+ account?: string;
550
+ client_id?: string;
551
+ };
552
+
553
+ if (!b.provider) throw new BadRequestError("provider is required");
554
+
555
+ if (isManagedMode(b.provider)) {
556
+ throw new BadRequestError(
557
+ "Token retrieval is not supported for platform-managed providers. " +
558
+ "When a provider is in managed mode, Vellum handles OAuth tokens on your behalf — " +
559
+ "they are not exposed directly.\n\n" +
560
+ `To verify your connection is working, run 'assistant oauth ping ${b.provider}'.\n` +
561
+ `To make authenticated requests, use 'assistant oauth request --provider ${b.provider} <url>'.`,
562
+ );
563
+ }
564
+
565
+ let tokenOpts: string | { connectionId: string } | undefined;
566
+
567
+ if (b.account || b.client_id) {
568
+ const conn = getActiveConnection(b.provider, {
569
+ clientId: b.client_id,
570
+ account: b.account,
571
+ });
572
+ if (!conn) {
573
+ const hint = b.account
574
+ ? ` for account "${b.account}"`
575
+ : b.client_id
576
+ ? ` with client ID "${b.client_id}"`
577
+ : "";
578
+ throw new NotFoundError(
579
+ `No active connection found for "${b.provider}"${hint}. Connect first with 'assistant oauth connect ${b.provider}'.`,
580
+ );
581
+ }
582
+ tokenOpts = { connectionId: conn.id };
583
+ }
584
+
585
+ const token = await withValidToken(
586
+ b.provider,
587
+ async (t) => t,
588
+ tokenOpts,
589
+ );
590
+
591
+ return { ok: true, token };
592
+ }
593
+
594
+ // ---------------------------------------------------------------------------
595
+ // Request handler
596
+ // ---------------------------------------------------------------------------
597
+
598
+ function tryJsonParse(raw: string): unknown {
599
+ try {
600
+ return JSON.parse(raw);
601
+ } catch {
602
+ return raw;
603
+ }
604
+ }
605
+
606
+ function readBodyData(data: string): unknown {
607
+ if (data === "@-") {
608
+ const raw = readFileSync("/dev/stdin", "utf-8");
609
+ return tryJsonParse(raw);
610
+ }
611
+
612
+ if (data.startsWith("@")) {
613
+ const filePath = data.slice(1);
614
+ const raw = readFileSync(filePath, "utf-8");
615
+ return tryJsonParse(raw);
616
+ }
617
+
618
+ return tryJsonParse(data);
619
+ }
620
+
621
+ async function handleRequest({ body = {} }: RouteHandlerArgs) {
622
+ const b = body as {
623
+ provider: string;
624
+ url: string;
625
+ method?: string;
626
+ headers?: Record<string, string>;
627
+ /** Pre-parsed body data (file/stdin reading happens CLI-side). */
628
+ parsed_data?: unknown;
629
+ /** Raw data string (for direct API callers, not the CLI). */
630
+ data?: string;
631
+ force_get?: boolean;
632
+ head?: boolean;
633
+ account?: string;
634
+ client_id?: string;
635
+ };
636
+
637
+ if (!b.provider) throw new BadRequestError("provider is required");
638
+ if (!b.url) throw new BadRequestError("url is required");
639
+
640
+ const providerRow = getProvider(b.provider);
641
+ if (!providerRow) {
642
+ throw new NotFoundError(
643
+ `Unknown provider "${b.provider}". Run 'assistant oauth providers list' to see available providers.`,
644
+ );
645
+ }
646
+
647
+ const managed = isManagedMode(b.provider);
648
+
649
+ if (b.client_id) {
650
+ if (managed) {
651
+ log.info("--client-id is ignored for platform-managed providers");
652
+ } else {
653
+ const app = getAppByProviderAndClientId(b.provider, b.client_id);
654
+ if (!app) {
655
+ throw new NotFoundError(
656
+ `No registered OAuth app found for "${b.provider}" with client ID "${b.client_id}".`,
657
+ );
658
+ }
659
+ }
660
+ }
661
+
662
+ // Parse URL
663
+ let baseUrl: string | undefined;
664
+ let requestPath: string;
665
+ const queryFromUrl: Record<string, string | string[]> = {};
666
+
667
+ if (b.url.startsWith("http://") || b.url.startsWith("https://")) {
668
+ const parsed = new URL(b.url);
669
+ baseUrl = `${parsed.protocol}//${parsed.host}`;
670
+ requestPath = parsed.pathname;
671
+ for (const [key, value] of parsed.searchParams.entries()) {
672
+ const existing = queryFromUrl[key];
673
+ if (existing !== undefined) {
674
+ queryFromUrl[key] = Array.isArray(existing)
675
+ ? [...existing, value]
676
+ : [existing, value];
677
+ } else {
678
+ queryFromUrl[key] = value;
679
+ }
680
+ }
681
+ } else {
682
+ const qIdx = b.url.indexOf("?");
683
+ if (qIdx !== -1) {
684
+ requestPath = b.url.slice(0, qIdx);
685
+ const embeddedParams = new URLSearchParams(b.url.slice(qIdx + 1));
686
+ for (const [key, value] of embeddedParams.entries()) {
687
+ const existing = queryFromUrl[key];
688
+ if (existing !== undefined) {
689
+ queryFromUrl[key] = Array.isArray(existing)
690
+ ? [...existing, value]
691
+ : [existing, value];
692
+ } else {
693
+ queryFromUrl[key] = value;
694
+ }
695
+ }
696
+ } else {
697
+ requestPath = b.url;
698
+ }
699
+ }
700
+
701
+ // Resolve method
702
+ let method: string;
703
+ if (b.head) {
704
+ method = "HEAD";
705
+ } else if (b.method) {
706
+ method = b.method.toUpperCase();
707
+ } else if (b.force_get) {
708
+ method = "GET";
709
+ } else if (b.data !== undefined || b.parsed_data !== undefined) {
710
+ method = "POST";
711
+ } else {
712
+ method = "GET";
713
+ }
714
+
715
+ // Handle body / query params
716
+ let reqBody: unknown = undefined;
717
+ const query: Record<string, string | string[]> = { ...queryFromUrl };
718
+
719
+ // Use pre-parsed data from CLI, or fall back to raw data string for direct API callers
720
+ const resolvedData = b.parsed_data !== undefined ? b.parsed_data : b.data !== undefined ? readBodyData(b.data) : undefined;
721
+
722
+ if (resolvedData !== undefined) {
723
+ const rawBody = resolvedData;
724
+
725
+ if (b.force_get) {
726
+ if (typeof rawBody === "string") {
727
+ const bodyParams = new URLSearchParams(rawBody);
728
+ for (const [key, value] of bodyParams.entries()) {
729
+ const existing = query[key];
730
+ if (existing !== undefined) {
731
+ query[key] = Array.isArray(existing)
732
+ ? [...existing, value]
733
+ : [existing, value];
734
+ } else {
735
+ query[key] = value;
736
+ }
737
+ }
738
+ } else if (
739
+ rawBody !== null &&
740
+ typeof rawBody === "object" &&
741
+ !Array.isArray(rawBody)
742
+ ) {
743
+ for (const [key, value] of Object.entries(
744
+ rawBody as Record<string, unknown>,
745
+ )) {
746
+ const existing = query[key];
747
+ const strValue = String(value);
748
+ if (existing !== undefined) {
749
+ query[key] = Array.isArray(existing)
750
+ ? [...existing, strValue]
751
+ : [existing, strValue];
752
+ } else {
753
+ query[key] = strValue;
754
+ }
755
+ }
756
+ }
757
+ } else {
758
+ reqBody = rawBody;
759
+ }
760
+ }
761
+
762
+ // Resolve connection and make request
763
+ const resolveOptions: ResolveOAuthConnectionOptions = {};
764
+ if (b.client_id && !managed) {
765
+ resolveOptions.clientId = b.client_id;
766
+ }
767
+ if (b.account) {
768
+ resolveOptions.account = b.account;
769
+ }
770
+
771
+ const connection = await resolveOAuthConnection(b.provider, resolveOptions);
772
+
773
+ const headers = b.headers ?? {};
774
+
775
+ const req: OAuthConnectionRequest = {
776
+ method,
777
+ path: requestPath,
778
+ ...(Object.keys(query).length > 0 ? { query } : {}),
779
+ ...(Object.keys(headers).length > 0 ? { headers } : {}),
780
+ ...(reqBody !== undefined ? { body: reqBody } : {}),
781
+ ...(baseUrl ? { baseUrl } : {}),
782
+ };
783
+
784
+ const response = await connection.request(req);
785
+
786
+ const result: Record<string, unknown> = {
787
+ ok: response.status >= 200 && response.status < 300,
788
+ status: response.status,
789
+ headers: response.headers,
790
+ body: response.body,
791
+ };
792
+
793
+ if (response.status === 401 || response.status === 403) {
794
+ result.hint = managed
795
+ ? `Request returned HTTP ${response.status}. The OAuth token may be expired or revoked.\n\n` +
796
+ `Run 'assistant oauth status ${b.provider}' to check connection health.\n` +
797
+ `To reconnect, run 'assistant oauth connect --help'.`
798
+ : `Request returned HTTP ${response.status}. The OAuth token may be expired or revoked.\n\n` +
799
+ `Run 'assistant oauth status ${b.provider}' to check connection status.\n` +
800
+ `To reconnect, run 'assistant oauth connect --help'.`;
801
+ }
802
+
803
+ return result;
804
+ }
805
+
806
+ // ---------------------------------------------------------------------------
807
+ // Connect handler (managed path for platform OAuth)
808
+ // ---------------------------------------------------------------------------
809
+
810
+ async function handleManagedConnect({ body = {} }: RouteHandlerArgs) {
811
+ const b = body as {
812
+ provider: string;
813
+ scopes?: string[];
814
+ redirect_after_connect?: string;
815
+ };
816
+
817
+ if (!b.provider) throw new BadRequestError("provider is required");
818
+
819
+ const client = await requirePlatformClient();
820
+
821
+ const startPath = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/${encodeURIComponent(b.provider)}/start/`;
822
+
823
+ const reqBody: Record<string, unknown> = {};
824
+ if (b.scopes && b.scopes.length > 0) {
825
+ reqBody.requested_scopes = b.scopes;
826
+ }
827
+ reqBody.redirect_after_connect =
828
+ b.redirect_after_connect ?? "/account/oauth/desktop-complete";
829
+
830
+ const response = await client.fetch(startPath, {
831
+ method: "POST",
832
+ headers: { "Content-Type": "application/json" },
833
+ body: JSON.stringify(reqBody),
834
+ });
835
+
836
+ if (!response.ok) {
837
+ const errorText = await response.text().catch(() => "");
838
+ const baseMsg = `Platform returned HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`;
839
+ if (response.status === 401 || response.status === 403) {
840
+ throw new InternalError(
841
+ `${baseMsg}. Your platform session may have expired. Run \`vellum platform connect\` to reconnect.`,
842
+ );
843
+ }
844
+ throw new InternalError(baseMsg);
845
+ }
846
+
847
+ const result = (await response.json()) as { connect_url?: string };
848
+
849
+ if (!result.connect_url) {
850
+ throw new InternalError(
851
+ "Platform did not return a connect URL — the OAuth flow could not be started",
852
+ );
853
+ }
854
+
855
+ return { ok: true, connect_url: result.connect_url };
856
+ }
857
+
858
+ async function handleManagedConnectPoll({ queryParams = {} }: RouteHandlerArgs) {
859
+ const provider = queryParams.provider;
860
+ if (!provider) throw new BadRequestError("provider query param is required");
861
+
862
+ const client = await requirePlatformClient();
863
+ const entries = await fetchActiveConnections(client, provider);
864
+
865
+ return {
866
+ ok: true,
867
+ connections: entries.map((e) => ({
868
+ id: e.id,
869
+ account_label: e.account_label ?? null,
870
+ scopes_granted: e.scopes_granted ?? [],
871
+ })),
872
+ };
873
+ }
874
+
875
+ // ---------------------------------------------------------------------------
876
+ // Route definitions
877
+ // ---------------------------------------------------------------------------
878
+
879
+ export const ROUTES: RouteDefinition[] = [
880
+ {
881
+ operationId: "oauth_disconnect",
882
+ endpoint: "oauth/disconnect",
883
+ method: "POST",
884
+ policyKey: "oauth/disconnect",
885
+ summary: "Disconnect OAuth provider",
886
+ description:
887
+ "Disconnect an OAuth provider and remove associated credentials (BYO or managed).",
888
+ tags: ["oauth"],
889
+ requirePolicyEnforcement: true,
890
+ handler: handleDisconnect,
891
+ },
892
+ {
893
+ operationId: "oauth_mode_get",
894
+ endpoint: "oauth/mode",
895
+ method: "GET",
896
+ summary: "Get OAuth mode",
897
+ description: "Get the current OAuth mode (managed or your-own) for a provider.",
898
+ tags: ["oauth"],
899
+ requirePolicyEnforcement: true,
900
+ queryParams: [
901
+ {
902
+ name: "provider",
903
+ type: "string",
904
+ required: true,
905
+ description: "Provider key",
906
+ },
907
+ ],
908
+ handler: handleModeGet,
909
+ },
910
+ {
911
+ operationId: "oauth_mode_set",
912
+ endpoint: "oauth/mode",
913
+ method: "POST",
914
+ policyKey: "oauth/mode.set",
915
+ summary: "Set OAuth mode",
916
+ description:
917
+ "Set the OAuth mode (managed or your-own) for a provider.",
918
+ tags: ["oauth"],
919
+ requirePolicyEnforcement: true,
920
+ handler: handleModeSet,
921
+ },
922
+ {
923
+ operationId: "oauth_status",
924
+ endpoint: "oauth/status",
925
+ method: "GET",
926
+ summary: "Get OAuth status",
927
+ description:
928
+ "Show OAuth connection status for a specified provider (BYO or managed).",
929
+ tags: ["oauth"],
930
+ requirePolicyEnforcement: true,
931
+ queryParams: [
932
+ {
933
+ name: "provider",
934
+ type: "string",
935
+ required: true,
936
+ description: "Provider key",
937
+ },
938
+ ],
939
+ handler: handleStatus,
940
+ },
941
+ {
942
+ operationId: "oauth_ping",
943
+ endpoint: "oauth/ping",
944
+ method: "POST",
945
+ summary: "Ping OAuth provider",
946
+ description:
947
+ "Verify an OAuth token is valid by hitting the provider's configured health-check endpoint.",
948
+ tags: ["oauth"],
949
+ requirePolicyEnforcement: true,
950
+ handler: handlePing,
951
+ },
952
+ {
953
+ operationId: "oauth_token",
954
+ endpoint: "oauth/token",
955
+ method: "POST",
956
+ policyKey: "oauth/token",
957
+ summary: "Get OAuth token",
958
+ description:
959
+ "Retrieve a valid OAuth access token for a BYO-mode provider.",
960
+ tags: ["oauth"],
961
+ requirePolicyEnforcement: true,
962
+ handler: handleToken,
963
+ },
964
+ {
965
+ operationId: "oauth_request",
966
+ endpoint: "oauth/request",
967
+ method: "POST",
968
+ policyKey: "oauth/request",
969
+ summary: "Make authenticated OAuth request",
970
+ description:
971
+ "Make an authenticated HTTP request through an OAuth connection (supports curl-like interface).",
972
+ tags: ["oauth"],
973
+ requirePolicyEnforcement: true,
974
+ handler: handleRequest,
975
+ },
976
+ {
977
+ operationId: "oauth_managed_connect_start",
978
+ endpoint: "oauth/managed-connect/start",
979
+ method: "POST",
980
+ policyKey: "oauth/managed-connect.start",
981
+ summary: "Start managed OAuth connect",
982
+ description:
983
+ "Start a managed (platform) OAuth connect flow and return the connect URL.",
984
+ tags: ["oauth"],
985
+ requirePolicyEnforcement: true,
986
+ handler: handleManagedConnect,
987
+ },
988
+ {
989
+ operationId: "oauth_managed_connect_poll",
990
+ endpoint: "oauth/managed-connect/poll",
991
+ method: "GET",
992
+ summary: "Poll managed OAuth connections",
993
+ description:
994
+ "Fetch active platform connections for a provider (used to detect new connections after managed connect).",
995
+ tags: ["oauth"],
996
+ requirePolicyEnforcement: true,
997
+ queryParams: [
998
+ {
999
+ name: "provider",
1000
+ type: "string",
1001
+ required: true,
1002
+ description: "Provider key",
1003
+ },
1004
+ ],
1005
+ handler: handleManagedConnectPoll,
1006
+ },
1007
+ ];