@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
package/Dockerfile CHANGED
@@ -38,22 +38,55 @@ WORKDIR /app/assistant
38
38
 
39
39
  # Install runtime dependencies for Playwright and tree-sitter
40
40
  RUN apt-get update && apt-get install -y \
41
- curl \
42
- unzip \
41
+ bubblewrap \
43
42
  ca-certificates \
44
- python3 \
45
- libnss3 \
46
- libfreetype6 \
47
- libharfbuzz0b \
43
+ curl \
44
+ ffmpeg \
48
45
  fonts-freefont-ttf \
49
- make \
50
46
  g++ \
51
47
  git \
52
- sudo \
53
48
  htop \
54
- procps \
55
49
  jq \
50
+ libasound2 \
51
+ libatk-bridge2.0-0 \
52
+ libatk1.0-0 \
53
+ libatspi2.0-0 \
54
+ libcairo2 \
55
+ libcups2 \
56
+ libdrm2 \
57
+ libfreetype6 \
58
+ libgbm1 \
59
+ libharfbuzz0b \
60
+ libnspr4 \
61
+ libnss3 \
62
+ libpango-1.0-0 \
63
+ libpangocairo-1.0-0 \
64
+ libx11-6 \
65
+ libx11-xcb1 \
66
+ libxcb-dri3-0 \
67
+ libxcb1 \
68
+ libxcomposite1 \
69
+ libxdamage1 \
70
+ libxext6 \
71
+ libxfixes3 \
72
+ libxi6 \
73
+ libxkbcommon0 \
74
+ libxrandr2 \
75
+ libxrender1 \
76
+ libxshmfence1 \
77
+ libxtst6 \
78
+ lsof \
79
+ make \
80
+ openssl \
81
+ procps \
82
+ python3 \
83
+ sqlite3 \
84
+ sudo \
85
+ unzip \
56
86
  uuid-runtime \
87
+ vim \
88
+ xclip \
89
+ xdg-utils \
57
90
  && rm -rf /var/lib/apt/lists/*
58
91
 
59
92
  # Copy bun binary from builder instead of re-installing
@@ -144,7 +144,7 @@ sequenceDiagram
144
144
 
145
145
  Note over UI,API: Tool Execution Flow
146
146
  Tool->>TokenMgr: withValidToken("gmail", callback)
147
- TokenMgr->>Store: getConnectionByProvider("integration:google")
147
+ TokenMgr->>Store: getConnectionByProvider("google")
148
148
  TokenMgr->>Vault: getSecureKeyAsync("oauth_connection/{conn.id}/access_token")
149
149
  TokenMgr->>Store: check oauth_connections.expires_at
150
150
  alt Token expired
@@ -196,32 +196,33 @@ sequenceDiagram
196
196
  | `assistant/src/watcher/providers/gmail.ts` | Gmail watcher using History API |
197
197
  | `assistant/src/watcher/providers/github.ts` | GitHub watcher for PRs, issues, review requests, and mentions |
198
198
  | `assistant/src/watcher/providers/linear.ts` | Linear watcher for assigned issues, status changes, and @mentions |
199
- | `assistant/src/oauth/provider-behaviors.ts` | Provider behavior registry: identity verifiers, setup metadata, injection templates |
199
+ | `assistant/src/oauth/seed-providers.ts` | Provider seed data: injection templates, identity config, setup metadata (seeded to DB on startup) |
200
200
  | `assistant/src/oauth/connect-orchestrator.ts` | Shared OAuth connect orchestrator: profile resolution, scope policy, flow execution, token storage |
201
201
  | `assistant/src/oauth/scope-policy.ts` | Deterministic scope resolution and policy enforcement |
202
- | `assistant/src/oauth/connect-types.ts` | Shared types: `OAuthProviderBehavior`, `OAuthScopePolicy`, `OAuthConnectResult` |
202
+ | `assistant/src/oauth/connect-types.ts` | Shared types: `OAuthScopePolicy`, `OAuthConnectResult` |
203
203
  | `assistant/src/oauth/token-persistence.ts` | Token storage helper: persists tokens, metadata, and runs post-connect hooks |
204
204
  | `assistant/src/daemon/handlers/oauth-connect.ts` | Generic OAuth connect handler (`oauth_connect_start` / `oauth_connect_result`) |
205
205
 
206
206
  ---
207
207
 
208
- ## OAuth Extensibility — Provider Behaviors, Scope Policy, and Connect Orchestrator
208
+ ## OAuth Extensibility — DB-Driven Provider Config, Scope Policy, and Connect Orchestrator
209
209
 
210
- The OAuth extensibility layer makes adding a new OAuth provider a declarative operation. Protocol fields (auth URLs, token URLs, scopes, scope policy) are stored in the `oauth_providers` database table, while behavioral fields (identity verifiers, setup metadata, injection templates) live in the **provider behavior registry**. The shared **connect orchestrator** handles the full flow from provider resolution through token storage.
210
+ The OAuth extensibility layer makes adding a new OAuth provider a fully declarative operation. All provider configuration — protocol fields (auth URLs, token URLs, scopes, scope policy), behavioral fields (identity verification, injection templates, setup metadata), and display metadata — is stored in the `oauth_providers` SQLite table and seeded on startup via `seed-providers.ts`. The shared **connect orchestrator** handles the full flow from provider resolution through token storage.
211
211
 
212
- ### Provider Behavior Registry
212
+ ### Provider Configuration (DB-Driven)
213
213
 
214
- `assistant/src/oauth/provider-behaviors.ts` contains the `PROVIDER_BEHAVIORS` map a registry of behavioral aspects for well-known OAuth providers. Each behavior (`OAuthProviderBehavior`) declares:
214
+ All provider config lives in the `oauth_providers` table. Built-in providers are seeded on every startup by `assistant/src/oauth/seed-providers.ts`, which upserts rows from the `PROVIDER_SEED_DATA` constant. Custom providers can be registered via the CLI (`assistant oauth providers register`).
215
215
 
216
- | Field | Purpose |
217
- | -------------------- | ------------------------------------------------------------------------------------------------------ |
218
- | `identityVerifier` | Async function that fetches human-readable account info (e.g. `@username`, email) after token exchange |
219
- | `setup` | Optional metadata for the generic OAuth setup skill (display name, dashboard URL, app type) |
220
- | `injectionTemplates` | Auto-applied credential injection rules for the script proxy |
216
+ Each provider row includes:
221
217
 
222
- Protocol fields (`authUrl`, `tokenUrl`, `defaultScopes`, `scopePolicy`, `callbackTransport`) are stored in the `oauth_providers` database table rather than in code.
223
-
224
- Registered providers: `integration:google`, `integration:slack`, `integration:notion`. Short aliases (e.g. `gmail`, `slack`) are resolved via `resolveService()`.
218
+ | Column group | Fields | Purpose |
219
+ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
220
+ | **Protocol** | `authUrl`, `tokenUrl`, `tokenEndpointAuthMethod`, `callbackTransport`, `extraParams`, `loopbackPort` | OAuth2 flow parameters |
221
+ | **Scopes** | `defaultScopes`, `scopePolicy` | Deterministic scope resolution (user-customizable, preserved across seed restarts) |
222
+ | **Identity verification** | `identityUrl`, `identityMethod`, `identityHeaders`, `identityBody`, `identityResponsePaths`, `identityFormat`, `identityOkField` | Data-driven identity verifier fetches human-readable account info after token exchange |
223
+ | **Injection templates** | `injectionTemplates` | Auto-applied credential injection rules for the script proxy |
224
+ | **Setup metadata** | `displayName`, `description`, `dashboardUrl`, `appType`, `setupNotes`, `requiresClientSecret` | Metadata for the generic OAuth setup skill |
225
+ | **Ping config** | `pingUrl`, `pingMethod`, `pingHeaders`, `pingBody` | Health-check endpoint for `assistant oauth ping` |
225
226
 
226
227
  ### Scope Policy Engine
227
228
 
@@ -240,12 +241,12 @@ Returns `{ ok: true, scopes }` or `{ ok: false, error, allowedScopes }`.
240
241
 
241
242
  `assistant/src/oauth/connect-orchestrator.ts` exports `orchestrateOAuthConnect(options)`, which runs the full OAuth2 flow:
242
243
 
243
- 1. **Resolve service** — alias expansion via `resolveService()`.
244
- 2. **Load behavior** — `getProviderBehavior()` from the registry; load protocol fields from the `oauth_providers` DB table.
244
+ 1. **Receive canonical provider name** — the orchestrator receives the canonical provider name directly (e.g. `google`, `slack`).
245
+ 2. **Load provider row** — reads all config from the `oauth_providers` DB table.
245
246
  3. **Compute scopes** — `resolveScopes()` with scope policy enforcement.
246
247
  4. **Build OAuth config** — assemble protocol-level config from the DB provider row.
247
248
  5. **Run flow** — interactive (opens browser, blocks until completion) or deferred (returns auth URL for the caller to deliver).
248
- 6. **Verify identity** — runs the profile's `identityVerifier` if defined.
249
+ 6. **Verify identity** — runs the generic data-driven identity verifier using the provider row's identity columns.
249
250
  7. **Store tokens** — `storeOAuth2Tokens()` persists access/refresh tokens, client credentials, and metadata.
250
251
 
251
252
  Result is a discriminated union: `{ success, deferred, grantedScopes, accountInfo }` or `{ success: false, error }`.
@@ -263,24 +264,25 @@ This replaces provider-specific handlers — any provider in the registry can be
263
264
 
264
265
  ### Adding a New OAuth Provider
265
266
 
266
- 1. **Register protocol fields** in the `oauth_providers` database table (via CLI or migration):
267
- - Set `authUrl`, `tokenUrl`, `defaultScopes`, `scopePolicy`, and `callbackTransport`.
268
- 2. **Optional: declare behavioral fields** in `PROVIDER_BEHAVIORS` (`oauth/provider-behaviors.ts`):
269
- - Add an `identityVerifier` an async function that fetches the user's account info from the provider's API.
270
- - Add `setup` metadata `displayName`, `dashboardUrl`, `appType` enable the generic OAuth setup skill to guide users through app creation.
271
- - Add `injectionTemplates` for providers whose tokens should be auto-injected by the script proxy.
267
+ 1. **Add seed data** to `PROVIDER_SEED_DATA` in `assistant/src/oauth/seed-providers.ts`:
268
+ - Set protocol fields: `authUrl`, `tokenUrl`, `defaultScopes`, `scopePolicy`, `callbackTransport`.
269
+ - Set identity verification: `identityUrl`, `identityMethod`, `identityHeaders`, `identityResponsePaths`, `identityFormat`.
270
+ - Set injection templates: `injectionTemplates` for providers whose tokens should be auto-injected by the script proxy.
271
+ - Set setup metadata: `displayName`, `dashboardUrl`, `appType` enable the generic OAuth setup skill to guide users through app creation.
272
+ 2. **Alternatively, register dynamically** via the CLI: `assistant oauth providers register <key> --auth-url ... --token-url ...`.
272
273
  3. **No handler code needed** — the generic `oauth_connect_start` handler and the connect orchestrator handle the flow automatically.
273
274
 
274
275
  ### Key Source Files
275
276
 
276
- | File | Role |
277
- | ------------------------------------------------ | -------------------------------------------------------------------------------- |
278
- | `assistant/src/oauth/provider-behaviors.ts` | Provider behavior registry and alias resolution |
279
- | `assistant/src/oauth/scope-policy.ts` | Scope resolution and policy enforcement (pure, no I/O) |
280
- | `assistant/src/oauth/connect-orchestrator.ts` | Shared connect orchestrator (profile → scopes → flow → tokens) |
281
- | `assistant/src/oauth/connect-types.ts` | Shared types (`OAuthProviderBehavior`, `OAuthScopePolicy`, `OAuthConnectResult`) |
282
- | `assistant/src/oauth/token-persistence.ts` | Token storage: credential store writes, metadata upsert, post-connect hooks |
283
- | `assistant/src/daemon/handlers/oauth-connect.ts` | Generic `oauth_connect_start` / `oauth_connect_result` handler |
277
+ | File | Role |
278
+ | ------------------------------------------------ | --------------------------------------------------------------------------- |
279
+ | `assistant/src/oauth/seed-providers.ts` | Provider seed data and startup seeding |
280
+ | `assistant/src/oauth/scope-policy.ts` | Scope resolution and policy enforcement (pure, no I/O) |
281
+ | `assistant/src/oauth/connect-orchestrator.ts` | Shared connect orchestrator (profile → scopes → flow → tokens) |
282
+ | `assistant/src/oauth/connect-types.ts` | Shared types (`OAuthScopePolicy`, `OAuthConnectResult`) |
283
+ | `assistant/src/oauth/token-persistence.ts` | Token storage: credential store writes, metadata upsert, post-connect hooks |
284
+ | `assistant/src/oauth/identity-verifier.ts` | Generic data-driven identity verifier (reads config from provider DB row) |
285
+ | `assistant/src/daemon/handlers/oauth-connect.ts` | Generic `oauth_connect_start` / `oauth_connect_result` handler |
284
286
 
285
287
  ---
286
288
 
@@ -71,19 +71,19 @@ describe("handles", () => {
71
71
 
72
72
  describe("localOAuthHandle", () => {
73
73
  test("constructs the expected format", () => {
74
- expect(localOAuthHandle("integration:google", "conn-123")).toBe(
75
- "local_oauth:integration:google/conn-123",
74
+ expect(localOAuthHandle("google", "conn-123")).toBe(
75
+ "local_oauth:google/conn-123",
76
76
  );
77
77
  });
78
78
 
79
- test("roundtrips with integration: prefix in providerKey", () => {
80
- const raw = localOAuthHandle("integration:slack", "conn-abc");
79
+ test("roundtrips through parseHandle", () => {
80
+ const raw = localOAuthHandle("slack", "conn-abc");
81
81
  const result = parseHandle(raw);
82
82
  expect(result.ok).toBe(true);
83
83
  if (!result.ok) return;
84
84
  expect(result.handle.type).toBe(HandleType.LocalOAuth);
85
85
  if (result.handle.type !== HandleType.LocalOAuth) return;
86
- expect(result.handle.providerKey).toBe("integration:slack");
86
+ expect(result.handle.providerKey).toBe("slack");
87
87
  expect(result.handle.connectionId).toBe("conn-abc");
88
88
  });
89
89
  });
@@ -133,7 +133,7 @@ describe("handles", () => {
133
133
  });
134
134
 
135
135
  test("rejects local_oauth with no slash", () => {
136
- const result = parseHandle("local_oauth:integration:google");
136
+ const result = parseHandle("local_oauth:google");
137
137
  expect(result.ok).toBe(false);
138
138
  });
139
139
 
@@ -150,7 +150,7 @@ describe("handles", () => {
150
150
  ).not.toThrow();
151
151
  expect(() =>
152
152
  CredentialHandleSchema.parse(
153
- "local_oauth:integration:google/conn-1",
153
+ "local_oauth:google/conn-1",
154
154
  ),
155
155
  ).not.toThrow();
156
156
  expect(() =>
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * 2. **Local OAuth** — references a locally persisted OAuth connection.
15
15
  * Format: `local_oauth:<providerKey>/<connectionId>` where providerKey
16
- * uses the existing `integration:*` keys (e.g. `integration:google`).
16
+ * is the bare provider name (e.g. `google`).
17
17
  *
18
18
  * 3. **Managed OAuth** — references an OAuth connection managed by the
19
19
  * platform. Format: `platform_oauth:<connectionId>` where connectionId
@@ -50,7 +50,7 @@ export interface LocalStaticHandle {
50
50
 
51
51
  export interface LocalOAuthHandle {
52
52
  type: typeof HandleType.LocalOAuth;
53
- /** Provider key (e.g. "integration:google", "integration:slack"). */
53
+ /** Provider key (e.g. "google", "slack"). */
54
54
  providerKey: string;
55
55
  /** Connection identifier. */
56
56
  connectionId: string;
@@ -146,8 +146,9 @@ export function parseHandle(raw: string): ParseHandleResult {
146
146
  }
147
147
 
148
148
  case HandleType.LocalOAuth: {
149
- // providerKey may itself contain a colon (e.g. "integration:google"),
150
- // so we split on the *last* "/" to separate providerKey from connectionId.
149
+ // providerKey is typically a bare name (e.g. "google"), but legacy handles
150
+ // may contain a colon (e.g. "integration:google"), so we split on the
151
+ // *last* "/" to separate providerKey from connectionId.
151
152
  const lastSlashIdx = rest.lastIndexOf("/");
152
153
  if (
153
154
  lastSlashIdx === -1 ||
@@ -33,6 +33,13 @@ export const HandshakeRequestSchema = z.object({
33
33
  * can use it for platform credential materialisation.
34
34
  */
35
35
  assistantApiKey: z.string().optional(),
36
+ /**
37
+ * Optional platform assistant ID passed from the assistant runtime.
38
+ * In managed (sidecar) mode with warm pools, the PLATFORM_ASSISTANT_ID
39
+ * env var may not be set at CES startup. The assistant forwards it here
40
+ * so CES can use it for platform credential materialisation.
41
+ */
42
+ assistantId: z.string().optional(),
36
43
  });
37
44
  export type HandshakeRequest = z.infer<typeof HandshakeRequestSchema>;
38
45
 
@@ -422,6 +422,11 @@ export type ListAuditRecordsResponse = z.infer<
422
422
  export const UpdateManagedCredentialSchema = z.object({
423
423
  /** The assistant API key to push to CES for platform credential materialization. */
424
424
  assistantApiKey: z.string(), // nosemgrep: not-a-secret
425
+ /**
426
+ * Optional platform assistant ID. In warm-pool mode the ID may not be
427
+ * available at CES startup; the assistant pushes it here once provisioned.
428
+ */
429
+ assistantId: z.string().optional(),
425
430
  });
426
431
  export type UpdateManagedCredential = z.infer<
427
432
  typeof UpdateManagedCredentialSchema
@@ -98,7 +98,7 @@ export interface SecureKeyBackend {
98
98
  export interface OAuthConnectionRecord {
99
99
  /** Unique identifier for this connection. */
100
100
  id: string;
101
- /** Provider key (e.g. "integration:google", "integration:slack"). */
101
+ /** Provider key (e.g. "google", "slack"). */
102
102
  providerKey: string;
103
103
  /** Account identifier (e.g. email address). */
104
104
  accountInfo: string | null;
package/openapi.yaml CHANGED
@@ -3,7 +3,7 @@
3
3
  openapi: 3.0.0
4
4
  info:
5
5
  title: Vellum Assistant API
6
- version: 0.5.10
6
+ version: 0.5.12
7
7
  description: Auto-generated OpenAPI specification for the Vellum Assistant runtime HTTP server.
8
8
  servers:
9
9
  - url: http://127.0.0.1:7821
@@ -3197,11 +3197,14 @@ paths:
3197
3197
  requestId:
3198
3198
  type: string
3199
3199
  reason:
3200
+ description: Decline reason (present only when applied is false)
3201
+ type: string
3202
+ replyText:
3203
+ description: Resolver reply text for the guardian (e.g. verification code)
3200
3204
  type: string
3201
3205
  required:
3202
3206
  - applied
3203
3207
  - requestId
3204
- - reason
3205
3208
  additionalProperties: false
3206
3209
  requestBody:
3207
3210
  required: true
@@ -4210,13 +4213,17 @@ paths:
4210
4213
  type: array
4211
4214
  items: {}
4212
4215
  description: Array of message objects
4213
- interfaceFiles:
4214
- type: array
4215
- items: {}
4216
- description: Interface file paths with modification timestamps
4216
+ hasMore:
4217
+ description: Whether older messages exist beyond this page
4218
+ type: boolean
4219
+ oldestTimestamp:
4220
+ description: Timestamp of the oldest message in this page (ms since epoch)
4221
+ type: number
4222
+ oldestMessageId:
4223
+ description: ID of the oldest message in this page
4224
+ type: string
4217
4225
  required:
4218
4226
  - messages
4219
- - interfaceFiles
4220
4227
  additionalProperties: false
4221
4228
  post:
4222
4229
  operationId: messages_post
@@ -5526,7 +5533,7 @@ paths:
5526
5533
  get:
5527
5534
  operationId: skills_get
5528
5535
  summary: List all skills
5529
- description: Return all installed skills.
5536
+ description: Return all installed skills. Pass ?include=catalog to also include available catalog skills.
5530
5537
  tags:
5531
5538
  - skills
5532
5539
  responses:
@@ -5539,11 +5546,82 @@ paths:
5539
5546
  properties:
5540
5547
  skills:
5541
5548
  type: array
5542
- items: {}
5549
+ items:
5550
+ type: object
5551
+ properties:
5552
+ id:
5553
+ type: string
5554
+ name:
5555
+ type: string
5556
+ description:
5557
+ type: string
5558
+ emoji:
5559
+ type: string
5560
+ homepage:
5561
+ type: string
5562
+ source:
5563
+ type: string
5564
+ enum:
5565
+ - bundled
5566
+ - managed
5567
+ - workspace
5568
+ - clawhub
5569
+ - extra
5570
+ - catalog
5571
+ state:
5572
+ type: string
5573
+ enum:
5574
+ - enabled
5575
+ - disabled
5576
+ installStatus:
5577
+ type: string
5578
+ enum:
5579
+ - bundled
5580
+ - installed
5581
+ - available
5582
+ updateAvailable:
5583
+ type: boolean
5584
+ provenance:
5585
+ type: object
5586
+ properties:
5587
+ kind:
5588
+ type: string
5589
+ enum:
5590
+ - first-party
5591
+ - third-party
5592
+ - local
5593
+ provider:
5594
+ type: string
5595
+ originId:
5596
+ type: string
5597
+ sourceUrl:
5598
+ type: string
5599
+ required:
5600
+ - kind
5601
+ additionalProperties: false
5602
+ required:
5603
+ - id
5604
+ - name
5605
+ - description
5606
+ - source
5607
+ - state
5608
+ - installStatus
5609
+ - updateAvailable
5610
+ - provenance
5611
+ additionalProperties: false
5543
5612
  description: Skill objects
5544
5613
  required:
5545
5614
  - skills
5546
5615
  additionalProperties: false
5616
+ parameters:
5617
+ - name: include
5618
+ in: query
5619
+ required: false
5620
+ schema:
5621
+ type: string
5622
+ enum:
5623
+ - catalog
5624
+ description: Optional inclusion flag. Use 'catalog' to merge available Vellum catalog skills into the response.
5547
5625
  post:
5548
5626
  operationId: skills_post
5549
5627
  summary: Create skill
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Unit tests for the catalog cache (catalog-cache.ts).
3
+ *
4
+ * Validates TTL-based caching, re-fetch after expiry, stale-cache fallback
5
+ * on fetch failure, and explicit cache invalidation.
6
+ */
7
+
8
+ import { afterEach, describe, expect, mock, test } from "bun:test";
9
+
10
+ import type { CatalogSkill } from "../skills/catalog-install.js";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Mocks — must be defined before importing the module under test
14
+ // ---------------------------------------------------------------------------
15
+
16
+ // Suppress logger output
17
+ mock.module("../util/logger.js", () => ({
18
+ getLogger: () =>
19
+ new Proxy({} as Record<string, unknown>, {
20
+ get: () => () => {},
21
+ }),
22
+ }));
23
+
24
+ let mockRepoSkillsDir: string | undefined = undefined;
25
+ let mockFetchCatalogResult: CatalogSkill[] = [];
26
+ let mockFetchCatalogError: Error | null = null;
27
+ let fetchCatalogCallCount = 0;
28
+ let readLocalCatalogCallCount = 0;
29
+
30
+ mock.module("../skills/catalog-install.js", () => ({
31
+ getRepoSkillsDir: () => mockRepoSkillsDir,
32
+ readLocalCatalog: (_dir: string) => {
33
+ readLocalCatalogCallCount++;
34
+ return mockFetchCatalogResult;
35
+ },
36
+ fetchCatalog: async () => {
37
+ fetchCatalogCallCount++;
38
+ if (mockFetchCatalogError) {
39
+ throw mockFetchCatalogError;
40
+ }
41
+ return mockFetchCatalogResult;
42
+ },
43
+ }));
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Imports (after mocks)
47
+ // ---------------------------------------------------------------------------
48
+
49
+ import { getCatalog, invalidateCatalogCache } from "../skills/catalog-cache.js";
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Helpers
53
+ // ---------------------------------------------------------------------------
54
+
55
+ const sampleCatalog: CatalogSkill[] = [
56
+ { id: "web-search", name: "Web Search", description: "Search the web" },
57
+ { id: "browser", name: "Browser", description: "Browse the web" },
58
+ ];
59
+
60
+ const updatedCatalog: CatalogSkill[] = [
61
+ { id: "web-search", name: "Web Search v2", description: "Updated search" },
62
+ ];
63
+
64
+ function resetState(): void {
65
+ invalidateCatalogCache();
66
+ mockRepoSkillsDir = undefined;
67
+ mockFetchCatalogResult = [];
68
+ mockFetchCatalogError = null;
69
+ fetchCatalogCallCount = 0;
70
+ readLocalCatalogCallCount = 0;
71
+ }
72
+
73
+ afterEach(resetState);
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Tests
77
+ // ---------------------------------------------------------------------------
78
+
79
+ describe("getCatalog", () => {
80
+ test("returns cached value within TTL without re-fetching", async () => {
81
+ mockFetchCatalogResult = sampleCatalog;
82
+
83
+ const first = await getCatalog();
84
+ expect(first).toEqual(sampleCatalog);
85
+ expect(fetchCatalogCallCount).toBe(1);
86
+
87
+ // Second call should use cache
88
+ const second = await getCatalog();
89
+ expect(second).toEqual(sampleCatalog);
90
+ expect(fetchCatalogCallCount).toBe(1); // no additional fetch
91
+ });
92
+
93
+ test("re-fetches after TTL expires", async () => {
94
+ mockFetchCatalogResult = sampleCatalog;
95
+
96
+ const first = await getCatalog();
97
+ expect(first).toEqual(sampleCatalog);
98
+ expect(fetchCatalogCallCount).toBe(1);
99
+
100
+ // Simulate TTL expiry by manipulating Date.now
101
+ const originalNow = Date.now;
102
+ Date.now = () => originalNow() + 5 * 60 * 1000 + 1;
103
+
104
+ try {
105
+ mockFetchCatalogResult = updatedCatalog;
106
+ const second = await getCatalog();
107
+ expect(second).toEqual(updatedCatalog);
108
+ expect(fetchCatalogCallCount).toBe(2);
109
+ } finally {
110
+ Date.now = originalNow;
111
+ }
112
+ });
113
+
114
+ test("falls back to stale cache on fetch failure", async () => {
115
+ mockFetchCatalogResult = sampleCatalog;
116
+
117
+ // Populate cache
118
+ const first = await getCatalog();
119
+ expect(first).toEqual(sampleCatalog);
120
+
121
+ // Expire cache and make fetch fail
122
+ const originalNow = Date.now;
123
+ Date.now = () => originalNow() + 5 * 60 * 1000 + 1;
124
+
125
+ try {
126
+ mockFetchCatalogError = new Error("Network timeout");
127
+ const fallback = await getCatalog();
128
+ expect(fallback).toEqual(sampleCatalog); // stale cache
129
+ } finally {
130
+ Date.now = originalNow;
131
+ }
132
+ });
133
+
134
+ test("returns empty array on fetch failure with no stale cache", async () => {
135
+ mockFetchCatalogError = new Error("Network timeout");
136
+
137
+ const result = await getCatalog();
138
+ expect(result).toEqual([]);
139
+ });
140
+
141
+ test("invalidateCatalogCache forces re-fetch", async () => {
142
+ mockFetchCatalogResult = sampleCatalog;
143
+
144
+ await getCatalog();
145
+ expect(fetchCatalogCallCount).toBe(1);
146
+
147
+ invalidateCatalogCache();
148
+
149
+ mockFetchCatalogResult = updatedCatalog;
150
+ const refreshed = await getCatalog();
151
+ expect(refreshed).toEqual(updatedCatalog);
152
+ expect(fetchCatalogCallCount).toBe(2);
153
+ });
154
+
155
+ test("uses local catalog when repoSkillsDir is set", async () => {
156
+ mockRepoSkillsDir = "/mock/repo/skills";
157
+ mockFetchCatalogResult = sampleCatalog;
158
+
159
+ const result = await getCatalog();
160
+ expect(result).toEqual(sampleCatalog);
161
+ expect(readLocalCatalogCallCount).toBe(1);
162
+ expect(fetchCatalogCallCount).toBe(0); // no remote fetch
163
+ });
164
+ });