@vellumai/assistant 0.5.11 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/Dockerfile +42 -9
  2. package/docs/architecture/integrations.md +34 -32
  3. package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
  4. package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
  5. package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
  6. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
  7. package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
  8. package/openapi.yaml +87 -9
  9. package/package.json +1 -1
  10. package/src/__tests__/catalog-cache.test.ts +164 -0
  11. package/src/__tests__/catalog-search.test.ts +61 -0
  12. package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
  13. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
  14. package/src/__tests__/conversation-error.test.ts +3 -2
  15. package/src/__tests__/credential-security-invariants.test.ts +9 -15
  16. package/src/__tests__/credential-vault-unit.test.ts +32 -34
  17. package/src/__tests__/credential-vault.test.ts +25 -33
  18. package/src/__tests__/credentials-cli.test.ts +3 -3
  19. package/src/__tests__/daemon-credential-client.test.ts +2 -2
  20. package/src/__tests__/first-greeting.test.ts +7 -0
  21. package/src/__tests__/host-bash-proxy.test.ts +79 -0
  22. package/src/__tests__/host-cu-proxy.test.ts +90 -0
  23. package/src/__tests__/host-file-proxy.test.ts +89 -0
  24. package/src/__tests__/integration-status.test.ts +5 -5
  25. package/src/__tests__/list-messages-attachments.test.ts +171 -0
  26. package/src/__tests__/mcp-abort-signal.test.ts +205 -0
  27. package/src/__tests__/messaging-send-tool.test.ts +5 -5
  28. package/src/__tests__/navigate-settings-tab.test.ts +6 -2
  29. package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
  30. package/src/__tests__/oauth-cli.test.ts +126 -119
  31. package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
  32. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  33. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  34. package/src/__tests__/platform.test.ts +3 -168
  35. package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
  36. package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
  37. package/src/__tests__/skill-feature-flags.test.ts +8 -0
  38. package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
  39. package/src/__tests__/skills-uninstall.test.ts +2 -2
  40. package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
  41. package/src/__tests__/slack-share-routes.test.ts +5 -5
  42. package/src/__tests__/system-prompt.test.ts +39 -0
  43. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
  44. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
  45. package/src/cli/AGENTS.md +47 -7
  46. package/src/cli/commands/browser-relay.ts +2 -17
  47. package/src/cli/commands/contacts.ts +6 -4
  48. package/src/cli/commands/conversations.ts +13 -1
  49. package/src/cli/commands/credential-execution.ts +16 -1
  50. package/src/cli/commands/credentials.ts +2 -8
  51. package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
  52. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
  53. package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
  54. package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
  55. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
  56. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
  57. package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
  58. package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
  59. package/src/cli/commands/oauth/apps.ts +63 -44
  60. package/src/cli/commands/oauth/connect.ts +187 -155
  61. package/src/cli/commands/oauth/disconnect.ts +27 -75
  62. package/src/cli/commands/oauth/index.ts +36 -46
  63. package/src/cli/commands/oauth/mode.ts +22 -34
  64. package/src/cli/commands/oauth/ping.ts +19 -45
  65. package/src/cli/commands/oauth/providers.ts +569 -62
  66. package/src/cli/commands/oauth/request.ts +36 -48
  67. package/src/cli/commands/oauth/shared.ts +1 -19
  68. package/src/cli/commands/oauth/status.ts +14 -25
  69. package/src/cli/commands/oauth/token.ts +25 -34
  70. package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
  71. package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
  72. package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
  73. package/src/cli/commands/platform/connect.ts +104 -0
  74. package/src/cli/commands/platform/disconnect.ts +118 -0
  75. package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
  76. package/src/cli/commands/sequence.ts +5 -4
  77. package/src/cli/commands/shotgun.ts +16 -0
  78. package/src/cli/commands/skills.ts +173 -41
  79. package/src/cli/commands/usage.ts +5 -11
  80. package/src/cli/lib/daemon-credential-client.ts +22 -38
  81. package/src/cli/program.ts +1 -1
  82. package/src/config/assistant-feature-flags.ts +3 -7
  83. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  84. package/src/config/bundled-skills/conversations/SKILL.md +20 -0
  85. package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
  86. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
  87. package/src/config/bundled-skills/gmail/SKILL.md +13 -13
  88. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
  89. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
  90. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
  91. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
  92. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
  93. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
  94. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
  95. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
  96. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
  97. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  98. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
  99. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
  100. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
  101. package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
  102. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  103. package/src/config/bundled-skills/messaging/SKILL.md +7 -7
  104. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
  105. package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
  106. package/src/config/bundled-skills/settings/TOOLS.json +5 -3
  107. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
  108. package/src/config/bundled-tool-registry.ts +5 -0
  109. package/src/config/feature-flag-registry.json +2 -2
  110. package/src/credential-execution/client.ts +15 -3
  111. package/src/daemon/conversation-agent-loop.ts +2 -0
  112. package/src/daemon/conversation-error.ts +36 -6
  113. package/src/daemon/conversation-messaging.ts +9 -0
  114. package/src/daemon/conversation-runtime-assembly.ts +33 -0
  115. package/src/daemon/conversation-surfaces.ts +120 -14
  116. package/src/daemon/conversation.ts +5 -0
  117. package/src/daemon/first-greeting.ts +6 -1
  118. package/src/daemon/handlers/skills.ts +148 -3
  119. package/src/daemon/host-bash-proxy.ts +16 -0
  120. package/src/daemon/host-cu-proxy.ts +16 -0
  121. package/src/daemon/host-file-proxy.ts +16 -0
  122. package/src/daemon/lifecycle.ts +56 -5
  123. package/src/daemon/message-types/conversations.ts +1 -0
  124. package/src/daemon/message-types/guardian-actions.ts +2 -0
  125. package/src/daemon/message-types/host-bash.ts +6 -1
  126. package/src/daemon/message-types/host-cu.ts +6 -1
  127. package/src/daemon/message-types/host-file.ts +6 -1
  128. package/src/daemon/message-types/integrations.ts +0 -1
  129. package/src/daemon/server.ts +29 -2
  130. package/src/hooks/cli.ts +74 -0
  131. package/src/inbound/platform-callback-registration.ts +7 -12
  132. package/src/index.ts +0 -12
  133. package/src/mcp/client.ts +6 -1
  134. package/src/mcp/manager.ts +2 -1
  135. package/src/memory/conversation-crud.ts +92 -3
  136. package/src/memory/conversation-key-store.ts +26 -0
  137. package/src/memory/conversation-queries.ts +6 -6
  138. package/src/memory/db-init.ts +16 -0
  139. package/src/memory/journal-memory.ts +8 -2
  140. package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
  141. package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
  142. package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
  143. package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
  144. package/src/memory/migrations/index.ts +4 -0
  145. package/src/memory/migrations/registry.ts +8 -0
  146. package/src/memory/schema/oauth.ts +11 -0
  147. package/src/messaging/provider.ts +13 -12
  148. package/src/messaging/providers/gmail/adapter.ts +44 -35
  149. package/src/messaging/providers/slack/adapter.ts +63 -33
  150. package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
  151. package/src/messaging/providers/whatsapp/adapter.ts +6 -8
  152. package/src/notifications/adapters/telegram.ts +78 -2
  153. package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
  154. package/src/oauth/byo-connection.test.ts +22 -24
  155. package/src/oauth/connect-orchestrator.ts +37 -76
  156. package/src/oauth/connect-types.ts +7 -65
  157. package/src/oauth/connection-resolver.test.ts +13 -13
  158. package/src/oauth/connection-resolver.ts +3 -4
  159. package/src/oauth/identity-verifier.ts +177 -0
  160. package/src/oauth/oauth-store.ts +228 -3
  161. package/src/oauth/platform-connection.test.ts +56 -6
  162. package/src/oauth/platform-connection.ts +8 -1
  163. package/src/oauth/seed-providers.ts +247 -34
  164. package/src/permissions/checker.ts +127 -1
  165. package/src/prompts/journal-context.ts +4 -1
  166. package/src/prompts/system-prompt.ts +54 -9
  167. package/src/prompts/templates/BOOTSTRAP.md +16 -5
  168. package/src/providers/anthropic/client.ts +2 -33
  169. package/src/runtime/guardian-action-service.ts +7 -2
  170. package/src/runtime/http-server.ts +12 -18
  171. package/src/runtime/http-types.ts +8 -1
  172. package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
  173. package/src/runtime/routes/conversation-management-routes.ts +31 -0
  174. package/src/runtime/routes/conversation-routes.ts +79 -4
  175. package/src/runtime/routes/guardian-action-routes.ts +15 -2
  176. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
  177. package/src/runtime/routes/integrations/slack/share.ts +1 -1
  178. package/src/runtime/routes/oauth-apps.ts +2 -1
  179. package/src/runtime/routes/secret-routes.ts +45 -15
  180. package/src/runtime/routes/settings-routes.ts +12 -19
  181. package/src/runtime/routes/skills-routes.ts +45 -4
  182. package/src/schedule/integration-status.ts +2 -2
  183. package/src/security/ces-rpc-credential-backend.ts +19 -16
  184. package/src/security/oauth-completion-page.ts +153 -0
  185. package/src/security/oauth2.ts +3 -17
  186. package/src/security/secure-keys.ts +207 -7
  187. package/src/security/token-manager.ts +3 -6
  188. package/src/signals/bash.ts +6 -1
  189. package/src/skills/catalog-cache.ts +44 -0
  190. package/src/skills/catalog-search.ts +18 -0
  191. package/src/tools/browser/browser-manager.ts +2 -2
  192. package/src/tools/credentials/post-connect-hooks.ts +1 -1
  193. package/src/tools/credentials/vault.ts +34 -45
  194. package/src/tools/host-terminal/host-shell.ts +16 -3
  195. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  196. package/src/tools/skills/sandbox-runner.ts +16 -3
  197. package/src/tools/terminal/shell.ts +16 -3
  198. package/src/util/logger.ts +11 -1
  199. package/src/util/platform.ts +1 -91
  200. package/src/util/sentry-log-stream.ts +51 -0
  201. package/src/watcher/providers/github.ts +2 -2
  202. package/src/watcher/providers/gmail.ts +1 -1
  203. package/src/watcher/providers/google-calendar.ts +1 -1
  204. package/src/watcher/providers/linear.ts +2 -2
  205. package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
  206. package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
  207. package/src/workspace/migrations/registry.ts +2 -0
  208. package/src/cli/commands/oauth/connections.ts +0 -255
  209. package/src/oauth/provider-behaviors.ts +0 -634
@@ -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 integration:google has expired."
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.** Call `credential_store` with `action: "oauth2_connect"` and `service: "gmail"`. 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.
31
- 2. **If it fails because no client_id is found:** The user needs to create Google Cloud OAuth credentials first. Load the **google-oauth-applescript** skill:
32
- - Call `skill_load` with `skill: "google-oauth-applescript"` to load the dependency skill.
33
- - 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:
34
- - **message:** "Ready to set up Gmail?"
35
- - **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."
36
- - **confirmLabel:** "Get Started"
37
- - **cancelLabel:** "Not Now"
38
- - 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.
39
- 3. **If the user provides a client_id directly in chat:** Call `credential_store` with `action: "oauth2_connect"`, `service: "gmail"`, and `client_id: "<their value>"`. Include `client_secret` too if they provide one. Everything else is auto-filled.
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 `credential_store` with `action: "oauth2_connect"` and `service: "gmail"`. 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-applescript**). The user came to you to get something done, not to troubleshoot OAuth - make it seamless.
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("integration:google", {
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("integration:google", {
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("integration:google", {
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("integration:google", {
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("integration:google", {
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("integration:google", {
26
+ const connection = await resolveOAuthConnection("google", {
27
27
  account,
28
28
  });
29
29
  const draft = await createDraft(
@@ -26,7 +26,7 @@ export async function run(
26
26
  }
27
27
 
28
28
  try {
29
- const connection = await resolveOAuthConnection("integration:google", {
29
+ const connection = await resolveOAuthConnection("google", {
30
30
  account,
31
31
  });
32
32
  switch (action) {
@@ -38,7 +38,7 @@ export async function run(
38
38
  }
39
39
 
40
40
  try {
41
- const connection = await resolveOAuthConnection("integration:google", {
41
+ const connection = await resolveOAuthConnection("google", {
42
42
  account,
43
43
  });
44
44
  switch (action) {
@@ -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("integration:google", {
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("integration:google", {
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("integration:google", {
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("integration:google", {
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("integration:google", {
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("integration:google", {
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,
@@ -18,7 +18,7 @@ export async function run(
18
18
  }
19
19
 
20
20
  try {
21
- const connection = await resolveOAuthConnection("integration:google", {
21
+ const connection = await resolveOAuthConnection("google", {
22
22
  account,
23
23
  });
24
24
  await trashMessage(connection, messageId);
@@ -32,7 +32,7 @@ export async function run(
32
32
  }
33
33
 
34
34
  try {
35
- const connection = await resolveOAuthConnection("integration:google", {
35
+ const connection = await resolveOAuthConnection("google", {
36
36
  account,
37
37
  });
38
38
  const message = await getMessage(connection, messageId, "metadata", [
@@ -22,7 +22,7 @@ export async function run(
22
22
  }
23
23
 
24
24
  try {
25
- const connection = await resolveOAuthConnection("integration:google", {
25
+ const connection = await resolveOAuthConnection("google", {
26
26
  account,
27
27
  });
28
28
  switch (action) {
@@ -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. **Do NOT call `credential_store oauth2_connect` yourself.** You do not have valid OAuth client credentials, and fabricating a client_id will cause a "401: invalid_client" error from Google.
18
- 2. Instead, load the **google-oauth-applescript** skill, which walks the user through creating real credentials in Google Cloud Console:
19
- - Call `skill_load` with `skill: "google-oauth-applescript"` to load the dependency skill.
20
- 3. Tell the user: _"Google Calendar isn't connected yet. I've loaded a setup guide that will walk you through connecting your Google account - it only takes a couple of minutes."_
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("integration:google", { account });
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 integration:google has expired."
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-applescript skill) handles public ingress internally when needed.
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: "gmail"`. 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-applescript** skill:
61
- - Call `skill_load` with `skill: "google-oauth-applescript"` to load the dependency skill.
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: "gmail"`, and `client_id: "<their value>"`. Include `client_secret` too if they provide one. Everything else is auto-filled.
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-applescript** for Gmail). The user came to you to get something done, not to troubleshoot OAuth - make it seamless.
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
- const gmailConn = conn as OAuthConnection;
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 (or empty string for non-OAuth providers)
130
- * for the given messaging provider.
129
+ * Resolve an OAuthConnection for the given messaging provider.
131
130
  *
132
- * Non-OAuth providers (e.g. Telegram) use isConnected() and don't need
133
- * tokens - they receive an empty string which the string overload handles.
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 | string> {
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, Channels, Voice). Use this when the user needs to review or adjust settings visually.",
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
- "Contacts",
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
- "Contacts",
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": false
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": false
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(assistantApiKey: string): Promise<{ updated: boolean }>;
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: "PROVIDER_BILLING",
206
- userMessage: "Your API key is invalid or expired.",
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: "provider_billing",
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: "PROVIDER_BILLING",
279
- userMessage: "Your API key is invalid.",
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: "provider_billing",
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 {