@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.
- package/Dockerfile +42 -9
- package/docs/architecture/integrations.md +34 -32
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
- package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
- package/openapi.yaml +87 -9
- package/package.json +1 -1
- package/src/__tests__/catalog-cache.test.ts +164 -0
- package/src/__tests__/catalog-search.test.ts +61 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
- package/src/__tests__/conversation-error.test.ts +3 -2
- package/src/__tests__/credential-security-invariants.test.ts +9 -15
- package/src/__tests__/credential-vault-unit.test.ts +32 -34
- package/src/__tests__/credential-vault.test.ts +25 -33
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/daemon-credential-client.test.ts +2 -2
- package/src/__tests__/first-greeting.test.ts +7 -0
- package/src/__tests__/host-bash-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-proxy.test.ts +90 -0
- package/src/__tests__/host-file-proxy.test.ts +89 -0
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/list-messages-attachments.test.ts +171 -0
- package/src/__tests__/mcp-abort-signal.test.ts +205 -0
- package/src/__tests__/messaging-send-tool.test.ts +5 -5
- package/src/__tests__/navigate-settings-tab.test.ts +6 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
- package/src/__tests__/oauth-cli.test.ts +126 -119
- package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/platform.test.ts +3 -168
- package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
- package/src/__tests__/skill-feature-flags.test.ts +8 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
- package/src/__tests__/slack-share-routes.test.ts +5 -5
- package/src/__tests__/system-prompt.test.ts +39 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
- package/src/cli/AGENTS.md +47 -7
- package/src/cli/commands/browser-relay.ts +2 -17
- package/src/cli/commands/contacts.ts +6 -4
- package/src/cli/commands/conversations.ts +13 -1
- package/src/cli/commands/credential-execution.ts +16 -1
- package/src/cli/commands/credentials.ts +2 -8
- package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
- package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
- package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
- package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
- package/src/cli/commands/oauth/apps.ts +63 -44
- package/src/cli/commands/oauth/connect.ts +187 -155
- package/src/cli/commands/oauth/disconnect.ts +27 -75
- package/src/cli/commands/oauth/index.ts +36 -46
- package/src/cli/commands/oauth/mode.ts +22 -34
- package/src/cli/commands/oauth/ping.ts +19 -45
- package/src/cli/commands/oauth/providers.ts +569 -62
- package/src/cli/commands/oauth/request.ts +36 -48
- package/src/cli/commands/oauth/shared.ts +1 -19
- package/src/cli/commands/oauth/status.ts +14 -25
- package/src/cli/commands/oauth/token.ts +25 -34
- package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
- package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
- package/src/cli/commands/platform/connect.ts +104 -0
- package/src/cli/commands/platform/disconnect.ts +118 -0
- package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
- package/src/cli/commands/sequence.ts +5 -4
- package/src/cli/commands/shotgun.ts +16 -0
- package/src/cli/commands/skills.ts +173 -41
- package/src/cli/commands/usage.ts +5 -11
- package/src/cli/lib/daemon-credential-client.ts +22 -38
- package/src/cli/program.ts +1 -1
- package/src/config/assistant-feature-flags.ts +3 -7
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/conversations/SKILL.md +20 -0
- package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
- package/src/config/bundled-skills/gmail/SKILL.md +13 -13
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
- package/src/config/bundled-skills/settings/TOOLS.json +5 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
- package/src/config/bundled-tool-registry.ts +5 -0
- package/src/config/feature-flag-registry.json +2 -2
- package/src/credential-execution/client.ts +15 -3
- package/src/daemon/conversation-agent-loop.ts +2 -0
- package/src/daemon/conversation-error.ts +36 -6
- package/src/daemon/conversation-messaging.ts +9 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -0
- package/src/daemon/conversation-surfaces.ts +120 -14
- package/src/daemon/conversation.ts +5 -0
- package/src/daemon/first-greeting.ts +6 -1
- package/src/daemon/handlers/skills.ts +148 -3
- package/src/daemon/host-bash-proxy.ts +16 -0
- package/src/daemon/host-cu-proxy.ts +16 -0
- package/src/daemon/host-file-proxy.ts +16 -0
- package/src/daemon/lifecycle.ts +56 -5
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/message-types/guardian-actions.ts +2 -0
- package/src/daemon/message-types/host-bash.ts +6 -1
- package/src/daemon/message-types/host-cu.ts +6 -1
- package/src/daemon/message-types/host-file.ts +6 -1
- package/src/daemon/message-types/integrations.ts +0 -1
- package/src/daemon/server.ts +29 -2
- package/src/hooks/cli.ts +74 -0
- package/src/inbound/platform-callback-registration.ts +7 -12
- package/src/index.ts +0 -12
- package/src/mcp/client.ts +6 -1
- package/src/mcp/manager.ts +2 -1
- package/src/memory/conversation-crud.ts +92 -3
- package/src/memory/conversation-key-store.ts +26 -0
- package/src/memory/conversation-queries.ts +6 -6
- package/src/memory/db-init.ts +16 -0
- package/src/memory/journal-memory.ts +8 -2
- package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
- package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
- package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/oauth.ts +11 -0
- package/src/messaging/provider.ts +13 -12
- package/src/messaging/providers/gmail/adapter.ts +44 -35
- package/src/messaging/providers/slack/adapter.ts +63 -33
- package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
- package/src/messaging/providers/whatsapp/adapter.ts +6 -8
- package/src/notifications/adapters/telegram.ts +78 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
- package/src/oauth/byo-connection.test.ts +22 -24
- package/src/oauth/connect-orchestrator.ts +37 -76
- package/src/oauth/connect-types.ts +7 -65
- package/src/oauth/connection-resolver.test.ts +13 -13
- package/src/oauth/connection-resolver.ts +3 -4
- package/src/oauth/identity-verifier.ts +177 -0
- package/src/oauth/oauth-store.ts +228 -3
- package/src/oauth/platform-connection.test.ts +56 -6
- package/src/oauth/platform-connection.ts +8 -1
- package/src/oauth/seed-providers.ts +247 -34
- package/src/permissions/checker.ts +127 -1
- package/src/prompts/journal-context.ts +4 -1
- package/src/prompts/system-prompt.ts +54 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -5
- package/src/providers/anthropic/client.ts +2 -33
- package/src/runtime/guardian-action-service.ts +7 -2
- package/src/runtime/http-server.ts +12 -18
- package/src/runtime/http-types.ts +8 -1
- package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
- package/src/runtime/routes/conversation-management-routes.ts +31 -0
- package/src/runtime/routes/conversation-routes.ts +79 -4
- package/src/runtime/routes/guardian-action-routes.ts +15 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
- package/src/runtime/routes/integrations/slack/share.ts +1 -1
- package/src/runtime/routes/oauth-apps.ts +2 -1
- package/src/runtime/routes/secret-routes.ts +45 -15
- package/src/runtime/routes/settings-routes.ts +12 -19
- package/src/runtime/routes/skills-routes.ts +45 -4
- package/src/schedule/integration-status.ts +2 -2
- package/src/security/ces-rpc-credential-backend.ts +19 -16
- package/src/security/oauth-completion-page.ts +153 -0
- package/src/security/oauth2.ts +3 -17
- package/src/security/secure-keys.ts +207 -7
- package/src/security/token-manager.ts +3 -6
- package/src/signals/bash.ts +6 -1
- package/src/skills/catalog-cache.ts +44 -0
- package/src/skills/catalog-search.ts +18 -0
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/credentials/post-connect-hooks.ts +1 -1
- package/src/tools/credentials/vault.ts +34 -45
- package/src/tools/host-terminal/host-shell.ts +16 -3
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +16 -3
- package/src/tools/terminal/shell.ts +16 -3
- package/src/util/logger.ts +11 -1
- package/src/util/platform.ts +1 -91
- package/src/util/sentry-log-stream.ts +51 -0
- package/src/watcher/providers/github.ts +2 -2
- package/src/watcher/providers/gmail.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +1 -1
- package/src/watcher/providers/linear.ts +2 -2
- package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
- package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/cli/commands/oauth/connections.ts +0 -255
- 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
|
-
|
|
42
|
-
unzip \
|
|
41
|
+
bubblewrap \
|
|
43
42
|
ca-certificates \
|
|
44
|
-
|
|
45
|
-
|
|
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("
|
|
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/
|
|
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: `
|
|
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
|
|
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.
|
|
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
|
|
212
|
+
### Provider Configuration (DB-Driven)
|
|
213
213
|
|
|
214
|
-
`assistant/src/oauth/
|
|
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
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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. **
|
|
244
|
-
2. **Load
|
|
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
|
|
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. **
|
|
267
|
-
- Set `authUrl`, `tokenUrl`, `defaultScopes`, `scopePolicy`,
|
|
268
|
-
|
|
269
|
-
-
|
|
270
|
-
-
|
|
271
|
-
|
|
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/
|
|
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 (`
|
|
282
|
-
| `assistant/src/oauth/token-persistence.ts` | Token storage: credential store writes, metadata upsert, post-connect hooks
|
|
283
|
-
| `assistant/src/
|
|
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("
|
|
75
|
-
"local_oauth:
|
|
74
|
+
expect(localOAuthHandle("google", "conn-123")).toBe(
|
|
75
|
+
"local_oauth:google/conn-123",
|
|
76
76
|
);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
test("roundtrips
|
|
80
|
-
const raw = localOAuthHandle("
|
|
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("
|
|
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:
|
|
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:
|
|
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
|
-
*
|
|
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. "
|
|
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
|
|
150
|
-
//
|
|
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. "
|
|
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.
|
|
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
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
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
|
@@ -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
|
+
});
|