@vellumai/assistant 0.4.45 → 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 (236) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/docs/architecture/memory.md +1 -1
  3. package/docs/architecture/scheduling.md +2 -3
  4. package/docs/architecture/security.md +5 -5
  5. package/docs/trusted-contact-access.md +5 -6
  6. package/package.json +4 -1
  7. package/src/__tests__/avatar-e2e.test.ts +18 -219
  8. package/src/__tests__/avatar-generator.test.ts +5 -57
  9. package/src/__tests__/browser-fill-credential.test.ts +5 -2
  10. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
  11. package/src/__tests__/channel-readiness-routes.test.ts +20 -19
  12. package/src/__tests__/cli.test.ts +23 -0
  13. package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
  14. package/src/__tests__/credential-broker-server-use.test.ts +22 -21
  15. package/src/__tests__/credential-broker.test.ts +2 -1
  16. package/src/__tests__/credential-metadata-store.test.ts +240 -18
  17. package/src/__tests__/credential-resolve.test.ts +5 -4
  18. package/src/__tests__/credential-security-e2e.test.ts +8 -8
  19. package/src/__tests__/credential-security-invariants.test.ts +104 -7
  20. package/src/__tests__/credential-vault-unit.test.ts +22 -20
  21. package/src/__tests__/credential-vault.test.ts +284 -12
  22. package/src/__tests__/credentials-cli.test.ts +11 -6
  23. package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
  24. package/src/__tests__/gemini-image-service.test.ts +75 -45
  25. package/src/__tests__/gemini-provider.test.ts +9 -6
  26. package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
  27. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
  28. package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
  29. package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
  30. package/src/__tests__/guardian-grant-minting.test.ts +35 -0
  31. package/src/__tests__/integration-status.test.ts +53 -21
  32. package/src/__tests__/managed-proxy-context.test.ts +5 -3
  33. package/src/__tests__/media-generate-image.test.ts +63 -2
  34. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
  35. package/src/__tests__/messaging-send-tool.test.ts +4 -6
  36. package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
  37. package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
  38. package/src/__tests__/schedule-store.test.ts +1 -1
  39. package/src/__tests__/schema-transforms.test.ts +226 -0
  40. package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
  41. package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
  42. package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
  43. package/src/__tests__/secret-onetime-send.test.ts +5 -3
  44. package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
  45. package/src/__tests__/skills-uninstall.test.ts +2 -2
  46. package/src/__tests__/skills.test.ts +0 -9
  47. package/src/__tests__/slack-channel-config.test.ts +9 -8
  48. package/src/__tests__/slack-share-routes.test.ts +11 -6
  49. package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
  50. package/src/__tests__/twilio-config.test.ts +2 -1
  51. package/src/__tests__/twilio-provider.test.ts +4 -2
  52. package/src/__tests__/twilio-routes.test.ts +5 -4
  53. package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
  54. package/src/approvals/AGENTS.md +1 -1
  55. package/src/calls/call-domain.ts +7 -4
  56. package/src/calls/twilio-config.ts +2 -1
  57. package/src/calls/twilio-provider.ts +2 -1
  58. package/src/calls/twilio-rest.ts +2 -2
  59. package/src/cli/commands/browser-relay.ts +40 -15
  60. package/src/cli/commands/credentials.ts +9 -8
  61. package/src/cli/commands/oauth.ts +1 -1
  62. package/src/cli.ts +3 -2
  63. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
  64. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
  65. package/src/config/bundled-skills/gmail/SKILL.md +4 -4
  66. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
  67. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
  68. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
  69. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
  70. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
  71. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
  72. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
  73. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
  74. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
  75. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
  76. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
  77. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
  78. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
  79. package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
  80. package/src/config/bundled-skills/google-calendar/calendar-client.ts +70 -29
  81. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
  82. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
  83. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
  84. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
  85. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
  86. package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
  87. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
  88. package/src/config/bundled-skills/messaging/SKILL.md +6 -6
  89. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
  90. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
  91. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
  92. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
  93. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
  94. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
  95. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
  96. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
  97. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
  98. package/src/config/bundled-skills/messaging/tools/shared.ts +11 -11
  99. package/src/config/bundled-skills/notifications/SKILL.md +1 -1
  100. package/src/config/bundled-skills/phone-calls/SKILL.md +5 -5
  101. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  102. package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
  103. package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
  104. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
  105. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
  106. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
  107. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
  108. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
  109. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
  110. package/src/config/loader.ts +6 -0
  111. package/src/daemon/computer-use-session.ts +7 -1
  112. package/src/daemon/guardian-action-generators.ts +4 -5
  113. package/src/daemon/handlers/config-slack-channel.ts +37 -20
  114. package/src/daemon/handlers/config-telegram.ts +33 -20
  115. package/src/daemon/lifecycle.ts +9 -1
  116. package/src/daemon/message-types/integrations.ts +1 -0
  117. package/src/daemon/ride-shotgun-handler.ts +3 -1
  118. package/src/daemon/session-messaging.ts +3 -1
  119. package/src/daemon/session-tool-setup.ts +18 -2
  120. package/src/daemon/session.ts +1 -1
  121. package/src/email/providers/index.ts +2 -1
  122. package/src/instrument.ts +15 -1
  123. package/src/media/app-icon-generator.ts +30 -4
  124. package/src/media/avatar-router.ts +28 -62
  125. package/src/media/gemini-image-service.ts +28 -2
  126. package/src/memory/canonical-guardian-store.ts +1 -1
  127. package/src/memory/guardian-action-store.ts +1 -1
  128. package/src/memory/schema/guardian.ts +1 -1
  129. package/src/messaging/provider.ts +16 -10
  130. package/src/messaging/providers/gmail/adapter.ts +40 -23
  131. package/src/messaging/providers/gmail/client.ts +203 -122
  132. package/src/messaging/providers/gmail/people-client.ts +26 -18
  133. package/src/messaging/providers/slack/adapter.ts +29 -19
  134. package/src/messaging/providers/slack/client.ts +265 -78
  135. package/src/messaging/providers/telegram-bot/adapter.ts +5 -4
  136. package/src/messaging/providers/whatsapp/adapter.ts +6 -3
  137. package/src/messaging/registry.ts +2 -1
  138. package/src/oauth/byo-connection.test.ts +436 -0
  139. package/src/oauth/byo-connection.ts +112 -0
  140. package/src/oauth/connect-orchestrator.ts +27 -0
  141. package/src/oauth/connection-resolver.ts +34 -0
  142. package/src/oauth/connection.ts +38 -0
  143. package/src/oauth/platform-connection.test.ts +163 -0
  144. package/src/oauth/platform-connection.ts +110 -0
  145. package/src/oauth/provider-base-urls.ts +21 -0
  146. package/src/oauth/provider-profiles.ts +1 -1
  147. package/src/oauth/token-persistence.ts +20 -20
  148. package/src/permissions/checker.ts +6 -1
  149. package/src/prompts/system-prompt.ts +52 -15
  150. package/src/prompts/templates/BOOTSTRAP.md +1 -1
  151. package/src/providers/gemini/client.ts +15 -6
  152. package/src/providers/managed-proxy/constants.ts +2 -2
  153. package/src/providers/managed-proxy/context.ts +5 -1
  154. package/src/providers/ratelimit.ts +17 -0
  155. package/src/providers/registry.ts +2 -2
  156. package/src/runtime/AGENTS.md +18 -1
  157. package/src/runtime/auth/route-policy.ts +1 -0
  158. package/src/runtime/channel-invite-transports/telegram.ts +2 -1
  159. package/src/runtime/channel-readiness-service.ts +168 -195
  160. package/src/runtime/channel-readiness-types.ts +4 -0
  161. package/src/runtime/guardian-action-conversation-turn.ts +1 -3
  162. package/src/runtime/guardian-action-followup-executor.ts +1 -2
  163. package/src/runtime/guardian-action-message-composer.ts +3 -23
  164. package/src/runtime/http-server.ts +9 -4
  165. package/src/runtime/http-types.ts +0 -1
  166. package/src/runtime/middleware/rate-limiter.ts +74 -20
  167. package/src/runtime/middleware/twilio-validation.ts +1 -3
  168. package/src/runtime/routes/channel-readiness-routes.ts +2 -0
  169. package/src/runtime/routes/diagnostics-routes.ts +11 -9
  170. package/src/runtime/routes/guardian-approval-interception.ts +20 -5
  171. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +71 -25
  172. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -5
  173. package/src/runtime/routes/integrations/slack/share.ts +3 -2
  174. package/src/runtime/routes/integrations/twilio.ts +6 -5
  175. package/src/runtime/routes/secret-routes.ts +3 -2
  176. package/src/runtime/routes/settings-routes.ts +75 -17
  177. package/src/runtime/telegram-streaming-delivery.test.ts +132 -0
  178. package/src/runtime/telegram-streaming-delivery.ts +11 -1
  179. package/src/schedule/integration-status.ts +5 -4
  180. package/src/security/credential-key.ts +170 -0
  181. package/src/security/token-manager.ts +36 -7
  182. package/src/tools/apps/definitions.ts +0 -5
  183. package/src/tools/assets/materialize.ts +0 -5
  184. package/src/tools/assets/search.ts +0 -5
  185. package/src/tools/browser/headless-browser.ts +1 -67
  186. package/src/tools/claude-code/claude-code.ts +0 -5
  187. package/src/tools/computer-use/request-computer-control.ts +0 -5
  188. package/src/tools/credentials/broker.ts +6 -4
  189. package/src/tools/credentials/metadata-store.ts +72 -20
  190. package/src/tools/credentials/resolve.ts +2 -1
  191. package/src/tools/credentials/vault.ts +77 -16
  192. package/src/tools/filesystem/edit.ts +1 -6
  193. package/src/tools/filesystem/read.ts +0 -5
  194. package/src/tools/filesystem/write.ts +1 -6
  195. package/src/tools/host-filesystem/edit.ts +1 -6
  196. package/src/tools/host-filesystem/read.ts +1 -6
  197. package/src/tools/host-filesystem/write.ts +1 -6
  198. package/src/tools/mcp/mcp-tool-factory.ts +18 -1
  199. package/src/tools/memory/definitions.ts +0 -5
  200. package/src/tools/network/web-fetch.ts +0 -5
  201. package/src/tools/network/web-search.ts +0 -5
  202. package/src/tools/schema-transforms.ts +99 -0
  203. package/src/tools/skills/load.ts +0 -5
  204. package/src/tools/swarm/delegate.ts +0 -5
  205. package/src/tools/system/avatar-generator.ts +3 -44
  206. package/src/tools/ui-surface/definitions.ts +0 -15
  207. package/src/tools/watch/screen-watch.ts +0 -5
  208. package/src/version.ts +10 -0
  209. package/src/watcher/providers/github.ts +51 -52
  210. package/src/watcher/providers/gmail.ts +88 -80
  211. package/src/watcher/providers/google-calendar.ts +93 -86
  212. package/src/watcher/providers/linear.ts +87 -93
  213. package/src/__tests__/avatar-router.test.ts +0 -149
  214. package/src/__tests__/managed-avatar-client.test.ts +0 -337
  215. package/src/config/bundled-skills/doordash/SKILL.md +0 -170
  216. package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +0 -205
  217. package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -74
  218. package/src/config/bundled-skills/doordash/doordash-cli.ts +0 -1081
  219. package/src/config/bundled-skills/doordash/doordash-entry.ts +0 -22
  220. package/src/config/bundled-skills/doordash/lib/cart-queries.ts +0 -787
  221. package/src/config/bundled-skills/doordash/lib/client.ts +0 -1069
  222. package/src/config/bundled-skills/doordash/lib/order-queries.ts +0 -85
  223. package/src/config/bundled-skills/doordash/lib/queries.ts +0 -28
  224. package/src/config/bundled-skills/doordash/lib/query-extractor.ts +0 -94
  225. package/src/config/bundled-skills/doordash/lib/search-queries.ts +0 -203
  226. package/src/config/bundled-skills/doordash/lib/session.ts +0 -96
  227. package/src/config/bundled-skills/doordash/lib/shared/errors.ts +0 -61
  228. package/src/config/bundled-skills/doordash/lib/shared/network-recorder.ts +0 -380
  229. package/src/config/bundled-skills/doordash/lib/shared/platform.ts +0 -55
  230. package/src/config/bundled-skills/doordash/lib/shared/recording-store.ts +0 -43
  231. package/src/config/bundled-skills/doordash/lib/shared/recording-types.ts +0 -49
  232. package/src/config/bundled-skills/doordash/lib/shared/truncate.ts +0 -6
  233. package/src/config/bundled-skills/doordash/lib/store-queries.ts +0 -246
  234. package/src/config/bundled-skills/doordash/lib/types.ts +0 -367
  235. package/src/media/avatar-types.ts +0 -53
  236. package/src/media/managed-avatar-client.ts +0 -225
@@ -5,8 +5,7 @@ import {
5
5
  } from "../../../../messaging/providers/gmail/client.js";
6
6
  import { buildMultipartMime } from "../../../../messaging/providers/gmail/mime-builder.js";
7
7
  import type { GmailMessagePart } from "../../../../messaging/providers/gmail/types.js";
8
- import { getMessagingProvider } from "../../../../messaging/registry.js";
9
- import { withValidToken } from "../../../../security/token-manager.js";
8
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
10
9
  import type {
11
10
  ToolContext,
12
11
  ToolExecutionResult,
@@ -76,66 +75,68 @@ export async function run(
76
75
  if (!forwardTo) return err("to is required.");
77
76
 
78
77
  try {
79
- const provider = getMessagingProvider("gmail");
80
- return await withValidToken(provider.credentialService, async (token) => {
81
- const message = await getMessage(token, messageId, "full");
82
- const headers = message.payload?.headers ?? [];
83
- const originalFrom = extractHeader(headers, "From");
84
- const originalDate = extractHeader(headers, "Date");
85
- const originalSubject = extractHeader(headers, "Subject");
86
- const originalBody = extractPlainTextBody(message.payload);
78
+ const connection = resolveOAuthConnection("integration:gmail");
79
+ const message = await getMessage(connection, messageId, "full");
80
+ const headers = message.payload?.headers ?? [];
81
+ const originalFrom = extractHeader(headers, "From");
82
+ const originalDate = extractHeader(headers, "Date");
83
+ const originalSubject = extractHeader(headers, "Subject");
84
+ const originalBody = extractPlainTextBody(message.payload);
87
85
 
88
- const forwardHeader = [
89
- additionalText ? `${additionalText}\n\n` : "",
90
- "---------- Forwarded message ----------",
91
- `From: ${originalFrom}`,
92
- `Date: ${originalDate}`,
93
- `Subject: ${originalSubject}`,
94
- "",
95
- originalBody,
96
- ].join("\n");
86
+ const forwardHeader = [
87
+ additionalText ? `${additionalText}\n\n` : "",
88
+ "---------- Forwarded message ----------",
89
+ `From: ${originalFrom}`,
90
+ `Date: ${originalDate}`,
91
+ `Subject: ${originalSubject}`,
92
+ "",
93
+ originalBody,
94
+ ].join("\n");
97
95
 
98
- const subject = originalSubject.startsWith("Fwd:")
99
- ? originalSubject
100
- : `Fwd: ${originalSubject}`;
96
+ const subject = originalSubject.startsWith("Fwd:")
97
+ ? originalSubject
98
+ : `Fwd: ${originalSubject}`;
101
99
 
102
- // Collect and download attachments from the original message
103
- const attachmentRefs = collectAttachmentRefs(message.payload?.parts);
104
- const attachments = await Promise.all(
105
- attachmentRefs.map(async (ref) => {
106
- const att = await getAttachment(token, messageId, ref.attachmentId);
107
- const data = Buffer.from(
108
- att.data.replace(/-/g, "+").replace(/_/g, "/"),
109
- "base64",
110
- );
111
- return { filename: ref.filename, mimeType: ref.mimeType, data };
112
- }),
113
- );
114
-
115
- if (attachments.length > 0) {
116
- const raw = buildMultipartMime({
117
- to: forwardTo,
118
- subject,
119
- body: forwardHeader,
120
- attachments,
121
- });
122
- const draft = await createDraftRaw(token, raw);
123
- return ok(
124
- `Forward draft created to ${forwardTo} with ${attachments.length} attachment(s) (Draft ID: ${draft.id}). Review in Gmail Drafts, then tell me to send it or send it yourself.`,
100
+ // Collect and download attachments from the original message
101
+ const attachmentRefs = collectAttachmentRefs(message.payload?.parts);
102
+ const attachments = await Promise.all(
103
+ attachmentRefs.map(async (ref) => {
104
+ const att = await getAttachment(
105
+ connection,
106
+ messageId,
107
+ ref.attachmentId,
108
+ );
109
+ const data = Buffer.from(
110
+ att.data.replace(/-/g, "+").replace(/_/g, "/"),
111
+ "base64",
125
112
  );
126
- }
113
+ return { filename: ref.filename, mimeType: ref.mimeType, data };
114
+ }),
115
+ );
127
116
 
117
+ if (attachments.length > 0) {
128
118
  const raw = buildMultipartMime({
129
119
  to: forwardTo,
130
120
  subject,
131
121
  body: forwardHeader,
132
- attachments: [],
122
+ attachments,
133
123
  });
134
- const draft = await createDraftRaw(token, raw);
124
+ const draft = await createDraftRaw(connection, raw);
135
125
  return ok(
136
- `Forward draft created to ${forwardTo} (Draft ID: ${draft.id}). Review in Gmail Drafts, then tell me to send it or send it yourself.`,
126
+ `Forward draft created to ${forwardTo} with ${attachments.length} attachment(s) (Draft ID: ${draft.id}). Review in Gmail Drafts, then tell me to send it or send it yourself.`,
137
127
  );
128
+ }
129
+
130
+ const raw = buildMultipartMime({
131
+ to: forwardTo,
132
+ subject,
133
+ body: forwardHeader,
134
+ attachments: [],
138
135
  });
136
+ const draft = await createDraftRaw(connection, raw);
137
+ return ok(
138
+ `Forward draft created to ${forwardTo} (Draft ID: ${draft.id}). Review in Gmail Drafts, then tell me to send it or send it yourself.`,
139
+ );
139
140
  } catch (e) {
140
141
  return err(e instanceof Error ? e.message : String(e));
141
142
  }
@@ -2,8 +2,7 @@ import {
2
2
  batchModifyMessages,
3
3
  modifyMessage,
4
4
  } from "../../../../messaging/providers/gmail/client.js";
5
- import { getMessagingProvider } from "../../../../messaging/registry.js";
6
- import { withValidToken } from "../../../../security/token-manager.js";
5
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
7
6
  import type {
8
7
  ToolContext,
9
8
  ToolExecutionResult,
@@ -21,14 +20,12 @@ export async function run(
21
20
 
22
21
  if (messageIds && messageIds.length > 0) {
23
22
  try {
24
- const provider = getMessagingProvider("gmail");
25
- return await withValidToken(provider.credentialService, async (token) => {
26
- await batchModifyMessages(token, messageIds, {
27
- addLabelIds,
28
- removeLabelIds,
29
- });
30
- return ok(`Labels updated on ${messageIds.length} message(s).`);
23
+ const connection = resolveOAuthConnection("integration:gmail");
24
+ await batchModifyMessages(connection, messageIds, {
25
+ addLabelIds,
26
+ removeLabelIds,
31
27
  });
28
+ return ok(`Labels updated on ${messageIds.length} message(s).`);
32
29
  } catch (e) {
33
30
  return err(e instanceof Error ? e.message : String(e));
34
31
  }
@@ -36,11 +33,12 @@ export async function run(
36
33
 
37
34
  if (messageId) {
38
35
  try {
39
- const provider = getMessagingProvider("gmail");
40
- return await withValidToken(provider.credentialService, async (token) => {
41
- await modifyMessage(token, messageId, { addLabelIds, removeLabelIds });
42
- return ok("Labels updated.");
36
+ const connection = resolveOAuthConnection("integration:gmail");
37
+ await modifyMessage(connection, messageId, {
38
+ addLabelIds,
39
+ removeLabelIds,
43
40
  });
41
+ return ok("Labels updated.");
44
42
  } catch (e) {
45
43
  return err(e instanceof Error ? e.message : String(e));
46
44
  }
@@ -3,8 +3,7 @@ import {
3
3
  listMessages,
4
4
  } from "../../../../messaging/providers/gmail/client.js";
5
5
  import type { GmailMessage } from "../../../../messaging/providers/gmail/types.js";
6
- import { getMessagingProvider } from "../../../../messaging/registry.js";
7
- import { withValidToken } from "../../../../security/token-manager.js";
6
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
8
7
  import type {
9
8
  ToolContext,
10
9
  ToolExecutionResult,
@@ -56,159 +55,162 @@ export async function run(
56
55
  const query = `in:inbox -has:unsubscribe newer_than:${timeRange}`;
57
56
 
58
57
  try {
59
- const provider = getMessagingProvider("gmail");
60
- return await withValidToken(provider.credentialService, async (token) => {
61
- // Pipeline: fire metadata fetches for each page of IDs as they arrive
62
- const allMessageIds: string[] = [];
63
- const fetchPromises: Promise<GmailMessage[]>[] = [];
64
- let pageToken: string | undefined = inputPageToken;
65
- let truncated = false;
66
- let timeBudgetExceeded = false;
67
- const metadataHeaders = ["From", "Subject", "Date"];
68
- const startTime = Date.now();
69
- const TIME_BUDGET_MS = 90_000;
70
-
71
- while (allMessageIds.length < maxMessages) {
72
- if (Date.now() - startTime > TIME_BUDGET_MS) {
73
- timeBudgetExceeded = true;
74
- truncated = true;
75
- break;
76
- }
77
- const pageSize = Math.min(100, maxMessages - allMessageIds.length);
78
- const listResp = await listMessages(token, query, pageSize, pageToken);
79
- const ids = (listResp.messages ?? []).map((m) => m.id);
80
- if (ids.length === 0) break;
81
- allMessageIds.push(...ids);
82
- fetchPromises.push(
83
- batchGetMessages(
84
- token,
85
- ids,
86
- "metadata",
87
- metadataHeaders,
88
- "id,internalDate,payload/headers",
89
- ),
90
- );
91
- pageToken = listResp.nextPageToken ?? undefined;
92
- if (!pageToken) break;
93
- }
94
-
95
- if (allMessageIds.length >= maxMessages && pageToken) {
58
+ const connection = resolveOAuthConnection("integration:gmail");
59
+ // Pipeline: fire metadata fetches for each page of IDs as they arrive
60
+ const allMessageIds: string[] = [];
61
+ const fetchPromises: Promise<GmailMessage[]>[] = [];
62
+ let pageToken: string | undefined = inputPageToken;
63
+ let truncated = false;
64
+ let timeBudgetExceeded = false;
65
+ const metadataHeaders = ["From", "Subject", "Date"];
66
+ const startTime = Date.now();
67
+ const TIME_BUDGET_MS = 90_000;
68
+
69
+ while (allMessageIds.length < maxMessages) {
70
+ if (Date.now() - startTime > TIME_BUDGET_MS) {
71
+ timeBudgetExceeded = true;
96
72
  truncated = true;
73
+ break;
97
74
  }
75
+ const pageSize = Math.min(100, maxMessages - allMessageIds.length);
76
+ const listResp = await listMessages(
77
+ connection,
78
+ query,
79
+ pageSize,
80
+ pageToken,
81
+ );
82
+ const ids = (listResp.messages ?? []).map((m) => m.id);
83
+ if (ids.length === 0) break;
84
+ allMessageIds.push(...ids);
85
+ fetchPromises.push(
86
+ batchGetMessages(
87
+ connection,
88
+ ids,
89
+ "metadata",
90
+ metadataHeaders,
91
+ "id,internalDate,payload/headers",
92
+ ),
93
+ );
94
+ pageToken = listResp.nextPageToken ?? undefined;
95
+ if (!pageToken) break;
96
+ }
97
+
98
+ if (allMessageIds.length >= maxMessages && pageToken) {
99
+ truncated = true;
100
+ }
98
101
 
99
- if (allMessageIds.length === 0) {
100
- return ok(
101
- JSON.stringify({
102
- senders: [],
103
- total_scanned: 0,
104
- note: "No emails found matching the query.",
105
- }),
106
- );
102
+ if (allMessageIds.length === 0) {
103
+ return ok(
104
+ JSON.stringify({
105
+ senders: [],
106
+ total_scanned: 0,
107
+ note: "No emails found matching the query.",
108
+ }),
109
+ );
110
+ }
111
+
112
+ const messages = (await Promise.all(fetchPromises)).flat();
113
+
114
+ // Aggregate all fetched messages by sender
115
+ const senderMap = new Map<string, OutreachSenderAggregation>();
116
+
117
+ for (const msg of messages) {
118
+ const headers = msg.payload?.headers ?? [];
119
+ const fromHeader =
120
+ headers.find((h) => h.name.toLowerCase() === "from")?.value ?? "";
121
+ const subject =
122
+ headers.find((h) => h.name.toLowerCase() === "subject")?.value ?? "";
123
+ const dateStr =
124
+ headers.find((h) => h.name.toLowerCase() === "date")?.value ?? "";
125
+
126
+ const { displayName, email } = parseFrom(fromHeader);
127
+ if (!email) continue;
128
+
129
+ let agg = senderMap.get(email);
130
+ if (!agg) {
131
+ agg = {
132
+ displayName,
133
+ email,
134
+ messageCount: 0,
135
+ newestMessageId: msg.id,
136
+ oldestDate: dateStr,
137
+ newestDate: dateStr,
138
+ messageIds: [],
139
+ hasMore: false,
140
+ sampleSubjects: [],
141
+ };
142
+ senderMap.set(email, agg);
107
143
  }
108
144
 
109
- const messages = (await Promise.all(fetchPromises)).flat();
110
-
111
- // Aggregate all fetched messages by sender
112
- const senderMap = new Map<string, OutreachSenderAggregation>();
113
-
114
- for (const msg of messages) {
115
- const headers = msg.payload?.headers ?? [];
116
- const fromHeader =
117
- headers.find((h) => h.name.toLowerCase() === "from")?.value ?? "";
118
- const subject =
119
- headers.find((h) => h.name.toLowerCase() === "subject")?.value ?? "";
120
- const dateStr =
121
- headers.find((h) => h.name.toLowerCase() === "date")?.value ?? "";
122
-
123
- const { displayName, email } = parseFrom(fromHeader);
124
- if (!email) continue;
125
-
126
- let agg = senderMap.get(email);
127
- if (!agg) {
128
- agg = {
129
- displayName,
130
- email,
131
- messageCount: 0,
132
- newestMessageId: msg.id,
133
- oldestDate: dateStr,
134
- newestDate: dateStr,
135
- messageIds: [],
136
- hasMore: false,
137
- sampleSubjects: [],
138
- };
139
- senderMap.set(email, agg);
140
- }
141
-
142
- agg.messageCount++;
143
-
144
- if (!agg.displayName && displayName) agg.displayName = displayName;
145
-
146
- if (agg.messageIds.length < MAX_IDS_PER_SENDER) {
147
- agg.messageIds.push(msg.id);
148
- } else {
149
- agg.hasMore = true;
150
- }
151
-
152
- // Track date range
153
- const msgEpoch = msg.internalDate ? Number(msg.internalDate) : 0;
154
- const oldestEpoch = agg.oldestDate
155
- ? new Date(agg.oldestDate).getTime()
156
- : Infinity;
157
- const newestEpoch = agg.newestDate
158
- ? new Date(agg.newestDate).getTime()
159
- : 0;
160
-
161
- if (msgEpoch > 0 && msgEpoch < oldestEpoch) {
162
- agg.oldestDate = dateStr || agg.oldestDate;
163
- }
164
- if (msgEpoch > newestEpoch) {
165
- agg.newestDate = dateStr || agg.newestDate;
166
- agg.newestMessageId = msg.id;
167
- }
168
-
169
- if (subject && agg.sampleSubjects.length < MAX_SAMPLE_SUBJECTS) {
170
- agg.sampleSubjects.push(subject);
171
- }
145
+ agg.messageCount++;
146
+
147
+ if (!agg.displayName && displayName) agg.displayName = displayName;
148
+
149
+ if (agg.messageIds.length < MAX_IDS_PER_SENDER) {
150
+ agg.messageIds.push(msg.id);
151
+ } else {
152
+ agg.hasMore = true;
172
153
  }
173
154
 
174
- // Sort by message count desc, take top N
175
- const sorted = [...senderMap.values()]
176
- .sort((a, b) => b.messageCount - a.messageCount)
177
- .slice(0, maxSenders);
155
+ // Track date range
156
+ const msgEpoch = msg.internalDate ? Number(msg.internalDate) : 0;
157
+ const oldestEpoch = agg.oldestDate
158
+ ? new Date(agg.oldestDate).getTime()
159
+ : Infinity;
160
+ const newestEpoch = agg.newestDate
161
+ ? new Date(agg.newestDate).getTime()
162
+ : 0;
163
+
164
+ if (msgEpoch > 0 && msgEpoch < oldestEpoch) {
165
+ agg.oldestDate = dateStr || agg.oldestDate;
166
+ }
167
+ if (msgEpoch > newestEpoch) {
168
+ agg.newestDate = dateStr || agg.newestDate;
169
+ agg.newestMessageId = msg.id;
170
+ }
178
171
 
179
- const senders = sorted.map((s) => ({
172
+ if (subject && agg.sampleSubjects.length < MAX_SAMPLE_SUBJECTS) {
173
+ agg.sampleSubjects.push(subject);
174
+ }
175
+ }
176
+
177
+ // Sort by message count desc, take top N
178
+ const sorted = [...senderMap.values()]
179
+ .sort((a, b) => b.messageCount - a.messageCount)
180
+ .slice(0, maxSenders);
181
+
182
+ const senders = sorted.map((s) => ({
183
+ id: Buffer.from(s.email).toString("base64url"),
184
+ display_name: s.displayName || s.email.split("@")[0],
185
+ email: s.email,
186
+ message_count: s.messageCount,
187
+ newest_message_id: s.newestMessageId,
188
+ oldest_date: s.oldestDate,
189
+ newest_date: s.newestDate,
190
+ search_query: `from:${s.email}`,
191
+ sample_subjects: s.sampleSubjects,
192
+ }));
193
+
194
+ // Store message IDs server-side to keep them out of LLM context
195
+ const scanId = storeScanResult(
196
+ sorted.map((s) => ({
180
197
  id: Buffer.from(s.email).toString("base64url"),
181
- display_name: s.displayName || s.email.split("@")[0],
182
- email: s.email,
183
- message_count: s.messageCount,
184
- newest_message_id: s.newestMessageId,
185
- oldest_date: s.oldestDate,
186
- newest_date: s.newestDate,
187
- search_query: `from:${s.email}`,
188
- sample_subjects: s.sampleSubjects,
189
- }));
190
-
191
- // Store message IDs server-side to keep them out of LLM context
192
- const scanId = storeScanResult(
193
- sorted.map((s) => ({
194
- id: Buffer.from(s.email).toString("base64url"),
195
- messageIds: s.messageIds,
196
- newestMessageId: s.newestMessageId,
197
- newestUnsubscribableMessageId: null,
198
- })),
199
- );
200
-
201
- return ok(
202
- JSON.stringify({
203
- scan_id: scanId,
204
- senders,
205
- total_scanned: allMessageIds.length,
206
- ...(truncated ? { truncated: true } : {}),
207
- ...(timeBudgetExceeded ? { time_budget_exceeded: true } : {}),
208
- note: "Scanned inbox for senders without List-Unsubscribe headers (potential cold outreach). Use gmail_archive and gmail_filters for cleanup.",
209
- }),
210
- );
211
- });
198
+ messageIds: s.messageIds,
199
+ newestMessageId: s.newestMessageId,
200
+ newestUnsubscribableMessageId: null,
201
+ })),
202
+ );
203
+
204
+ return ok(
205
+ JSON.stringify({
206
+ scan_id: scanId,
207
+ senders,
208
+ total_scanned: allMessageIds.length,
209
+ ...(truncated ? { truncated: true } : {}),
210
+ ...(timeBudgetExceeded ? { time_budget_exceeded: true } : {}),
211
+ note: "Scanned inbox for senders without List-Unsubscribe headers (potential cold outreach). Use gmail_archive and gmail_filters for cleanup.",
212
+ }),
213
+ );
212
214
  } catch (e) {
213
215
  return err(e instanceof Error ? e.message : String(e));
214
216
  }
@@ -1,6 +1,5 @@
1
1
  import { sendDraft } from "../../../../messaging/providers/gmail/client.js";
2
- import { getMessagingProvider } from "../../../../messaging/registry.js";
3
- import { withValidToken } from "../../../../security/token-manager.js";
2
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
4
3
  import type {
5
4
  ToolContext,
6
5
  ToolExecutionResult,
@@ -15,11 +14,9 @@ export async function run(
15
14
  if (!draftId) return err("draft_id is required.");
16
15
 
17
16
  try {
18
- const provider = getMessagingProvider("gmail");
19
- return await withValidToken(provider.credentialService, async (token) => {
20
- const msg = await sendDraft(token, draftId);
21
- return ok(`Draft sent (Message ID: ${msg.id}).`);
22
- });
17
+ const connection = resolveOAuthConnection("integration:gmail");
18
+ const msg = await sendDraft(connection, draftId);
19
+ return ok(`Draft sent (Message ID: ${msg.id}).`);
23
20
  } catch (e) {
24
21
  return err(e instanceof Error ? e.message : String(e));
25
22
  }