@vellumai/assistant 0.5.11 → 0.5.13

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 (209) hide show
  1. package/Dockerfile +42 -9
  2. package/docs/architecture/integrations.md +34 -32
  3. package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
  4. package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
  5. package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
  6. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
  7. package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
  8. package/openapi.yaml +87 -9
  9. package/package.json +1 -1
  10. package/src/__tests__/catalog-cache.test.ts +164 -0
  11. package/src/__tests__/catalog-search.test.ts +61 -0
  12. package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
  13. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
  14. package/src/__tests__/conversation-error.test.ts +3 -2
  15. package/src/__tests__/credential-security-invariants.test.ts +9 -15
  16. package/src/__tests__/credential-vault-unit.test.ts +32 -34
  17. package/src/__tests__/credential-vault.test.ts +25 -33
  18. package/src/__tests__/credentials-cli.test.ts +3 -3
  19. package/src/__tests__/daemon-credential-client.test.ts +2 -2
  20. package/src/__tests__/first-greeting.test.ts +7 -0
  21. package/src/__tests__/host-bash-proxy.test.ts +79 -0
  22. package/src/__tests__/host-cu-proxy.test.ts +90 -0
  23. package/src/__tests__/host-file-proxy.test.ts +89 -0
  24. package/src/__tests__/integration-status.test.ts +5 -5
  25. package/src/__tests__/list-messages-attachments.test.ts +171 -0
  26. package/src/__tests__/mcp-abort-signal.test.ts +205 -0
  27. package/src/__tests__/messaging-send-tool.test.ts +5 -5
  28. package/src/__tests__/navigate-settings-tab.test.ts +6 -2
  29. package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
  30. package/src/__tests__/oauth-cli.test.ts +126 -119
  31. package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
  32. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  33. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  34. package/src/__tests__/platform.test.ts +3 -168
  35. package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
  36. package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
  37. package/src/__tests__/skill-feature-flags.test.ts +8 -0
  38. package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
  39. package/src/__tests__/skills-uninstall.test.ts +2 -2
  40. package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
  41. package/src/__tests__/slack-share-routes.test.ts +5 -5
  42. package/src/__tests__/system-prompt.test.ts +39 -0
  43. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
  44. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
  45. package/src/cli/AGENTS.md +47 -7
  46. package/src/cli/commands/browser-relay.ts +2 -17
  47. package/src/cli/commands/contacts.ts +6 -4
  48. package/src/cli/commands/conversations.ts +13 -1
  49. package/src/cli/commands/credential-execution.ts +16 -1
  50. package/src/cli/commands/credentials.ts +2 -8
  51. package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
  52. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
  53. package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
  54. package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
  55. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
  56. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
  57. package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
  58. package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
  59. package/src/cli/commands/oauth/apps.ts +63 -44
  60. package/src/cli/commands/oauth/connect.ts +187 -155
  61. package/src/cli/commands/oauth/disconnect.ts +27 -75
  62. package/src/cli/commands/oauth/index.ts +36 -46
  63. package/src/cli/commands/oauth/mode.ts +22 -34
  64. package/src/cli/commands/oauth/ping.ts +19 -45
  65. package/src/cli/commands/oauth/providers.ts +569 -62
  66. package/src/cli/commands/oauth/request.ts +36 -48
  67. package/src/cli/commands/oauth/shared.ts +1 -19
  68. package/src/cli/commands/oauth/status.ts +14 -25
  69. package/src/cli/commands/oauth/token.ts +25 -34
  70. package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
  71. package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
  72. package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
  73. package/src/cli/commands/platform/connect.ts +104 -0
  74. package/src/cli/commands/platform/disconnect.ts +118 -0
  75. package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
  76. package/src/cli/commands/sequence.ts +5 -4
  77. package/src/cli/commands/shotgun.ts +16 -0
  78. package/src/cli/commands/skills.ts +173 -41
  79. package/src/cli/commands/usage.ts +5 -11
  80. package/src/cli/lib/daemon-credential-client.ts +22 -38
  81. package/src/cli/program.ts +1 -1
  82. package/src/config/assistant-feature-flags.ts +3 -7
  83. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  84. package/src/config/bundled-skills/conversations/SKILL.md +20 -0
  85. package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
  86. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
  87. package/src/config/bundled-skills/gmail/SKILL.md +13 -13
  88. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
  89. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
  90. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
  91. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
  92. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
  93. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
  94. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
  95. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
  96. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
  97. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  98. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
  99. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
  100. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
  101. package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
  102. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  103. package/src/config/bundled-skills/messaging/SKILL.md +7 -7
  104. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
  105. package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
  106. package/src/config/bundled-skills/settings/TOOLS.json +5 -3
  107. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
  108. package/src/config/bundled-tool-registry.ts +5 -0
  109. package/src/config/feature-flag-registry.json +2 -2
  110. package/src/credential-execution/client.ts +15 -3
  111. package/src/daemon/conversation-agent-loop.ts +2 -0
  112. package/src/daemon/conversation-error.ts +36 -6
  113. package/src/daemon/conversation-messaging.ts +9 -0
  114. package/src/daemon/conversation-runtime-assembly.ts +33 -0
  115. package/src/daemon/conversation-surfaces.ts +120 -14
  116. package/src/daemon/conversation.ts +5 -0
  117. package/src/daemon/first-greeting.ts +6 -1
  118. package/src/daemon/handlers/skills.ts +148 -3
  119. package/src/daemon/host-bash-proxy.ts +16 -0
  120. package/src/daemon/host-cu-proxy.ts +16 -0
  121. package/src/daemon/host-file-proxy.ts +16 -0
  122. package/src/daemon/lifecycle.ts +56 -5
  123. package/src/daemon/message-types/conversations.ts +1 -0
  124. package/src/daemon/message-types/guardian-actions.ts +2 -0
  125. package/src/daemon/message-types/host-bash.ts +6 -1
  126. package/src/daemon/message-types/host-cu.ts +6 -1
  127. package/src/daemon/message-types/host-file.ts +6 -1
  128. package/src/daemon/message-types/integrations.ts +0 -1
  129. package/src/daemon/server.ts +29 -2
  130. package/src/hooks/cli.ts +74 -0
  131. package/src/inbound/platform-callback-registration.ts +7 -12
  132. package/src/index.ts +0 -12
  133. package/src/mcp/client.ts +6 -1
  134. package/src/mcp/manager.ts +2 -1
  135. package/src/memory/conversation-crud.ts +92 -3
  136. package/src/memory/conversation-key-store.ts +26 -0
  137. package/src/memory/conversation-queries.ts +6 -6
  138. package/src/memory/db-init.ts +16 -0
  139. package/src/memory/journal-memory.ts +8 -2
  140. package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
  141. package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
  142. package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
  143. package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
  144. package/src/memory/migrations/index.ts +4 -0
  145. package/src/memory/migrations/registry.ts +8 -0
  146. package/src/memory/schema/oauth.ts +11 -0
  147. package/src/messaging/provider.ts +13 -12
  148. package/src/messaging/providers/gmail/adapter.ts +44 -35
  149. package/src/messaging/providers/slack/adapter.ts +63 -33
  150. package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
  151. package/src/messaging/providers/whatsapp/adapter.ts +6 -8
  152. package/src/notifications/adapters/telegram.ts +78 -2
  153. package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
  154. package/src/oauth/byo-connection.test.ts +22 -24
  155. package/src/oauth/connect-orchestrator.ts +37 -76
  156. package/src/oauth/connect-types.ts +7 -65
  157. package/src/oauth/connection-resolver.test.ts +13 -13
  158. package/src/oauth/connection-resolver.ts +3 -4
  159. package/src/oauth/identity-verifier.ts +177 -0
  160. package/src/oauth/oauth-store.ts +228 -3
  161. package/src/oauth/platform-connection.test.ts +56 -6
  162. package/src/oauth/platform-connection.ts +8 -1
  163. package/src/oauth/seed-providers.ts +247 -34
  164. package/src/permissions/checker.ts +127 -1
  165. package/src/prompts/journal-context.ts +4 -1
  166. package/src/prompts/system-prompt.ts +54 -9
  167. package/src/prompts/templates/BOOTSTRAP.md +16 -5
  168. package/src/providers/anthropic/client.ts +2 -33
  169. package/src/runtime/guardian-action-service.ts +7 -2
  170. package/src/runtime/http-server.ts +12 -18
  171. package/src/runtime/http-types.ts +8 -1
  172. package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
  173. package/src/runtime/routes/conversation-management-routes.ts +31 -0
  174. package/src/runtime/routes/conversation-routes.ts +79 -4
  175. package/src/runtime/routes/guardian-action-routes.ts +15 -2
  176. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
  177. package/src/runtime/routes/integrations/slack/share.ts +1 -1
  178. package/src/runtime/routes/oauth-apps.ts +2 -1
  179. package/src/runtime/routes/secret-routes.ts +45 -15
  180. package/src/runtime/routes/settings-routes.ts +12 -19
  181. package/src/runtime/routes/skills-routes.ts +45 -4
  182. package/src/schedule/integration-status.ts +2 -2
  183. package/src/security/ces-rpc-credential-backend.ts +19 -16
  184. package/src/security/oauth-completion-page.ts +153 -0
  185. package/src/security/oauth2.ts +3 -17
  186. package/src/security/secure-keys.ts +207 -7
  187. package/src/security/token-manager.ts +3 -6
  188. package/src/signals/bash.ts +6 -1
  189. package/src/skills/catalog-cache.ts +44 -0
  190. package/src/skills/catalog-search.ts +18 -0
  191. package/src/tools/browser/browser-manager.ts +2 -2
  192. package/src/tools/credentials/post-connect-hooks.ts +1 -1
  193. package/src/tools/credentials/vault.ts +34 -45
  194. package/src/tools/host-terminal/host-shell.ts +16 -3
  195. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  196. package/src/tools/skills/sandbox-runner.ts +16 -3
  197. package/src/tools/terminal/shell.ts +16 -3
  198. package/src/util/logger.ts +11 -1
  199. package/src/util/platform.ts +1 -91
  200. package/src/util/sentry-log-stream.ts +51 -0
  201. package/src/watcher/providers/github.ts +2 -2
  202. package/src/watcher/providers/gmail.ts +1 -1
  203. package/src/watcher/providers/google-calendar.ts +1 -1
  204. package/src/watcher/providers/linear.ts +2 -2
  205. package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
  206. package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
  207. package/src/workspace/migrations/registry.ts +2 -0
  208. package/src/cli/commands/oauth/connections.ts +0 -255
  209. package/src/oauth/provider-behaviors.ts +0 -634
@@ -0,0 +1,416 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import { Command } from "commander";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Mock state
7
+ // ---------------------------------------------------------------------------
8
+
9
+ let mockGetProvider: (
10
+ key: string,
11
+ ) => Record<string, unknown> | undefined = () => undefined;
12
+
13
+ let mockUpdateProvider: (
14
+ key: string,
15
+ params: Record<string, unknown>,
16
+ ) => Record<string, unknown> | undefined = () => undefined;
17
+
18
+ let mockUpdateProviderCalls: Array<{
19
+ key: string;
20
+ params: Record<string, unknown>;
21
+ }> = [];
22
+
23
+ let mockSeededProviderKeys = new Set<string>(["google", "slack", "github"]);
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Mocks
27
+ // ---------------------------------------------------------------------------
28
+
29
+ mock.module("../../../../config/loader.js", () => ({
30
+ getConfig: () => ({ services: {} }),
31
+ loadConfig: () => ({ services: {} }),
32
+ API_KEY_PROVIDERS: [],
33
+ }));
34
+
35
+ mock.module("../../../../oauth/oauth-store.js", () => ({
36
+ getProvider: (key: string) => mockGetProvider(key),
37
+ updateProvider: (key: string, params: Record<string, unknown>) => {
38
+ mockUpdateProviderCalls.push({ key, params });
39
+ return mockUpdateProvider(key, params);
40
+ },
41
+ listProviders: () => [],
42
+ registerProvider: () => ({}),
43
+ seedProviders: () => {},
44
+ getConnection: () => undefined,
45
+ getConnectionByProvider: () => undefined,
46
+ getActiveConnection: () => undefined,
47
+ listActiveConnectionsByProvider: () => [],
48
+ isProviderConnected: () => false,
49
+ createConnection: () => ({}),
50
+ updateConnection: () => ({}),
51
+ deleteConnection: () => false,
52
+ upsertApp: async () => ({}),
53
+ getApp: () => undefined,
54
+ getAppByProviderAndClientId: () => undefined,
55
+ getMostRecentAppByProvider: () => undefined,
56
+ listApps: () => [],
57
+ deleteApp: async () => false,
58
+ listConnections: () => [],
59
+ }));
60
+
61
+ mock.module("../../../../oauth/seed-providers.js", () => ({
62
+ SEEDED_PROVIDER_KEYS: mockSeededProviderKeys,
63
+ PROVIDER_SEED_DATA: {},
64
+ seedAllProviders: () => {},
65
+ }));
66
+
67
+ mock.module("../../../../inbound/public-ingress-urls.js", () => ({
68
+ getOAuthCallbackUrl: () => null,
69
+ }));
70
+
71
+ mock.module("../../../../util/logger.js", () => ({
72
+ getLogger: () => ({
73
+ info: () => {},
74
+ warn: () => {},
75
+ error: () => {},
76
+ debug: () => {},
77
+ }),
78
+ getCliLogger: () => ({
79
+ info: () => {},
80
+ warn: () => {},
81
+ error: () => {},
82
+ debug: () => {},
83
+ }),
84
+ }));
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Import module under test (after mocks are registered)
88
+ // ---------------------------------------------------------------------------
89
+
90
+ const { registerProviderCommands } = await import("../providers.js");
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Test helper
94
+ // ---------------------------------------------------------------------------
95
+
96
+ async function runCommand(
97
+ args: string[],
98
+ ): Promise<{ stdout: string; exitCode: number }> {
99
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
100
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
101
+ const stdoutChunks: string[] = [];
102
+
103
+ process.stdout.write = ((chunk: unknown) => {
104
+ stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
105
+ return true;
106
+ }) as typeof process.stdout.write;
107
+
108
+ process.stderr.write = (() => true) as typeof process.stderr.write;
109
+
110
+ process.exitCode = 0;
111
+
112
+ try {
113
+ const program = new Command();
114
+ program.exitOverride();
115
+ program.option("--json", "JSON output");
116
+ program.configureOutput({
117
+ writeErr: () => {},
118
+ writeOut: (str: string) => stdoutChunks.push(str),
119
+ });
120
+ registerProviderCommands(program);
121
+ await program.parseAsync(["node", "assistant", ...args]);
122
+ } catch {
123
+ if (process.exitCode === 0) process.exitCode = 1;
124
+ } finally {
125
+ process.stdout.write = originalStdoutWrite;
126
+ process.stderr.write = originalStderrWrite;
127
+ }
128
+
129
+ const exitCode = process.exitCode ?? 0;
130
+ process.exitCode = 0;
131
+
132
+ return { exitCode, stdout: stdoutChunks.join("") };
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Sample provider row
137
+ // ---------------------------------------------------------------------------
138
+
139
+ const sampleProviderRow = {
140
+ providerKey: "custom-api",
141
+ authUrl: "https://custom-api.example.com/oauth/authorize",
142
+ tokenUrl: "https://custom-api.example.com/oauth/token",
143
+ tokenEndpointAuthMethod: null,
144
+ userinfoUrl: null,
145
+ baseUrl: null,
146
+ defaultScopes: "[]",
147
+ scopePolicy: "{}",
148
+ extraParams: null,
149
+ callbackTransport: null,
150
+ managedServiceConfigKey: null,
151
+ pingUrl: null,
152
+ pingMethod: null,
153
+ pingHeaders: null,
154
+ pingBody: null,
155
+ displayName: null,
156
+ description: null,
157
+ dashboardUrl: null,
158
+ clientIdPlaceholder: null,
159
+ requiresClientSecret: 1,
160
+ loopbackPort: null,
161
+ injectionTemplates: null,
162
+ appType: null,
163
+ setupNotes: null,
164
+ identityUrl: null,
165
+ identityMethod: null,
166
+ identityHeaders: null,
167
+ identityBody: null,
168
+ identityResponsePaths: null,
169
+ identityFormat: null,
170
+ identityOkField: null,
171
+ createdAt: Date.now(),
172
+ updatedAt: Date.now(),
173
+ };
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Tests
177
+ // ---------------------------------------------------------------------------
178
+
179
+ describe("assistant oauth providers update", () => {
180
+ beforeEach(() => {
181
+ mockGetProvider = () => undefined;
182
+ mockUpdateProvider = () => undefined;
183
+ mockUpdateProviderCalls = [];
184
+ mockSeededProviderKeys = new Set(["google", "slack", "github"]);
185
+ process.exitCode = 0;
186
+ });
187
+
188
+ // -------------------------------------------------------------------------
189
+ // Provider not found
190
+ // -------------------------------------------------------------------------
191
+
192
+ test("provider not found returns error with hint", async () => {
193
+ mockGetProvider = () => undefined;
194
+
195
+ const { exitCode, stdout } = await runCommand([
196
+ "providers",
197
+ "update",
198
+ "nonexistent",
199
+ "--display-name",
200
+ "Foo",
201
+ "--json",
202
+ ]);
203
+ expect(exitCode).toBe(1);
204
+ const parsed = JSON.parse(stdout);
205
+ expect(parsed.ok).toBe(false);
206
+ expect(parsed.error).toContain("not found");
207
+ expect(parsed.error).toContain("providers list");
208
+ });
209
+
210
+ // -------------------------------------------------------------------------
211
+ // Built-in provider
212
+ // -------------------------------------------------------------------------
213
+
214
+ test("built-in provider returns error suggesting register", async () => {
215
+ mockGetProvider = () => ({
216
+ ...sampleProviderRow,
217
+ providerKey: "google",
218
+ });
219
+
220
+ const { exitCode, stdout } = await runCommand([
221
+ "providers",
222
+ "update",
223
+ "google",
224
+ "--display-name",
225
+ "Foo",
226
+ "--json",
227
+ ]);
228
+ expect(exitCode).toBe(1);
229
+ const parsed = JSON.parse(stdout);
230
+ expect(parsed.ok).toBe(false);
231
+ expect(parsed.error).toContain("Cannot update built-in");
232
+ expect(parsed.error).toContain("providers register");
233
+ });
234
+
235
+ // -------------------------------------------------------------------------
236
+ // No options provided
237
+ // -------------------------------------------------------------------------
238
+
239
+ test("no options provided returns error", async () => {
240
+ mockGetProvider = () => ({ ...sampleProviderRow });
241
+
242
+ const { exitCode, stdout } = await runCommand([
243
+ "providers",
244
+ "update",
245
+ "custom-api",
246
+ "--json",
247
+ ]);
248
+ expect(exitCode).toBe(1);
249
+ const parsed = JSON.parse(stdout);
250
+ expect(parsed.ok).toBe(false);
251
+ expect(parsed.error).toContain("Nothing to update");
252
+ });
253
+
254
+ // -------------------------------------------------------------------------
255
+ // Successful update with --display-name
256
+ // -------------------------------------------------------------------------
257
+
258
+ test("successful update with --display-name returns updated provider row", async () => {
259
+ mockGetProvider = () => ({ ...sampleProviderRow });
260
+ mockUpdateProvider = (_key, _params) => ({
261
+ ...sampleProviderRow,
262
+ displayName: "New Name",
263
+ updatedAt: Date.now(),
264
+ });
265
+
266
+ const { exitCode, stdout } = await runCommand([
267
+ "providers",
268
+ "update",
269
+ "custom-api",
270
+ "--display-name",
271
+ "New Name",
272
+ "--json",
273
+ ]);
274
+ expect(exitCode).toBe(0);
275
+ const parsed = JSON.parse(stdout);
276
+ expect(parsed.providerKey).toBe("custom-api");
277
+ expect(parsed.displayName).toBe("New Name");
278
+ });
279
+
280
+ // -------------------------------------------------------------------------
281
+ // Successful update with multiple options
282
+ // -------------------------------------------------------------------------
283
+
284
+ test("successful update with multiple options passes all fields to updateProvider", async () => {
285
+ mockGetProvider = () => ({ ...sampleProviderRow });
286
+ mockUpdateProvider = (_key, _params) => ({
287
+ ...sampleProviderRow,
288
+ displayName: "My API",
289
+ defaultScopes: '["read","write"]',
290
+ authUrl: "https://new.example.com/auth",
291
+ updatedAt: Date.now(),
292
+ });
293
+
294
+ const { exitCode, stdout } = await runCommand([
295
+ "providers",
296
+ "update",
297
+ "custom-api",
298
+ "--display-name",
299
+ "My API",
300
+ "--scopes",
301
+ "read,write",
302
+ "--auth-url",
303
+ "https://new.example.com/auth",
304
+ "--json",
305
+ ]);
306
+ expect(exitCode).toBe(0);
307
+ const parsed = JSON.parse(stdout);
308
+ expect(parsed.providerKey).toBe("custom-api");
309
+
310
+ // Verify updateProvider was called with the correct params
311
+ expect(mockUpdateProviderCalls).toHaveLength(1);
312
+ expect(mockUpdateProviderCalls[0].key).toBe("custom-api");
313
+ expect(mockUpdateProviderCalls[0].params).toEqual({
314
+ displayName: "My API",
315
+ defaultScopes: ["read", "write"],
316
+ authUrl: "https://new.example.com/auth",
317
+ });
318
+ });
319
+
320
+ // -------------------------------------------------------------------------
321
+ // Successful update with injection templates, identity config, and setup metadata
322
+ // -------------------------------------------------------------------------
323
+
324
+ test("successful update with injection templates and identity config passes new fields to updateProvider", async () => {
325
+ const injectionTemplates = [
326
+ {
327
+ hostPattern: "api.example.com",
328
+ injectionType: "header",
329
+ headerName: "Authorization",
330
+ valuePrefix: "Bearer ",
331
+ },
332
+ ];
333
+ const identityHeaders = { "X-Custom": "value" };
334
+ const identityBody = { query: "{ viewer { email } }" };
335
+ const setupNotes = ["Enable the API", "Add test users"];
336
+
337
+ mockGetProvider = () => ({ ...sampleProviderRow });
338
+ mockUpdateProvider = (_key, _params) => ({
339
+ ...sampleProviderRow,
340
+ loopbackPort: 17400,
341
+ injectionTemplates: JSON.stringify(injectionTemplates),
342
+ appType: "OAuth App",
343
+ setupNotes: JSON.stringify(setupNotes),
344
+ identityUrl: "https://api.example.com/me",
345
+ identityMethod: "POST",
346
+ identityHeaders: JSON.stringify(identityHeaders),
347
+ identityBody: JSON.stringify(identityBody),
348
+ identityResponsePaths: JSON.stringify(["email", "name"]),
349
+ identityFormat: "@${email}",
350
+ identityOkField: "ok",
351
+ updatedAt: Date.now(),
352
+ });
353
+
354
+ const { exitCode, stdout } = await runCommand([
355
+ "providers",
356
+ "update",
357
+ "custom-api",
358
+ "--loopback-port",
359
+ "17400",
360
+ "--injection-templates",
361
+ JSON.stringify(injectionTemplates),
362
+ "--app-type",
363
+ "OAuth App",
364
+ "--setup-notes",
365
+ JSON.stringify(setupNotes),
366
+ "--identity-url",
367
+ "https://api.example.com/me",
368
+ "--identity-method",
369
+ "POST",
370
+ "--identity-headers",
371
+ JSON.stringify(identityHeaders),
372
+ "--identity-body",
373
+ JSON.stringify(identityBody),
374
+ "--identity-response-paths",
375
+ "email,name",
376
+ "--identity-format",
377
+ "@${email}",
378
+ "--identity-ok-field",
379
+ "ok",
380
+ "--json",
381
+ ]);
382
+ expect(exitCode).toBe(0);
383
+ const parsed = JSON.parse(stdout);
384
+ expect(parsed.providerKey).toBe("custom-api");
385
+
386
+ // Verify the new fields are present in the output (parsed from JSON strings)
387
+ expect(parsed.loopbackPort).toBe(17400);
388
+ expect(parsed.injectionTemplates).toEqual(injectionTemplates);
389
+ expect(parsed.appType).toBe("OAuth App");
390
+ expect(parsed.setupNotes).toEqual(setupNotes);
391
+ expect(parsed.identityUrl).toBe("https://api.example.com/me");
392
+ expect(parsed.identityMethod).toBe("POST");
393
+ expect(parsed.identityHeaders).toEqual(identityHeaders);
394
+ expect(parsed.identityBody).toEqual(identityBody);
395
+ expect(parsed.identityResponsePaths).toEqual(["email", "name"]);
396
+ expect(parsed.identityFormat).toBe("@${email}");
397
+ expect(parsed.identityOkField).toBe("ok");
398
+
399
+ // Verify updateProvider was called with the correct params
400
+ expect(mockUpdateProviderCalls).toHaveLength(1);
401
+ expect(mockUpdateProviderCalls[0].key).toBe("custom-api");
402
+ expect(mockUpdateProviderCalls[0].params).toEqual({
403
+ loopbackPort: 17400,
404
+ injectionTemplates,
405
+ appType: "OAuth App",
406
+ setupNotes,
407
+ identityUrl: "https://api.example.com/me",
408
+ identityMethod: "POST",
409
+ identityHeaders,
410
+ identityBody,
411
+ identityResponsePaths: ["email", "name"],
412
+ identityFormat: "@${email}",
413
+ identityOkField: "ok",
414
+ });
415
+ });
416
+ });
@@ -56,20 +56,6 @@ mock.module("../../../../oauth/oauth-store.js", () => ({
56
56
  deleteConnection: () => false,
57
57
  }));
58
58
 
59
- mock.module("../../../../oauth/provider-behaviors.js", () => ({
60
- resolveService: (service: string) => {
61
- const aliases: Record<string, string> = {
62
- gmail: "integration:google",
63
- google: "integration:google",
64
- slack: "integration:slack",
65
- };
66
- if (aliases[service]) return aliases[service];
67
- if (!service.includes(":")) return `integration:${service}`;
68
- return service;
69
- },
70
- getProviderBehavior: () => undefined,
71
- }));
72
-
73
59
  mock.module("../../../../oauth/connect-orchestrator.js", () => ({
74
60
  orchestrateOAuthConnect: async () => ({
75
61
  success: true,
@@ -110,16 +96,6 @@ mock.module("../../../lib/daemon-credential-client.js", () => ({
110
96
 
111
97
  // Mock shared.js helpers to control managed vs BYO mode routing
112
98
  mock.module("../shared.js", () => ({
113
- resolveService: (service: string) => {
114
- const aliases: Record<string, string> = {
115
- gmail: "integration:google",
116
- google: "integration:google",
117
- slack: "integration:slack",
118
- };
119
- if (aliases[service]) return aliases[service];
120
- if (!service.includes(":")) return `integration:${service}`;
121
- return service;
122
- },
123
99
  isManagedMode: (key: string) => mockIsManagedMode(key),
124
100
  requirePlatformClient: async (_cmd: Command) => {
125
101
  if (
@@ -167,10 +143,6 @@ mock.module("../shared.js", () => ({
167
143
  if (!result.ok) return null;
168
144
  return result.body as Array<Record<string, unknown>>;
169
145
  },
170
- toBareProvider: (provider: string): string =>
171
- provider.startsWith("integration:")
172
- ? provider.slice("integration:".length)
173
- : provider,
174
146
  }));
175
147
 
176
148
  // ---------------------------------------------------------------------------
@@ -263,7 +235,7 @@ describe("assistant oauth status", () => {
263
235
  describe("managed mode", () => {
264
236
  beforeEach(() => {
265
237
  mockGetProvider = () => ({
266
- providerKey: "integration:google",
238
+ providerKey: "google",
267
239
  managedServiceConfigKey: "google-oauth",
268
240
  });
269
241
  mockIsManagedMode = () => true;
@@ -300,7 +272,7 @@ describe("assistant oauth status", () => {
300
272
  expect(exitCode).toBe(0);
301
273
  const parsed = JSON.parse(stdout);
302
274
  expect(parsed.ok).toBe(true);
303
- expect(parsed.provider).toBe("integration:google");
275
+ expect(parsed.provider).toBe("google");
304
276
  expect(parsed.mode).toBe("managed");
305
277
  expect(parsed.connections).toHaveLength(2);
306
278
 
@@ -327,7 +299,7 @@ describe("assistant oauth status", () => {
327
299
  expect(exitCode).toBe(0);
328
300
  const parsed = JSON.parse(stdout);
329
301
  expect(parsed.ok).toBe(true);
330
- expect(parsed.provider).toBe("integration:google");
302
+ expect(parsed.provider).toBe("google");
331
303
  expect(parsed.mode).toBe("managed");
332
304
  expect(parsed.connections).toEqual([]);
333
305
  });
@@ -388,7 +360,7 @@ describe("assistant oauth status", () => {
388
360
  describe("BYO mode", () => {
389
361
  beforeEach(() => {
390
362
  mockGetProvider = () => ({
391
- providerKey: "integration:google",
363
+ providerKey: "google",
392
364
  managedServiceConfigKey: null,
393
365
  });
394
366
  mockIsManagedMode = () => false;
@@ -399,7 +371,7 @@ describe("assistant oauth status", () => {
399
371
  mockListConnections = () => [
400
372
  {
401
373
  id: "conn-local-1",
402
- providerKey: "integration:google",
374
+ providerKey: "google",
403
375
  accountInfo: "localuser@gmail.com",
404
376
  grantedScopes: '["email","profile"]',
405
377
  expiresAt,
@@ -416,7 +388,7 @@ describe("assistant oauth status", () => {
416
388
  expect(exitCode).toBe(0);
417
389
  const parsed = JSON.parse(stdout);
418
390
  expect(parsed.ok).toBe(true);
419
- expect(parsed.provider).toBe("integration:google");
391
+ expect(parsed.provider).toBe("google");
420
392
  expect(parsed.mode).toBe("byo");
421
393
  expect(parsed.connections).toHaveLength(1);
422
394
 
@@ -433,7 +405,7 @@ describe("assistant oauth status", () => {
433
405
  mockListConnections = () => [
434
406
  {
435
407
  id: "conn-local-2",
436
- providerKey: "integration:google",
408
+ providerKey: "google",
437
409
  accountInfo: null,
438
410
  grantedScopes: "[]",
439
411
  expiresAt: null,
@@ -460,7 +432,7 @@ describe("assistant oauth status", () => {
460
432
  mockListConnections = () => [
461
433
  {
462
434
  id: "conn-active",
463
- providerKey: "integration:google",
435
+ providerKey: "google",
464
436
  accountInfo: "user@gmail.com",
465
437
  grantedScopes: "[]",
466
438
  expiresAt: null,
@@ -469,7 +441,7 @@ describe("assistant oauth status", () => {
469
441
  },
470
442
  {
471
443
  id: "conn-revoked",
472
- providerKey: "integration:google",
444
+ providerKey: "google",
473
445
  accountInfo: "old@gmail.com",
474
446
  grantedScopes: "[]",
475
447
  expiresAt: null,
@@ -500,7 +472,7 @@ describe("assistant oauth status", () => {
500
472
  expect(exitCode).toBe(0);
501
473
  const parsed = JSON.parse(stdout);
502
474
  expect(parsed.ok).toBe(true);
503
- expect(parsed.provider).toBe("integration:google");
475
+ expect(parsed.provider).toBe("google");
504
476
  expect(parsed.mode).toBe("byo");
505
477
  expect(parsed.connections).toEqual([]);
506
478
  });
@@ -517,7 +489,7 @@ describe("assistant oauth status", () => {
517
489
  mockListConnections = () => [
518
490
  {
519
491
  id: "conn-structure",
520
- providerKey: "integration:google",
492
+ providerKey: "google",
521
493
  accountInfo: "check@gmail.com",
522
494
  grantedScopes: '["scope1"]',
523
495
  expiresAt: Date.now() + 60_000,
@@ -555,7 +527,7 @@ describe("assistant oauth status", () => {
555
527
  mockListConnections = () => [
556
528
  {
557
529
  id: "conn-bad-scopes",
558
- providerKey: "integration:google",
530
+ providerKey: "google",
559
531
  accountInfo: null,
560
532
  grantedScopes: "not-valid-json",
561
533
  expiresAt: null,
@@ -54,20 +54,6 @@ mock.module("../../../../oauth/oauth-store.js", () => ({
54
54
  deleteConnection: () => false,
55
55
  }));
56
56
 
57
- mock.module("../../../../oauth/provider-behaviors.js", () => ({
58
- resolveService: (service: string) => {
59
- const aliases: Record<string, string> = {
60
- gmail: "integration:google",
61
- google: "integration:google",
62
- slack: "integration:slack",
63
- };
64
- if (aliases[service]) return aliases[service];
65
- if (!service.includes(":")) return `integration:${service}`;
66
- return service;
67
- },
68
- getProviderBehavior: () => undefined,
69
- }));
70
-
71
57
  mock.module("../../../../security/token-manager.js", () => ({
72
58
  withValidToken: async (
73
59
  service: string,
@@ -98,21 +84,7 @@ mock.module("../../../lib/daemon-credential-client.js", () => ({
98
84
 
99
85
  // Mock shared.js helpers to control managed vs BYO mode routing
100
86
  mock.module("../shared.js", () => ({
101
- resolveService: (service: string) => {
102
- const aliases: Record<string, string> = {
103
- gmail: "integration:google",
104
- google: "integration:google",
105
- slack: "integration:slack",
106
- };
107
- if (aliases[service]) return aliases[service];
108
- if (!service.includes(":")) return `integration:${service}`;
109
- return service;
110
- },
111
87
  isManagedMode: (key: string) => mockIsManagedMode(key),
112
- toBareProvider: (provider: string): string =>
113
- provider.startsWith("integration:")
114
- ? provider.slice("integration:".length)
115
- : provider,
116
88
  }));
117
89
 
118
90
  // ---------------------------------------------------------------------------
@@ -219,7 +191,7 @@ describe("assistant oauth token", () => {
219
191
  test("no active connection returns error", async () => {
220
192
  mockWithValidToken = async () => {
221
193
  throw new Error(
222
- 'No access token found for "integration:google". Authorization required.',
194
+ 'No access token found for "google". Authorization required.',
223
195
  );
224
196
  };
225
197
 
@@ -235,25 +207,6 @@ describe("assistant oauth token", () => {
235
207
  });
236
208
  });
237
209
 
238
- // =========================================================================
239
- // Provider alias resolution
240
- // =========================================================================
241
-
242
- test("resolves provider alias (gmail -> integration:google)", async () => {
243
- let calledWithService = "";
244
- mockWithValidToken = async (service, callback) => {
245
- calledWithService = service;
246
- return callback("alias-token");
247
- };
248
-
249
- const { exitCode, stdout } = await runCommand(["token", "gmail", "--json"]);
250
- expect(exitCode).toBe(0);
251
- expect(calledWithService).toBe("integration:google");
252
- const parsed = JSON.parse(stdout);
253
- expect(parsed.ok).toBe(true);
254
- expect(parsed.token).toBe("alias-token");
255
- });
256
-
257
210
  // =========================================================================
258
211
  // Managed mode — user-friendly error
259
212
  // =========================================================================
@@ -316,7 +269,7 @@ describe("assistant oauth token", () => {
316
269
  if (options?.account === "user@gmail.com") {
317
270
  return {
318
271
  id: "conn-abc-123",
319
- providerKey: "integration:google",
272
+ providerKey: "google",
320
273
  accountInfo: "user@gmail.com",
321
274
  status: "active",
322
275
  };
@@ -373,7 +326,7 @@ describe("assistant oauth token", () => {
373
326
  if (options?.clientId === "my-client-id") {
374
327
  return {
375
328
  id: "conn-client-456",
376
- providerKey: "integration:google",
329
+ providerKey: "google",
377
330
  accountInfo: null,
378
331
  status: "active",
379
332
  };