@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
@@ -37,7 +37,7 @@ let mockUpsertAppCalls: Array<{
37
37
  }> = [];
38
38
  let mockUpsertAppResult: Record<string, unknown> = {
39
39
  id: "app-upsert-1",
40
- providerKey: "integration:test",
40
+ providerKey: "test",
41
41
  clientId: "test-client-id",
42
42
  createdAt: 1700000000000,
43
43
  updatedAt: 1700000000000,
@@ -53,7 +53,7 @@ let mockUpsertAppImpl:
53
53
  ) => Promise<Record<string, unknown>>)
54
54
  | undefined;
55
55
 
56
- // Transitive mock state (connect-orchestrator, provider-behaviors, etc.)
56
+ // Transitive mock state (connect-orchestrator, etc.)
57
57
  let mockOrchestrateOAuthConnect: (
58
58
  opts: Record<string, unknown>,
59
59
  ) => Promise<Record<string, unknown>>;
@@ -67,9 +67,6 @@ let mockGetMostRecentAppByProvider: (
67
67
  let mockGetProvider: (
68
68
  providerKey: string,
69
69
  ) => Record<string, unknown> | undefined = () => undefined;
70
- let mockGetProviderBehavior: (
71
- providerKey: string,
72
- ) => Record<string, unknown> | undefined = () => undefined;
73
70
  let mockGetSecureKey: (account: string) => string | undefined = () => undefined;
74
71
  let mockResolveOAuthConnection: (
75
72
  providerKey: string,
@@ -153,6 +150,8 @@ mock.module("../oauth/oauth-store.js", () => ({
153
150
  getProvider: (providerKey: string) => mockGetProvider(providerKey),
154
151
  listProviders: () => mockListProviders(),
155
152
  registerProvider: () => ({}),
153
+ updateProvider: () => undefined,
154
+ deleteProvider: () => false,
156
155
  seedProviders: () => {},
157
156
  getActiveConnection: () => undefined,
158
157
  listActiveConnectionsByProvider: () => [],
@@ -208,14 +207,16 @@ mock.module("../oauth/connect-orchestrator.js", () => ({
208
207
  mockOrchestrateOAuthConnect(opts),
209
208
  }));
210
209
 
211
- // ---------------------------------------------------------------------------
212
- // Mock provider-behaviors
213
- // ---------------------------------------------------------------------------
214
-
215
- mock.module("../oauth/provider-behaviors.js", () => ({
216
- resolveService: (service: string) => service,
217
- getProviderBehavior: (providerKey: string) =>
218
- mockGetProviderBehavior(providerKey),
210
+ mock.module("../oauth/seed-providers.js", () => ({
211
+ SEEDED_PROVIDER_KEYS: new Set([
212
+ "google",
213
+ "slack",
214
+ "github",
215
+ "notion",
216
+ "twitter",
217
+ "linear",
218
+ ]),
219
+ seedOAuthProviders: () => {},
219
220
  }));
220
221
 
221
222
  // ---------------------------------------------------------------------------
@@ -315,17 +316,13 @@ describe("assistant oauth token <provider-key>", () => {
315
316
  });
316
317
 
317
318
  test("prints bare token in human mode", async () => {
318
- const { exitCode, stdout } = await runCli(["token", "integration:twitter"]);
319
+ const { exitCode, stdout } = await runCli(["token", "twitter"]);
319
320
  expect(exitCode).toBe(0);
320
321
  expect(stdout).toBe("mock-access-token-xyz\n");
321
322
  });
322
323
 
323
324
  test("prints JSON in --json mode", async () => {
324
- const { exitCode, stdout } = await runCli([
325
- "token",
326
- "integration:twitter",
327
- "--json",
328
- ]);
325
+ const { exitCode, stdout } = await runCli(["token", "twitter", "--json"]);
329
326
  expect(exitCode).toBe(0);
330
327
  const parsed = JSON.parse(stdout);
331
328
  expect(parsed).toEqual({ ok: true, token: "mock-access-token-xyz" });
@@ -338,8 +335,8 @@ describe("assistant oauth token <provider-key>", () => {
338
335
  return cb("tok");
339
336
  };
340
337
 
341
- await runCli(["token", "integration:twitter"]);
342
- expect(capturedService).toBe("integration:twitter");
338
+ await runCli(["token", "twitter"]);
339
+ expect(capturedService).toBe("twitter");
343
340
  });
344
341
 
345
342
  test("works with other provider keys", async () => {
@@ -349,24 +346,20 @@ describe("assistant oauth token <provider-key>", () => {
349
346
  return cb("gmail-token");
350
347
  };
351
348
 
352
- const { exitCode, stdout } = await runCli(["token", "integration:google"]);
349
+ const { exitCode, stdout } = await runCli(["token", "google"]);
353
350
  expect(exitCode).toBe(0);
354
351
  expect(stdout).toBe("gmail-token\n");
355
- expect(capturedService).toBe("integration:google");
352
+ expect(capturedService).toBe("google");
356
353
  });
357
354
 
358
355
  test("exits 1 when no token exists", async () => {
359
356
  mockWithValidToken = async () => {
360
357
  throw new Error(
361
- 'No access token found for "integration:twitter". Authorization required.',
358
+ 'No access token found for "twitter". Authorization required.',
362
359
  );
363
360
  };
364
361
 
365
- const { exitCode, stdout } = await runCli([
366
- "token",
367
- "integration:twitter",
368
- "--json",
369
- ]);
362
+ const { exitCode, stdout } = await runCli(["token", "twitter", "--json"]);
370
363
  expect(exitCode).toBe(1);
371
364
  const parsed = JSON.parse(stdout);
372
365
  expect(parsed.ok).toBe(false);
@@ -375,16 +368,10 @@ describe("assistant oauth token <provider-key>", () => {
375
368
 
376
369
  test("exits 1 when refresh fails", async () => {
377
370
  mockWithValidToken = async () => {
378
- throw new Error(
379
- 'Token refresh failed for "integration:twitter": invalid_grant.',
380
- );
371
+ throw new Error('Token refresh failed for "twitter": invalid_grant.');
381
372
  };
382
373
 
383
- const { exitCode, stdout } = await runCli([
384
- "token",
385
- "integration:twitter",
386
- "--json",
387
- ]);
374
+ const { exitCode, stdout } = await runCli(["token", "twitter", "--json"]);
388
375
  expect(exitCode).toBe(1);
389
376
  const parsed = JSON.parse(stdout);
390
377
  expect(parsed.ok).toBe(false);
@@ -395,7 +382,7 @@ describe("assistant oauth token <provider-key>", () => {
395
382
  // Simulate withValidToken refreshing and returning a new token
396
383
  mockWithValidToken = async (_service, cb) => cb("refreshed-new-token");
397
384
 
398
- const { exitCode, stdout } = await runCli(["token", "integration:twitter"]);
385
+ const { exitCode, stdout } = await runCli(["token", "twitter"]);
399
386
  expect(exitCode).toBe(0);
400
387
  expect(stdout).toBe("refreshed-new-token\n");
401
388
  });
@@ -413,7 +400,7 @@ describe("assistant oauth token <provider-key>", () => {
413
400
  describe("assistant oauth providers list", () => {
414
401
  const fakeProviders = [
415
402
  {
416
- providerKey: "integration:google",
403
+ providerKey: "google",
417
404
  authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
418
405
  tokenUrl: "https://oauth2.googleapis.com/token",
419
406
  defaultScopes: "[]",
@@ -423,7 +410,7 @@ describe("assistant oauth providers list", () => {
423
410
  updatedAt: "2025-01-01T00:00:00.000Z",
424
411
  },
425
412
  {
426
- providerKey: "integration:google-calendar",
413
+ providerKey: "google-calendar",
427
414
  authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
428
415
  tokenUrl: "https://oauth2.googleapis.com/token",
429
416
  defaultScopes: "[]",
@@ -433,7 +420,7 @@ describe("assistant oauth providers list", () => {
433
420
  updatedAt: "2025-01-01T00:00:00.000Z",
434
421
  },
435
422
  {
436
- providerKey: "integration:slack",
423
+ providerKey: "slack",
437
424
  authUrl: "https://slack.com/oauth/v2/authorize",
438
425
  tokenUrl: "https://slack.com/api/oauth.v2.access",
439
426
  defaultScopes: "[]",
@@ -443,7 +430,7 @@ describe("assistant oauth providers list", () => {
443
430
  updatedAt: "2025-01-01T00:00:00.000Z",
444
431
  },
445
432
  {
446
- providerKey: "integration:twitter",
433
+ providerKey: "twitter",
447
434
  authUrl: "https://twitter.com/i/oauth2/authorize",
448
435
  tokenUrl: "https://api.twitter.com/2/oauth2/token",
449
436
  defaultScopes: "[]",
@@ -465,10 +452,10 @@ describe("assistant oauth providers list", () => {
465
452
  const parsed = JSON.parse(stdout);
466
453
  expect(parsed).toHaveLength(4);
467
454
  const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
468
- expect(keys).toContain("integration:google");
469
- expect(keys).toContain("integration:google-calendar");
470
- expect(keys).toContain("integration:slack");
471
- expect(keys).toContain("integration:twitter");
455
+ expect(keys).toContain("google");
456
+ expect(keys).toContain("google-calendar");
457
+ expect(keys).toContain("slack");
458
+ expect(keys).toContain("twitter");
472
459
  });
473
460
 
474
461
  test("filters by single --provider-key value", async () => {
@@ -482,7 +469,7 @@ describe("assistant oauth providers list", () => {
482
469
  expect(exitCode).toBe(0);
483
470
  const parsed = JSON.parse(stdout);
484
471
  expect(parsed).toHaveLength(1);
485
- expect(parsed[0].providerKey).toBe("integration:slack");
472
+ expect(parsed[0].providerKey).toBe("slack");
486
473
  });
487
474
 
488
475
  test("filters by comma-separated OR values", async () => {
@@ -497,9 +484,9 @@ describe("assistant oauth providers list", () => {
497
484
  const parsed = JSON.parse(stdout);
498
485
  expect(parsed).toHaveLength(3);
499
486
  const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
500
- expect(keys).toContain("integration:google");
501
- expect(keys).toContain("integration:google-calendar");
502
- expect(keys).toContain("integration:slack");
487
+ expect(keys).toContain("google");
488
+ expect(keys).toContain("google-calendar");
489
+ expect(keys).toContain("slack");
503
490
  });
504
491
 
505
492
  test("returns empty array when comma-separated filter has no matches", async () => {
@@ -527,9 +514,9 @@ describe("assistant oauth providers list", () => {
527
514
  const parsed = JSON.parse(stdout);
528
515
  expect(parsed).toHaveLength(3);
529
516
  const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
530
- expect(keys).toContain("integration:google");
531
- expect(keys).toContain("integration:google-calendar");
532
- expect(keys).toContain("integration:slack");
517
+ expect(keys).toContain("google");
518
+ expect(keys).toContain("google-calendar");
519
+ expect(keys).toContain("slack");
533
520
  });
534
521
 
535
522
  test("ignores empty segments from extra commas in --provider-key", async () => {
@@ -544,9 +531,9 @@ describe("assistant oauth providers list", () => {
544
531
  const parsed = JSON.parse(stdout);
545
532
  expect(parsed).toHaveLength(3);
546
533
  const keys = parsed.map((p: { providerKey: string }) => p.providerKey);
547
- expect(keys).toContain("integration:google");
548
- expect(keys).toContain("integration:google-calendar");
549
- expect(keys).toContain("integration:slack");
534
+ expect(keys).toContain("google");
535
+ expect(keys).toContain("google-calendar");
536
+ expect(keys).toContain("slack");
550
537
  });
551
538
  });
552
539
 
@@ -560,7 +547,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
560
547
  mockUpsertAppCalls = [];
561
548
  mockUpsertAppResult = {
562
549
  id: "app-upsert-1",
563
- providerKey: "integration:google",
550
+ providerKey: "google",
564
551
  clientId: "abc123",
565
552
  createdAt: 1700000000000,
566
553
  updatedAt: 1700000000000,
@@ -573,18 +560,20 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
573
560
  mockGetAppByProviderAndClientId = () => undefined;
574
561
  mockGetMostRecentAppByProvider = () => undefined;
575
562
  mockGetProvider = () => undefined;
576
- mockGetProviderBehavior = () => undefined;
577
563
  mockGetSecureKey = () => undefined;
578
564
  mockGetCredentialMetadata = () => undefined;
579
565
  mockUpsertAppImpl = undefined;
580
566
  });
581
567
 
582
568
  test("upsert with --client-secret-credential-path passes path to upsertApp", async () => {
569
+ // "custom/path" has no colon and no credential/ or oauth_app/ prefix.
570
+ // resolveCredentialPath passes it through unchanged since it doesn't
571
+ // match the service:field shorthand pattern.
583
572
  const { exitCode, stdout } = await runCli([
584
573
  "apps",
585
574
  "upsert",
586
575
  "--provider",
587
- "integration:google",
576
+ "google",
588
577
  "--client-id",
589
578
  "abc123",
590
579
  "--client-secret-credential-path",
@@ -594,7 +583,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
594
583
  expect(exitCode).toBe(0);
595
584
  expect(mockUpsertAppCalls).toHaveLength(1);
596
585
  expect(mockUpsertAppCalls[0]).toEqual({
597
- provider: "integration:google",
586
+ provider: "google",
598
587
  clientId: "abc123",
599
588
  clientSecretOpts: { clientSecretCredentialPath: "custom/path" },
600
589
  });
@@ -607,7 +596,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
607
596
  "apps",
608
597
  "upsert",
609
598
  "--provider",
610
- "integration:google",
599
+ "google",
611
600
  "--client-id",
612
601
  "abc123",
613
602
  "--client-secret",
@@ -631,7 +620,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
631
620
  "apps",
632
621
  "upsert",
633
622
  "--provider",
634
- "integration:google",
623
+ "google",
635
624
  "--client-id",
636
625
  "abc123",
637
626
  "--client-secret",
@@ -641,7 +630,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
641
630
  expect(exitCode).toBe(0);
642
631
  expect(mockUpsertAppCalls).toHaveLength(1);
643
632
  expect(mockUpsertAppCalls[0]).toEqual({
644
- provider: "integration:google",
633
+ provider: "google",
645
634
  clientId: "abc123",
646
635
  clientSecretOpts: { clientSecretValue: "s3cret" },
647
636
  });
@@ -652,7 +641,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
652
641
  "apps",
653
642
  "upsert",
654
643
  "--provider",
655
- "integration:google",
644
+ "google",
656
645
  "--client-id",
657
646
  "abc123",
658
647
  "--json",
@@ -660,48 +649,57 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
660
649
  expect(exitCode).toBe(0);
661
650
  expect(mockUpsertAppCalls).toHaveLength(1);
662
651
  expect(mockUpsertAppCalls[0]).toEqual({
663
- provider: "integration:google",
652
+ provider: "google",
664
653
  clientId: "abc123",
665
654
  clientSecretOpts: undefined,
666
655
  });
667
656
  });
668
657
 
669
- test("upsert resolves non-prefixed credential path via metadata store", async () => {
670
- // The resolution logic splits on the LAST colon, so
671
- // "integration:google:client_secret" → service="integration:google", field="client_secret"
672
- mockGetCredentialMetadata = (service, field) =>
673
- service === "integration:google" && field === "client_secret"
674
- ? {
675
- credentialId: "cred-1",
676
- service: "integration:google",
677
- field: "client_secret",
678
- allowedTools: [],
679
- allowedDomains: [],
680
- createdAt: Date.now(),
681
- updatedAt: Date.now(),
682
- }
683
- : undefined;
684
-
658
+ test("upsert resolves service:field shorthand to full credential path", async () => {
659
+ // The service:field shorthand is resolved to credential/{service}/{field}
685
660
  const { exitCode, stdout } = await runCli([
686
661
  "apps",
687
662
  "upsert",
688
663
  "--provider",
689
- "integration:google",
664
+ "google",
690
665
  "--client-id",
691
666
  "abc",
692
667
  "--client-secret-credential-path",
693
- "integration:google:client_secret",
668
+ "google:client_secret",
694
669
  "--json",
695
670
  ]);
696
671
  expect(exitCode).toBe(0);
697
672
  expect(mockUpsertAppCalls).toHaveLength(1);
698
- // The non-prefixed path should have been resolved to the full credential key
699
673
  expect(mockUpsertAppCalls[0]).toEqual({
700
- provider: "integration:google",
674
+ provider: "google",
701
675
  clientId: "abc",
702
676
  clientSecretOpts: {
703
- clientSecretCredentialPath:
704
- "credential/integration:google/client_secret",
677
+ clientSecretCredentialPath: "credential/google/client_secret",
678
+ },
679
+ });
680
+ const parsed = JSON.parse(stdout);
681
+ expect(parsed.id).toBe("app-upsert-1");
682
+ });
683
+
684
+ test("upsert resolves slack:client_secret shorthand to full credential path", async () => {
685
+ const { exitCode, stdout } = await runCli([
686
+ "apps",
687
+ "upsert",
688
+ "--provider",
689
+ "slack",
690
+ "--client-id",
691
+ "slack-abc",
692
+ "--client-secret-credential-path",
693
+ "slack:client_secret",
694
+ "--json",
695
+ ]);
696
+ expect(exitCode).toBe(0);
697
+ expect(mockUpsertAppCalls).toHaveLength(1);
698
+ expect(mockUpsertAppCalls[0]).toEqual({
699
+ provider: "slack",
700
+ clientId: "slack-abc",
701
+ clientSecretOpts: {
702
+ clientSecretCredentialPath: "credential/slack/client_secret",
705
703
  },
706
704
  });
707
705
  const parsed = JSON.parse(stdout);
@@ -713,22 +711,47 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
713
711
  "apps",
714
712
  "upsert",
715
713
  "--provider",
716
- "integration:google",
714
+ "google",
717
715
  "--client-id",
718
716
  "abc",
719
717
  "--client-secret-credential-path",
720
- "credential/integration:google/client_secret",
718
+ "credential/google/client_secret",
721
719
  "--json",
722
720
  ]);
723
721
  expect(exitCode).toBe(0);
724
722
  expect(mockUpsertAppCalls).toHaveLength(1);
725
723
  // Already-prefixed path should be passed through as-is
726
724
  expect(mockUpsertAppCalls[0]).toEqual({
727
- provider: "integration:google",
725
+ provider: "google",
726
+ clientId: "abc",
727
+ clientSecretOpts: {
728
+ clientSecretCredentialPath: "credential/google/client_secret",
729
+ },
730
+ });
731
+ const parsed = JSON.parse(stdout);
732
+ expect(parsed.id).toBe("app-upsert-1");
733
+ });
734
+
735
+ test("upsert passes oauth_app/ prefixed credential path through unchanged", async () => {
736
+ const { exitCode, stdout } = await runCli([
737
+ "apps",
738
+ "upsert",
739
+ "--provider",
740
+ "google",
741
+ "--client-id",
742
+ "abc",
743
+ "--client-secret-credential-path",
744
+ "oauth_app/some-id/client_secret",
745
+ "--json",
746
+ ]);
747
+ expect(exitCode).toBe(0);
748
+ expect(mockUpsertAppCalls).toHaveLength(1);
749
+ // oauth_app/ prefixed path should be passed through as-is
750
+ expect(mockUpsertAppCalls[0]).toEqual({
751
+ provider: "google",
728
752
  clientId: "abc",
729
753
  clientSecretOpts: {
730
- clientSecretCredentialPath:
731
- "credential/integration:google/client_secret",
754
+ clientSecretCredentialPath: "oauth_app/some-id/client_secret",
732
755
  },
733
756
  });
734
757
  const parsed = JSON.parse(stdout);
@@ -747,7 +770,7 @@ describe("assistant oauth apps upsert --client-secret-credential-path", () => {
747
770
  "apps",
748
771
  "upsert",
749
772
  "--provider",
750
- "integration:google",
773
+ "google",
751
774
  "--client-id",
752
775
  "abc",
753
776
  "--client-secret-credential-path",
@@ -776,7 +799,7 @@ describe("assistant oauth ping <provider-key>", () => {
776
799
 
777
800
  test("returns ok when ping endpoint returns 200", async () => {
778
801
  mockGetProvider = () => ({
779
- providerKey: "integration:google",
802
+ providerKey: "google",
780
803
  pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
781
804
  authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
782
805
  tokenUrl: "https://oauth2.googleapis.com/token",
@@ -788,16 +811,12 @@ describe("assistant oauth ping <provider-key>", () => {
788
811
  });
789
812
  mockResolveOAuthConnection = async () => ({
790
813
  id: "conn-1",
791
- providerKey: "integration:google",
814
+ providerKey: "google",
792
815
  accountInfo: null,
793
816
  request: async () => ({ status: 200, headers: {}, body: {} }),
794
817
  withToken: async (fn) => fn("mock-access-token-xyz"),
795
818
  });
796
- const { exitCode, stdout } = await runCli([
797
- "ping",
798
- "integration:google",
799
- "--json",
800
- ]);
819
+ const { exitCode, stdout } = await runCli(["ping", "google", "--json"]);
801
820
  expect(exitCode).toBe(0);
802
821
  const parsed = JSON.parse(stdout);
803
822
  expect(parsed.ok).toBe(true);
@@ -806,11 +825,7 @@ describe("assistant oauth ping <provider-key>", () => {
806
825
 
807
826
  test("exits 1 when provider not found", async () => {
808
827
  mockGetProvider = () => undefined;
809
- const { exitCode, stdout } = await runCli([
810
- "ping",
811
- "integration:unknown",
812
- "--json",
813
- ]);
828
+ const { exitCode, stdout } = await runCli(["ping", "unknown", "--json"]);
814
829
  expect(exitCode).toBe(1);
815
830
  const parsed = JSON.parse(stdout);
816
831
  expect(parsed.ok).toBe(false);
@@ -838,7 +853,7 @@ describe("assistant oauth ping <provider-key>", () => {
838
853
 
839
854
  test("exits 1 when ping endpoint returns non-2xx", async () => {
840
855
  mockGetProvider = () => ({
841
- providerKey: "integration:google",
856
+ providerKey: "google",
842
857
  pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
843
858
  authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
844
859
  tokenUrl: "https://oauth2.googleapis.com/token",
@@ -850,16 +865,12 @@ describe("assistant oauth ping <provider-key>", () => {
850
865
  });
851
866
  mockResolveOAuthConnection = async () => ({
852
867
  id: "conn-1",
853
- providerKey: "integration:google",
868
+ providerKey: "google",
854
869
  accountInfo: null,
855
870
  request: async () => ({ status: 403, headers: {}, body: "Forbidden" }),
856
871
  withToken: async (fn) => fn("mock-access-token-xyz"),
857
872
  });
858
- const { exitCode, stdout } = await runCli([
859
- "ping",
860
- "integration:google",
861
- "--json",
862
- ]);
873
+ const { exitCode, stdout } = await runCli(["ping", "google", "--json"]);
863
874
  expect(exitCode).toBe(1);
864
875
  const parsed = JSON.parse(stdout);
865
876
  expect(parsed.ok).toBe(false);
@@ -868,7 +879,7 @@ describe("assistant oauth ping <provider-key>", () => {
868
879
 
869
880
  test("exits 1 when no connection can be resolved", async () => {
870
881
  mockGetProvider = () => ({
871
- providerKey: "integration:google",
882
+ providerKey: "google",
872
883
  pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
873
884
  authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
874
885
  tokenUrl: "https://oauth2.googleapis.com/token",
@@ -879,13 +890,9 @@ describe("assistant oauth ping <provider-key>", () => {
879
890
  updatedAt: Date.now(),
880
891
  });
881
892
  mockResolveOAuthConnection = async () => {
882
- throw new Error('No access token found for "integration:google".');
893
+ throw new Error('No access token found for "google".');
883
894
  };
884
- const { exitCode, stdout } = await runCli([
885
- "ping",
886
- "integration:google",
887
- "--json",
888
- ]);
895
+ const { exitCode, stdout } = await runCli(["ping", "google", "--json"]);
889
896
  expect(exitCode).toBe(1);
890
897
  const parsed = JSON.parse(stdout);
891
898
  expect(parsed.ok).toBe(false);
@@ -1,23 +1,58 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import {
4
- getProviderBehavior,
5
- resolveService,
6
- } from "../oauth/provider-behaviors.js";
7
-
8
- describe("oauth provider behaviors", () => {
9
- test("gmail behavior defines bearer injection templates for Google API hosts", () => {
10
- const service = resolveService("gmail");
11
- const behavior = getProviderBehavior(service);
12
-
13
- expect(service).toBe("integration:google");
14
- expect(behavior).toBeDefined();
15
- expect(behavior?.injectionTemplates).toBeDefined();
16
- expect(behavior?.injectionTemplates).toHaveLength(3);
17
-
18
- const byHost = new Map(
19
- (behavior?.injectionTemplates ?? []).map((t) => [t.hostPattern, t]),
20
- );
1
+ import { mkdtempSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, mock, test } from "bun:test";
5
+
6
+ const testDir = mkdtempSync(join(tmpdir(), "oauth-provider-profiles-test-"));
7
+
8
+ mock.module("../util/platform.js", () => ({
9
+ getDataDir: () => testDir,
10
+ isMacOS: () => process.platform === "darwin",
11
+ isLinux: () => process.platform === "linux",
12
+ isWindows: () => process.platform === "win32",
13
+ getPidPath: () => join(testDir, "test.pid"),
14
+ getDbPath: () => ":memory:",
15
+ getLogPath: () => join(testDir, "test.log"),
16
+ ensureDataDir: () => {},
17
+ }));
18
+
19
+ mock.module("../util/logger.js", () => ({
20
+ getLogger: () =>
21
+ new Proxy({} as Record<string, unknown>, {
22
+ get: () => () => {},
23
+ }),
24
+ }));
25
+
26
+ mock.module("../security/secure-keys.js", () => ({
27
+ deleteSecureKeyAsync: async () => "deleted" as const,
28
+ setSecureKeyAsync: async () => true,
29
+ getSecureKeyAsync: async () => undefined,
30
+ }));
31
+
32
+ import { initializeDb } from "../memory/db.js";
33
+ import { getProvider } from "../oauth/oauth-store.js";
34
+ import { seedOAuthProviders } from "../oauth/seed-providers.js";
35
+
36
+ initializeDb();
37
+ seedOAuthProviders();
38
+
39
+ describe("oauth provider profiles (DB-seeded)", () => {
40
+ test("google provider row contains bearer injection templates for 3 Google API hosts", () => {
41
+ const provider = getProvider("google");
42
+
43
+ expect(provider).toBeDefined();
44
+ expect(provider?.injectionTemplates).toBeDefined();
45
+
46
+ const templates = JSON.parse(provider!.injectionTemplates!) as Array<{
47
+ hostPattern: string;
48
+ injectionType: string;
49
+ headerName: string;
50
+ valuePrefix: string;
51
+ }>;
52
+
53
+ expect(templates).toHaveLength(3);
54
+
55
+ const byHost = new Map(templates.map((t) => [t.hostPattern, t]));
21
56
 
22
57
  for (const host of [
23
58
  "gmail.googleapis.com",
@@ -12,7 +12,7 @@ function makeProfile(
12
12
  overrides: Partial<ScopeResolverInput> = {},
13
13
  ): ScopeResolverInput {
14
14
  return {
15
- service: "integration:test-service",
15
+ service: "test-service",
16
16
  defaultScopes: ["read", "write"],
17
17
  scopePolicy: {
18
18
  allowAdditionalScopes: false,
@@ -57,9 +57,7 @@ describe("resolveScopes", () => {
57
57
  const result = resolveScopes(profile, ["delete"]);
58
58
  expect(result.ok).toBe(false);
59
59
  if (!result.ok) {
60
- expect(result.error).toBe(
61
- "Scope 'delete' is forbidden for integration:test-service",
62
- );
60
+ expect(result.error).toBe("Scope 'delete' is forbidden for test-service");
63
61
  }
64
62
  });
65
63
 
@@ -76,7 +74,7 @@ describe("resolveScopes", () => {
76
74
  expect(result.ok).toBe(false);
77
75
  if (!result.ok) {
78
76
  expect(result.error).toContain(
79
- "Additional scopes are not allowed for integration:test-service",
77
+ "Additional scopes are not allowed for test-service",
80
78
  );
81
79
  expect(result.allowedScopes).toEqual(["read", "write"]);
82
80
  }
@@ -95,7 +93,7 @@ describe("resolveScopes", () => {
95
93
  expect(result.ok).toBe(false);
96
94
  if (!result.ok) {
97
95
  expect(result.error).toBe(
98
- "Scope 'admin' is not in the allowed optional scopes for integration:test-service",
96
+ "Scope 'admin' is not in the allowed optional scopes for test-service",
99
97
  );
100
98
  expect(result.allowedScopes).toEqual(["read", "write"]);
101
99
  }