@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
@@ -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,
@@ -58,187 +57,190 @@ export async function run(
58
57
  const inputPageToken = input.page_token as string | undefined;
59
58
 
60
59
  try {
61
- const provider = getMessagingProvider("gmail");
62
- return await withValidToken(provider.credentialService, async (token) => {
63
- // Pipeline: fire metadata fetches for each page of IDs as they arrive,
64
- // overlapping fetch latency with pagination latency
65
- const allMessageIds: string[] = [];
66
- const fetchPromises: Promise<GmailMessage[]>[] = [];
67
- let pageToken: string | undefined = inputPageToken;
68
- let truncated = false;
69
- let timeBudgetExceeded = false;
70
- const metadataHeaders = ["From", "List-Unsubscribe", "Subject", "Date"];
71
- const startTime = Date.now();
72
- const TIME_BUDGET_MS = 90_000;
73
-
74
- while (allMessageIds.length < maxMessages) {
75
- if (Date.now() - startTime > TIME_BUDGET_MS) {
76
- timeBudgetExceeded = true;
77
- truncated = true;
78
- break;
79
- }
80
- const pageSize = Math.min(100, maxMessages - allMessageIds.length);
81
- const listResp = await listMessages(token, query, pageSize, pageToken);
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
- token,
88
- ids,
89
- "metadata",
90
- metadataHeaders,
91
- "id,internalDate,payload/headers",
92
- ),
93
- );
94
- pageToken = listResp.nextPageToken ?? undefined;
95
- if (!pageToken) break;
60
+ const connection = resolveOAuthConnection("integration:gmail");
61
+ // Pipeline: fire metadata fetches for each page of IDs as they arrive,
62
+ // overlapping fetch latency with pagination latency
63
+ const allMessageIds: string[] = [];
64
+ const fetchPromises: Promise<GmailMessage[]>[] = [];
65
+ let pageToken: string | undefined = inputPageToken;
66
+ let truncated = false;
67
+ let timeBudgetExceeded = false;
68
+ const metadataHeaders = ["From", "List-Unsubscribe", "Subject", "Date"];
69
+ const startTime = Date.now();
70
+ const TIME_BUDGET_MS = 90_000;
71
+
72
+ while (allMessageIds.length < maxMessages) {
73
+ if (Date.now() - startTime > TIME_BUDGET_MS) {
74
+ timeBudgetExceeded = true;
75
+ truncated = true;
76
+ break;
96
77
  }
78
+ const pageSize = Math.min(100, maxMessages - allMessageIds.length);
79
+ const listResp = await listMessages(
80
+ connection,
81
+ query,
82
+ pageSize,
83
+ pageToken,
84
+ );
85
+ const ids = (listResp.messages ?? []).map((m) => m.id);
86
+ if (ids.length === 0) break;
87
+ allMessageIds.push(...ids);
88
+ fetchPromises.push(
89
+ batchGetMessages(
90
+ connection,
91
+ ids,
92
+ "metadata",
93
+ metadataHeaders,
94
+ "id,internalDate,payload/headers",
95
+ ),
96
+ );
97
+ pageToken = listResp.nextPageToken ?? undefined;
98
+ if (!pageToken) break;
99
+ }
97
100
 
98
- // If we stopped because we hit the cap but there were still more pages, flag truncation
99
- if (allMessageIds.length >= maxMessages && pageToken) {
100
- truncated = true;
101
+ // If we stopped because we hit the cap but there were still more pages, flag truncation
102
+ if (allMessageIds.length >= maxMessages && pageToken) {
103
+ truncated = true;
104
+ }
105
+
106
+ if (allMessageIds.length === 0) {
107
+ return ok(
108
+ JSON.stringify({
109
+ senders: [],
110
+ total_scanned: 0,
111
+ message:
112
+ "No emails found matching the query. Try broadening the search (e.g. remove category filter or extend date range).",
113
+ }),
114
+ );
115
+ }
116
+
117
+ const messages = (await Promise.all(fetchPromises)).flat();
118
+
119
+ // Group by sender email
120
+ const senderMap = new Map<string, SenderAggregation>();
121
+
122
+ for (const msg of messages) {
123
+ const headers = msg.payload?.headers ?? [];
124
+ const fromHeader =
125
+ headers.find((h) => h.name.toLowerCase() === "from")?.value ?? "";
126
+ const subject =
127
+ headers.find((h) => h.name.toLowerCase() === "subject")?.value ?? "";
128
+ const dateStr =
129
+ headers.find((h) => h.name.toLowerCase() === "date")?.value ?? "";
130
+ const listUnsub = headers.find(
131
+ (h) => h.name.toLowerCase() === "list-unsubscribe",
132
+ )?.value;
133
+
134
+ const { displayName, email } = parseFrom(fromHeader);
135
+ if (!email) continue;
136
+
137
+ let agg = senderMap.get(email);
138
+ if (!agg) {
139
+ agg = {
140
+ displayName,
141
+ email,
142
+ messageCount: 0,
143
+ hasUnsubscribe: false,
144
+ newestMessageId: msg.id,
145
+ newestUnsubscribableMessageId: null,
146
+ newestUnsubscribableEpoch: 0,
147
+ oldestDate: dateStr,
148
+ newestDate: dateStr,
149
+ messageIds: [],
150
+ hasMore: false,
151
+ sampleSubjects: [],
152
+ };
153
+ senderMap.set(email, agg);
101
154
  }
102
155
 
103
- if (allMessageIds.length === 0) {
104
- return ok(
105
- JSON.stringify({
106
- senders: [],
107
- total_scanned: 0,
108
- message:
109
- "No emails found matching the query. Try broadening the search (e.g. remove category filter or extend date range).",
110
- }),
111
- );
156
+ agg.messageCount++;
157
+
158
+ if (listUnsub) agg.hasUnsubscribe = true;
159
+
160
+ // Use displayName from earliest message that has one
161
+ if (!agg.displayName && displayName) agg.displayName = displayName;
162
+
163
+ // Track message IDs (cap at MAX_IDS_PER_SENDER)
164
+ if (agg.messageIds.length < MAX_IDS_PER_SENDER) {
165
+ agg.messageIds.push(msg.id);
166
+ } else {
167
+ agg.hasMore = true;
112
168
  }
113
169
 
114
- const messages = (await Promise.all(fetchPromises)).flat();
115
-
116
- // Group by sender email
117
- const senderMap = new Map<string, SenderAggregation>();
118
-
119
- for (const msg of messages) {
120
- const headers = msg.payload?.headers ?? [];
121
- const fromHeader =
122
- headers.find((h) => h.name.toLowerCase() === "from")?.value ?? "";
123
- const subject =
124
- headers.find((h) => h.name.toLowerCase() === "subject")?.value ?? "";
125
- const dateStr =
126
- headers.find((h) => h.name.toLowerCase() === "date")?.value ?? "";
127
- const listUnsub = headers.find(
128
- (h) => h.name.toLowerCase() === "list-unsubscribe",
129
- )?.value;
130
-
131
- const { displayName, email } = parseFrom(fromHeader);
132
- if (!email) continue;
133
-
134
- let agg = senderMap.get(email);
135
- if (!agg) {
136
- agg = {
137
- displayName,
138
- email,
139
- messageCount: 0,
140
- hasUnsubscribe: false,
141
- newestMessageId: msg.id,
142
- newestUnsubscribableMessageId: null,
143
- newestUnsubscribableEpoch: 0,
144
- oldestDate: dateStr,
145
- newestDate: dateStr,
146
- messageIds: [],
147
- hasMore: false,
148
- sampleSubjects: [],
149
- };
150
- senderMap.set(email, agg);
151
- }
152
-
153
- agg.messageCount++;
154
-
155
- if (listUnsub) agg.hasUnsubscribe = true;
156
-
157
- // Use displayName from earliest message that has one
158
- if (!agg.displayName && displayName) agg.displayName = displayName;
159
-
160
- // Track message IDs (cap at MAX_IDS_PER_SENDER)
161
- if (agg.messageIds.length < MAX_IDS_PER_SENDER) {
162
- agg.messageIds.push(msg.id);
163
- } else {
164
- agg.hasMore = true;
165
- }
166
-
167
- // Track date range — compare using internalDate (epoch ms) for reliability
168
- const msgEpoch = msg.internalDate ? Number(msg.internalDate) : 0;
169
- const oldestEpoch = agg.oldestDate
170
- ? new Date(agg.oldestDate).getTime()
171
- : Infinity;
172
- const newestEpoch = agg.newestDate
173
- ? new Date(agg.newestDate).getTime()
174
- : 0;
175
-
176
- if (msgEpoch > 0 && msgEpoch < oldestEpoch) {
177
- agg.oldestDate = dateStr || agg.oldestDate;
178
- }
179
- if (msgEpoch > newestEpoch) {
180
- agg.newestDate = dateStr || agg.newestDate;
181
- agg.newestMessageId = msg.id;
182
- }
183
-
184
- // Track the newest message that actually has List-Unsubscribe so
185
- // gmail_unsubscribe() is called with a message that carries the header
186
- if (listUnsub && msgEpoch >= agg.newestUnsubscribableEpoch) {
187
- agg.newestUnsubscribableMessageId = msg.id;
188
- agg.newestUnsubscribableEpoch = msgEpoch;
189
- }
190
-
191
- // Collect sample subjects
192
- if (subject && agg.sampleSubjects.length < MAX_SAMPLE_SUBJECTS) {
193
- agg.sampleSubjects.push(subject);
194
- }
170
+ // Track date range — compare using internalDate (epoch ms) for reliability
171
+ const msgEpoch = msg.internalDate ? Number(msg.internalDate) : 0;
172
+ const oldestEpoch = agg.oldestDate
173
+ ? new Date(agg.oldestDate).getTime()
174
+ : Infinity;
175
+ const newestEpoch = agg.newestDate
176
+ ? new Date(agg.newestDate).getTime()
177
+ : 0;
178
+
179
+ if (msgEpoch > 0 && msgEpoch < oldestEpoch) {
180
+ agg.oldestDate = dateStr || agg.oldestDate;
181
+ }
182
+ if (msgEpoch > newestEpoch) {
183
+ agg.newestDate = dateStr || agg.newestDate;
184
+ agg.newestMessageId = msg.id;
195
185
  }
196
186
 
197
- // Sort by message count descending, take top N
198
- const sorted = [...senderMap.values()]
199
- .sort((a, b) => b.messageCount - a.messageCount)
200
- .slice(0, maxSenders);
187
+ // Track the newest message that actually has List-Unsubscribe so
188
+ // gmail_unsubscribe() is called with a message that carries the header
189
+ if (listUnsub && msgEpoch >= agg.newestUnsubscribableEpoch) {
190
+ agg.newestUnsubscribableMessageId = msg.id;
191
+ agg.newestUnsubscribableEpoch = msgEpoch;
192
+ }
201
193
 
202
- const resultSenders = sorted.map((s) => ({
194
+ // Collect sample subjects
195
+ if (subject && agg.sampleSubjects.length < MAX_SAMPLE_SUBJECTS) {
196
+ agg.sampleSubjects.push(subject);
197
+ }
198
+ }
199
+
200
+ // Sort by message count descending, take top N
201
+ const sorted = [...senderMap.values()]
202
+ .sort((a, b) => b.messageCount - a.messageCount)
203
+ .slice(0, maxSenders);
204
+
205
+ const resultSenders = sorted.map((s) => ({
206
+ id: Buffer.from(s.email).toString("base64url"),
207
+ display_name: s.displayName || s.email.split("@")[0],
208
+ email: s.email,
209
+ message_count: s.messageCount,
210
+ has_unsubscribe: s.hasUnsubscribe,
211
+ // When unsubscribe is available, point to a message that carries the header
212
+ newest_message_id:
213
+ s.hasUnsubscribe && s.newestUnsubscribableMessageId
214
+ ? s.newestUnsubscribableMessageId
215
+ : s.newestMessageId,
216
+ oldest_date: s.oldestDate,
217
+ newest_date: s.newestDate,
218
+ // Preserve original query filters so follow-up searches stay scoped
219
+ search_query: `from:${s.email} ${query}`,
220
+ sample_subjects: s.sampleSubjects,
221
+ }));
222
+
223
+ // Store message IDs server-side to keep them out of LLM context
224
+ const scanId = storeScanResult(
225
+ sorted.map((s) => ({
203
226
  id: Buffer.from(s.email).toString("base64url"),
204
- display_name: s.displayName || s.email.split("@")[0],
205
- email: s.email,
206
- message_count: s.messageCount,
207
- has_unsubscribe: s.hasUnsubscribe,
208
- // When unsubscribe is available, point to a message that carries the header
209
- newest_message_id:
210
- s.hasUnsubscribe && s.newestUnsubscribableMessageId
211
- ? s.newestUnsubscribableMessageId
212
- : s.newestMessageId,
213
- oldest_date: s.oldestDate,
214
- newest_date: s.newestDate,
215
- // Preserve original query filters so follow-up searches stay scoped
216
- search_query: `from:${s.email} ${query}`,
217
- sample_subjects: s.sampleSubjects,
218
- }));
219
-
220
- // Store message IDs server-side to keep them out of LLM context
221
- const scanId = storeScanResult(
222
- sorted.map((s) => ({
223
- id: Buffer.from(s.email).toString("base64url"),
224
- messageIds: s.messageIds,
225
- newestMessageId: s.newestMessageId,
226
- newestUnsubscribableMessageId: s.newestUnsubscribableMessageId,
227
- })),
228
- );
229
-
230
- return ok(
231
- JSON.stringify({
232
- scan_id: scanId,
233
- senders: resultSenders,
234
- total_scanned: allMessageIds.length,
235
- query_used: query,
236
- ...(truncated ? { truncated: true } : {}),
237
- ...(timeBudgetExceeded ? { time_budget_exceeded: true } : {}),
238
- note: `message_count reflects emails found per sender within the ${allMessageIds.length} messages scanned. Use scan_id with gmail_archive to archive messages (pass scan_id + sender_ids instead of message_ids).`,
239
- }),
240
- );
241
- });
227
+ messageIds: s.messageIds,
228
+ newestMessageId: s.newestMessageId,
229
+ newestUnsubscribableMessageId: s.newestUnsubscribableMessageId,
230
+ })),
231
+ );
232
+
233
+ return ok(
234
+ JSON.stringify({
235
+ scan_id: scanId,
236
+ senders: resultSenders,
237
+ total_scanned: allMessageIds.length,
238
+ query_used: query,
239
+ ...(truncated ? { truncated: true } : {}),
240
+ ...(timeBudgetExceeded ? { time_budget_exceeded: true } : {}),
241
+ note: `message_count reflects emails found per sender within the ${allMessageIds.length} messages scanned. Use scan_id with gmail_archive to archive messages (pass scan_id + sender_ids instead of message_ids).`,
242
+ }),
243
+ );
242
244
  } catch (e) {
243
245
  return err(e instanceof Error ? e.message : String(e));
244
246
  }
@@ -1,6 +1,5 @@
1
1
  import { trashMessage } 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,
@@ -18,11 +17,9 @@ export async function run(
18
17
  }
19
18
 
20
19
  try {
21
- const provider = getMessagingProvider("gmail");
22
- return await withValidToken(provider.credentialService, async (token) => {
23
- await trashMessage(token, messageId);
24
- return ok("Message moved to trash.");
25
- });
20
+ const connection = resolveOAuthConnection("integration:gmail");
21
+ await trashMessage(connection, messageId);
22
+ return ok("Message moved to trash.");
26
23
  } catch (e) {
27
24
  return err(e instanceof Error ? e.message : String(e));
28
25
  }
@@ -2,8 +2,7 @@ import {
2
2
  getMessage,
3
3
  sendMessage,
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 {
8
7
  isPrivateOrLocalHost,
9
8
  resolveHostAddresses,
@@ -32,92 +31,88 @@ export async function run(
32
31
  }
33
32
 
34
33
  try {
35
- const provider = getMessagingProvider("gmail");
36
- return await withValidToken(provider.credentialService, async (token) => {
37
- const message = await getMessage(token, messageId, "metadata", [
38
- "List-Unsubscribe",
39
- "List-Unsubscribe-Post",
40
- ]);
41
- const headers = message.payload?.headers ?? [];
42
- const unsubHeader = headers.find(
43
- (h) => h.name.toLowerCase() === "list-unsubscribe",
44
- )?.value;
34
+ const connection = resolveOAuthConnection("integration:gmail");
35
+ const message = await getMessage(connection, messageId, "metadata", [
36
+ "List-Unsubscribe",
37
+ "List-Unsubscribe-Post",
38
+ ]);
39
+ const headers = message.payload?.headers ?? [];
40
+ const unsubHeader = headers.find(
41
+ (h) => h.name.toLowerCase() === "list-unsubscribe",
42
+ )?.value;
45
43
 
46
- if (!unsubHeader) {
47
- return err(
48
- "No List-Unsubscribe header found. Manual unsubscribe may be required.",
44
+ if (!unsubHeader) {
45
+ return err(
46
+ "No List-Unsubscribe header found. Manual unsubscribe may be required.",
47
+ );
48
+ }
49
+
50
+ const httpsMatch = unsubHeader.match(/<(https:\/\/[^>]+)>/);
51
+ const mailtoMatch = unsubHeader.match(/<mailto:([^>]+)>/);
52
+ const postHeader = headers.find(
53
+ (h) => h.name.toLowerCase() === "list-unsubscribe-post",
54
+ )?.value;
55
+
56
+ if (httpsMatch) {
57
+ const url = httpsMatch[1];
58
+ let parsed: URL;
59
+ let validatedAddresses: string[];
60
+ try {
61
+ parsed = new URL(url);
62
+ if (parsed.protocol !== "https:") {
63
+ return err("Unsubscribe URL must use HTTPS.");
64
+ }
65
+ if (isPrivateOrLocalHost(parsed.hostname)) {
66
+ return err("Unsubscribe URL points to a private or local address.");
67
+ }
68
+ const { addresses, blockedAddress } = await resolveRequestAddress(
69
+ parsed.hostname,
70
+ resolveHostAddresses,
71
+ false,
49
72
  );
73
+ if (blockedAddress) {
74
+ return err("Unsubscribe URL resolves to a private or local address.");
75
+ }
76
+ if (addresses.length === 0) {
77
+ return err("Unable to resolve unsubscribe URL hostname.");
78
+ }
79
+ validatedAddresses = addresses;
80
+ } catch {
81
+ return err("Invalid unsubscribe URL.");
50
82
  }
51
83
 
52
- const httpsMatch = unsubHeader.match(/<(https:\/\/[^>]+)>/);
53
- const mailtoMatch = unsubHeader.match(/<mailto:([^>]+)>/);
54
- const postHeader = headers.find(
55
- (h) => h.name.toLowerCase() === "list-unsubscribe-post",
56
- )?.value;
84
+ const method = postHeader ? "POST" : "GET";
85
+ const reqOpts = postHeader
86
+ ? {
87
+ method: "POST" as const,
88
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
89
+ body: postHeader,
90
+ }
91
+ : undefined;
57
92
 
58
- if (httpsMatch) {
59
- const url = httpsMatch[1];
60
- let parsed: URL;
61
- let validatedAddresses: string[];
93
+ let lastStatus = 0;
94
+ for (const address of validatedAddresses) {
62
95
  try {
63
- parsed = new URL(url);
64
- if (parsed.protocol !== "https:") {
65
- return err("Unsubscribe URL must use HTTPS.");
66
- }
67
- if (isPrivateOrLocalHost(parsed.hostname)) {
68
- return err("Unsubscribe URL points to a private or local address.");
69
- }
70
- const { addresses, blockedAddress } = await resolveRequestAddress(
71
- parsed.hostname,
72
- resolveHostAddresses,
73
- false,
74
- );
75
- if (blockedAddress) {
76
- return err(
77
- "Unsubscribe URL resolves to a private or local address.",
78
- );
96
+ lastStatus = await pinnedHttpsRequest(parsed, address, reqOpts);
97
+ if (lastStatus >= 200 && lastStatus < 400) {
98
+ return ok(`Successfully unsubscribed via HTTPS ${method}.`);
79
99
  }
80
- if (addresses.length === 0) {
81
- return err("Unable to resolve unsubscribe URL hostname.");
82
- }
83
- validatedAddresses = addresses;
84
100
  } catch {
85
- return err("Invalid unsubscribe URL.");
86
- }
87
-
88
- const method = postHeader ? "POST" : "GET";
89
- const reqOpts = postHeader
90
- ? {
91
- method: "POST" as const,
92
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
93
- body: postHeader,
94
- }
95
- : undefined;
96
-
97
- let lastStatus = 0;
98
- for (const address of validatedAddresses) {
99
- try {
100
- lastStatus = await pinnedHttpsRequest(parsed, address, reqOpts);
101
- if (lastStatus >= 200 && lastStatus < 400) {
102
- return ok(`Successfully unsubscribed via HTTPS ${method}.`);
103
- }
104
- } catch {
105
- continue;
106
- }
101
+ continue;
107
102
  }
108
- return err(`Unsubscribe request failed: ${lastStatus}`);
109
103
  }
104
+ return err(`Unsubscribe request failed: ${lastStatus}`);
105
+ }
110
106
 
111
- if (mailtoMatch) {
112
- const mailtoAddr = mailtoMatch[1].split("?")[0];
113
- await sendMessage(token, mailtoAddr, "Unsubscribe", "Unsubscribe");
114
- return ok(`Unsubscribe email sent to ${mailtoAddr}.`);
115
- }
107
+ if (mailtoMatch) {
108
+ const mailtoAddr = mailtoMatch[1].split("?")[0];
109
+ await sendMessage(connection, mailtoAddr, "Unsubscribe", "Unsubscribe");
110
+ return ok(`Unsubscribe email sent to ${mailtoAddr}.`);
111
+ }
116
112
 
117
- return err(
118
- "No supported unsubscribe method found (requires https: or mailto: URL).",
119
- );
120
- });
113
+ return err(
114
+ "No supported unsubscribe method found (requires https: or mailto: URL).",
115
+ );
121
116
  } catch (e) {
122
117
  return err(e instanceof Error ? e.message : String(e));
123
118
  }
@@ -3,8 +3,7 @@ import {
3
3
  updateVacation,
4
4
  } from "../../../../messaging/providers/gmail/client.js";
5
5
  import type { GmailVacationSettings } 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,
@@ -22,48 +21,43 @@ export async function run(
22
21
  }
23
22
 
24
23
  try {
25
- const provider = getMessagingProvider("gmail");
26
- return await withValidToken(provider.credentialService, async (token) => {
27
- switch (action) {
28
- case "get": {
29
- const settings = await getVacation(token);
30
- return ok(JSON.stringify(settings, null, 2));
31
- }
32
-
33
- case "enable": {
34
- const message = input.message as string;
35
- if (!message)
36
- return err("message is required when enabling vacation responder.");
24
+ const connection = resolveOAuthConnection("integration:gmail");
25
+ switch (action) {
26
+ case "get": {
27
+ const settings = await getVacation(connection);
28
+ return ok(JSON.stringify(settings, null, 2));
29
+ }
37
30
 
38
- const settings: GmailVacationSettings = {
39
- enableAutoReply: true,
40
- responseSubject: (input.subject as string) ?? "Out of Office",
41
- responseBodyPlainText: message,
42
- restrictToContacts:
43
- (input.restrict_to_contacts as boolean) ?? false,
44
- restrictToDomain: (input.restrict_to_domain as boolean) ?? false,
45
- };
31
+ case "enable": {
32
+ const message = input.message as string;
33
+ if (!message)
34
+ return err("message is required when enabling vacation responder.");
46
35
 
47
- if (input.start_time) settings.startTime = String(input.start_time);
48
- if (input.end_time) settings.endTime = String(input.end_time);
36
+ const settings: GmailVacationSettings = {
37
+ enableAutoReply: true,
38
+ responseSubject: (input.subject as string) ?? "Out of Office",
39
+ responseBodyPlainText: message,
40
+ restrictToContacts: (input.restrict_to_contacts as boolean) ?? false,
41
+ restrictToDomain: (input.restrict_to_domain as boolean) ?? false,
42
+ };
49
43
 
50
- const updated = await updateVacation(token, settings);
51
- return ok(
52
- `Vacation responder enabled.\n${JSON.stringify(updated, null, 2)}`,
53
- );
54
- }
44
+ if (input.start_time) settings.startTime = String(input.start_time);
45
+ if (input.end_time) settings.endTime = String(input.end_time);
55
46
 
56
- case "disable": {
57
- await updateVacation(token, { enableAutoReply: false });
58
- return ok("Vacation responder disabled.");
59
- }
47
+ const updated = await updateVacation(connection, settings);
48
+ return ok(
49
+ `Vacation responder enabled.\n${JSON.stringify(updated, null, 2)}`,
50
+ );
51
+ }
60
52
 
61
- default:
62
- return err(
63
- `Unknown action "${action}". Use get, enable, or disable.`,
64
- );
53
+ case "disable": {
54
+ await updateVacation(connection, { enableAutoReply: false });
55
+ return ok("Vacation responder disabled.");
65
56
  }
66
- });
57
+
58
+ default:
59
+ return err(`Unknown action "${action}". Use get, enable, or disable.`);
60
+ }
67
61
  } catch (e) {
68
62
  return err(e instanceof Error ? e.message : String(e));
69
63
  }