@vellumai/assistant 0.4.46 → 0.4.49

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 (382) hide show
  1. package/ARCHITECTURE.md +7 -7
  2. package/README.md +2 -23
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/architecture/security.md +5 -5
  6. package/docs/runbook-trusted-contacts.md +3 -8
  7. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  8. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  9. package/package.json +1 -1
  10. package/src/__tests__/actor-token-service.test.ts +0 -1
  11. package/src/__tests__/anthropic-provider.test.ts +156 -0
  12. package/src/__tests__/approval-cascade.test.ts +810 -0
  13. package/src/__tests__/approval-primitive.test.ts +0 -1
  14. package/src/__tests__/approval-routes-http.test.ts +2 -0
  15. package/src/__tests__/assistant-attachments.test.ts +12 -34
  16. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  17. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  18. package/src/__tests__/browser-fill-credential.test.ts +5 -2
  19. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  20. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
  21. package/src/__tests__/channel-guardian.test.ts +0 -2
  22. package/src/__tests__/channel-readiness-routes.test.ts +35 -25
  23. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  24. package/src/__tests__/checker.test.ts +9 -29
  25. package/src/__tests__/cli.test.ts +23 -0
  26. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  27. package/src/__tests__/computer-use-tools.test.ts +2 -19
  28. package/src/__tests__/config-watcher.test.ts +0 -1
  29. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  30. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  31. package/src/__tests__/context-token-estimator.test.ts +196 -13
  32. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  33. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  34. package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  36. package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
  37. package/src/__tests__/credential-broker-server-use.test.ts +22 -21
  38. package/src/__tests__/credential-broker.test.ts +2 -1
  39. package/src/__tests__/credential-metadata-store.test.ts +239 -26
  40. package/src/__tests__/credential-resolve.test.ts +5 -4
  41. package/src/__tests__/credential-security-e2e.test.ts +8 -8
  42. package/src/__tests__/credential-security-invariants.test.ts +111 -7
  43. package/src/__tests__/credential-vault-unit.test.ts +287 -54
  44. package/src/__tests__/credential-vault.test.ts +406 -12
  45. package/src/__tests__/credentials-cli.test.ts +82 -6
  46. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  47. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  48. package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
  49. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  50. package/src/__tests__/gemini-image-service.test.ts +75 -45
  51. package/src/__tests__/gemini-provider.test.ts +9 -6
  52. package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
  53. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
  54. package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
  55. package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
  56. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  57. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  58. package/src/__tests__/guardian-grant-minting.test.ts +35 -0
  59. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  60. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  61. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  62. package/src/__tests__/heartbeat-service.test.ts +0 -1
  63. package/src/__tests__/host-cu-proxy.test.ts +629 -0
  64. package/src/__tests__/host-shell-tool.test.ts +27 -15
  65. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  66. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  67. package/src/__tests__/integration-status.test.ts +38 -25
  68. package/src/__tests__/intent-routing.test.ts +0 -1
  69. package/src/__tests__/invite-routes-http.test.ts +10 -9
  70. package/src/__tests__/keychain-broker-client.test.ts +11 -43
  71. package/src/__tests__/managed-proxy-context.test.ts +5 -3
  72. package/src/__tests__/media-generate-image.test.ts +63 -2
  73. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
  74. package/src/__tests__/messaging-send-tool.test.ts +4 -6
  75. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  76. package/src/__tests__/oauth-cli.test.ts +373 -14
  77. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  78. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  79. package/src/__tests__/oauth-store.test.ts +756 -0
  80. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  81. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  82. package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
  83. package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
  84. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  85. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  86. package/src/__tests__/recording-handler.test.ts +3 -4
  87. package/src/__tests__/registry.test.ts +2 -2
  88. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  89. package/src/__tests__/schedule-store.test.ts +0 -1
  90. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  91. package/src/__tests__/schema-transforms.test.ts +226 -0
  92. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  93. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  94. package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
  95. package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
  96. package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
  97. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  98. package/src/__tests__/secret-onetime-send.test.ts +5 -3
  99. package/src/__tests__/send-endpoint-busy.test.ts +21 -6
  100. package/src/__tests__/sequence-store.test.ts +0 -1
  101. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  102. package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
  103. package/src/__tests__/skill-include-graph.test.ts +66 -0
  104. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  105. package/src/__tests__/skill-load-tool.test.ts +149 -1
  106. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  107. package/src/__tests__/skills-uninstall.test.ts +3 -3
  108. package/src/__tests__/skills.test.ts +3 -12
  109. package/src/__tests__/slack-channel-config.test.ts +76 -11
  110. package/src/__tests__/slack-share-routes.test.ts +17 -14
  111. package/src/__tests__/system-prompt.test.ts +0 -1
  112. package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
  113. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  114. package/src/__tests__/terminal-tools.test.ts +4 -3
  115. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  116. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  117. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  118. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  119. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  120. package/src/__tests__/tool-executor.test.ts +0 -1
  121. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  122. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  123. package/src/__tests__/trust-store.test.ts +1 -22
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  126. package/src/__tests__/twilio-config.test.ts +2 -1
  127. package/src/__tests__/twilio-provider.test.ts +4 -2
  128. package/src/__tests__/twilio-routes.test.ts +5 -20
  129. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  130. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  131. package/src/agent/ax-tree-compaction.test.ts +235 -0
  132. package/src/agent/loop.ts +76 -130
  133. package/src/calls/call-domain.ts +8 -10
  134. package/src/calls/relay-server.ts +9 -13
  135. package/src/calls/twilio-config.ts +4 -8
  136. package/src/calls/twilio-provider.ts +2 -1
  137. package/src/calls/twilio-rest.ts +2 -1
  138. package/src/calls/twilio-routes.ts +1 -2
  139. package/src/calls/voice-ingress-preflight.ts +1 -1
  140. package/src/cli/commands/browser-relay.ts +46 -15
  141. package/src/cli/commands/completions.ts +0 -3
  142. package/src/cli/commands/credentials.ts +110 -23
  143. package/src/cli/commands/oauth/apps.ts +255 -0
  144. package/src/cli/commands/oauth/connections.ts +299 -0
  145. package/src/cli/commands/oauth/index.ts +52 -0
  146. package/src/cli/commands/oauth/providers.ts +242 -0
  147. package/src/cli/commands/skills.ts +4 -338
  148. package/src/cli/program.ts +1 -5
  149. package/src/cli/reference.ts +1 -3
  150. package/src/cli.ts +3 -2
  151. package/src/config/assistant-feature-flags.ts +0 -3
  152. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  153. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
  154. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  155. package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
  156. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
  157. package/src/config/bundled-skills/gmail/SKILL.md +4 -4
  158. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
  159. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
  160. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
  161. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
  162. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
  163. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
  164. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
  165. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
  166. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
  167. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
  168. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
  169. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
  170. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
  171. package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
  172. package/src/config/bundled-skills/google-calendar/calendar-client.ts +90 -44
  173. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
  174. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
  175. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
  176. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
  177. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
  178. package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
  179. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
  180. package/src/config/bundled-skills/messaging/SKILL.md +6 -6
  181. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
  182. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
  183. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
  184. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
  185. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
  186. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
  187. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
  188. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
  189. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
  190. package/src/config/bundled-skills/messaging/tools/shared.ts +12 -15
  191. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  192. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  193. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  194. package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
  195. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
  196. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
  197. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
  198. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
  199. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
  200. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
  201. package/src/config/env-registry.ts +14 -83
  202. package/src/config/env.ts +11 -50
  203. package/src/config/feature-flag-registry.json +16 -16
  204. package/src/config/schema.ts +3 -1
  205. package/src/config/skills.ts +21 -2
  206. package/src/context/image-dimensions.ts +229 -0
  207. package/src/context/token-estimator.ts +75 -12
  208. package/src/context/window-manager.ts +49 -10
  209. package/src/daemon/assistant-attachments.ts +1 -13
  210. package/src/daemon/guardian-action-generators.ts +4 -5
  211. package/src/daemon/handlers/config-ingress.ts +8 -33
  212. package/src/daemon/handlers/config-slack-channel.ts +76 -56
  213. package/src/daemon/handlers/config-telegram.ts +53 -24
  214. package/src/daemon/handlers/sessions.ts +10 -24
  215. package/src/daemon/handlers/shared.ts +0 -130
  216. package/src/daemon/host-cu-proxy.ts +401 -0
  217. package/src/daemon/lifecycle.ts +39 -63
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/computer-use.ts +2 -119
  220. package/src/daemon/message-types/host-cu.ts +19 -0
  221. package/src/daemon/message-types/integrations.ts +1 -0
  222. package/src/daemon/message-types/messages.ts +3 -0
  223. package/src/daemon/server.ts +14 -21
  224. package/src/daemon/session-agent-loop-handlers.ts +2 -0
  225. package/src/daemon/session-attachments.ts +1 -2
  226. package/src/daemon/session-messaging.ts +3 -1
  227. package/src/daemon/session-slash.ts +1 -1
  228. package/src/daemon/session-surfaces.ts +40 -28
  229. package/src/daemon/session-tool-setup.ts +20 -11
  230. package/src/daemon/session.ts +139 -16
  231. package/src/daemon/tool-side-effects.ts +2 -8
  232. package/src/daemon/watch-handler.ts +2 -2
  233. package/src/email/providers/index.ts +2 -1
  234. package/src/events/tool-metrics-listener.ts +2 -2
  235. package/src/hooks/manager.ts +1 -4
  236. package/src/inbound/public-ingress-urls.ts +7 -7
  237. package/src/instrument.ts +15 -1
  238. package/src/logfire.ts +16 -5
  239. package/src/media/app-icon-generator.ts +30 -4
  240. package/src/media/avatar-router.ts +26 -3
  241. package/src/media/gemini-image-service.ts +28 -2
  242. package/src/memory/conversation-key-store.ts +21 -0
  243. package/src/memory/db-init.ts +4 -0
  244. package/src/memory/guardian-action-store.ts +1 -1
  245. package/src/memory/migrations/149-oauth-tables.ts +60 -0
  246. package/src/memory/migrations/index.ts +1 -0
  247. package/src/memory/schema/guardian.ts +1 -1
  248. package/src/memory/schema/index.ts +1 -0
  249. package/src/memory/schema/oauth.ts +65 -0
  250. package/src/messaging/provider.ts +19 -13
  251. package/src/messaging/providers/gmail/adapter.ts +40 -23
  252. package/src/messaging/providers/gmail/client.ts +283 -122
  253. package/src/messaging/providers/gmail/people-client.ts +32 -24
  254. package/src/messaging/providers/slack/adapter.ts +29 -19
  255. package/src/messaging/providers/slack/client.ts +265 -78
  256. package/src/messaging/providers/telegram-bot/adapter.ts +19 -18
  257. package/src/messaging/providers/whatsapp/adapter.ts +17 -11
  258. package/src/messaging/registry.ts +2 -31
  259. package/src/notifications/copy-composer.ts +0 -5
  260. package/src/notifications/signal.ts +4 -5
  261. package/src/oauth/byo-connection.test.ts +537 -0
  262. package/src/oauth/byo-connection.ts +128 -0
  263. package/src/oauth/connect-orchestrator.ts +139 -56
  264. package/src/oauth/connect-types.ts +17 -23
  265. package/src/oauth/connection-resolver.ts +58 -0
  266. package/src/oauth/connection.ts +38 -0
  267. package/src/oauth/manual-token-connection.ts +104 -0
  268. package/src/oauth/oauth-store.ts +496 -0
  269. package/src/oauth/platform-connection.test.ts +192 -0
  270. package/src/oauth/platform-connection.ts +111 -0
  271. package/src/oauth/provider-behaviors.ts +124 -0
  272. package/src/oauth/scope-policy.ts +9 -2
  273. package/src/oauth/seed-providers.ts +161 -0
  274. package/src/oauth/token-persistence.ts +74 -78
  275. package/src/permissions/checker.ts +8 -4
  276. package/src/permissions/defaults.ts +0 -1
  277. package/src/permissions/prompter.ts +10 -1
  278. package/src/permissions/trust-store.ts +13 -0
  279. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  280. package/src/prompts/system-prompt.ts +70 -45
  281. package/src/providers/anthropic/client.ts +133 -24
  282. package/src/providers/gemini/client.ts +15 -6
  283. package/src/providers/managed-proxy/constants.ts +2 -2
  284. package/src/providers/managed-proxy/context.ts +5 -1
  285. package/src/providers/ratelimit.ts +17 -0
  286. package/src/providers/registry.ts +2 -2
  287. package/src/providers/retry.ts +1 -27
  288. package/src/runtime/AGENTS.md +17 -0
  289. package/src/runtime/auth/route-policy.ts +0 -3
  290. package/src/runtime/channel-invite-transports/telegram.ts +2 -1
  291. package/src/runtime/channel-readiness-service.ts +168 -195
  292. package/src/runtime/channel-readiness-types.ts +4 -0
  293. package/src/runtime/channel-reply-delivery.ts +0 -40
  294. package/src/runtime/gateway-client.ts +0 -7
  295. package/src/runtime/guardian-action-conversation-turn.ts +1 -3
  296. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  297. package/src/runtime/guardian-action-message-composer.ts +3 -23
  298. package/src/runtime/http-server.ts +17 -10
  299. package/src/runtime/http-types.ts +2 -3
  300. package/src/runtime/middleware/rate-limiter.ts +74 -20
  301. package/src/runtime/middleware/twilio-validation.ts +1 -11
  302. package/src/runtime/pending-interactions.ts +14 -12
  303. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  304. package/src/runtime/routes/channel-readiness-routes.ts +2 -0
  305. package/src/runtime/routes/conversation-routes.ts +73 -19
  306. package/src/runtime/routes/diagnostics-routes.ts +11 -9
  307. package/src/runtime/routes/events-routes.ts +21 -11
  308. package/src/runtime/routes/guardian-approval-interception.ts +20 -5
  309. package/src/runtime/routes/host-cu-routes.ts +97 -0
  310. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  311. package/src/runtime/routes/integrations/slack/share.ts +6 -6
  312. package/src/runtime/routes/integrations/twilio.ts +6 -5
  313. package/src/runtime/routes/log-export-routes.ts +126 -8
  314. package/src/runtime/routes/secret-routes.ts +3 -2
  315. package/src/runtime/routes/settings-routes.ts +113 -48
  316. package/src/runtime/routes/surface-action-routes.ts +1 -1
  317. package/src/runtime/routes/watch-routes.ts +128 -0
  318. package/src/schedule/integration-status.ts +10 -8
  319. package/src/security/credential-key.ts +14 -0
  320. package/src/security/keychain-broker-client.ts +5 -6
  321. package/src/security/oauth2.ts +1 -1
  322. package/src/security/token-manager.ts +145 -43
  323. package/src/skills/catalog-install.ts +358 -0
  324. package/src/skills/include-graph.ts +32 -0
  325. package/src/telegram/bot-username.ts +2 -3
  326. package/src/tools/apps/definitions.ts +0 -5
  327. package/src/tools/assets/materialize.ts +0 -5
  328. package/src/tools/assets/search.ts +0 -5
  329. package/src/tools/browser/headless-browser.ts +1 -67
  330. package/src/tools/browser/network-recorder.ts +1 -1
  331. package/src/tools/browser/network-recording-types.ts +1 -1
  332. package/src/tools/claude-code/claude-code.ts +0 -5
  333. package/src/tools/computer-use/definitions.ts +46 -11
  334. package/src/tools/computer-use/registry.ts +4 -5
  335. package/src/tools/credentials/broker.ts +5 -4
  336. package/src/tools/credentials/metadata-store.ts +22 -74
  337. package/src/tools/credentials/resolve.ts +2 -1
  338. package/src/tools/credentials/vault.ts +139 -151
  339. package/src/tools/filesystem/edit.ts +1 -6
  340. package/src/tools/filesystem/read.ts +0 -5
  341. package/src/tools/filesystem/write.ts +1 -6
  342. package/src/tools/host-filesystem/edit.ts +1 -6
  343. package/src/tools/host-filesystem/read.ts +1 -6
  344. package/src/tools/host-filesystem/write.ts +1 -6
  345. package/src/tools/mcp/mcp-tool-factory.ts +18 -1
  346. package/src/tools/memory/definitions.ts +0 -5
  347. package/src/tools/network/web-fetch.ts +0 -5
  348. package/src/tools/network/web-search.ts +0 -5
  349. package/src/tools/registry.ts +2 -7
  350. package/src/tools/schema-transforms.ts +99 -0
  351. package/src/tools/skills/load.ts +62 -8
  352. package/src/tools/swarm/delegate.ts +0 -5
  353. package/src/tools/system/avatar-generator.ts +0 -5
  354. package/src/tools/ui-surface/definitions.ts +0 -15
  355. package/src/tools/watch/screen-watch.ts +0 -5
  356. package/src/tools/watch/watch-state.ts +0 -12
  357. package/src/util/logger.ts +7 -41
  358. package/src/util/platform.ts +9 -28
  359. package/src/version.ts +10 -0
  360. package/src/watcher/providers/github.ts +51 -52
  361. package/src/watcher/providers/gmail.ts +88 -80
  362. package/src/watcher/providers/google-calendar.ts +94 -86
  363. package/src/watcher/providers/linear.ts +87 -93
  364. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  365. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  366. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  367. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  368. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  369. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  370. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  371. package/src/cli/commands/dev.ts +0 -129
  372. package/src/cli/commands/map.ts +0 -391
  373. package/src/cli/commands/oauth.ts +0 -77
  374. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
  375. package/src/daemon/computer-use-session.ts +0 -1020
  376. package/src/daemon/ride-shotgun-handler.ts +0 -567
  377. package/src/oauth/provider-profiles.ts +0 -192
  378. package/src/prompts/computer-use-prompt.ts +0 -98
  379. package/src/runtime/routes/computer-use-routes.ts +0 -641
  380. package/src/runtime/telegram-streaming-delivery.test.ts +0 -597
  381. package/src/runtime/telegram-streaming-delivery.ts +0 -383
  382. package/src/tools/computer-use/request-computer-control.ts +0 -61
@@ -5,8 +5,8 @@ import {
5
5
  listMessages,
6
6
  modifyMessage,
7
7
  } from "../../../../messaging/providers/gmail/client.js";
8
- import { getMessagingProvider } from "../../../../messaging/registry.js";
9
- import { withValidToken } from "../../../../security/token-manager.js";
8
+ import type { OAuthConnection } from "../../../../oauth/connection.js";
9
+ import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
10
10
  import type {
11
11
  ToolContext,
12
12
  ToolExecutionResult,
@@ -15,12 +15,14 @@ import { err, ok } from "./shared.js";
15
15
 
16
16
  const FOLLOW_UP_LABEL_NAME = "Follow-up";
17
17
 
18
- async function getOrCreateFollowUpLabel(token: string): Promise<string> {
19
- const labels = await listLabels(token);
18
+ async function getOrCreateFollowUpLabel(
19
+ connection: OAuthConnection,
20
+ ): Promise<string> {
21
+ const labels = await listLabels(connection);
20
22
  const existing = labels.find((l) => l.name === FOLLOW_UP_LABEL_NAME);
21
23
  if (existing) return existing.id;
22
24
 
23
- const created = await createLabel(token, FOLLOW_UP_LABEL_NAME);
25
+ const created = await createLabel(connection, FOLLOW_UP_LABEL_NAME);
24
26
  return created.id;
25
27
  }
26
28
 
@@ -35,67 +37,68 @@ export async function run(
35
37
  }
36
38
 
37
39
  try {
38
- const provider = getMessagingProvider("gmail");
39
- return await withValidToken(provider.credentialService, async (token) => {
40
- switch (action) {
41
- case "track": {
42
- const messageId = input.message_id as string;
43
- if (!messageId)
44
- return err("message_id is required for track action.");
40
+ const connection = resolveOAuthConnection("integration:gmail");
41
+ switch (action) {
42
+ case "track": {
43
+ const messageId = input.message_id as string;
44
+ if (!messageId) return err("message_id is required for track action.");
45
45
 
46
- const labelId = await getOrCreateFollowUpLabel(token);
47
- await modifyMessage(token, messageId, { addLabelIds: [labelId] });
48
- return ok("Message marked for follow-up.");
49
- }
50
-
51
- case "list": {
52
- const labelId = await getOrCreateFollowUpLabel(token);
53
- const listResp = await listMessages(token, undefined, 50, undefined, [
54
- labelId,
55
- ]);
56
- const messageIds = (listResp.messages ?? []).map((m) => m.id);
57
-
58
- if (messageIds.length === 0) {
59
- return ok("No messages are currently tracked for follow-up.");
60
- }
46
+ const labelId = await getOrCreateFollowUpLabel(connection);
47
+ await modifyMessage(connection, messageId, { addLabelIds: [labelId] });
48
+ return ok("Message marked for follow-up.");
49
+ }
61
50
 
62
- const messages = await batchGetMessages(
63
- token,
64
- messageIds,
65
- "metadata",
66
- ["From", "Subject", "Date"],
67
- );
68
- const items = messages.map((m) => {
69
- const headers = m.payload?.headers ?? [];
70
- const from =
71
- headers.find((h) => h.name.toLowerCase() === "from")?.value ?? "";
72
- const subject =
73
- headers.find((h) => h.name.toLowerCase() === "subject")?.value ??
74
- "";
75
- const date =
76
- headers.find((h) => h.name.toLowerCase() === "date")?.value ?? "";
77
- return { id: m.id, threadId: m.threadId, from, subject, date };
78
- });
51
+ case "list": {
52
+ const labelId = await getOrCreateFollowUpLabel(connection);
53
+ const listResp = await listMessages(
54
+ connection,
55
+ undefined,
56
+ 50,
57
+ undefined,
58
+ [labelId],
59
+ );
60
+ const messageIds = (listResp.messages ?? []).map((m) => m.id);
79
61
 
80
- return ok(JSON.stringify(items, null, 2));
62
+ if (messageIds.length === 0) {
63
+ return ok("No messages are currently tracked for follow-up.");
81
64
  }
82
65
 
83
- case "untrack": {
84
- const messageId = input.message_id as string;
85
- if (!messageId)
86
- return err("message_id is required for untrack action.");
66
+ const messages = await batchGetMessages(
67
+ connection,
68
+ messageIds,
69
+ "metadata",
70
+ ["From", "Subject", "Date"],
71
+ );
72
+ const items = messages.map((m) => {
73
+ const headers = m.payload?.headers ?? [];
74
+ const from =
75
+ headers.find((h) => h.name.toLowerCase() === "from")?.value ?? "";
76
+ const subject =
77
+ headers.find((h) => h.name.toLowerCase() === "subject")?.value ??
78
+ "";
79
+ const date =
80
+ headers.find((h) => h.name.toLowerCase() === "date")?.value ?? "";
81
+ return { id: m.id, threadId: m.threadId, from, subject, date };
82
+ });
87
83
 
88
- const labelId = await getOrCreateFollowUpLabel(token);
89
- await modifyMessage(token, messageId, { removeLabelIds: [labelId] });
90
- return ok("Follow-up tracking removed from message.");
91
- }
84
+ return ok(JSON.stringify(items, null, 2));
85
+ }
92
86
 
93
- default:
94
- return err(
95
- `Unknown action "${action}". Use track, list, or untrack.`,
96
- );
87
+ case "untrack": {
88
+ const messageId = input.message_id as string;
89
+ if (!messageId)
90
+ return err("message_id is required for untrack action.");
91
+
92
+ const labelId = await getOrCreateFollowUpLabel(connection);
93
+ await modifyMessage(connection, messageId, {
94
+ removeLabelIds: [labelId],
95
+ });
96
+ return ok("Follow-up tracking removed from message.");
97
97
  }
98
- });
98
+
99
+ default:
100
+ return err(`Unknown action "${action}". Use track, list, or untrack.`);
101
+ }
99
102
  } catch (e) {
100
103
  return err(e instanceof Error ? e.message : String(e));
101
104
  }
@@ -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
  }