@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
|
@@ -19,7 +19,7 @@ Do not offer AgentMail as an option or mention it unless the user specifically a
|
|
|
19
19
|
## Communication Style
|
|
20
20
|
|
|
21
21
|
- **Be action-oriented.** When the user asks to do something ("declutter", "check my email"), start doing it immediately. Don't ask for permission to read their inbox - that's obviously what they want.
|
|
22
|
-
- **Keep it human.** Never mention OAuth, tokens, APIs, sandboxes, credential proxies, or other technical internals. If something isn't working, say "Gmail needs to be reconnected" - not "the OAuth2 access token for
|
|
22
|
+
- **Keep it human.** Never mention OAuth, tokens, APIs, sandboxes, credential proxies, or other technical internals. If something isn't working, say "Gmail needs to be reconnected" - not "the OAuth2 access token for google has expired."
|
|
23
23
|
- **Show progress.** When running a tool that scans many emails, tell the user what you're doing: "Scanning your inbox for clutter..." Don't go silent.
|
|
24
24
|
- **Be brief and warm.** One or two sentences per update is plenty. Don't over-explain what you're about to do - just do it and narrate lightly.
|
|
25
25
|
|
|
@@ -27,23 +27,23 @@ Do not offer AgentMail as an option or mention it unless the user specifically a
|
|
|
27
27
|
|
|
28
28
|
### Gmail
|
|
29
29
|
|
|
30
|
-
1. **Try connecting directly first.**
|
|
31
|
-
2. **If
|
|
32
|
-
- Call `skill_load` with `skill: "
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
1. **Try connecting directly first.** Run `assistant oauth status google`. This will show whether or not the user had previously connected their google account. If so, they are ready to go.
|
|
31
|
+
2. **If no connections are found:** The user needs to either use Vellum's managed google integration or set up their own google oauth app.
|
|
32
|
+
- Call `skill_load` with `skill: "vellum-oauth-integrations"` with `provider-key: google` throughout.
|
|
33
|
+
- To use `your-own` mode, you will need to call `skill_load` with `skill: google-oauth-app-setup`. In this case:
|
|
34
|
+
- Tell the user Gmail isn't connected yet and briefly explain what the setup involves, then use `ui_show` with `surface_type: "confirmation"` to ask for permission to start:
|
|
35
|
+
- **message:** "Ready to set up Gmail?"
|
|
36
|
+
- **detail:** "I'll open a few pages in your browser and walk you through setting up Google Cloud credentials - creating a project, enabling APIs, and connecting your account. Takes about 5 minutes.\n\n**Your emails stay under your control** — I only ever create drafts. Nothing gets sent without your explicit say-so."
|
|
37
|
+
- **confirmLabel:** "Get Started"
|
|
38
|
+
- **cancelLabel:** "Not Now"
|
|
39
|
+
- If the user confirms, briefly acknowledge (e.g., "Setting up Gmail now...") and proceed with the setup guide. If they decline, acknowledge and let them know they can set it up later.
|
|
40
40
|
|
|
41
41
|
## Error Recovery
|
|
42
42
|
|
|
43
43
|
When a Gmail tool fails with a token or authorization error:
|
|
44
44
|
|
|
45
|
-
1. **Try to reconnect silently.** Call `
|
|
46
|
-
2. **If reconnection fails, go straight to setup.** Don't present options, ask which route the user prefers, or explain what went wrong technically. Just tell the user briefly (e.g., "Gmail needs to be reconnected - let me set that up") and immediately follow the connection setup flow for Gmail (e.g., install and load **google-oauth-
|
|
45
|
+
1. **Try to reconnect silently.** Call `assistant oauth ping google`. This often resolves expired tokens automatically.
|
|
46
|
+
2. **If reconnection fails, go straight to setup.** Don't present options, ask which route the user prefers, or explain what went wrong technically. Just tell the user briefly (e.g., "Gmail needs to be reconnected - let me set that up") and immediately follow the connection setup flow for Gmail (e.g., install and load **google-oauth-app-setup**). The user came to you to get something done, not to troubleshoot OAuth - make it seamless.
|
|
47
47
|
3. **Never try alternative approaches.** Don't use bash, curl, browser automation, or any workaround. If the Gmail tools can't do it, the reconnection flow is the answer.
|
|
48
48
|
4. **Never expose error details.** The user doesn't need to see error messages about tokens, OAuth, or API failures. Translate errors into plain language.
|
|
49
49
|
|
|
@@ -35,7 +35,7 @@ export async function run(
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
try {
|
|
38
|
-
const connection = await resolveOAuthConnection("
|
|
38
|
+
const connection = await resolveOAuthConnection("google", {
|
|
39
39
|
account,
|
|
40
40
|
});
|
|
41
41
|
const allMessageIds: string[] = [];
|
|
@@ -106,7 +106,7 @@ export async function run(
|
|
|
106
106
|
} else if (messageId) {
|
|
107
107
|
// Single message path
|
|
108
108
|
try {
|
|
109
|
-
const connection = await resolveOAuthConnection("
|
|
109
|
+
const connection = await resolveOAuthConnection("google", {
|
|
110
110
|
account,
|
|
111
111
|
});
|
|
112
112
|
await modifyMessage(connection, messageId, { removeLabelIds: ["INBOX"] });
|
|
@@ -126,7 +126,7 @@ export async function run(
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
try {
|
|
129
|
-
const connection = await resolveOAuthConnection("
|
|
129
|
+
const connection = await resolveOAuthConnection("google", {
|
|
130
130
|
account,
|
|
131
131
|
});
|
|
132
132
|
if (messageIds.length === 1) {
|
|
@@ -57,7 +57,7 @@ export async function run(
|
|
|
57
57
|
|
|
58
58
|
if (action === "list") {
|
|
59
59
|
try {
|
|
60
|
-
const connection = await resolveOAuthConnection("
|
|
60
|
+
const connection = await resolveOAuthConnection("google", {
|
|
61
61
|
account,
|
|
62
62
|
});
|
|
63
63
|
const message = await getMessage(connection, messageId, "full");
|
|
@@ -81,7 +81,7 @@ export async function run(
|
|
|
81
81
|
if (!filename) return err("filename is required for download.");
|
|
82
82
|
|
|
83
83
|
try {
|
|
84
|
-
const connection = await resolveOAuthConnection("
|
|
84
|
+
const connection = await resolveOAuthConnection("google", {
|
|
85
85
|
account,
|
|
86
86
|
});
|
|
87
87
|
const attachment = await getAttachment(
|
|
@@ -23,7 +23,7 @@ export async function run(
|
|
|
23
23
|
if (!body) return err("body is required.");
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
|
-
const connection = await resolveOAuthConnection("
|
|
26
|
+
const connection = await resolveOAuthConnection("google", {
|
|
27
27
|
account,
|
|
28
28
|
});
|
|
29
29
|
const draft = await createDraft(
|
|
@@ -76,7 +76,7 @@ export async function run(
|
|
|
76
76
|
if (!forwardTo) return err("to is required.");
|
|
77
77
|
|
|
78
78
|
try {
|
|
79
|
-
const connection = await resolveOAuthConnection("
|
|
79
|
+
const connection = await resolveOAuthConnection("google", {
|
|
80
80
|
account,
|
|
81
81
|
});
|
|
82
82
|
const message = await getMessage(connection, messageId, "full");
|
|
@@ -21,7 +21,7 @@ export async function run(
|
|
|
21
21
|
|
|
22
22
|
if (messageIds && messageIds.length > 0) {
|
|
23
23
|
try {
|
|
24
|
-
const connection = await resolveOAuthConnection("
|
|
24
|
+
const connection = await resolveOAuthConnection("google", {
|
|
25
25
|
account,
|
|
26
26
|
});
|
|
27
27
|
await batchModifyMessages(connection, messageIds, {
|
|
@@ -36,7 +36,7 @@ export async function run(
|
|
|
36
36
|
|
|
37
37
|
if (messageId) {
|
|
38
38
|
try {
|
|
39
|
-
const connection = await resolveOAuthConnection("
|
|
39
|
+
const connection = await resolveOAuthConnection("google", {
|
|
40
40
|
account,
|
|
41
41
|
});
|
|
42
42
|
await modifyMessage(connection, messageId, {
|
|
@@ -56,7 +56,7 @@ export async function run(
|
|
|
56
56
|
const query = `in:inbox -has:unsubscribe newer_than:${timeRange}`;
|
|
57
57
|
|
|
58
58
|
try {
|
|
59
|
-
const connection = await resolveOAuthConnection("
|
|
59
|
+
const connection = await resolveOAuthConnection("google", {
|
|
60
60
|
account,
|
|
61
61
|
});
|
|
62
62
|
// Pipeline: fire metadata fetches for each page of IDs as they arrive
|
|
@@ -15,7 +15,7 @@ export async function run(
|
|
|
15
15
|
if (!draftId) return err("draft_id is required.");
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
const connection = await resolveOAuthConnection("
|
|
18
|
+
const connection = await resolveOAuthConnection("google", {
|
|
19
19
|
account,
|
|
20
20
|
});
|
|
21
21
|
const msg = await sendDraft(connection, draftId);
|
|
@@ -58,7 +58,7 @@ export async function run(
|
|
|
58
58
|
const inputPageToken = input.page_token as string | undefined;
|
|
59
59
|
|
|
60
60
|
try {
|
|
61
|
-
const connection = await resolveOAuthConnection("
|
|
61
|
+
const connection = await resolveOAuthConnection("google", {
|
|
62
62
|
account,
|
|
63
63
|
});
|
|
64
64
|
// Pipeline: fire metadata fetches for each page of IDs as they arrive,
|
|
@@ -32,7 +32,7 @@ export async function run(
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
try {
|
|
35
|
-
const connection = await resolveOAuthConnection("
|
|
35
|
+
const connection = await resolveOAuthConnection("google", {
|
|
36
36
|
account,
|
|
37
37
|
});
|
|
38
38
|
const message = await getMessage(connection, messageId, "metadata", [
|
|
@@ -14,10 +14,16 @@ You are a Google Calendar assistant with full access to the user's calendar. Use
|
|
|
14
14
|
|
|
15
15
|
Before using any Calendar tool, verify that Google Calendar is connected by attempting a lightweight call (e.g., `calendar_list_events` with a narrow date range). If the call fails with a token/authorization error:
|
|
16
16
|
|
|
17
|
-
1. **
|
|
18
|
-
2.
|
|
19
|
-
- Call `skill_load` with `skill: "
|
|
20
|
-
|
|
17
|
+
1. **Try connecting directly first.** Run `assistant oauth status google`. This will show whether or not the user had previously connected their google account. If so, they are ready to go.
|
|
18
|
+
2. **If no connections are found:** The user needs to either use Vellum's managed google integration or set up their own google oauth app.
|
|
19
|
+
- Call `skill_load` with `skill: "vellum-oauth-integrations"` with `provider-key: google` throughout.
|
|
20
|
+
- To use `your-own` mode, you will need to call `skill_load` with `skill: google-oauth-app-setup`. In this case:
|
|
21
|
+
- Tell the user Google account isn't connected yet and briefly explain what the setup involves, then use `ui_show` with `surface_type: "confirmation"` to ask for permission to start:
|
|
22
|
+
- **message:** "Ready to set up Google Calendar?"
|
|
23
|
+
- **detail:** "I'll open a few pages in your browser and walk you through setting up Google Cloud credentials - creating a project, enabling APIs, and connecting your account. Takes about 5 minutes.\n\n**Your emails stay under your control** — I only ever create drafts. Nothing gets sent without your explicit say-so."
|
|
24
|
+
- **confirmLabel:** "Get Started"
|
|
25
|
+
- **cancelLabel:** "Not Now"
|
|
26
|
+
- If the user confirms, briefly acknowledge (e.g., "Setting up Google Calendar now...") and proceed with the setup guide. If they decline, acknowledge and let them know they can set it up later.
|
|
21
27
|
|
|
22
28
|
## Capabilities
|
|
23
29
|
|
|
@@ -13,5 +13,5 @@ export function ok(content: string): ToolExecutionResult {
|
|
|
13
13
|
export async function getCalendarConnection(
|
|
14
14
|
account?: string,
|
|
15
15
|
): Promise<OAuthConnection> {
|
|
16
|
-
return resolveOAuthConnection("
|
|
16
|
+
return resolveOAuthConnection("google", { account });
|
|
17
17
|
}
|
|
@@ -30,7 +30,7 @@ Do not offer AgentMail as an option or mention it unless the user specifically a
|
|
|
30
30
|
## Communication Style
|
|
31
31
|
|
|
32
32
|
- **Be action-oriented.** When the user asks to do something ("declutter", "check my email"), start doing it immediately. Don't ask for permission to read their inbox - that's obviously what they want.
|
|
33
|
-
- **Keep it human.** Never mention OAuth, tokens, APIs, sandboxes, credential proxies, or other technical internals. If something isn't working, say "Gmail needs to be reconnected" - not "the OAuth2 access token for
|
|
33
|
+
- **Keep it human.** Never mention OAuth, tokens, APIs, sandboxes, credential proxies, or other technical internals. If something isn't working, say "Gmail needs to be reconnected" - not "the OAuth2 access token for google has expired."
|
|
34
34
|
- **Show progress.** When running a tool that scans many emails, tell the user what you're doing: "Scanning your inbox for clutter..." Don't go silent.
|
|
35
35
|
- **Be brief and warm.** One or two sentences per update is plenty. Don't over-explain what you're about to do - just do it and narrate lightly.
|
|
36
36
|
|
|
@@ -44,7 +44,7 @@ Before using any messaging tool, verify that the platform is connected by callin
|
|
|
44
44
|
|
|
45
45
|
### Public Ingress (required for Telegram)
|
|
46
46
|
|
|
47
|
-
Telegram setup requires webhook routing, but it does **not** always require ngrok. Before suggesting public ingress for Telegram, check managed callback availability with `assistant platform status --json`. If that reports `containerized: true` with a non-empty `assistantId` and `available: true`, use the platform callback route flow and do not prompt for ngrok. Only use the **public-ingress** skill for local assistants that genuinely need a public gateway URL. Slack uses Socket Mode and does not require public ingress. Gmail on the desktop app uses a loopback callback and does not require public ingress; the channel path (Path B in the google-oauth-
|
|
47
|
+
Telegram setup requires webhook routing, but it does **not** always require ngrok. Before suggesting public ingress for Telegram, check managed callback availability with `assistant platform status --json`. If that reports `containerized: true` with a non-empty `assistantId` and `available: true`, use the platform callback route flow and do not prompt for ngrok. Only use the **public-ingress** skill for local assistants that genuinely need a public gateway URL. Slack uses Socket Mode and does not require public ingress. Gmail on the desktop app uses a loopback callback and does not require public ingress; the channel path (Path B in the google-oauth-app-setup skill) handles public ingress internally when needed.
|
|
48
48
|
|
|
49
49
|
### Email Connection Flow
|
|
50
50
|
|
|
@@ -56,16 +56,16 @@ When the user asks to "connect my email", "set up email", "manage my email", or
|
|
|
56
56
|
|
|
57
57
|
### Gmail
|
|
58
58
|
|
|
59
|
-
1. **Try connecting directly first.** Call `credential_store` with `action: "oauth2_connect"` and `service: "
|
|
60
|
-
2. **If it fails because no client_id is found:** The user needs to create Google Cloud OAuth credentials first. Load the **google-oauth-
|
|
61
|
-
- Call `skill_load` with `skill: "google-oauth-
|
|
59
|
+
1. **Try connecting directly first.** Call `credential_store` with `action: "oauth2_connect"` and `service: "google"`. The tool auto-fills Google's OAuth endpoints and looks up any previously stored client credentials - so this single call may be all that's needed.
|
|
60
|
+
2. **If it fails because no client_id is found:** The user needs to create Google Cloud OAuth credentials first. Load the **google-oauth-app-setup** skill:
|
|
61
|
+
- Call `skill_load` with `skill: "google-oauth-app-setup"` to load the dependency skill.
|
|
62
62
|
- Tell the user Gmail isn't connected yet and briefly explain what the setup involves, then use `ui_show` with `surface_type: "confirmation"` to ask for permission to start:
|
|
63
63
|
- **message:** "Ready to set up Gmail?"
|
|
64
64
|
- **detail:** "I'll open a few pages in your browser and walk you through setting up Google Cloud credentials - creating a project, enabling APIs, and connecting your account. Takes about 5 minutes."
|
|
65
65
|
- **confirmLabel:** "Get Started"
|
|
66
66
|
- **cancelLabel:** "Not Now"
|
|
67
67
|
- If the user confirms, briefly acknowledge (e.g., "Setting up Gmail now...") and proceed with the setup guide. If they decline, acknowledge and let them know they can set it up later.
|
|
68
|
-
3. **If the user provides a client_id directly in chat:** Call `credential_store` with `action: "oauth2_connect"`, `service: "
|
|
68
|
+
3. **If the user provides a client_id directly in chat:** Call `credential_store` with `action: "oauth2_connect"`, `service: "google"`, and `client_id: "<their value>"`. If a `client_secret` is also needed, collect it securely via `credential_store` with `action: "prompt"` — never accept it pasted in chat. Everything else is auto-filled.
|
|
69
69
|
|
|
70
70
|
### Slack
|
|
71
71
|
|
|
@@ -96,7 +96,7 @@ The guardian-verify-setup skill handles the full outbound verification flow for
|
|
|
96
96
|
When a messaging tool fails with a token or authorization error:
|
|
97
97
|
|
|
98
98
|
1. **Try to reconnect silently.** Call `credential_store` with `action: "oauth2_connect"` and the appropriate `service`. This often resolves expired tokens automatically.
|
|
99
|
-
2. **If reconnection fails, go straight to setup.** Don't present options, ask which route the user prefers, or explain what went wrong technically. Just tell the user briefly (e.g., "Gmail needs to be reconnected - let me set that up") and immediately follow the connection setup flow for that platform (e.g., install and load **google-oauth-
|
|
99
|
+
2. **If reconnection fails, go straight to setup.** Don't present options, ask which route the user prefers, or explain what went wrong technically. Just tell the user briefly (e.g., "Gmail needs to be reconnected - let me set that up") and immediately follow the connection setup flow for that platform (e.g., install and load **google-oauth-app-setup** for Gmail). The user came to you to get something done, not to troubleshoot OAuth - make it seamless.
|
|
100
100
|
3. **Never try alternative approaches.** Don't use bash, curl, browser automation, or any workaround. If the messaging tools can't do it, the reconnection flow is the answer.
|
|
101
101
|
4. **Never expose error details.** The user doesn't need to see error messages about tokens, OAuth, or API failures. Translate errors into plain language.
|
|
102
102
|
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
listMessages,
|
|
10
10
|
} from "../../../../messaging/providers/gmail/client.js";
|
|
11
11
|
import { buildMultipartMime } from "../../../../messaging/providers/gmail/mime-builder.js";
|
|
12
|
-
import type { OAuthConnection } from "../../../../oauth/connection.js";
|
|
13
12
|
import type {
|
|
14
13
|
ToolContext,
|
|
15
14
|
ToolExecutionResult,
|
|
@@ -57,7 +56,11 @@ export async function run(
|
|
|
57
56
|
|
|
58
57
|
// Gmail: create a draft instead of sending directly
|
|
59
58
|
if (provider.id === "gmail") {
|
|
60
|
-
|
|
59
|
+
if (!conn)
|
|
60
|
+
return err(
|
|
61
|
+
"Gmail requires an OAuth connection — is the account connected?",
|
|
62
|
+
);
|
|
63
|
+
const gmailConn = conn;
|
|
61
64
|
// Reply mode: thread_id provided - create a threaded draft with reply-all recipients
|
|
62
65
|
if (threadId) {
|
|
63
66
|
// Fetch thread messages to extract recipients and threading headers
|
|
@@ -126,17 +126,16 @@ export async function resolveProvider(
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
* Resolve an OAuthConnection
|
|
130
|
-
* for the given messaging provider.
|
|
129
|
+
* Resolve an OAuthConnection for the given messaging provider.
|
|
131
130
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
131
|
+
* Returns undefined for providers that manage credentials internally
|
|
132
|
+
* (e.g. Telegram bot tokens, Slack Socket Mode bot tokens).
|
|
134
133
|
*/
|
|
135
134
|
export async function getProviderConnection(
|
|
136
135
|
provider: MessagingProvider,
|
|
137
136
|
account?: string,
|
|
138
|
-
): Promise<OAuthConnection |
|
|
137
|
+
): Promise<OAuthConnection | undefined> {
|
|
139
138
|
if (provider.resolveConnection) return provider.resolveConnection(account);
|
|
140
|
-
if (await provider.isConnected?.()) return
|
|
139
|
+
if (await provider.isConnected?.()) return undefined;
|
|
141
140
|
return resolveOAuthConnection(provider.credentialService, { account });
|
|
142
141
|
}
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"name": "navigate_settings_tab",
|
|
60
|
-
"description": "Open the Vellum settings panel to a specific tab (e.g. General,
|
|
60
|
+
"description": "Open the Vellum settings panel to a specific tab (e.g. General, Models & Services, Voice). Use this when the user needs to review or adjust settings visually.",
|
|
61
61
|
"category": "system",
|
|
62
62
|
"risk": "low",
|
|
63
63
|
"input_schema": {
|
|
@@ -67,11 +67,13 @@
|
|
|
67
67
|
"type": "string",
|
|
68
68
|
"enum": [
|
|
69
69
|
"General",
|
|
70
|
-
"Channels",
|
|
71
70
|
"Models & Services",
|
|
72
71
|
"Voice",
|
|
72
|
+
"Sounds",
|
|
73
73
|
"Permissions & Privacy",
|
|
74
|
-
"
|
|
74
|
+
"Billing",
|
|
75
|
+
"Archived Conversations",
|
|
76
|
+
"Schedules",
|
|
75
77
|
"Developer"
|
|
76
78
|
],
|
|
77
79
|
"description": "The settings tab to navigate to"
|
|
@@ -5,11 +5,13 @@ import type {
|
|
|
5
5
|
|
|
6
6
|
const SETTINGS_TABS = [
|
|
7
7
|
"General",
|
|
8
|
-
"Channels",
|
|
9
8
|
"Models & Services",
|
|
10
9
|
"Voice",
|
|
10
|
+
"Sounds",
|
|
11
11
|
"Permissions & Privacy",
|
|
12
|
-
"
|
|
12
|
+
"Billing",
|
|
13
|
+
"Archived Conversations",
|
|
14
|
+
"Schedules",
|
|
13
15
|
"Developer",
|
|
14
16
|
] as const;
|
|
15
17
|
|
|
@@ -56,6 +56,8 @@ import * as contactMerge from "./bundled-skills/contacts/tools/contact-merge.js"
|
|
|
56
56
|
import * as contactSearch from "./bundled-skills/contacts/tools/contact-search.js";
|
|
57
57
|
import * as contactUpsert from "./bundled-skills/contacts/tools/contact-upsert.js";
|
|
58
58
|
import * as googleContacts from "./bundled-skills/contacts/tools/google-contacts.js";
|
|
59
|
+
// ── conversations ─────────────────────────────────────────────────────────────
|
|
60
|
+
import * as renameConversation from "./bundled-skills/conversations/tools/rename-conversation.js";
|
|
59
61
|
// ── document ───────────────────────────────────────────────────────────────────
|
|
60
62
|
import * as documentCreate from "./bundled-skills/document/tools/document-create.js";
|
|
61
63
|
import * as documentUpdate from "./bundled-skills/document/tools/document-update.js";
|
|
@@ -222,6 +224,9 @@ export const bundledToolRegistry = new Map<string, SkillToolScript>([
|
|
|
222
224
|
["contacts:tools/contact-merge.ts", contactMerge],
|
|
223
225
|
["contacts:tools/google-contacts.ts", googleContacts],
|
|
224
226
|
|
|
227
|
+
// conversations
|
|
228
|
+
["conversations:tools/rename-conversation.ts", renameConversation],
|
|
229
|
+
|
|
225
230
|
// document
|
|
226
231
|
["document:tools/document-create.ts", documentCreate],
|
|
227
232
|
["document:tools/document-update.ts", documentUpdate],
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"key": "app-builder-multifile",
|
|
48
48
|
"label": "App Builder Multi-file",
|
|
49
49
|
"description": "Enable multi-file TSX app creation with esbuild compilation instead of single-HTML apps",
|
|
50
|
-
"defaultEnabled":
|
|
50
|
+
"defaultEnabled": true
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
"id": "mobile-pairing",
|
|
@@ -255,7 +255,7 @@
|
|
|
255
255
|
"key": "managed-google-oauth",
|
|
256
256
|
"label": "Managed Google OAuth",
|
|
257
257
|
"description": "Show the Google OAuth service card in Models & Services settings",
|
|
258
|
-
"defaultEnabled":
|
|
258
|
+
"defaultEnabled": true
|
|
259
259
|
},
|
|
260
260
|
{
|
|
261
261
|
"id": "settings-embedding-provider",
|
|
@@ -88,6 +88,12 @@ export interface CesClientHandshakeOptions {
|
|
|
88
88
|
* credential materialisation without relying on env vars.
|
|
89
89
|
*/
|
|
90
90
|
assistantApiKey?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Optional platform assistant ID to pass to CES during the handshake.
|
|
93
|
+
* For warm-pool pods the PLATFORM_ASSISTANT_ID env var is empty at CES
|
|
94
|
+
* startup, so the assistant forwards it here once it is known.
|
|
95
|
+
*/
|
|
96
|
+
assistantId?: string;
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
export interface CesClient {
|
|
@@ -111,13 +117,16 @@ export interface CesClient {
|
|
|
111
117
|
): Promise<CesRpcContract[M]["response"]>;
|
|
112
118
|
|
|
113
119
|
/**
|
|
114
|
-
* Push an updated assistant API key to CES.
|
|
120
|
+
* Push an updated assistant API key (and optionally assistant ID) to CES.
|
|
115
121
|
*
|
|
116
122
|
* In managed mode the API key is provisioned after hatch, so the initial
|
|
117
123
|
* handshake may have been sent without one. This method pushes the key
|
|
118
124
|
* to CES after it arrives, without requiring a re-handshake.
|
|
119
125
|
*/
|
|
120
|
-
updateAssistantApiKey(
|
|
126
|
+
updateAssistantApiKey(
|
|
127
|
+
assistantApiKey: string,
|
|
128
|
+
assistantId?: string,
|
|
129
|
+
): Promise<{ updated: boolean }>;
|
|
121
130
|
|
|
122
131
|
/** Whether the client has completed a successful handshake. */
|
|
123
132
|
isReady(): boolean;
|
|
@@ -312,6 +321,7 @@ export function createCesClient(
|
|
|
312
321
|
...(options?.assistantApiKey
|
|
313
322
|
? { assistantApiKey: options.assistantApiKey }
|
|
314
323
|
: {}),
|
|
324
|
+
...(options?.assistantId ? { assistantId: options.assistantId } : {}),
|
|
315
325
|
});
|
|
316
326
|
} catch (err) {
|
|
317
327
|
const entry = pending.get("handshake");
|
|
@@ -341,14 +351,16 @@ export function createCesClient(
|
|
|
341
351
|
|
|
342
352
|
async updateAssistantApiKey(
|
|
343
353
|
assistantApiKey: string,
|
|
354
|
+
assistantId?: string,
|
|
344
355
|
): Promise<{ updated: boolean }> {
|
|
345
356
|
return call(CesRpcMethod.UpdateManagedCredential, {
|
|
346
357
|
assistantApiKey,
|
|
358
|
+
...(assistantId ? { assistantId } : {}),
|
|
347
359
|
});
|
|
348
360
|
},
|
|
349
361
|
|
|
350
362
|
isReady(): boolean {
|
|
351
|
-
return ready;
|
|
363
|
+
return ready && transport.isAlive();
|
|
352
364
|
},
|
|
353
365
|
|
|
354
366
|
close(): void {
|
|
@@ -245,6 +245,7 @@ export interface AgentLoopConversationContext {
|
|
|
245
245
|
trustContext?: TrustContext;
|
|
246
246
|
assistantId?: string;
|
|
247
247
|
voiceCallControlPrompt?: string;
|
|
248
|
+
transportHints?: string[];
|
|
248
249
|
|
|
249
250
|
readonly coreToolNames: Set<string>;
|
|
250
251
|
allowedToolNames?: Set<string>;
|
|
@@ -748,6 +749,7 @@ export async function runAgentLoopImpl(
|
|
|
748
749
|
temporalContext,
|
|
749
750
|
nowScratchpad,
|
|
750
751
|
voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
|
|
752
|
+
transportHints: ctx.transportHints ?? null,
|
|
751
753
|
isNonInteractive: !isInteractiveResolved,
|
|
752
754
|
} as const;
|
|
753
755
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getProviderRoutingSource } from "../providers/registry.js";
|
|
1
2
|
import { ProviderError, ProviderNotConfiguredError } from "../util/errors.js";
|
|
2
3
|
import type {
|
|
3
4
|
ConversationErrorCode,
|
|
@@ -200,12 +201,40 @@ function classifyCore(
|
|
|
200
201
|
errorCategory: "context_too_large",
|
|
201
202
|
};
|
|
202
203
|
}
|
|
204
|
+
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
205
|
+
if (
|
|
206
|
+
/invalid.*api.?key|invalid.*x-api-key|authentication.?error/i.test(
|
|
207
|
+
message,
|
|
208
|
+
)
|
|
209
|
+
) {
|
|
210
|
+
// Check if this provider is routed through the managed proxy.
|
|
211
|
+
// If so, the assistant API key is stale — the client should reprovision.
|
|
212
|
+
const providerName = error.provider;
|
|
213
|
+
if (getProviderRoutingSource(providerName) === "managed-proxy") {
|
|
214
|
+
return {
|
|
215
|
+
code: "MANAGED_KEY_INVALID",
|
|
216
|
+
userMessage:
|
|
217
|
+
"The assistant API key is invalid. Attempting to re-provision…",
|
|
218
|
+
retryable: true,
|
|
219
|
+
errorCategory: "managed_key_invalid",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
code: "PROVIDER_NOT_CONFIGURED",
|
|
224
|
+
userMessage:
|
|
225
|
+
"Your API key is invalid or expired. Update it in Settings or switch to managed mode.",
|
|
226
|
+
retryable: false,
|
|
227
|
+
errorCategory: "provider_not_configured",
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
203
231
|
if (error.statusCode === 401) {
|
|
204
232
|
return {
|
|
205
|
-
code: "
|
|
206
|
-
userMessage:
|
|
233
|
+
code: "PROVIDER_NOT_CONFIGURED",
|
|
234
|
+
userMessage:
|
|
235
|
+
"Your API key is invalid or expired. Update it in Settings or switch to managed mode.",
|
|
207
236
|
retryable: false,
|
|
208
|
-
errorCategory: "
|
|
237
|
+
errorCategory: "provider_not_configured",
|
|
209
238
|
};
|
|
210
239
|
}
|
|
211
240
|
if (error.statusCode === 402) {
|
|
@@ -275,10 +304,11 @@ function classifyCore(
|
|
|
275
304
|
)
|
|
276
305
|
) {
|
|
277
306
|
return {
|
|
278
|
-
code: "
|
|
279
|
-
userMessage:
|
|
307
|
+
code: "PROVIDER_NOT_CONFIGURED",
|
|
308
|
+
userMessage:
|
|
309
|
+
"Your API key is invalid. Update it in Settings or switch to managed mode.",
|
|
280
310
|
retryable: false,
|
|
281
|
-
errorCategory: "
|
|
311
|
+
errorCategory: "provider_not_configured",
|
|
282
312
|
};
|
|
283
313
|
}
|
|
284
314
|
return {
|
|
@@ -296,6 +296,15 @@ export async function persistUserMessage(
|
|
|
296
296
|
cleanMessage,
|
|
297
297
|
attachmentInputs,
|
|
298
298
|
);
|
|
299
|
+
log.info(
|
|
300
|
+
{
|
|
301
|
+
contentBlockTypes: Array.isArray(llmMessage.content)
|
|
302
|
+
? llmMessage.content.map((b) => b.type)
|
|
303
|
+
: typeof llmMessage.content,
|
|
304
|
+
attachmentCount: attachments.length,
|
|
305
|
+
},
|
|
306
|
+
"persistUserMessage: content blocks being sent to model",
|
|
307
|
+
);
|
|
299
308
|
ctx.messages.push(llmMessage);
|
|
300
309
|
|
|
301
310
|
try {
|