@vellumai/assistant 0.5.9 → 0.5.11

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 (278) hide show
  1. package/AGENTS.md +9 -1
  2. package/ARCHITECTURE.md +48 -48
  3. package/Dockerfile +2 -0
  4. package/README.md +1 -1
  5. package/docs/architecture/integrations.md +6 -13
  6. package/docs/architecture/memory.md +7 -12
  7. package/docs/architecture/security.md +5 -5
  8. package/docs/credential-execution-service.md +9 -9
  9. package/docs/skills.md +1 -1
  10. package/node_modules/@vellumai/credential-storage/src/index.ts +2 -2
  11. package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +1 -1
  12. package/openapi.yaml +7130 -0
  13. package/package.json +2 -1
  14. package/scripts/generate-openapi.ts +562 -0
  15. package/src/__tests__/acp-session.test.ts +239 -44
  16. package/src/__tests__/assistant-feature-flag-guard.test.ts +8 -8
  17. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +5 -86
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -14
  19. package/src/__tests__/browser-skill-endstate.test.ts +1 -1
  20. package/src/__tests__/btw-routes.test.ts +8 -0
  21. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +10 -10
  22. package/src/__tests__/channel-approvals.test.ts +7 -7
  23. package/src/__tests__/channel-readiness-service.test.ts +41 -0
  24. package/src/__tests__/config-schema.test.ts +10 -2
  25. package/src/__tests__/context-memory-e2e.test.ts +2 -6
  26. package/src/__tests__/conversation-skill-tools.test.ts +1 -3
  27. package/src/__tests__/conversation-title-service.test.ts +2 -15
  28. package/src/__tests__/credential-execution-feature-gates.test.ts +4 -8
  29. package/src/__tests__/credential-execution-managed-contract.test.ts +8 -8
  30. package/src/__tests__/credential-security-e2e.test.ts +4 -4
  31. package/src/__tests__/credential-security-invariants.test.ts +3 -3
  32. package/src/__tests__/credentials-cli.test.ts +3 -3
  33. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -1
  34. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  35. package/src/__tests__/heartbeat-service.test.ts +35 -0
  36. package/src/__tests__/host-shell-tool.test.ts +1 -1
  37. package/src/__tests__/inline-skill-load-permissions.test.ts +3 -3
  38. package/src/__tests__/llm-request-log-turn-query.test.ts +64 -0
  39. package/src/__tests__/log-export-workspace.test.ts +1 -1
  40. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  41. package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
  42. package/src/__tests__/memory-recall-log-store.test.ts +182 -0
  43. package/src/__tests__/memory-recall-quality.test.ts +6 -8
  44. package/src/__tests__/memory-regressions.test.ts +53 -42
  45. package/src/__tests__/memory-retrieval.benchmark.test.ts +5 -9
  46. package/src/__tests__/messaging-skill-split.test.ts +2 -17
  47. package/src/__tests__/oauth-cli.test.ts +98 -551
  48. package/src/__tests__/platform-callback-registration.test.ts +119 -0
  49. package/src/__tests__/secret-ingress-channel.test.ts +261 -0
  50. package/src/__tests__/secret-ingress-cli.test.ts +201 -0
  51. package/src/__tests__/secret-ingress-http.test.ts +312 -0
  52. package/src/__tests__/secret-ingress.test.ts +283 -0
  53. package/src/__tests__/secret-onetime-send.test.ts +4 -4
  54. package/src/__tests__/skill-feature-flags-integration.test.ts +4 -4
  55. package/src/__tests__/skill-feature-flags.test.ts +11 -19
  56. package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
  57. package/src/__tests__/skill-load-inline-command.test.ts +3 -3
  58. package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
  59. package/src/__tests__/skill-memory.test.ts +2 -4
  60. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -4
  61. package/src/__tests__/skill-projection.benchmark.test.ts +1 -3
  62. package/src/__tests__/skills.test.ts +16 -2
  63. package/src/__tests__/slack-channel-config.test.ts +1 -1
  64. package/src/__tests__/slack-skill.test.ts +5 -69
  65. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -1
  66. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +5 -238
  67. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -206
  68. package/src/__tests__/workspace-migration-018-rekey-compound-credential-keys.test.ts +181 -0
  69. package/src/__tests__/workspace-migrations-runner.test.ts +15 -7
  70. package/src/acp/client-handler.ts +113 -31
  71. package/src/acp/session-manager.ts +29 -27
  72. package/src/approvals/guardian-request-resolvers.ts +1 -1
  73. package/src/cli/AGENTS.md +73 -0
  74. package/src/cli/commands/autonomy.ts +3 -5
  75. package/src/cli/commands/credential-execution.ts +1 -2
  76. package/src/cli/commands/credentials.ts +4 -4
  77. package/src/cli/commands/memory.ts +2 -3
  78. package/src/cli/commands/oauth/__tests__/connect.test.ts +785 -0
  79. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +760 -0
  80. package/src/cli/commands/oauth/__tests__/mode.test.ts +672 -0
  81. package/src/cli/commands/oauth/__tests__/ping.test.ts +690 -0
  82. package/src/cli/commands/oauth/__tests__/status.test.ts +579 -0
  83. package/src/cli/commands/oauth/__tests__/token.test.ts +467 -0
  84. package/src/cli/commands/oauth/apps.ts +29 -11
  85. package/src/cli/commands/oauth/connect.ts +373 -0
  86. package/src/cli/commands/oauth/connections.ts +14 -493
  87. package/src/cli/commands/oauth/disconnect.ts +333 -0
  88. package/src/cli/commands/oauth/index.ts +62 -10
  89. package/src/cli/commands/oauth/mode.ts +263 -0
  90. package/src/cli/commands/oauth/ping.ts +222 -0
  91. package/src/cli/commands/oauth/providers.ts +30 -3
  92. package/src/cli/commands/oauth/request.ts +576 -0
  93. package/src/cli/commands/oauth/shared.ts +132 -0
  94. package/src/cli/commands/oauth/status.ts +202 -0
  95. package/src/cli/commands/oauth/token.ts +159 -0
  96. package/src/cli/commands/platform.ts +20 -14
  97. package/src/cli.ts +82 -17
  98. package/src/config/assistant-feature-flags.ts +74 -11
  99. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  100. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -1
  101. package/src/config/bundled-skills/messaging/SKILL.md +13 -36
  102. package/src/config/bundled-skills/messaging/TOOLS.json +9 -9
  103. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
  104. package/src/config/bundled-skills/notifications/SKILL.md +1 -1
  105. package/src/config/bundled-skills/schedule/SKILL.md +2 -2
  106. package/src/config/bundled-skills/settings/SKILL.md +5 -3
  107. package/src/config/bundled-skills/settings/TOOLS.json +17 -0
  108. package/src/config/bundled-skills/settings/tools/avatar-get.ts +50 -0
  109. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +7 -0
  110. package/src/config/bundled-skills/settings/tools/avatar-update.ts +6 -1
  111. package/src/config/bundled-skills/settings/tools/identity-avatar.ts +55 -0
  112. package/src/config/bundled-skills/skills-catalog/SKILL.md +3 -3
  113. package/src/config/bundled-skills/slack/SKILL.md +58 -44
  114. package/src/config/bundled-tool-registry.ts +2 -19
  115. package/src/config/env.ts +5 -1
  116. package/src/config/feature-flag-registry.json +57 -41
  117. package/src/config/loader.ts +4 -0
  118. package/src/config/schemas/platform.ts +0 -8
  119. package/src/config/schemas/security.ts +9 -1
  120. package/src/config/schemas/services.ts +1 -1
  121. package/src/config/skill-state.ts +1 -3
  122. package/src/config/skills.ts +2 -4
  123. package/src/credential-execution/feature-gates.ts +9 -16
  124. package/src/credential-execution/process-manager.ts +12 -0
  125. package/src/daemon/config-watcher.ts +4 -0
  126. package/src/daemon/conversation-agent-loop-handlers.ts +10 -0
  127. package/src/daemon/conversation-agent-loop.ts +49 -2
  128. package/src/daemon/conversation-memory.ts +0 -1
  129. package/src/daemon/handlers/config-slack-channel.ts +43 -1
  130. package/src/daemon/handlers/conversations.ts +41 -33
  131. package/src/daemon/lifecycle.ts +28 -5
  132. package/src/daemon/message-types/acp.ts +0 -15
  133. package/src/daemon/message-types/memory.ts +0 -1
  134. package/src/daemon/message-types/messages.ts +9 -1
  135. package/src/daemon/message-types/schedules.ts +9 -0
  136. package/src/daemon/server.ts +19 -7
  137. package/src/email/feature-gate.ts +3 -3
  138. package/src/heartbeat/heartbeat-service.ts +48 -0
  139. package/src/inbound/platform-callback-registration.ts +61 -7
  140. package/src/mcp/mcp-oauth-provider.ts +3 -3
  141. package/src/memory/app-store.ts +3 -3
  142. package/src/memory/conversation-crud.ts +124 -0
  143. package/src/memory/conversation-title-service.ts +7 -17
  144. package/src/memory/db-init.ts +8 -0
  145. package/src/memory/embedding-local.ts +47 -2
  146. package/src/memory/indexer.ts +13 -10
  147. package/src/memory/items-extractor.ts +12 -4
  148. package/src/memory/job-utils.ts +5 -0
  149. package/src/memory/jobs-store.ts +10 -2
  150. package/src/memory/journal-memory.ts +6 -2
  151. package/src/memory/llm-request-log-store.ts +88 -21
  152. package/src/memory/memory-recall-log-store.ts +128 -0
  153. package/src/memory/migrations/194-memory-recall-logs.ts +50 -0
  154. package/src/memory/migrations/195-oauth-providers-ping-config.ts +23 -0
  155. package/src/memory/migrations/index.ts +2 -0
  156. package/src/memory/migrations/validate-migration-state.ts +14 -1
  157. package/src/memory/retriever.test.ts +4 -5
  158. package/src/memory/schema/infrastructure.ts +31 -0
  159. package/src/memory/schema/oauth.ts +3 -0
  160. package/src/messaging/providers/telegram-bot/adapter.ts +1 -1
  161. package/src/oauth/connect-orchestrator.ts +54 -0
  162. package/src/oauth/manual-token-connection.ts +5 -5
  163. package/src/oauth/oauth-store.ts +26 -5
  164. package/src/oauth/seed-providers.ts +10 -1
  165. package/src/permissions/checker.ts +2 -2
  166. package/src/permissions/trust-client.ts +2 -2
  167. package/src/platform/client.ts +2 -2
  168. package/src/prompts/journal-context.ts +6 -1
  169. package/src/providers/anthropic/client.ts +143 -1
  170. package/src/runtime/auth/__tests__/middleware.test.ts +19 -0
  171. package/src/runtime/auth/route-policy.ts +0 -1
  172. package/src/runtime/btw-sidechain.ts +7 -1
  173. package/src/runtime/channel-approvals.ts +2 -2
  174. package/src/runtime/channel-readiness-service.ts +30 -7
  175. package/src/runtime/http-router.ts +31 -0
  176. package/src/runtime/http-server.ts +21 -4
  177. package/src/runtime/http-types.ts +2 -0
  178. package/src/runtime/pending-interactions.ts +21 -3
  179. package/src/runtime/routes/acp-routes.ts +46 -28
  180. package/src/runtime/routes/app-management-routes.ts +123 -0
  181. package/src/runtime/routes/app-routes.ts +31 -0
  182. package/src/runtime/routes/approval-routes.ts +108 -3
  183. package/src/runtime/routes/attachment-routes.ts +45 -0
  184. package/src/runtime/routes/avatar-routes.ts +16 -0
  185. package/src/runtime/routes/brain-graph-routes.ts +18 -0
  186. package/src/runtime/routes/btw-routes.ts +20 -0
  187. package/src/runtime/routes/call-routes.ts +81 -0
  188. package/src/runtime/routes/channel-readiness-routes.ts +48 -7
  189. package/src/runtime/routes/channel-routes.ts +18 -0
  190. package/src/runtime/routes/channel-verification-routes.ts +49 -1
  191. package/src/runtime/routes/contact-routes.ts +77 -0
  192. package/src/runtime/routes/conversation-attention-routes.ts +37 -0
  193. package/src/runtime/routes/conversation-management-routes.ts +94 -0
  194. package/src/runtime/routes/conversation-query-routes.ts +78 -0
  195. package/src/runtime/routes/conversation-routes.ts +115 -38
  196. package/src/runtime/routes/conversation-starter-routes.ts +29 -0
  197. package/src/runtime/routes/debug-routes.ts +23 -0
  198. package/src/runtime/routes/diagnostics-routes.ts +30 -0
  199. package/src/runtime/routes/documents-routes.ts +42 -0
  200. package/src/runtime/routes/events-routes.ts +10 -0
  201. package/src/runtime/routes/global-search-routes.ts +35 -0
  202. package/src/runtime/routes/guardian-action-routes.ts +47 -2
  203. package/src/runtime/routes/guardian-approval-prompt.ts +77 -2
  204. package/src/runtime/routes/heartbeat-routes.ts +278 -0
  205. package/src/runtime/routes/host-bash-routes.ts +16 -1
  206. package/src/runtime/routes/host-cu-routes.ts +23 -1
  207. package/src/runtime/routes/host-file-routes.ts +18 -1
  208. package/src/runtime/routes/identity-routes.ts +35 -0
  209. package/src/runtime/routes/inbound-message-handler.ts +46 -25
  210. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +30 -2
  211. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -2
  212. package/src/runtime/routes/integrations/twilio.ts +32 -22
  213. package/src/runtime/routes/invite-routes.ts +83 -0
  214. package/src/runtime/routes/log-export-routes.ts +14 -0
  215. package/src/runtime/routes/memory-item-routes.ts +99 -1
  216. package/src/runtime/routes/migration-rollback-routes.ts +25 -0
  217. package/src/runtime/routes/migration-routes.ts +40 -0
  218. package/src/runtime/routes/notification-routes.ts +20 -0
  219. package/src/runtime/routes/oauth-apps.ts +11 -3
  220. package/src/runtime/routes/pairing-routes.ts +15 -0
  221. package/src/runtime/routes/recording-routes.ts +72 -0
  222. package/src/runtime/routes/schedule-routes.ts +77 -5
  223. package/src/runtime/routes/secret-routes.ts +63 -1
  224. package/src/runtime/routes/settings-routes.ts +91 -1
  225. package/src/runtime/routes/skills-routes.ts +98 -16
  226. package/src/runtime/routes/subagents-routes.ts +38 -3
  227. package/src/runtime/routes/surface-action-routes.ts +66 -24
  228. package/src/runtime/routes/surface-content-routes.ts +20 -0
  229. package/src/runtime/routes/telemetry-routes.ts +12 -0
  230. package/src/runtime/routes/trace-event-routes.ts +25 -0
  231. package/src/runtime/routes/trust-rules-routes.ts +46 -0
  232. package/src/runtime/routes/tts-routes.ts +15 -4
  233. package/src/runtime/routes/upgrade-broadcast-routes.ts +38 -0
  234. package/src/runtime/routes/usage-routes.ts +59 -0
  235. package/src/runtime/routes/watch-routes.ts +28 -0
  236. package/src/runtime/routes/work-items-routes.ts +59 -0
  237. package/src/runtime/routes/workspace-commit-routes.ts +12 -0
  238. package/src/runtime/routes/workspace-routes.ts +102 -0
  239. package/src/schedule/scheduler.ts +7 -1
  240. package/src/security/AGENTS.md +7 -0
  241. package/src/security/credential-backend.ts +1 -1
  242. package/src/security/encrypted-store.ts +3 -3
  243. package/src/security/oauth2.ts +55 -0
  244. package/src/security/secret-ingress.ts +174 -0
  245. package/src/security/secret-patterns.ts +133 -0
  246. package/src/security/secret-scanner.ts +28 -117
  247. package/src/signals/confirm.ts +12 -8
  248. package/src/signals/user-message.ts +18 -3
  249. package/src/skills/skill-memory.ts +1 -2
  250. package/src/tasks/task-runner.ts +7 -1
  251. package/src/tools/credentials/broker.ts +1 -1
  252. package/src/tools/credentials/metadata-store.ts +1 -1
  253. package/src/tools/credentials/vault.ts +2 -3
  254. package/src/tools/memory/definitions.ts +1 -1
  255. package/src/tools/memory/handlers.test.ts +2 -4
  256. package/src/tools/skills/load.ts +1 -1
  257. package/src/tools/terminal/safe-env.ts +7 -0
  258. package/src/tools/tool-manifest.ts +1 -1
  259. package/src/util/log-redact.ts +9 -34
  260. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +13 -148
  261. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +7 -145
  262. package/src/workspace/migrations/AGENTS.md +11 -0
  263. package/src/workspace/migrations/runner.ts +16 -6
  264. package/src/workspace/migrations/types.ts +7 -0
  265. package/docs/architecture/keychain-broker.md +0 -69
  266. package/src/__tests__/keychain-broker-client.test.ts +0 -800
  267. package/src/cli/commands/oauth/platform.ts +0 -525
  268. package/src/config/bundled-skills/slack/TOOLS.json +0 -272
  269. package/src/config/bundled-skills/slack/tools/shared.ts +0 -34
  270. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +0 -27
  271. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +0 -38
  272. package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +0 -146
  273. package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +0 -105
  274. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +0 -26
  275. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +0 -27
  276. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +0 -25
  277. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +0 -372
  278. package/src/security/keychain-broker-client.ts +0 -446
@@ -0,0 +1,373 @@
1
+ import type { Command } from "commander";
2
+
3
+ import { orchestrateOAuthConnect } from "../../../oauth/connect-orchestrator.js";
4
+ import {
5
+ getAppByProviderAndClientId,
6
+ getMostRecentAppByProvider,
7
+ getProvider,
8
+ } from "../../../oauth/oauth-store.js";
9
+ import { getProviderBehavior } from "../../../oauth/provider-behaviors.js";
10
+ import { openInBrowser } from "../../../util/browser.js";
11
+ import { getSecureKeyViaDaemon } from "../../lib/daemon-credential-client.js";
12
+ import { getCliLogger } from "../../logger.js";
13
+ import { shouldOutputJson, writeOutput } from "../../output.js";
14
+ import {
15
+ fetchActiveConnections,
16
+ isManagedMode,
17
+ requirePlatformClient,
18
+ resolveService,
19
+ toBareProvider,
20
+ } from "./shared.js";
21
+
22
+ const log = getCliLogger("cli");
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Command registration
26
+ // ---------------------------------------------------------------------------
27
+
28
+ export function registerConnectCommand(oauth: Command): void {
29
+ oauth
30
+ .command("connect <provider>")
31
+ .description(
32
+ "Initiate an OAuth authorization flow for a provider (auto-detects managed vs BYO mode)",
33
+ )
34
+ .option("--scopes <scopes...>", "Scopes to request for the authorization")
35
+ .option(
36
+ "--open-browser",
37
+ "Open the auth URL in the browser and wait for completion",
38
+ )
39
+ .option("--client-id <id>", "BYO app client ID disambiguation")
40
+ .addHelpText(
41
+ "after",
42
+ `
43
+ Arguments:
44
+ provider Provider key, alias, or ID from 'assistant oauth providers list'.
45
+ Accepts canonical keys (e.g. integration:google), aliases (e.g.
46
+ gmail), or bare provider names (e.g. google).
47
+
48
+ Options:
49
+ --scopes <scopes...> Scopes to request for the authorization. In managed
50
+ mode, each scope must be in the provider's allowed set
51
+ (use full scope URLs where required). In BYO mode,
52
+ scopes are appended to the provider's defaults.
53
+ --open-browser Open the authorization URL in your browser and wait
54
+ for completion. In managed mode, polls for a new
55
+ platform connection. In BYO mode, starts a local
56
+ callback server and blocks until the OAuth redirect.
57
+ --client-id <id> BYO-only: select a specific OAuth app when multiple
58
+ apps exist for the same provider. Ignored for
59
+ platform-managed providers.
60
+
61
+ Mode detection:
62
+ The command checks the services config to determine whether the provider
63
+ runs in platform-managed or BYO (bring-your-own credentials) mode.
64
+
65
+ Managed mode: Calls the platform /start/ endpoint, returns a connect URL.
66
+ With --open-browser, opens the URL and polls for a new connection.
67
+ BYO mode: Resolves local client credentials from the OAuth app store and
68
+ runs the OAuth2 authorization code flow via the local orchestrator.
69
+
70
+ Examples:
71
+ $ assistant oauth connect google
72
+ $ assistant oauth connect gmail --open-browser
73
+ $ assistant oauth connect integration:google --scopes https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events
74
+ $ assistant oauth connect google --client-id abc123 --open-browser`,
75
+ )
76
+ .action(
77
+ async (
78
+ provider: string,
79
+ opts: {
80
+ scopes?: string[];
81
+ openBrowser?: boolean;
82
+ clientId?: string;
83
+ },
84
+ cmd: Command,
85
+ ) => {
86
+ const jsonMode = shouldOutputJson(cmd);
87
+
88
+ // Helper: write an error and set exit code
89
+ const writeError = (error: string): void => {
90
+ writeOutput(cmd, { ok: false, error });
91
+ process.exitCode = 1;
92
+ };
93
+
94
+ try {
95
+ // ---------------------------------------------------------------
96
+ // 1. Resolve provider key
97
+ // ---------------------------------------------------------------
98
+ const providerKey = resolveService(provider);
99
+
100
+ // ---------------------------------------------------------------
101
+ // 2. Validate provider exists
102
+ // ---------------------------------------------------------------
103
+ const providerRow = getProvider(providerKey);
104
+ if (!providerRow) {
105
+ writeError(
106
+ `Unknown provider "${providerKey}". ` +
107
+ `Run 'assistant oauth providers list' to see available providers.`,
108
+ );
109
+ return;
110
+ }
111
+
112
+ // ---------------------------------------------------------------
113
+ // 3. Detect mode
114
+ // ---------------------------------------------------------------
115
+ const managed = isManagedMode(providerKey);
116
+
117
+ if (managed) {
118
+ // =============================================================
119
+ // MANAGED PATH
120
+ // =============================================================
121
+
122
+ // Warn about --client-id being ignored in managed mode
123
+ if (opts.clientId) {
124
+ log.info(
125
+ `Warning: --client-id is ignored for platform-managed providers. The platform manages OAuth apps for "${providerKey}".`,
126
+ );
127
+ }
128
+
129
+ const client = await requirePlatformClient(cmd);
130
+ if (!client) return;
131
+
132
+ // Call the platform's OAuth start endpoint
133
+ const startPath = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/${encodeURIComponent(toBareProvider(providerKey))}/start/`;
134
+
135
+ const body: Record<string, unknown> = {};
136
+ if (opts.scopes && opts.scopes.length > 0) {
137
+ body.requested_scopes = opts.scopes;
138
+ }
139
+
140
+ const response = await client.fetch(startPath, {
141
+ method: "POST",
142
+ headers: { "Content-Type": "application/json" },
143
+ body: JSON.stringify(body),
144
+ });
145
+
146
+ if (!response.ok) {
147
+ const errorText = await response.text().catch(() => "");
148
+ writeError(
149
+ `Platform returned HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`,
150
+ );
151
+ return;
152
+ }
153
+
154
+ const result = (await response.json()) as {
155
+ connect_url?: string;
156
+ };
157
+
158
+ if (!result.connect_url) {
159
+ writeError(
160
+ "Platform did not return a connect URL — the OAuth flow could not be started",
161
+ );
162
+ return;
163
+ }
164
+
165
+ if (opts.openBrowser) {
166
+ // Snapshot existing connection IDs before opening browser
167
+ const snapshotEntries = await fetchActiveConnections(
168
+ client,
169
+ providerKey,
170
+ cmd,
171
+ );
172
+ if (!snapshotEntries) {
173
+ // fetchActiveConnections already wrote the error output
174
+ return;
175
+ }
176
+ const snapshotIds = new Set(snapshotEntries.map((e) => e.id));
177
+
178
+ openInBrowser(result.connect_url);
179
+
180
+ if (!jsonMode) {
181
+ log.info(
182
+ `Opening browser to connect ${providerKey}. Waiting for authorization...`,
183
+ );
184
+ }
185
+
186
+ // Poll for a new connection every 2s for up to 5 minutes
187
+ const pollIntervalMs = 2000;
188
+ const timeoutMs = 5 * 60 * 1000;
189
+ const deadline = Date.now() + timeoutMs;
190
+ let newConnection: {
191
+ id: string;
192
+ account_label?: string;
193
+ scopes_granted?: string[];
194
+ } | null = null;
195
+
196
+ while (Date.now() < deadline) {
197
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
198
+
199
+ const currentEntries = await fetchActiveConnections(
200
+ client,
201
+ providerKey,
202
+ cmd,
203
+ { silent: true },
204
+ );
205
+ if (!currentEntries) continue;
206
+
207
+ const found = currentEntries.find(
208
+ (e) => !snapshotIds.has(e.id),
209
+ );
210
+ if (found) {
211
+ newConnection = found;
212
+ break;
213
+ }
214
+ }
215
+
216
+ if (newConnection) {
217
+ // Success — new connection found
218
+ if (jsonMode) {
219
+ writeOutput(cmd, {
220
+ ok: true,
221
+ provider: providerKey,
222
+ connectionId: newConnection.id,
223
+ accountLabel: newConnection.account_label ?? null,
224
+ scopesGranted: newConnection.scopes_granted ?? [],
225
+ });
226
+ } else {
227
+ const label = newConnection.account_label
228
+ ? ` as ${newConnection.account_label}`
229
+ : "";
230
+ process.stdout.write(`Connected to ${providerKey}${label}\n`);
231
+ }
232
+ } else {
233
+ // Timeout — authorization may still be in progress
234
+ if (jsonMode) {
235
+ writeOutput(cmd, {
236
+ ok: true,
237
+ deferred: true,
238
+ provider: providerKey,
239
+ connectUrl: result.connect_url,
240
+ message:
241
+ "Authorization may still be in progress. Check with 'assistant oauth status <provider>'.",
242
+ });
243
+ } else {
244
+ process.stdout.write(
245
+ `Timed out waiting for authorization. It may still be in progress.\n` +
246
+ `Check with: assistant oauth status ${providerKey}\n`,
247
+ );
248
+ }
249
+ }
250
+ } else {
251
+ // No --open-browser: output the connect URL
252
+ if (jsonMode) {
253
+ writeOutput(cmd, {
254
+ ok: true,
255
+ deferred: true,
256
+ connectUrl: result.connect_url,
257
+ provider: providerKey,
258
+ });
259
+ } else {
260
+ process.stdout.write(result.connect_url + "\n");
261
+ }
262
+ }
263
+ } else {
264
+ // =============================================================
265
+ // BYO PATH
266
+ // =============================================================
267
+
268
+ // a. Resolve service alias (already done above via resolveService)
269
+ const resolvedServiceKey = providerKey;
270
+
271
+ // b. Resolve client credentials from the DB
272
+ const dbApp = opts.clientId
273
+ ? getAppByProviderAndClientId(resolvedServiceKey, opts.clientId)
274
+ : getMostRecentAppByProvider(resolvedServiceKey);
275
+
276
+ let clientId = opts.clientId;
277
+ let clientSecret: string | undefined;
278
+
279
+ if (dbApp) {
280
+ if (!clientId) clientId = dbApp.clientId;
281
+ const storedSecret = await getSecureKeyViaDaemon(
282
+ dbApp.clientSecretCredentialPath,
283
+ );
284
+ if (storedSecret) clientSecret = storedSecret;
285
+ } else if (opts.clientId) {
286
+ // --client-id was explicitly provided but no matching app exists
287
+ writeError(
288
+ `No registered app found for "${resolvedServiceKey}" with client ID "${opts.clientId}". ` +
289
+ `Register one with 'assistant oauth apps upsert'.`,
290
+ );
291
+ return;
292
+ }
293
+
294
+ // c. Validate client_id exists
295
+ if (!clientId) {
296
+ writeError(
297
+ `No client_id found for "${resolvedServiceKey}". ` +
298
+ `Register one with 'assistant oauth apps upsert'.`,
299
+ );
300
+ return;
301
+ }
302
+
303
+ // d. Check if client_secret is required but missing
304
+ if (clientSecret === undefined) {
305
+ const behavior = getProviderBehavior(resolvedServiceKey);
306
+
307
+ const requiresSecret =
308
+ behavior?.setup?.requiresClientSecret ??
309
+ !!(
310
+ providerRow?.tokenEndpointAuthMethod ||
311
+ providerRow?.extraParams
312
+ );
313
+
314
+ if (requiresSecret) {
315
+ writeError(
316
+ `client_secret is required for ${resolvedServiceKey} but not found. ` +
317
+ `Store it with 'assistant oauth apps upsert --client-secret'.`,
318
+ );
319
+ return;
320
+ }
321
+ }
322
+
323
+ // e. Call the orchestrator
324
+ const result = await orchestrateOAuthConnect({
325
+ service: provider,
326
+ clientId,
327
+ clientSecret,
328
+ isInteractive: !!opts.openBrowser,
329
+ openUrl: opts.openBrowser ? openInBrowser : undefined,
330
+ ...(opts.scopes ? { requestedScopes: opts.scopes } : {}),
331
+ });
332
+
333
+ // f. Handle results
334
+ if (!result.success) {
335
+ writeError(result.error ?? "OAuth connect failed");
336
+ return;
337
+ }
338
+
339
+ if (result.deferred) {
340
+ if (jsonMode) {
341
+ writeOutput(cmd, {
342
+ ok: true,
343
+ deferred: true,
344
+ authUrl: result.authUrl,
345
+ service: result.service,
346
+ });
347
+ } else {
348
+ process.stdout.write(
349
+ `\nAuthorize with ${resolvedServiceKey}:\n\n${result.authUrl}\n\nThe connection will complete automatically once you authorize.\n`,
350
+ );
351
+ }
352
+ return;
353
+ }
354
+
355
+ // Interactive mode completed
356
+ if (jsonMode) {
357
+ writeOutput(cmd, {
358
+ ok: true,
359
+ grantedScopes: result.grantedScopes,
360
+ accountInfo: result.accountInfo,
361
+ });
362
+ } else {
363
+ const msg = `Connected to ${resolvedServiceKey}${result.accountInfo ? ` as ${result.accountInfo}` : ""}`;
364
+ process.stdout.write(msg + "\n");
365
+ }
366
+ }
367
+ } catch (err) {
368
+ const message = err instanceof Error ? err.message : String(err);
369
+ writeError(message);
370
+ }
371
+ },
372
+ );
373
+ }