@vellumai/assistant 0.4.46 → 0.4.48

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 (194) hide show
  1. package/ARCHITECTURE.md +5 -5
  2. package/docs/architecture/security.md +5 -5
  3. package/package.json +1 -1
  4. package/src/__tests__/browser-fill-credential.test.ts +5 -2
  5. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
  6. package/src/__tests__/channel-readiness-routes.test.ts +20 -19
  7. package/src/__tests__/cli.test.ts +23 -0
  8. package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
  9. package/src/__tests__/credential-broker-server-use.test.ts +22 -21
  10. package/src/__tests__/credential-broker.test.ts +2 -1
  11. package/src/__tests__/credential-metadata-store.test.ts +240 -18
  12. package/src/__tests__/credential-resolve.test.ts +5 -4
  13. package/src/__tests__/credential-security-e2e.test.ts +8 -8
  14. package/src/__tests__/credential-security-invariants.test.ts +104 -6
  15. package/src/__tests__/credential-vault-unit.test.ts +22 -20
  16. package/src/__tests__/credential-vault.test.ts +284 -12
  17. package/src/__tests__/credentials-cli.test.ts +11 -6
  18. package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
  19. package/src/__tests__/gemini-image-service.test.ts +75 -45
  20. package/src/__tests__/gemini-provider.test.ts +9 -6
  21. package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
  22. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
  23. package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
  24. package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
  25. package/src/__tests__/guardian-grant-minting.test.ts +35 -0
  26. package/src/__tests__/integration-status.test.ts +53 -21
  27. package/src/__tests__/managed-proxy-context.test.ts +5 -3
  28. package/src/__tests__/media-generate-image.test.ts +63 -2
  29. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
  30. package/src/__tests__/messaging-send-tool.test.ts +4 -6
  31. package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
  32. package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
  33. package/src/__tests__/schema-transforms.test.ts +226 -0
  34. package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
  35. package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
  36. package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
  37. package/src/__tests__/secret-onetime-send.test.ts +5 -3
  38. package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
  39. package/src/__tests__/skills-uninstall.test.ts +2 -2
  40. package/src/__tests__/skills.test.ts +0 -9
  41. package/src/__tests__/slack-channel-config.test.ts +9 -8
  42. package/src/__tests__/slack-share-routes.test.ts +11 -6
  43. package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
  44. package/src/__tests__/twilio-config.test.ts +2 -1
  45. package/src/__tests__/twilio-provider.test.ts +4 -2
  46. package/src/__tests__/twilio-routes.test.ts +5 -4
  47. package/src/calls/call-domain.ts +7 -4
  48. package/src/calls/twilio-config.ts +2 -1
  49. package/src/calls/twilio-provider.ts +2 -1
  50. package/src/calls/twilio-rest.ts +2 -1
  51. package/src/cli/commands/browser-relay.ts +40 -15
  52. package/src/cli/commands/credentials.ts +9 -8
  53. package/src/cli/commands/oauth.ts +1 -1
  54. package/src/cli.ts +3 -2
  55. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
  56. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
  57. package/src/config/bundled-skills/gmail/SKILL.md +4 -4
  58. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
  59. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
  60. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
  61. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
  62. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
  63. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
  64. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
  65. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
  66. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
  67. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
  68. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
  69. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
  70. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
  71. package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
  72. package/src/config/bundled-skills/google-calendar/calendar-client.ts +70 -29
  73. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
  74. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
  75. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
  76. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
  77. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
  78. package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
  79. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
  80. package/src/config/bundled-skills/messaging/SKILL.md +6 -6
  81. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
  82. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
  83. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
  84. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
  85. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
  86. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
  87. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
  88. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
  89. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
  90. package/src/config/bundled-skills/messaging/tools/shared.ts +11 -11
  91. package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
  92. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
  93. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
  94. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
  95. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
  96. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
  97. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
  98. package/src/config/loader.ts +6 -0
  99. package/src/daemon/computer-use-session.ts +7 -1
  100. package/src/daemon/guardian-action-generators.ts +4 -5
  101. package/src/daemon/handlers/config-slack-channel.ts +37 -20
  102. package/src/daemon/handlers/config-telegram.ts +33 -20
  103. package/src/daemon/lifecycle.ts +9 -1
  104. package/src/daemon/message-types/integrations.ts +1 -0
  105. package/src/daemon/ride-shotgun-handler.ts +3 -1
  106. package/src/daemon/session-messaging.ts +3 -1
  107. package/src/daemon/session-tool-setup.ts +18 -2
  108. package/src/daemon/session.ts +1 -1
  109. package/src/email/providers/index.ts +2 -1
  110. package/src/instrument.ts +15 -1
  111. package/src/media/app-icon-generator.ts +30 -4
  112. package/src/media/avatar-router.ts +26 -3
  113. package/src/media/gemini-image-service.ts +28 -2
  114. package/src/memory/guardian-action-store.ts +1 -1
  115. package/src/memory/schema/guardian.ts +1 -1
  116. package/src/messaging/provider.ts +16 -10
  117. package/src/messaging/providers/gmail/adapter.ts +40 -23
  118. package/src/messaging/providers/gmail/client.ts +203 -122
  119. package/src/messaging/providers/gmail/people-client.ts +26 -18
  120. package/src/messaging/providers/slack/adapter.ts +29 -19
  121. package/src/messaging/providers/slack/client.ts +265 -78
  122. package/src/messaging/providers/telegram-bot/adapter.ts +5 -4
  123. package/src/messaging/providers/whatsapp/adapter.ts +6 -3
  124. package/src/messaging/registry.ts +2 -1
  125. package/src/oauth/byo-connection.test.ts +436 -0
  126. package/src/oauth/byo-connection.ts +112 -0
  127. package/src/oauth/connect-orchestrator.ts +27 -0
  128. package/src/oauth/connection-resolver.ts +34 -0
  129. package/src/oauth/connection.ts +38 -0
  130. package/src/oauth/platform-connection.test.ts +163 -0
  131. package/src/oauth/platform-connection.ts +110 -0
  132. package/src/oauth/provider-base-urls.ts +21 -0
  133. package/src/oauth/provider-profiles.ts +1 -1
  134. package/src/oauth/token-persistence.ts +20 -20
  135. package/src/permissions/checker.ts +5 -1
  136. package/src/prompts/system-prompt.ts +49 -12
  137. package/src/providers/gemini/client.ts +15 -6
  138. package/src/providers/managed-proxy/constants.ts +2 -2
  139. package/src/providers/managed-proxy/context.ts +5 -1
  140. package/src/providers/ratelimit.ts +17 -0
  141. package/src/providers/registry.ts +2 -2
  142. package/src/runtime/AGENTS.md +17 -0
  143. package/src/runtime/channel-invite-transports/telegram.ts +2 -1
  144. package/src/runtime/channel-readiness-service.ts +168 -195
  145. package/src/runtime/channel-readiness-types.ts +4 -0
  146. package/src/runtime/guardian-action-conversation-turn.ts +1 -3
  147. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  148. package/src/runtime/guardian-action-message-composer.ts +3 -23
  149. package/src/runtime/http-server.ts +9 -4
  150. package/src/runtime/http-types.ts +0 -1
  151. package/src/runtime/middleware/rate-limiter.ts +74 -20
  152. package/src/runtime/routes/channel-readiness-routes.ts +2 -0
  153. package/src/runtime/routes/diagnostics-routes.ts +11 -9
  154. package/src/runtime/routes/guardian-approval-interception.ts +20 -5
  155. package/src/runtime/routes/integrations/slack/share.ts +3 -2
  156. package/src/runtime/routes/integrations/twilio.ts +6 -5
  157. package/src/runtime/routes/secret-routes.ts +3 -2
  158. package/src/runtime/routes/settings-routes.ts +75 -17
  159. package/src/runtime/telegram-streaming-delivery.test.ts +132 -0
  160. package/src/runtime/telegram-streaming-delivery.ts +11 -1
  161. package/src/schedule/integration-status.ts +5 -4
  162. package/src/security/credential-key.ts +170 -0
  163. package/src/security/token-manager.ts +36 -7
  164. package/src/tools/apps/definitions.ts +0 -5
  165. package/src/tools/assets/materialize.ts +0 -5
  166. package/src/tools/assets/search.ts +0 -5
  167. package/src/tools/browser/headless-browser.ts +1 -67
  168. package/src/tools/claude-code/claude-code.ts +0 -5
  169. package/src/tools/computer-use/request-computer-control.ts +0 -5
  170. package/src/tools/credentials/broker.ts +6 -4
  171. package/src/tools/credentials/metadata-store.ts +72 -20
  172. package/src/tools/credentials/resolve.ts +2 -1
  173. package/src/tools/credentials/vault.ts +77 -16
  174. package/src/tools/filesystem/edit.ts +1 -6
  175. package/src/tools/filesystem/read.ts +0 -5
  176. package/src/tools/filesystem/write.ts +1 -6
  177. package/src/tools/host-filesystem/edit.ts +1 -6
  178. package/src/tools/host-filesystem/read.ts +1 -6
  179. package/src/tools/host-filesystem/write.ts +1 -6
  180. package/src/tools/mcp/mcp-tool-factory.ts +18 -1
  181. package/src/tools/memory/definitions.ts +0 -5
  182. package/src/tools/network/web-fetch.ts +0 -5
  183. package/src/tools/network/web-search.ts +0 -5
  184. package/src/tools/schema-transforms.ts +99 -0
  185. package/src/tools/skills/load.ts +0 -5
  186. package/src/tools/swarm/delegate.ts +0 -5
  187. package/src/tools/system/avatar-generator.ts +0 -5
  188. package/src/tools/ui-surface/definitions.ts +0 -15
  189. package/src/tools/watch/screen-watch.ts +0 -5
  190. package/src/version.ts +10 -0
  191. package/src/watcher/providers/github.ts +51 -52
  192. package/src/watcher/providers/gmail.ts +88 -80
  193. package/src/watcher/providers/google-calendar.ts +93 -86
  194. package/src/watcher/providers/linear.ts +87 -93
@@ -2,7 +2,7 @@ import type {
2
2
  ToolContext,
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
- import { err, ok, resolveProvider, withProviderToken } from "./shared.js";
5
+ import { err, getProviderConnection, ok, resolveProvider } from "./shared.js";
6
6
 
7
7
  export async function run(
8
8
  input: Record<string, unknown>,
@@ -23,47 +23,46 @@ export async function run(
23
23
  );
24
24
  }
25
25
 
26
- return withProviderToken(provider, async (token) => {
27
- const result = await provider.senderDigest!(token, query, {
28
- maxMessages,
29
- maxSenders,
30
- pageToken,
31
- });
32
-
33
- if (result.senders.length === 0) {
34
- return ok(
35
- JSON.stringify({
36
- senders: [],
37
- total_scanned: result.totalScanned,
38
- query_used: result.queryUsed,
39
- ...(result.truncated ? { truncated: true } : {}),
40
- message:
41
- "No emails found matching the query. Try broadening the search (e.g. remove category filter or extend date range).",
42
- }),
43
- );
44
- }
45
-
46
- // Map to snake_case output format for LLM consumption
47
- const senders = result.senders.map((s) => ({
48
- id: s.id,
49
- display_name: s.displayName,
50
- email: s.email,
51
- message_count: s.messageCount,
52
- has_unsubscribe: s.hasUnsubscribe,
53
- newest_message_id: s.newestMessageId,
54
- search_query: s.searchQuery,
55
- }));
26
+ const conn = getProviderConnection(provider);
27
+ const result = await provider.senderDigest!(conn, query, {
28
+ maxMessages,
29
+ maxSenders,
30
+ pageToken,
31
+ });
56
32
 
33
+ if (result.senders.length === 0) {
57
34
  return ok(
58
35
  JSON.stringify({
59
- senders,
36
+ senders: [],
60
37
  total_scanned: result.totalScanned,
61
38
  query_used: result.queryUsed,
62
39
  ...(result.truncated ? { truncated: true } : {}),
63
- note: `message_count reflects emails found per sender within the ${result.totalScanned} messages scanned. Use messaging_archive_by_sender with the sender's search_query to archive their messages.`,
40
+ message:
41
+ "No emails found matching the query. Try broadening the search (e.g. remove category filter or extend date range).",
64
42
  }),
65
43
  );
66
- });
44
+ }
45
+
46
+ // Map to snake_case output format for LLM consumption
47
+ const senders = result.senders.map((s) => ({
48
+ id: s.id,
49
+ display_name: s.displayName,
50
+ email: s.email,
51
+ message_count: s.messageCount,
52
+ has_unsubscribe: s.hasUnsubscribe,
53
+ newest_message_id: s.newestMessageId,
54
+ search_query: s.searchQuery,
55
+ }));
56
+
57
+ return ok(
58
+ JSON.stringify({
59
+ senders,
60
+ total_scanned: result.totalScanned,
61
+ query_used: result.queryUsed,
62
+ ...(result.truncated ? { truncated: true } : {}),
63
+ note: `message_count reflects emails found per sender within the ${result.totalScanned} messages scanned. Use messaging_archive_by_sender with the sender's search_query to archive their messages.`,
64
+ }),
65
+ );
67
66
  } catch (e) {
68
67
  return err(e instanceof Error ? e.message : String(e));
69
68
  }
@@ -8,7 +8,8 @@ import {
8
8
  getMessagingProvider,
9
9
  isPlatformEnabled,
10
10
  } from "../../../../messaging/registry.js";
11
- import { withValidToken } from "../../../../security/token-manager.js";
11
+ import type { OAuthConnection } from "../../../../oauth/connection.js";
12
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
12
13
  import type { ToolExecutionResult } from "../../../../tools/types.js";
13
14
 
14
15
  export function ok(content: string): ToolExecutionResult {
@@ -126,16 +127,15 @@ export function resolveProvider(platformInput?: string): MessagingProvider {
126
127
  }
127
128
 
128
129
  /**
129
- * Execute a callback with a valid OAuth token for the given provider.
130
- * Providers that manage their own auth (e.g. Telegram with a bot token)
131
- * expose isConnected() and don't need an OAuth access_token lookup.
130
+ * Resolve an OAuthConnection (or empty string for non-OAuth providers)
131
+ * for the given messaging provider.
132
+ *
133
+ * Non-OAuth providers (e.g. Telegram) use isConnected() and don't need
134
+ * tokens — they receive an empty string which the string overload handles.
132
135
  */
133
- export async function withProviderToken<T>(
136
+ export function getProviderConnection(
134
137
  provider: MessagingProvider,
135
- fn: (token: string) => Promise<T>,
136
- ): Promise<T> {
137
- if (provider.isConnected?.()) {
138
- return fn("");
139
- }
140
- return withValidToken(provider.credentialService, fn);
138
+ ): OAuthConnection | string {
139
+ if (provider.isConnected?.()) return "";
140
+ return resolveOAuthConnection(provider.credentialService);
141
141
  }
@@ -2,8 +2,8 @@
2
2
  * Shared utilities for slack skill tools.
3
3
  */
4
4
 
5
- import { getMessagingProvider } from "../../../../messaging/registry.js";
6
- import { withValidToken } from "../../../../security/token-manager.js";
5
+ import type { OAuthConnection } from "../../../../oauth/connection.js";
6
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
7
7
  import type { ToolExecutionResult } from "../../../../tools/types.js";
8
8
 
9
9
  export function ok(content: string): ToolExecutionResult {
@@ -14,12 +14,6 @@ export function err(message: string): ToolExecutionResult {
14
14
  return { content: message, isError: true };
15
15
  }
16
16
 
17
- /**
18
- * Execute a callback with a valid Slack OAuth token.
19
- */
20
- export async function withSlackToken<T>(
21
- fn: (token: string) => Promise<T>,
22
- ): Promise<T> {
23
- const provider = getMessagingProvider("slack");
24
- return withValidToken(provider.credentialService, fn);
17
+ export function getSlackConnection(): OAuthConnection {
18
+ return resolveOAuthConnection("integration:slack");
25
19
  }
@@ -3,7 +3,7 @@ import type {
3
3
  ToolContext,
4
4
  ToolExecutionResult,
5
5
  } from "../../../../tools/types.js";
6
- import { err, ok, withSlackToken } from "./shared.js";
6
+ import { err, getSlackConnection, ok } from "./shared.js";
7
7
 
8
8
  export async function run(
9
9
  input: Record<string, unknown>,
@@ -18,10 +18,9 @@ export async function run(
18
18
  }
19
19
 
20
20
  try {
21
- return await withSlackToken(async (token) => {
22
- await addReaction(token, channel, timestamp, emoji);
23
- return ok(`Added :${emoji}: reaction.`);
24
- });
21
+ const connection = getSlackConnection();
22
+ await addReaction(connection, channel, timestamp, emoji);
23
+ return ok(`Added :${emoji}: reaction.`);
25
24
  } catch (e) {
26
25
  return err(e instanceof Error ? e.message : String(e));
27
26
  }
@@ -3,7 +3,7 @@ import type {
3
3
  ToolContext,
4
4
  ToolExecutionResult,
5
5
  } from "../../../../tools/types.js";
6
- import { err, ok, withSlackToken } from "./shared.js";
6
+ import { err, getSlackConnection, ok } from "./shared.js";
7
7
 
8
8
  export async function run(
9
9
  input: Record<string, unknown>,
@@ -16,23 +16,22 @@ export async function run(
16
16
  }
17
17
 
18
18
  try {
19
- return await withSlackToken(async (token) => {
20
- const resp = await slack.conversationInfo(token, channelId);
21
- const conv = resp.channel;
19
+ const connection = getSlackConnection();
20
+ const resp = await slack.conversationInfo(connection, channelId);
21
+ const conv = resp.channel;
22
22
 
23
- const result = {
24
- channelId: conv.id,
25
- name: conv.name ?? conv.id,
26
- topic: conv.topic?.value || null,
27
- purpose: conv.purpose?.value || null,
28
- isPrivate: conv.is_private ?? conv.is_group ?? false,
29
- isArchived: conv.is_archived ?? false,
30
- memberCount: conv.num_members ?? null,
31
- latestActivityTs: conv.latest?.ts ?? null,
32
- };
23
+ const result = {
24
+ channelId: conv.id,
25
+ name: conv.name ?? conv.id,
26
+ topic: conv.topic?.value || null,
27
+ purpose: conv.purpose?.value || null,
28
+ isPrivate: conv.is_private ?? conv.is_group ?? false,
29
+ isArchived: conv.is_archived ?? false,
30
+ memberCount: conv.num_members ?? null,
31
+ latestActivityTs: conv.latest?.ts ?? null,
32
+ };
33
33
 
34
- return ok(JSON.stringify(result, null, 2));
35
- });
34
+ return ok(JSON.stringify(result, null, 2));
36
35
  } catch (e) {
37
36
  return err(e instanceof Error ? e.message : String(e));
38
37
  }
@@ -3,7 +3,7 @@ import type {
3
3
  ToolContext,
4
4
  ToolExecutionResult,
5
5
  } from "../../../../tools/types.js";
6
- import { err, ok, withSlackToken } from "./shared.js";
6
+ import { err, getSlackConnection, ok } from "./shared.js";
7
7
 
8
8
  export async function run(
9
9
  input: Record<string, unknown>,
@@ -17,10 +17,9 @@ export async function run(
17
17
  }
18
18
 
19
19
  try {
20
- return await withSlackToken(async (token) => {
21
- await deleteMessage(token, channel, timestamp);
22
- return ok(`Message deleted.`);
23
- });
20
+ const connection = getSlackConnection();
21
+ await deleteMessage(connection, channel, timestamp);
22
+ return ok(`Message deleted.`);
24
23
  } catch (e) {
25
24
  return err(e instanceof Error ? e.message : String(e));
26
25
  }
@@ -3,7 +3,7 @@ import type {
3
3
  ToolContext,
4
4
  ToolExecutionResult,
5
5
  } from "../../../../tools/types.js";
6
- import { err, ok, withSlackToken } from "./shared.js";
6
+ import { err, getSlackConnection, ok } from "./shared.js";
7
7
 
8
8
  export async function run(
9
9
  input: Record<string, unknown>,
@@ -18,10 +18,9 @@ export async function run(
18
18
  }
19
19
 
20
20
  try {
21
- return await withSlackToken(async (token) => {
22
- await updateMessage(token, channel, timestamp, text);
23
- return ok(`Message updated.`);
24
- });
21
+ const connection = getSlackConnection();
22
+ await updateMessage(connection, channel, timestamp, text);
23
+ return ok(`Message updated.`);
25
24
  } catch (e) {
26
25
  return err(e instanceof Error ? e.message : String(e));
27
26
  }
@@ -3,7 +3,7 @@ import type {
3
3
  ToolContext,
4
4
  ToolExecutionResult,
5
5
  } from "../../../../tools/types.js";
6
- import { err, ok, withSlackToken } from "./shared.js";
6
+ import { err, getSlackConnection, ok } from "./shared.js";
7
7
 
8
8
  export async function run(
9
9
  input: Record<string, unknown>,
@@ -16,10 +16,9 @@ export async function run(
16
16
  }
17
17
 
18
18
  try {
19
- return await withSlackToken(async (token) => {
20
- await leaveConversation(token, channel);
21
- return ok("Left channel.");
22
- });
19
+ const connection = getSlackConnection();
20
+ await leaveConversation(connection, channel);
21
+ return ok("Left channel.");
23
22
  } catch (e) {
24
23
  return err(e instanceof Error ? e.message : String(e));
25
24
  }
@@ -1,11 +1,12 @@
1
1
  import { getConfig } from "../../../../config/loader.js";
2
2
  import * as slack from "../../../../messaging/providers/slack/client.js";
3
3
  import type { SlackConversation } from "../../../../messaging/providers/slack/types.js";
4
+ import type { OAuthConnection } from "../../../../oauth/connection.js";
4
5
  import type {
5
6
  ToolContext,
6
7
  ToolExecutionResult,
7
8
  } from "../../../../tools/types.js";
8
- import { err, ok, withSlackToken } from "./shared.js";
9
+ import { err, getSlackConnection, ok } from "./shared.js";
9
10
 
10
11
  interface ThreadSummary {
11
12
  threadTs: string;
@@ -26,13 +27,16 @@ interface ChannelDigest {
26
27
 
27
28
  const userNameCache = new Map<string, string>();
28
29
 
29
- async function resolveUserName(token: string, userId: string): Promise<string> {
30
+ async function resolveUserName(
31
+ connection: OAuthConnection,
32
+ userId: string,
33
+ ): Promise<string> {
30
34
  if (!userId) return "unknown";
31
35
  const cached = userNameCache.get(userId);
32
36
  if (cached) return cached;
33
37
 
34
38
  try {
35
- const resp = await slack.userInfo(token, userId);
39
+ const resp = await slack.userInfo(connection, userId);
36
40
  const name =
37
41
  resp.user.profile?.display_name ||
38
42
  resp.user.profile?.real_name ||
@@ -46,7 +50,7 @@ async function resolveUserName(token: string, userId: string): Promise<string> {
46
50
  }
47
51
 
48
52
  async function scanChannel(
49
- token: string,
53
+ connection: OAuthConnection,
50
54
  conv: SlackConversation,
51
55
  oldestTs: string,
52
56
  includeThreads: boolean,
@@ -57,7 +61,7 @@ async function scanChannel(
57
61
 
58
62
  try {
59
63
  const history = await slack.conversationHistory(
60
- token,
64
+ connection,
61
65
  channelId,
62
66
  100,
63
67
  undefined,
@@ -71,7 +75,7 @@ async function scanChannel(
71
75
  }
72
76
 
73
77
  const keyParticipants = await Promise.all(
74
- [...participantIds].map((uid) => resolveUserName(token, uid)),
78
+ [...participantIds].map((uid) => resolveUserName(connection, uid)),
75
79
  );
76
80
 
77
81
  const threadMessages = messages
@@ -86,7 +90,7 @@ async function scanChannel(
86
90
  if (includeThreads) {
87
91
  try {
88
92
  const replies = await slack.conversationReplies(
89
- token,
93
+ connection,
90
94
  channelId,
91
95
  msg.ts,
92
96
  10,
@@ -97,11 +101,11 @@ async function scanChannel(
97
101
  }
98
102
  participants = await Promise.all(
99
103
  [...threadParticipantIds].map((uid) =>
100
- resolveUserName(token, uid),
104
+ resolveUserName(connection, uid),
101
105
  ),
102
106
  );
103
107
  } catch {
104
- participants = [await resolveUserName(token, msg.user ?? "")];
108
+ participants = [await resolveUserName(connection, msg.user ?? "")];
105
109
  }
106
110
  }
107
111
 
@@ -260,15 +264,34 @@ export async function run(
260
264
  const format = (input.format as string) ?? "text";
261
265
 
262
266
  try {
263
- return await withSlackToken(async (token) => {
264
- const oldestTs = String((Date.now() - hoursBack * 60 * 60 * 1000) / 1000);
267
+ const connection = getSlackConnection();
268
+ const oldestTs = String((Date.now() - hoursBack * 60 * 60 * 1000) / 1000);
265
269
 
266
- let channelsToScan: SlackConversation[];
267
- let failedLookups = 0;
270
+ let channelsToScan: SlackConversation[];
271
+ let failedLookups = 0;
268
272
 
269
- if (channelIds?.length) {
273
+ if (channelIds?.length) {
274
+ const results = await Promise.allSettled(
275
+ channelIds.map((id) => slack.conversationInfo(connection, id)),
276
+ );
277
+ channelsToScan = results
278
+ .filter(
279
+ (
280
+ r,
281
+ ): r is PromiseFulfilledResult<
282
+ Awaited<ReturnType<typeof slack.conversationInfo>>
283
+ > => r.status === "fulfilled",
284
+ )
285
+ .map((r) => r.value.channel);
286
+ failedLookups = results.filter((r) => r.status === "rejected").length;
287
+ } else {
288
+ const config = getConfig();
289
+ const preferredIds = config.skills?.entries?.slack?.config
290
+ ?.preferredChannels as string[] | undefined;
291
+
292
+ if (preferredIds?.length) {
270
293
  const results = await Promise.allSettled(
271
- channelIds.map((id) => slack.conversationInfo(token, id)),
294
+ preferredIds.map((id) => slack.conversationInfo(connection, id)),
272
295
  );
273
296
  channelsToScan = results
274
297
  .filter(
@@ -281,89 +304,69 @@ export async function run(
281
304
  .map((r) => r.value.channel);
282
305
  failedLookups = results.filter((r) => r.status === "rejected").length;
283
306
  } else {
284
- const config = getConfig();
285
- const preferredIds = config.skills?.entries?.slack?.config
286
- ?.preferredChannels as string[] | undefined;
287
-
288
- if (preferredIds?.length) {
289
- const results = await Promise.allSettled(
290
- preferredIds.map((id) => slack.conversationInfo(token, id)),
307
+ const allChannels: SlackConversation[] = [];
308
+ let cursor: string | undefined;
309
+ do {
310
+ const resp = await slack.listConversations(
311
+ connection,
312
+ "public_channel,private_channel",
313
+ true,
314
+ 200,
315
+ cursor,
291
316
  );
292
- channelsToScan = results
293
- .filter(
294
- (
295
- r,
296
- ): r is PromiseFulfilledResult<
297
- Awaited<ReturnType<typeof slack.conversationInfo>>
298
- > => r.status === "fulfilled",
299
- )
300
- .map((r) => r.value.channel);
301
- failedLookups = results.filter((r) => r.status === "rejected").length;
302
- } else {
303
- const allChannels: SlackConversation[] = [];
304
- let cursor: string | undefined;
305
- do {
306
- const resp = await slack.listConversations(
307
- token,
308
- "public_channel,private_channel",
309
- true,
310
- 200,
311
- cursor,
312
- );
313
- allChannels.push(...resp.channels);
314
- cursor = resp.response_metadata?.next_cursor || undefined;
315
- } while (cursor);
316
-
317
- channelsToScan = allChannels
318
- .filter((c) => c.is_member)
319
- .sort((a, b) => {
320
- const aTs = a.latest?.ts ? parseFloat(a.latest.ts) : 0;
321
- const bTs = b.latest?.ts ? parseFloat(b.latest.ts) : 0;
322
- return bTs - aTs;
323
- })
324
- .slice(0, maxChannels);
325
- }
317
+ allChannels.push(...resp.channels);
318
+ cursor = resp.response_metadata?.next_cursor || undefined;
319
+ } while (cursor);
320
+
321
+ channelsToScan = allChannels
322
+ .filter((c) => c.is_member)
323
+ .sort((a, b) => {
324
+ const aTs = a.latest?.ts ? parseFloat(a.latest.ts) : 0;
325
+ const bTs = b.latest?.ts ? parseFloat(b.latest.ts) : 0;
326
+ return bTs - aTs;
327
+ })
328
+ .slice(0, maxChannels);
326
329
  }
330
+ }
327
331
 
328
- const scanResults = await Promise.allSettled(
329
- channelsToScan.map((conv) =>
330
- scanChannel(token, conv, oldestTs, includeThreads),
331
- ),
332
- );
333
-
334
- const digests: ChannelDigest[] = scanResults
335
- .filter(
336
- (r): r is PromiseFulfilledResult<ChannelDigest> =>
337
- r.status === "fulfilled",
338
- )
339
- .map((r) => r.value)
340
- .filter((d) => d.messageCount > 0 || d.error);
341
-
342
- const skippedCount = scanResults.filter(
343
- (r) => r.status === "rejected",
344
- ).length;
345
-
346
- if (format === "blocks") {
347
- const blocks = buildBlockKitOutput(
348
- digests,
349
- hoursBack,
350
- channelsToScan.length,
351
- skippedCount,
352
- );
353
- return ok(JSON.stringify({ blocks }, null, 2));
354
- }
332
+ const scanResults = await Promise.allSettled(
333
+ channelsToScan.map((conv) =>
334
+ scanChannel(connection, conv, oldestTs, includeThreads),
335
+ ),
336
+ );
355
337
 
356
- const result = {
357
- scannedChannels: digests.length,
358
- totalChannelsAttempted: channelsToScan.length,
359
- skippedDueToErrors: skippedCount,
360
- failedLookups,
338
+ const digests: ChannelDigest[] = scanResults
339
+ .filter(
340
+ (r): r is PromiseFulfilledResult<ChannelDigest> =>
341
+ r.status === "fulfilled",
342
+ )
343
+ .map((r) => r.value)
344
+ .filter((d) => d.messageCount > 0 || d.error);
345
+
346
+ const skippedCount = scanResults.filter(
347
+ (r) => r.status === "rejected",
348
+ ).length;
349
+
350
+ if (format === "blocks") {
351
+ const blocks = buildBlockKitOutput(
352
+ digests,
361
353
  hoursBack,
362
- channels: digests,
363
- };
354
+ channelsToScan.length,
355
+ skippedCount,
356
+ );
357
+ return ok(JSON.stringify({ blocks }, null, 2));
358
+ }
364
359
 
365
- return ok(JSON.stringify(result, null, 2));
366
- });
360
+ const result = {
361
+ scannedChannels: digests.length,
362
+ totalChannelsAttempted: channelsToScan.length,
363
+ skippedDueToErrors: skippedCount,
364
+ failedLookups,
365
+ hoursBack,
366
+ channels: digests,
367
+ };
368
+
369
+ return ok(JSON.stringify(result, null, 2));
367
370
  } catch (e) {
368
371
  return err(e instanceof Error ? e.message : String(e));
369
372
  }
@@ -333,6 +333,12 @@ export function loadConfig(): AssistantConfig {
333
333
  }
334
334
 
335
335
  // Environment variables override everything
336
+ if (process.env.VELLUM_CONFIG_SANDBOX_ENABLED === "false") {
337
+ config.sandbox.enabled = false;
338
+ } else if (process.env.VELLUM_CONFIG_SANDBOX_ENABLED === "true") {
339
+ config.sandbox.enabled = true;
340
+ }
341
+
336
342
  if (process.env.ANTHROPIC_API_KEY) {
337
343
  config.apiKeys.anthropic = process.env.ANTHROPIC_API_KEY;
338
344
  }
@@ -23,6 +23,10 @@ import type {
23
23
  import { allComputerUseTools } from "../tools/computer-use/definitions.js";
24
24
  import { ToolExecutor } from "../tools/executor.js";
25
25
  import { getTool, registerSkillTools } from "../tools/registry.js";
26
+ import {
27
+ injectReasonField,
28
+ REASON_SKIP_SET,
29
+ } from "../tools/schema-transforms.js";
26
30
  import type { Tool, ToolExecutionResult } from "../tools/types.js";
27
31
  import { allUiSurfaceTools } from "../tools/ui-surface/definitions.js";
28
32
  import { getLogger } from "../util/logger.js";
@@ -581,6 +585,8 @@ export class ComputerUseSession {
581
585
  },
582
586
  };
583
587
 
588
+ const toolDefsWithReason = injectReasonField(toolDefs, REASON_SKIP_SET);
589
+
584
590
  const cuConfig = getConfig();
585
591
  const agentLoop = new AgentLoop(
586
592
  compactingProvider,
@@ -590,7 +596,7 @@ export class ComputerUseSession {
590
596
  maxInputTokens: cuConfig.contextWindow.maxInputTokens,
591
597
  toolChoice: { type: "any" },
592
598
  },
593
- toolDefs,
599
+ toolDefsWithReason,
594
600
  toolExecutor,
595
601
  );
596
602
 
@@ -83,8 +83,8 @@ const FOLLOWUP_CONVERSATION_MAX_TOKENS = 300;
83
83
  const FOLLOWUP_CONVERSATION_SYSTEM_PROMPT =
84
84
  "You are an assistant helping route a guardian's reply to a post-timeout follow-up message. " +
85
85
  "A voice caller asked a question, but the call timed out before the guardian could answer. " +
86
- "The guardian has now replied late, and was asked whether they want to call the caller back, " +
87
- "send them a text message, or skip it. " +
86
+ "The guardian has now replied late, and was asked whether they want to call the caller back " +
87
+ "or skip it. " +
88
88
  "Analyze the guardian's latest reply to determine their intent. " +
89
89
  "When uncertain, default to keep_pending and ask a clarifying question. " +
90
90
  "Always provide a natural, helpful reply along with your decision.";
@@ -101,10 +101,10 @@ const FOLLOWUP_CONVERSATION_TOOL_SCHEMA = {
101
101
  properties: {
102
102
  disposition: {
103
103
  type: "string",
104
- enum: ["call_back", "message_back", "decline", "keep_pending"],
104
+ enum: ["call_back", "decline", "keep_pending"],
105
105
  description:
106
106
  "The guardian's intent: call_back to call the original caller, " +
107
- "message_back to send a text message, decline to skip the follow-up, " +
107
+ "decline to skip the follow-up, " +
108
108
  "keep_pending if the intent is unclear (ask for clarification).",
109
109
  },
110
110
  replyText: {
@@ -118,7 +118,6 @@ const FOLLOWUP_CONVERSATION_TOOL_SCHEMA = {
118
118
 
119
119
  const VALID_FOLLOWUP_DISPOSITIONS: ReadonlySet<string> = new Set([
120
120
  "call_back",
121
- "message_back",
122
121
  "decline",
123
122
  "keep_pending",
124
123
  ]);