@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
|
@@ -143,7 +143,10 @@ export function upsertJournalMemoriesFromDisk(
|
|
|
143
143
|
|
|
144
144
|
// Filter for .md files, excluding readme.md (case-insensitive)
|
|
145
145
|
const mdFiles = files.filter(
|
|
146
|
-
(f) =>
|
|
146
|
+
(f) =>
|
|
147
|
+
f.endsWith(".md") &&
|
|
148
|
+
!f.startsWith(".") &&
|
|
149
|
+
f.toLowerCase() !== "readme.md",
|
|
147
150
|
);
|
|
148
151
|
|
|
149
152
|
let upserted = 0;
|
|
@@ -178,7 +181,10 @@ export function upsertJournalMemoriesFromDisk(
|
|
|
178
181
|
if (!statSync(subdirPath).isDirectory()) continue;
|
|
179
182
|
|
|
180
183
|
const subFiles = readdirSync(subdirPath).filter(
|
|
181
|
-
(f) =>
|
|
184
|
+
(f) =>
|
|
185
|
+
f.endsWith(".md") &&
|
|
186
|
+
!f.startsWith(".") &&
|
|
187
|
+
f.toLowerCase() !== "readme.md",
|
|
182
188
|
);
|
|
183
189
|
|
|
184
190
|
for (const filename of subFiles) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
|
|
3
|
+
export function migrateMessagesConversationCreatedAtIndex(
|
|
4
|
+
database: DrizzleDb,
|
|
5
|
+
): void {
|
|
6
|
+
database.run(
|
|
7
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_messages_conversation_created_at ON messages(conversation_id, created_at)`,
|
|
8
|
+
);
|
|
9
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
import { withCrashRecovery } from "./validate-migration-state.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One-shot migration: strip the `integration:` prefix from provider_key
|
|
7
|
+
* values across all three OAuth tables (oauth_providers, oauth_apps,
|
|
8
|
+
* oauth_connections).
|
|
9
|
+
*
|
|
10
|
+
* Historically provider keys were stored as `integration:google`,
|
|
11
|
+
* `integration:slack`, etc. The codebase is moving to bare-name keys
|
|
12
|
+
* (`google`, `slack`) for simplicity. Providers that were already stored
|
|
13
|
+
* with bare names (e.g. `slack_channel`, `telegram`) are unaffected.
|
|
14
|
+
*
|
|
15
|
+
* If a bare-name key already exists (runtime seed data created it), the
|
|
16
|
+
* old `integration:` rows are orphaned — we delete them instead of
|
|
17
|
+
* renaming to avoid UNIQUE constraint violations.
|
|
18
|
+
*
|
|
19
|
+
* FK constraints require us to update child tables (oauth_apps,
|
|
20
|
+
* oauth_connections) before the parent (oauth_providers), or disable FKs.
|
|
21
|
+
* We disable FKs for safety and update all three tables atomically.
|
|
22
|
+
*/
|
|
23
|
+
export function migrateStripIntegrationPrefixFromProviderKeys(
|
|
24
|
+
database: DrizzleDb,
|
|
25
|
+
): void {
|
|
26
|
+
withCrashRecovery(
|
|
27
|
+
database,
|
|
28
|
+
"migration_strip_integration_prefix_from_provider_keys_v1",
|
|
29
|
+
() => {
|
|
30
|
+
const raw = getSqliteFrom(database);
|
|
31
|
+
|
|
32
|
+
raw.exec("PRAGMA foreign_keys = OFF");
|
|
33
|
+
try {
|
|
34
|
+
// Find all provider keys with the integration: prefix.
|
|
35
|
+
const rows = raw
|
|
36
|
+
.prepare(
|
|
37
|
+
/*sql*/ `SELECT provider_key FROM oauth_providers WHERE provider_key LIKE 'integration:%'`,
|
|
38
|
+
)
|
|
39
|
+
.all() as Array<{ provider_key: string }>;
|
|
40
|
+
|
|
41
|
+
for (const { provider_key: oldKey } of rows) {
|
|
42
|
+
const newKey = oldKey.replace(/^integration:/, "");
|
|
43
|
+
|
|
44
|
+
// Check if the bare-name key already exists (seed data may have created it).
|
|
45
|
+
const bareExists = raw
|
|
46
|
+
.prepare(
|
|
47
|
+
/*sql*/ `SELECT 1 FROM oauth_providers WHERE provider_key = ?`,
|
|
48
|
+
)
|
|
49
|
+
.get(newKey);
|
|
50
|
+
|
|
51
|
+
if (bareExists) {
|
|
52
|
+
// Bare-name provider already exists — delete the old prefixed rows
|
|
53
|
+
// to avoid UNIQUE constraint violations.
|
|
54
|
+
raw
|
|
55
|
+
.prepare(
|
|
56
|
+
/*sql*/ `DELETE FROM oauth_connections WHERE provider_key = ?`,
|
|
57
|
+
)
|
|
58
|
+
.run(oldKey);
|
|
59
|
+
raw
|
|
60
|
+
.prepare(/*sql*/ `DELETE FROM oauth_apps WHERE provider_key = ?`)
|
|
61
|
+
.run(oldKey);
|
|
62
|
+
raw
|
|
63
|
+
.prepare(
|
|
64
|
+
/*sql*/ `DELETE FROM oauth_providers WHERE provider_key = ?`,
|
|
65
|
+
)
|
|
66
|
+
.run(oldKey);
|
|
67
|
+
} else {
|
|
68
|
+
// Rename: update child tables first, then parent.
|
|
69
|
+
raw
|
|
70
|
+
.prepare(
|
|
71
|
+
/*sql*/ `UPDATE oauth_connections SET provider_key = ? WHERE provider_key = ?`,
|
|
72
|
+
)
|
|
73
|
+
.run(newKey, oldKey);
|
|
74
|
+
raw
|
|
75
|
+
.prepare(
|
|
76
|
+
/*sql*/ `UPDATE oauth_apps SET provider_key = ? WHERE provider_key = ?`,
|
|
77
|
+
)
|
|
78
|
+
.run(newKey, oldKey);
|
|
79
|
+
raw
|
|
80
|
+
.prepare(
|
|
81
|
+
/*sql*/ `UPDATE oauth_providers SET provider_key = ? WHERE provider_key = ?`,
|
|
82
|
+
)
|
|
83
|
+
.run(newKey, oldKey);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Also update the watchers table — credential_service stores provider
|
|
88
|
+
// keys like "integration:google" that feed into resolveOAuthConnection().
|
|
89
|
+
raw
|
|
90
|
+
.prepare(
|
|
91
|
+
/*sql*/ `UPDATE watchers SET credential_service = REPLACE(credential_service, 'integration:', '') WHERE credential_service LIKE 'integration:%'`,
|
|
92
|
+
)
|
|
93
|
+
.run();
|
|
94
|
+
} finally {
|
|
95
|
+
raw.exec("PRAGMA foreign_keys = ON");
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Reverse: re-add the `integration:` prefix to provider keys that don't
|
|
103
|
+
* already have one and aren't known bare-name providers.
|
|
104
|
+
*
|
|
105
|
+
* This is a best-effort rollback — we prefix all keys that look like they
|
|
106
|
+
* were originally `integration:` prefixed. Known bare-name keys
|
|
107
|
+
* (`slack_channel`, `telegram`) are left as-is because they never had the
|
|
108
|
+
* prefix.
|
|
109
|
+
*/
|
|
110
|
+
export function migrateStripIntegrationPrefixFromProviderKeysDown(
|
|
111
|
+
database: DrizzleDb,
|
|
112
|
+
): void {
|
|
113
|
+
const raw = getSqliteFrom(database);
|
|
114
|
+
|
|
115
|
+
// Keys that were always bare — never had an integration: prefix.
|
|
116
|
+
const ALWAYS_BARE = new Set(["slack_channel", "telegram"]);
|
|
117
|
+
|
|
118
|
+
raw.exec("PRAGMA foreign_keys = OFF");
|
|
119
|
+
try {
|
|
120
|
+
const rows = raw
|
|
121
|
+
.prepare(
|
|
122
|
+
/*sql*/ `SELECT provider_key FROM oauth_providers WHERE provider_key NOT LIKE 'integration:%'`,
|
|
123
|
+
)
|
|
124
|
+
.all() as Array<{ provider_key: string }>;
|
|
125
|
+
|
|
126
|
+
for (const { provider_key: bareKey } of rows) {
|
|
127
|
+
if (ALWAYS_BARE.has(bareKey)) continue;
|
|
128
|
+
|
|
129
|
+
const prefixedKey = `integration:${bareKey}`;
|
|
130
|
+
|
|
131
|
+
// If the prefixed key already exists, delete the bare rows.
|
|
132
|
+
const prefixedExists = raw
|
|
133
|
+
.prepare(/*sql*/ `SELECT 1 FROM oauth_providers WHERE provider_key = ?`)
|
|
134
|
+
.get(prefixedKey);
|
|
135
|
+
|
|
136
|
+
if (prefixedExists) {
|
|
137
|
+
raw
|
|
138
|
+
.prepare(
|
|
139
|
+
/*sql*/ `DELETE FROM oauth_connections WHERE provider_key = ?`,
|
|
140
|
+
)
|
|
141
|
+
.run(bareKey);
|
|
142
|
+
raw
|
|
143
|
+
.prepare(/*sql*/ `DELETE FROM oauth_apps WHERE provider_key = ?`)
|
|
144
|
+
.run(bareKey);
|
|
145
|
+
raw
|
|
146
|
+
.prepare(/*sql*/ `DELETE FROM oauth_providers WHERE provider_key = ?`)
|
|
147
|
+
.run(bareKey);
|
|
148
|
+
} else {
|
|
149
|
+
raw
|
|
150
|
+
.prepare(
|
|
151
|
+
/*sql*/ `UPDATE oauth_connections SET provider_key = ? WHERE provider_key = ?`,
|
|
152
|
+
)
|
|
153
|
+
.run(prefixedKey, bareKey);
|
|
154
|
+
raw
|
|
155
|
+
.prepare(
|
|
156
|
+
/*sql*/ `UPDATE oauth_apps SET provider_key = ? WHERE provider_key = ?`,
|
|
157
|
+
)
|
|
158
|
+
.run(prefixedKey, bareKey);
|
|
159
|
+
raw
|
|
160
|
+
.prepare(
|
|
161
|
+
/*sql*/ `UPDATE oauth_providers SET provider_key = ? WHERE provider_key = ?`,
|
|
162
|
+
)
|
|
163
|
+
.run(prefixedKey, bareKey);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Reverse the watchers table update — re-add the prefix for keys that
|
|
168
|
+
// aren't known bare-name providers.
|
|
169
|
+
const watcherRows = raw
|
|
170
|
+
.prepare(
|
|
171
|
+
/*sql*/ `SELECT DISTINCT credential_service FROM watchers WHERE credential_service NOT LIKE 'integration:%'`,
|
|
172
|
+
)
|
|
173
|
+
.all() as Array<{ credential_service: string }>;
|
|
174
|
+
|
|
175
|
+
for (const { credential_service: bareKey } of watcherRows) {
|
|
176
|
+
if (ALWAYS_BARE.has(bareKey)) continue;
|
|
177
|
+
raw
|
|
178
|
+
.prepare(
|
|
179
|
+
/*sql*/ `UPDATE watchers SET credential_service = ? WHERE credential_service = ?`,
|
|
180
|
+
)
|
|
181
|
+
.run(`integration:${bareKey}`, bareKey);
|
|
182
|
+
}
|
|
183
|
+
} finally {
|
|
184
|
+
raw.exec("PRAGMA foreign_keys = ON");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateOAuthProvidersBehaviorColumns(
|
|
5
|
+
database: DrizzleDb,
|
|
6
|
+
): void {
|
|
7
|
+
const raw = getSqliteFrom(database);
|
|
8
|
+
const columns = [
|
|
9
|
+
"loopback_port INTEGER",
|
|
10
|
+
"injection_templates TEXT",
|
|
11
|
+
"setup_skill_id TEXT",
|
|
12
|
+
"app_type TEXT",
|
|
13
|
+
"setup_notes TEXT",
|
|
14
|
+
"identity_url TEXT",
|
|
15
|
+
"identity_method TEXT",
|
|
16
|
+
"identity_headers TEXT",
|
|
17
|
+
"identity_body TEXT",
|
|
18
|
+
"identity_response_paths TEXT",
|
|
19
|
+
"identity_format TEXT",
|
|
20
|
+
"identity_ok_field TEXT",
|
|
21
|
+
];
|
|
22
|
+
for (const col of columns) {
|
|
23
|
+
try {
|
|
24
|
+
raw.exec(`ALTER TABLE oauth_providers ADD COLUMN ${col}`);
|
|
25
|
+
} catch {
|
|
26
|
+
// Column already exists — nothing to do.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateDropSetupSkillIdColumn(database: DrizzleDb): void {
|
|
5
|
+
const raw = getSqliteFrom(database);
|
|
6
|
+
try {
|
|
7
|
+
raw.exec(/*sql*/ `ALTER TABLE oauth_providers DROP COLUMN setup_skill_id`);
|
|
8
|
+
} catch {
|
|
9
|
+
// Column already dropped or doesn't exist — nothing to do.
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -134,6 +134,10 @@ export { migrateContactsUserFileColumn } from "./192-contacts-user-file-column.j
|
|
|
134
134
|
export { migrateAddSourceTypeColumns } from "./193-add-source-type-columns.js";
|
|
135
135
|
export { migrateCreateMemoryRecallLogs } from "./194-memory-recall-logs.js";
|
|
136
136
|
export { migrateOAuthProvidersPingConfig } from "./195-oauth-providers-ping-config.js";
|
|
137
|
+
export { migrateMessagesConversationCreatedAtIndex } from "./196-messages-conversation-created-at-index.js";
|
|
138
|
+
export { migrateStripIntegrationPrefixFromProviderKeys } from "./196-strip-integration-prefix-from-provider-keys.js";
|
|
139
|
+
export { migrateOAuthProvidersBehaviorColumns } from "./197-oauth-providers-behavior-columns.js";
|
|
140
|
+
export { migrateDropSetupSkillIdColumn } from "./198-drop-setup-skill-id-column.js";
|
|
137
141
|
export {
|
|
138
142
|
MIGRATION_REGISTRY,
|
|
139
143
|
type MigrationRegistryEntry,
|
|
@@ -38,6 +38,7 @@ import { migrateBackfillInlineAttachmentsToDiskDown } from "./180-backfill-inlin
|
|
|
38
38
|
import { migrateRenameThreadStartersCheckpointsDown } from "./181-rename-thread-starters-checkpoints.js";
|
|
39
39
|
import { migrateBackfillAudioAttachmentMimeTypesDown } from "./191-backfill-audio-attachment-mime-types.js";
|
|
40
40
|
import { migrateAddSourceTypeColumnsDown } from "./193-add-source-type-columns.js";
|
|
41
|
+
import { migrateStripIntegrationPrefixFromProviderKeysDown } from "./196-strip-integration-prefix-from-provider-keys.js";
|
|
41
42
|
|
|
42
43
|
export interface MigrationRegistryEntry {
|
|
43
44
|
/** The checkpoint key written to memory_checkpoints on completion. */
|
|
@@ -333,6 +334,13 @@ export const MIGRATION_REGISTRY: MigrationRegistryEntry[] = [
|
|
|
333
334
|
"Add source_type and source_message_role columns to memory_items with backfill from verification_state and source messages",
|
|
334
335
|
down: migrateAddSourceTypeColumnsDown,
|
|
335
336
|
},
|
|
337
|
+
{
|
|
338
|
+
key: "migration_strip_integration_prefix_from_provider_keys_v1",
|
|
339
|
+
version: 38,
|
|
340
|
+
description:
|
|
341
|
+
"Strip integration: prefix from provider_key across oauth_providers, oauth_apps, and oauth_connections",
|
|
342
|
+
down: migrateStripIntegrationPrefixFromProviderKeysDown,
|
|
343
|
+
},
|
|
336
344
|
];
|
|
337
345
|
|
|
338
346
|
export function getMaxMigrationVersion(): number {
|
|
@@ -27,6 +27,17 @@ export const oauthProviders = sqliteTable("oauth_providers", {
|
|
|
27
27
|
dashboardUrl: text("dashboard_url"),
|
|
28
28
|
clientIdPlaceholder: text("client_id_placeholder"),
|
|
29
29
|
requiresClientSecret: integer("requires_client_secret").notNull().default(1),
|
|
30
|
+
loopbackPort: integer("loopback_port"),
|
|
31
|
+
injectionTemplates: text("injection_templates"),
|
|
32
|
+
appType: text("app_type"),
|
|
33
|
+
setupNotes: text("setup_notes"),
|
|
34
|
+
identityUrl: text("identity_url"),
|
|
35
|
+
identityMethod: text("identity_method"),
|
|
36
|
+
identityHeaders: text("identity_headers"),
|
|
37
|
+
identityBody: text("identity_body"),
|
|
38
|
+
identityResponsePaths: text("identity_response_paths"),
|
|
39
|
+
identityFormat: text("identity_format"),
|
|
40
|
+
identityOkField: text("identity_ok_field"),
|
|
30
41
|
createdAt: integer("created_at").notNull(),
|
|
31
42
|
updatedAt: integer("updated_at").notNull(),
|
|
32
43
|
});
|
|
@@ -30,25 +30,23 @@ export interface MessagingProvider {
|
|
|
30
30
|
|
|
31
31
|
// ── Universal operations (every platform must implement) ──────────
|
|
32
32
|
|
|
33
|
-
testConnection(
|
|
34
|
-
connectionOrToken: OAuthConnection | string,
|
|
35
|
-
): Promise<ConnectionInfo>;
|
|
33
|
+
testConnection(connection?: OAuthConnection): Promise<ConnectionInfo>;
|
|
36
34
|
listConversations(
|
|
37
|
-
|
|
35
|
+
connection: OAuthConnection | undefined,
|
|
38
36
|
options?: ListOptions,
|
|
39
37
|
): Promise<Conversation[]>;
|
|
40
38
|
getHistory(
|
|
41
|
-
|
|
39
|
+
connection: OAuthConnection | undefined,
|
|
42
40
|
conversationId: string,
|
|
43
41
|
options?: HistoryOptions,
|
|
44
42
|
): Promise<Message[]>;
|
|
45
43
|
search(
|
|
46
|
-
|
|
44
|
+
connection: OAuthConnection | undefined,
|
|
47
45
|
query: string,
|
|
48
46
|
options?: SearchOptions,
|
|
49
47
|
): Promise<SearchResult>;
|
|
50
48
|
sendMessage(
|
|
51
|
-
|
|
49
|
+
connection: OAuthConnection | undefined,
|
|
52
50
|
conversationId: string,
|
|
53
51
|
text: string,
|
|
54
52
|
options?: SendOptions,
|
|
@@ -57,26 +55,26 @@ export interface MessagingProvider {
|
|
|
57
55
|
// ── Optional operations (platforms implement what they support) ───
|
|
58
56
|
|
|
59
57
|
getThreadReplies?(
|
|
60
|
-
|
|
58
|
+
connection: OAuthConnection | undefined,
|
|
61
59
|
conversationId: string,
|
|
62
60
|
threadId: string,
|
|
63
61
|
options?: HistoryOptions,
|
|
64
62
|
): Promise<Message[]>;
|
|
65
63
|
markRead?(
|
|
66
|
-
|
|
64
|
+
connection: OAuthConnection | undefined,
|
|
67
65
|
conversationId: string,
|
|
68
66
|
messageId?: string,
|
|
69
67
|
): Promise<void>;
|
|
70
68
|
|
|
71
69
|
/** Scan messages and group by sender for bulk cleanup (e.g. newsletter decluttering). */
|
|
72
70
|
senderDigest?(
|
|
73
|
-
|
|
71
|
+
connection: OAuthConnection | undefined,
|
|
74
72
|
query: string,
|
|
75
73
|
options?: { maxMessages?: number; maxSenders?: number; pageToken?: string },
|
|
76
74
|
): Promise<SenderDigestResult>;
|
|
77
75
|
/** Archive messages matching a search query. */
|
|
78
76
|
archiveByQuery?(
|
|
79
|
-
|
|
77
|
+
connection: OAuthConnection | undefined,
|
|
80
78
|
query: string,
|
|
81
79
|
): Promise<ArchiveResult>;
|
|
82
80
|
|
|
@@ -95,8 +93,11 @@ export interface MessagingProvider {
|
|
|
95
93
|
* than the OAuth provider key). When present, getProviderConnection() calls
|
|
96
94
|
* this instead of resolveOAuthConnection(), giving the provider full control
|
|
97
95
|
* over credential lookup including fallback strategies.
|
|
96
|
+
*
|
|
97
|
+
* Returns an OAuthConnection if the provider uses OAuth, or undefined if
|
|
98
|
+
* the provider manages credentials internally (e.g. raw bot tokens).
|
|
98
99
|
*/
|
|
99
|
-
resolveConnection?(account?: string): Promise<OAuthConnection |
|
|
100
|
+
resolveConnection?(account?: string): Promise<OAuthConnection | undefined>;
|
|
100
101
|
|
|
101
102
|
/** Platform-specific capabilities for tool routing (e.g. 'reactions', 'threads', 'labels'). */
|
|
102
103
|
capabilities: Set<string>;
|
|
@@ -24,6 +24,17 @@ import type {
|
|
|
24
24
|
import * as gmail from "./client.js";
|
|
25
25
|
import type { GmailMessage, GmailMessagePart } from "./types.js";
|
|
26
26
|
|
|
27
|
+
function requireConnection(
|
|
28
|
+
connection: OAuthConnection | undefined,
|
|
29
|
+
): OAuthConnection {
|
|
30
|
+
if (!connection) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
"Gmail requires an OAuth connection — is the account connected?",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return connection;
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
function extractHeader(msg: GmailMessage, name: string): string {
|
|
28
39
|
const lower = name.toLowerCase();
|
|
29
40
|
return (
|
|
@@ -86,7 +97,7 @@ function mapGmailMessage(msg: GmailMessage): Message {
|
|
|
86
97
|
export const gmailMessagingProvider: MessagingProvider = {
|
|
87
98
|
id: "gmail",
|
|
88
99
|
displayName: "Gmail",
|
|
89
|
-
credentialService: "
|
|
100
|
+
credentialService: "google",
|
|
90
101
|
capabilities: new Set([
|
|
91
102
|
"threads",
|
|
92
103
|
"labels",
|
|
@@ -95,11 +106,9 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
95
106
|
"unsubscribe",
|
|
96
107
|
]),
|
|
97
108
|
|
|
98
|
-
async testConnection(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const connection = connectionOrToken as OAuthConnection;
|
|
102
|
-
const profile = await gmail.getProfile(connection);
|
|
109
|
+
async testConnection(connection?: OAuthConnection): Promise<ConnectionInfo> {
|
|
110
|
+
const conn = requireConnection(connection);
|
|
111
|
+
const profile = await gmail.getProfile(conn);
|
|
103
112
|
return {
|
|
104
113
|
connected: true,
|
|
105
114
|
user: profile.emailAddress,
|
|
@@ -112,12 +121,12 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
112
121
|
},
|
|
113
122
|
|
|
114
123
|
async listConversations(
|
|
115
|
-
|
|
124
|
+
connection: OAuthConnection | undefined,
|
|
116
125
|
_options?: ListOptions,
|
|
117
126
|
): Promise<Conversation[]> {
|
|
118
|
-
const
|
|
127
|
+
const conn = requireConnection(connection);
|
|
119
128
|
// Gmail "conversations" are modeled as labels with unread counts
|
|
120
|
-
const labels = await gmail.listLabels(
|
|
129
|
+
const labels = await gmail.listLabels(conn);
|
|
121
130
|
const conversations: Conversation[] = [];
|
|
122
131
|
|
|
123
132
|
for (const label of labels) {
|
|
@@ -156,15 +165,15 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
156
165
|
},
|
|
157
166
|
|
|
158
167
|
async getHistory(
|
|
159
|
-
|
|
168
|
+
connection: OAuthConnection | undefined,
|
|
160
169
|
conversationId: string,
|
|
161
170
|
options?: HistoryOptions,
|
|
162
171
|
): Promise<Message[]> {
|
|
163
|
-
const
|
|
172
|
+
const conn = requireConnection(connection);
|
|
164
173
|
// conversationId is a label ID — list messages in that label
|
|
165
174
|
const limit = options?.limit ?? 50;
|
|
166
175
|
const listResult = await gmail.listMessages(
|
|
167
|
-
|
|
176
|
+
conn,
|
|
168
177
|
undefined,
|
|
169
178
|
limit,
|
|
170
179
|
undefined,
|
|
@@ -174,7 +183,7 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
174
183
|
if (!listResult.messages?.length) return [];
|
|
175
184
|
|
|
176
185
|
const messages = await gmail.batchGetMessages(
|
|
177
|
-
|
|
186
|
+
conn,
|
|
178
187
|
listResult.messages.map((m) => m.id),
|
|
179
188
|
"full",
|
|
180
189
|
);
|
|
@@ -183,20 +192,20 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
183
192
|
},
|
|
184
193
|
|
|
185
194
|
async search(
|
|
186
|
-
|
|
195
|
+
connection: OAuthConnection | undefined,
|
|
187
196
|
query: string,
|
|
188
197
|
options?: SearchOptions,
|
|
189
198
|
): Promise<SearchResult> {
|
|
190
|
-
const
|
|
199
|
+
const conn = requireConnection(connection);
|
|
191
200
|
const count = options?.count ?? 20;
|
|
192
|
-
const listResult = await gmail.listMessages(
|
|
201
|
+
const listResult = await gmail.listMessages(conn, query, count);
|
|
193
202
|
|
|
194
203
|
if (!listResult.messages?.length) {
|
|
195
204
|
return { total: 0, messages: [], hasMore: false };
|
|
196
205
|
}
|
|
197
206
|
|
|
198
207
|
const messages = await gmail.batchGetMessages(
|
|
199
|
-
|
|
208
|
+
conn,
|
|
200
209
|
listResult.messages.map((m) => m.id),
|
|
201
210
|
"full",
|
|
202
211
|
);
|
|
@@ -210,17 +219,17 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
210
219
|
},
|
|
211
220
|
|
|
212
221
|
async sendMessage(
|
|
213
|
-
|
|
222
|
+
connection: OAuthConnection | undefined,
|
|
214
223
|
conversationId: string,
|
|
215
224
|
text: string,
|
|
216
225
|
options?: SendOptions,
|
|
217
226
|
): Promise<SendResult> {
|
|
218
|
-
const
|
|
227
|
+
const conn = requireConnection(connection);
|
|
219
228
|
// conversationId is the recipient email for Gmail
|
|
220
229
|
const to = conversationId;
|
|
221
230
|
const subject = options?.subject ?? "";
|
|
222
231
|
const msg = await gmail.sendMessage(
|
|
223
|
-
|
|
232
|
+
conn,
|
|
224
233
|
to,
|
|
225
234
|
subject,
|
|
226
235
|
text,
|
|
@@ -236,16 +245,16 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
236
245
|
},
|
|
237
246
|
|
|
238
247
|
async getThreadReplies(
|
|
239
|
-
|
|
248
|
+
connection: OAuthConnection | undefined,
|
|
240
249
|
_conversationId: string,
|
|
241
250
|
threadId: string,
|
|
242
251
|
options?: HistoryOptions,
|
|
243
252
|
): Promise<Message[]> {
|
|
244
|
-
const
|
|
253
|
+
const conn = requireConnection(connection);
|
|
245
254
|
// Get all messages in a Gmail thread
|
|
246
255
|
const limit = options?.limit ?? 50;
|
|
247
256
|
const listResult = await gmail.listMessages(
|
|
248
|
-
|
|
257
|
+
conn,
|
|
249
258
|
`thread:${threadId}`,
|
|
250
259
|
limit,
|
|
251
260
|
);
|
|
@@ -253,7 +262,7 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
253
262
|
if (!listResult.messages?.length) return [];
|
|
254
263
|
|
|
255
264
|
const messages = await gmail.batchGetMessages(
|
|
256
|
-
|
|
265
|
+
conn,
|
|
257
266
|
listResult.messages.map((m) => m.id),
|
|
258
267
|
"full",
|
|
259
268
|
);
|
|
@@ -262,23 +271,23 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
262
271
|
},
|
|
263
272
|
|
|
264
273
|
async markRead(
|
|
265
|
-
|
|
274
|
+
connection: OAuthConnection | undefined,
|
|
266
275
|
_conversationId: string,
|
|
267
276
|
messageId?: string,
|
|
268
277
|
): Promise<void> {
|
|
269
|
-
const
|
|
278
|
+
const conn = requireConnection(connection);
|
|
270
279
|
if (!messageId) return;
|
|
271
|
-
await gmail.modifyMessage(
|
|
280
|
+
await gmail.modifyMessage(conn, messageId, {
|
|
272
281
|
removeLabelIds: ["UNREAD"],
|
|
273
282
|
});
|
|
274
283
|
},
|
|
275
284
|
|
|
276
285
|
async senderDigest(
|
|
277
|
-
|
|
286
|
+
connection: OAuthConnection | undefined,
|
|
278
287
|
query: string,
|
|
279
288
|
options?: { maxMessages?: number; maxSenders?: number; pageToken?: string },
|
|
280
289
|
): Promise<SenderDigestResult> {
|
|
281
|
-
const
|
|
290
|
+
const conn = requireConnection(connection);
|
|
282
291
|
const maxMessages = Math.min(options?.maxMessages ?? 5000, 5000);
|
|
283
292
|
const maxSenders = options?.maxSenders ?? 30;
|
|
284
293
|
const maxIdsPerSender = 5000;
|
|
@@ -298,7 +307,7 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
298
307
|
}
|
|
299
308
|
const pageSize = Math.min(100, maxMessages - allMessageIds.length);
|
|
300
309
|
const listResp = await gmail.listMessages(
|
|
301
|
-
|
|
310
|
+
conn,
|
|
302
311
|
query,
|
|
303
312
|
pageSize,
|
|
304
313
|
pageToken,
|
|
@@ -308,7 +317,7 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
308
317
|
allMessageIds.push(...ids);
|
|
309
318
|
fetchPromises.push(
|
|
310
319
|
gmail.batchGetMessages(
|
|
311
|
-
|
|
320
|
+
conn,
|
|
312
321
|
ids,
|
|
313
322
|
"metadata",
|
|
314
323
|
metadataHeaders,
|
|
@@ -423,10 +432,10 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
423
432
|
},
|
|
424
433
|
|
|
425
434
|
async archiveByQuery(
|
|
426
|
-
|
|
435
|
+
connection: OAuthConnection | undefined,
|
|
427
436
|
query: string,
|
|
428
437
|
): Promise<ArchiveResult> {
|
|
429
|
-
const
|
|
438
|
+
const conn = requireConnection(connection);
|
|
430
439
|
const maxMessages = 5000;
|
|
431
440
|
const batchModifyLimit = 1000;
|
|
432
441
|
|
|
@@ -436,7 +445,7 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
436
445
|
|
|
437
446
|
while (allMessageIds.length < maxMessages) {
|
|
438
447
|
const listResp = await gmail.listMessages(
|
|
439
|
-
|
|
448
|
+
conn,
|
|
440
449
|
query,
|
|
441
450
|
Math.min(500, maxMessages - allMessageIds.length),
|
|
442
451
|
pageToken,
|
|
@@ -458,7 +467,7 @@ export const gmailMessagingProvider: MessagingProvider = {
|
|
|
458
467
|
|
|
459
468
|
for (let i = 0; i < allMessageIds.length; i += batchModifyLimit) {
|
|
460
469
|
const chunk = allMessageIds.slice(i, i + batchModifyLimit);
|
|
461
|
-
await gmail.batchModifyMessages(
|
|
470
|
+
await gmail.batchModifyMessages(conn, chunk, {
|
|
462
471
|
removeLabelIds: ["INBOX"],
|
|
463
472
|
});
|
|
464
473
|
}
|