@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
@@ -12,7 +12,7 @@ import type {
12
12
  ToolExecutionResult,
13
13
  } from "../../../../tools/types.js";
14
14
  import { truncate } from "../../../../util/truncate.js";
15
- import { err, ok, resolveProvider, withProviderToken } from "./shared.js";
15
+ import { err, getProviderConnection, ok, resolveProvider } from "./shared.js";
16
16
 
17
17
  function upsertMemoryItem(opts: {
18
18
  kind: string;
@@ -91,76 +91,75 @@ export async function run(
91
91
 
92
92
  try {
93
93
  const provider = resolveProvider(platform);
94
- return withProviderToken(provider, async (token) => {
95
- // Search for sent messages using the platform's search
96
- const query =
97
- queryFilter ?? (provider.id === "gmail" ? "in:sent" : "from:me");
98
- const searchResult = await provider.search(token, query, {
99
- count: maxMessages,
100
- });
94
+ const conn = getProviderConnection(provider);
95
+ // Search for sent messages using the platform's search
96
+ const query =
97
+ queryFilter ?? (provider.id === "gmail" ? "in:sent" : "from:me");
98
+ const searchResult = await provider.search(conn, query, {
99
+ count: maxMessages,
100
+ });
101
101
 
102
- if (searchResult.messages.length === 0) {
103
- return err(
104
- "No sent messages found. Send some messages first, then try again.",
105
- );
106
- }
102
+ if (searchResult.messages.length === 0) {
103
+ return err(
104
+ "No sent messages found. Send some messages first, then try again.",
105
+ );
106
+ }
107
107
 
108
- const result = await extractStylePatterns(searchResult.messages);
108
+ const result = await extractStylePatterns(searchResult.messages);
109
109
 
110
- if (result.stylePatterns.length === 0) {
111
- return err("No style patterns were extracted. Try with more messages.");
112
- }
110
+ if (result.stylePatterns.length === 0) {
111
+ return err("No style patterns were extracted. Try with more messages.");
112
+ }
113
113
 
114
- const scopeId = context.memoryScopeId ?? "default";
115
- let savedCount = 0;
114
+ const scopeId = context.memoryScopeId ?? "default";
115
+ let savedCount = 0;
116
116
 
117
- for (const pattern of result.stylePatterns) {
118
- const subject = `${provider.id} writing style: ${pattern.aspect}`;
119
- const importance = clampUnitInterval(
120
- Math.min(0.85, Math.max(0.55, pattern.importance ?? 0.65)),
121
- );
122
- upsertMemoryItem({
123
- kind: "style",
124
- subject,
125
- statement: pattern.summary,
126
- importance,
127
- scopeId,
128
- });
129
- savedCount++;
130
- }
117
+ for (const pattern of result.stylePatterns) {
118
+ const subject = `${provider.id} writing style: ${pattern.aspect}`;
119
+ const importance = clampUnitInterval(
120
+ Math.min(0.85, Math.max(0.55, pattern.importance ?? 0.65)),
121
+ );
122
+ upsertMemoryItem({
123
+ kind: "style",
124
+ subject,
125
+ statement: pattern.summary,
126
+ importance,
127
+ scopeId,
128
+ });
129
+ savedCount++;
130
+ }
131
131
 
132
- for (const contact of result.contactObservations) {
133
- if (!contact.name || !contact.toneNote) continue;
134
- const subject = `${provider.id} relationship: ${contact.name}`;
135
- upsertMemoryItem({
136
- kind: "relationship",
137
- subject,
138
- statement: truncate(
139
- `${contact.name} (${contact.email}): ${contact.toneNote}`,
140
- 500,
141
- "",
142
- ),
143
- importance: 0.6,
144
- scopeId,
145
- });
146
- savedCount++;
147
- }
132
+ for (const contact of result.contactObservations) {
133
+ if (!contact.name || !contact.toneNote) continue;
134
+ const subject = `${provider.id} relationship: ${contact.name}`;
135
+ upsertMemoryItem({
136
+ kind: "relationship",
137
+ subject,
138
+ statement: truncate(
139
+ `${contact.name} (${contact.email}): ${contact.toneNote}`,
140
+ 500,
141
+ "",
142
+ ),
143
+ importance: 0.6,
144
+ scopeId,
145
+ });
146
+ savedCount++;
147
+ }
148
148
 
149
- const aspects = result.stylePatterns.map((p) => p.aspect).join(", ");
150
- const contactCount = result.contactObservations.length;
151
- const summary = [
152
- `Analyzed ${searchResult.messages.length} messages on ${provider.displayName}.`,
153
- `Extracted ${result.stylePatterns.length} style patterns (${aspects}).`,
154
- contactCount > 0
155
- ? `Noted ${contactCount} recurring contact relationship(s).`
156
- : "",
157
- `Saved ${savedCount} memory items. Future drafts will automatically reflect your writing style.`,
158
- ]
159
- .filter(Boolean)
160
- .join(" ");
149
+ const aspects = result.stylePatterns.map((p) => p.aspect).join(", ");
150
+ const contactCount = result.contactObservations.length;
151
+ const summary = [
152
+ `Analyzed ${searchResult.messages.length} messages on ${provider.displayName}.`,
153
+ `Extracted ${result.stylePatterns.length} style patterns (${aspects}).`,
154
+ contactCount > 0
155
+ ? `Noted ${contactCount} recurring contact relationship(s).`
156
+ : "",
157
+ `Saved ${savedCount} memory items. Future drafts will automatically reflect your writing style.`,
158
+ ]
159
+ .filter(Boolean)
160
+ .join(" ");
161
161
 
162
- return ok(summary);
163
- });
162
+ return ok(summary);
164
163
  } catch (e) {
165
164
  return err(e instanceof Error ? e.message : String(e));
166
165
  }
@@ -2,7 +2,7 @@ import type {
2
2
  ToolContext,
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
- import { err, ok, resolveProvider, withProviderToken } from "./shared.js";
5
+ import { err, getProviderConnection, ok, resolveProvider } from "./shared.js";
6
6
 
7
7
  export async function run(
8
8
  input: Record<string, unknown>,
@@ -30,21 +30,20 @@ export async function run(
30
30
  );
31
31
  }
32
32
 
33
- return withProviderToken(provider, async (token) => {
34
- const result = await provider.archiveByQuery!(token, query);
35
-
36
- if (result.archived === 0) {
37
- return ok("No messages matched the query. Nothing archived.");
38
- }
39
-
40
- const summary = `Archived ${result.archived} message(s) matching query: ${query}`;
41
- if (result.truncated) {
42
- return ok(
43
- `${summary}\n\nNote: this operation was capped at 5000 messages. Additional messages matching the query may remain in the inbox. Run the command again to archive more.`,
44
- );
45
- }
46
- return ok(summary);
47
- });
33
+ const conn = getProviderConnection(provider);
34
+ const result = await provider.archiveByQuery!(conn, query);
35
+
36
+ if (result.archived === 0) {
37
+ return ok("No messages matched the query. Nothing archived.");
38
+ }
39
+
40
+ const summary = `Archived ${result.archived} message(s) matching query: ${query}`;
41
+ if (result.truncated) {
42
+ return ok(
43
+ `${summary}\n\nNote: this operation was capped at 5000 messages. Additional messages matching the query may remain in the inbox. Run the command again to archive more.`,
44
+ );
45
+ }
46
+ return ok(summary);
48
47
  } catch (e) {
49
48
  return err(e instanceof Error ? e.message : String(e));
50
49
  }
@@ -2,7 +2,7 @@ import type {
2
2
  ToolContext,
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
- import { err, ok, resolveProvider, withProviderToken } from "./shared.js";
5
+ import { err, getProviderConnection, ok, resolveProvider } from "./shared.js";
6
6
 
7
7
  export async function run(
8
8
  input: Record<string, unknown>,
@@ -12,10 +12,9 @@ export async function run(
12
12
 
13
13
  try {
14
14
  const provider = resolveProvider(platform);
15
- return withProviderToken(provider, async (token) => {
16
- const info = await provider.testConnection(token);
17
- return ok(JSON.stringify(info, null, 2));
18
- });
15
+ const conn = getProviderConnection(provider);
16
+ const info = await provider.testConnection(conn);
17
+ return ok(JSON.stringify(info, null, 2));
19
18
  } catch (e) {
20
19
  return err(e instanceof Error ? e.message : String(e));
21
20
  }
@@ -2,7 +2,7 @@ import type {
2
2
  ToolContext,
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
- import { err, ok, resolveProvider, withProviderToken } from "./shared.js";
5
+ import { err, getProviderConnection, ok, resolveProvider } from "./shared.js";
6
6
 
7
7
  export async function run(
8
8
  input: Record<string, unknown>,
@@ -14,13 +14,12 @@ export async function run(
14
14
 
15
15
  try {
16
16
  const provider = resolveProvider(platform);
17
- return withProviderToken(provider, async (token) => {
18
- const conversations = await provider.listConversations(token, {
19
- types: types as Array<"channel" | "dm" | "group" | "inbox"> | undefined,
20
- limit,
21
- });
22
- return ok(JSON.stringify(conversations, null, 2));
17
+ const conn = getProviderConnection(provider);
18
+ const conversations = await provider.listConversations(conn, {
19
+ types: types as Array<"channel" | "dm" | "group" | "inbox"> | undefined,
20
+ limit,
23
21
  });
22
+ return ok(JSON.stringify(conversations, null, 2));
24
23
  } catch (e) {
25
24
  return err(e instanceof Error ? e.message : String(e));
26
25
  }
@@ -2,7 +2,7 @@ import type {
2
2
  ToolContext,
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
- import { err, ok, resolveProvider, withProviderToken } from "./shared.js";
5
+ import { err, getProviderConnection, ok, resolveProvider } from "./shared.js";
6
6
 
7
7
  export async function run(
8
8
  input: Record<string, unknown>,
@@ -23,10 +23,9 @@ export async function run(
23
23
  `${provider.displayName} does not support marking messages as read.`,
24
24
  );
25
25
  }
26
- return withProviderToken(provider, async (token) => {
27
- await provider.markRead!(token, conversationId, messageId);
28
- return ok("Marked as read.");
29
- });
26
+ const conn = getProviderConnection(provider);
27
+ await provider.markRead(conn, conversationId, messageId);
28
+ return ok("Marked as read.");
30
29
  } catch (e) {
31
30
  return err(e instanceof Error ? e.message : String(e));
32
31
  }
@@ -2,7 +2,7 @@ import type {
2
2
  ToolContext,
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
- import { err, ok, resolveProvider, withProviderToken } from "./shared.js";
5
+ import { err, getProviderConnection, ok, resolveProvider } from "./shared.js";
6
6
 
7
7
  export async function run(
8
8
  input: Record<string, unknown>,
@@ -19,20 +19,19 @@ export async function run(
19
19
 
20
20
  try {
21
21
  const provider = resolveProvider(platform);
22
- return withProviderToken(provider, async (token) => {
23
- let messages;
24
- if (threadId && provider.getThreadReplies) {
25
- messages = await provider.getThreadReplies(
26
- token,
27
- conversationId,
28
- threadId,
29
- { limit },
30
- );
31
- } else {
32
- messages = await provider.getHistory(token, conversationId, { limit });
33
- }
34
- return ok(JSON.stringify(messages, null, 2));
35
- });
22
+ const conn = getProviderConnection(provider);
23
+ let messages;
24
+ if (threadId && provider.getThreadReplies) {
25
+ messages = await provider.getThreadReplies(
26
+ conn,
27
+ conversationId,
28
+ threadId,
29
+ { limit },
30
+ );
31
+ } else {
32
+ messages = await provider.getHistory(conn, conversationId, { limit });
33
+ }
34
+ return ok(JSON.stringify(messages, null, 2));
36
35
  } catch (e) {
37
36
  return err(e instanceof Error ? e.message : String(e));
38
37
  }
@@ -2,7 +2,7 @@ import type {
2
2
  ToolContext,
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
- import { err, ok, resolveProvider, withProviderToken } from "./shared.js";
5
+ import { err, getProviderConnection, ok, resolveProvider } from "./shared.js";
6
6
 
7
7
  export async function run(
8
8
  input: Record<string, unknown>,
@@ -18,10 +18,9 @@ export async function run(
18
18
 
19
19
  try {
20
20
  const provider = resolveProvider(platform);
21
- return withProviderToken(provider, async (token) => {
22
- const result = await provider.search(token, query, { count: maxResults });
23
- return ok(JSON.stringify(result, null, 2));
24
- });
21
+ const conn = getProviderConnection(provider);
22
+ const result = await provider.search(conn, query, { count: maxResults });
23
+ return ok(JSON.stringify(result, null, 2));
25
24
  } catch (e) {
26
25
  return err(e instanceof Error ? e.message : String(e));
27
26
  }
@@ -9,6 +9,7 @@ import {
9
9
  listMessages,
10
10
  } from "../../../../messaging/providers/gmail/client.js";
11
11
  import { buildMultipartMime } from "../../../../messaging/providers/gmail/mime-builder.js";
12
+ import type { OAuthConnection } from "../../../../oauth/connection.js";
12
13
  import type {
13
14
  ToolContext,
14
15
  ToolExecutionResult,
@@ -18,10 +19,10 @@ import {
18
19
  err,
19
20
  extractEmail,
20
21
  extractHeader,
22
+ getProviderConnection,
21
23
  ok,
22
24
  parseAddressList,
23
25
  resolveProvider,
24
- withProviderToken,
25
26
  } from "./shared.js";
26
27
 
27
28
  export async function run(
@@ -51,115 +52,66 @@ export async function run(
51
52
  return err("Attachments are only supported on Gmail.");
52
53
  }
53
54
 
55
+ const conn = getProviderConnection(provider);
56
+
54
57
  // Gmail: create a draft instead of sending directly
55
58
  if (provider.id === "gmail") {
56
- return withProviderToken(provider, async (token) => {
57
- // Reply mode: thread_id provided — create a threaded draft with reply-all recipients
58
- if (threadId) {
59
- // Fetch thread messages to extract recipients and threading headers
60
- const list = await listMessages(token, `thread:${threadId}`, 10);
61
- if (!list.messages?.length) {
62
- return err("No messages found in this thread.");
63
- }
64
-
65
- const messages = await batchGetMessages(
66
- token,
67
- list.messages.map((m) => m.id),
68
- "metadata",
69
- ["From", "To", "Cc", "Message-ID", "Subject"],
70
- );
59
+ const gmailConn = conn as OAuthConnection;
60
+ // Reply mode: thread_id provided — create a threaded draft with reply-all recipients
61
+ if (threadId) {
62
+ // Fetch thread messages to extract recipients and threading headers
63
+ const list = await listMessages(gmailConn, `thread:${threadId}`, 10);
64
+ if (!list.messages?.length) {
65
+ return err("No messages found in this thread.");
66
+ }
71
67
 
72
- // Use the latest message for threading and recipient extraction
73
- const latest = messages[messages.length - 1];
74
- const latestHeaders = latest.payload?.headers ?? [];
75
-
76
- const messageIdHeader = extractHeader(latestHeaders, "Message-ID");
77
- let replySubject = extractHeader(latestHeaders, "Subject");
78
- if (replySubject && !replySubject.startsWith("Re:")) {
79
- replySubject = `Re: ${replySubject}`;
80
- }
81
-
82
- // Build reply-all recipient list, excluding the user's own email
83
- const profile = await getProfile(token);
84
- const userEmail = profile.emailAddress.toLowerCase();
85
-
86
- const allRecipients = new Set<string>();
87
- const allCc = new Set<string>();
88
-
89
- // From the latest message: From goes to To, original To/Cc go to Cc
90
- const fromAddr = extractHeader(latestHeaders, "From");
91
- const toAddrs = extractHeader(latestHeaders, "To");
92
- const ccAddrs = extractHeader(latestHeaders, "Cc");
93
-
94
- if (fromAddr) allRecipients.add(fromAddr);
95
- for (const addr of parseAddressList(toAddrs)) {
96
- allRecipients.add(addr);
97
- }
98
- for (const addr of parseAddressList(ccAddrs)) {
99
- allCc.add(addr);
100
- }
101
-
102
- // Remove user's own email from recipients using exact email comparison
103
- const filterSelf = (addr: string) => extractEmail(addr) !== userEmail;
104
- const toList = [...allRecipients].filter(filterSelf);
105
- const ccList = [...allCc].filter(filterSelf);
106
-
107
- if (toList.length === 0) {
108
- return err("Could not determine reply recipients from thread.");
109
- }
110
-
111
- // With attachments: build multipart MIME for threaded reply
112
- if (attachmentPaths?.length) {
113
- const attachments = await Promise.all(
114
- attachmentPaths.map(async (filePath) => {
115
- const data = await readFile(filePath);
116
- const filename = basename(filePath);
117
- const mimeType = guessMimeType(filePath);
118
- return { filename, mimeType, data };
119
- }),
120
- );
121
-
122
- const raw = buildMultipartMime({
123
- to: toList.join(", "),
124
- subject: replySubject,
125
- body: text,
126
- inReplyTo: messageIdHeader || undefined,
127
- cc: ccList.length > 0 ? ccList.join(", ") : undefined,
128
- attachments,
129
- });
130
- const draft = await createDraftRaw(token, raw, threadId);
131
-
132
- const filenames = attachments.map((a) => a.filename).join(", ");
133
- const recipientSummary =
134
- ccList.length > 0
135
- ? `To: ${toList.join(", ")}; Cc: ${ccList.join(", ")}`
136
- : `To: ${toList.join(", ")}`;
137
- return ok(
138
- `Gmail draft created with ${attachments.length} attachment(s): ${filenames} (Draft ID: ${draft.id}). ${recipientSummary}. Review in Gmail Drafts, then tell me to send it or send it yourself.`,
139
- );
140
- }
141
-
142
- const draft = await createDraft(
143
- token,
144
- toList.join(", "),
145
- replySubject,
146
- text,
147
- messageIdHeader || undefined,
148
- ccList.length > 0 ? ccList.join(", ") : undefined,
149
- undefined,
150
- threadId,
151
- );
68
+ const messages = await batchGetMessages(
69
+ gmailConn,
70
+ list.messages.map((m) => m.id),
71
+ "metadata",
72
+ ["From", "To", "Cc", "Message-ID", "Subject"],
73
+ );
152
74
 
153
- const recipientSummary =
154
- ccList.length > 0
155
- ? `To: ${toList.join(", ")}; Cc: ${ccList.join(", ")}`
156
- : `To: ${toList.join(", ")}`;
157
- return ok(
158
- `Gmail draft created (ID: ${draft.id}). ${recipientSummary}. Review in Gmail Drafts, then tell me to send it or send it yourself.`,
159
- );
75
+ // Use the latest message for threading and recipient extraction
76
+ const latest = messages[messages.length - 1];
77
+ const latestHeaders = latest.payload?.headers ?? [];
78
+
79
+ const messageIdHeader = extractHeader(latestHeaders, "Message-ID");
80
+ let replySubject = extractHeader(latestHeaders, "Subject");
81
+ if (replySubject && !replySubject.startsWith("Re:")) {
82
+ replySubject = `Re: ${replySubject}`;
83
+ }
84
+
85
+ // Build reply-all recipient list, excluding the user's own email
86
+ const profile = await getProfile(gmailConn);
87
+ const userEmail = profile.emailAddress.toLowerCase();
88
+
89
+ const allRecipients = new Set<string>();
90
+ const allCc = new Set<string>();
91
+
92
+ // From the latest message: From goes to To, original To/Cc go to Cc
93
+ const fromAddr = extractHeader(latestHeaders, "From");
94
+ const toAddrs = extractHeader(latestHeaders, "To");
95
+ const ccAddrs = extractHeader(latestHeaders, "Cc");
96
+
97
+ if (fromAddr) allRecipients.add(fromAddr);
98
+ for (const addr of parseAddressList(toAddrs)) {
99
+ allRecipients.add(addr);
100
+ }
101
+ for (const addr of parseAddressList(ccAddrs)) {
102
+ allCc.add(addr);
103
+ }
104
+
105
+ // Remove user's own email from recipients using exact email comparison
106
+ const filterSelf = (addr: string) => extractEmail(addr) !== userEmail;
107
+ const toList = [...allRecipients].filter(filterSelf);
108
+ const ccList = [...allCc].filter(filterSelf);
109
+
110
+ if (toList.length === 0) {
111
+ return err("Could not determine reply recipients from thread.");
160
112
  }
161
113
 
162
- // With attachments: build multipart MIME and use createDraftRaw
114
+ // With attachments: build multipart MIME for threaded reply
163
115
  if (attachmentPaths?.length) {
164
116
  const attachments = await Promise.all(
165
117
  attachmentPaths.map(async (filePath) => {
@@ -171,51 +123,99 @@ export async function run(
171
123
  );
172
124
 
173
125
  const raw = buildMultipartMime({
174
- to: conversationId,
175
- subject: subject ?? "",
126
+ to: toList.join(", "),
127
+ subject: replySubject,
176
128
  body: text,
177
- inReplyTo,
129
+ inReplyTo: messageIdHeader || undefined,
130
+ cc: ccList.length > 0 ? ccList.join(", ") : undefined,
178
131
  attachments,
179
132
  });
180
- const draft = await createDraftRaw(token, raw, threadId);
133
+ const draft = await createDraftRaw(gmailConn, raw, threadId);
181
134
 
182
135
  const filenames = attachments.map((a) => a.filename).join(", ");
136
+ const recipientSummary =
137
+ ccList.length > 0
138
+ ? `To: ${toList.join(", ")}; Cc: ${ccList.join(", ")}`
139
+ : `To: ${toList.join(", ")}`;
183
140
  return ok(
184
- `Gmail draft created with ${attachments.length} attachment(s): ${filenames} (Draft ID: ${draft.id}). Review in Gmail Drafts, then tell me to send it or send it yourself.`,
141
+ `Gmail draft created with ${attachments.length} attachment(s): ${filenames} (Draft ID: ${draft.id}). ${recipientSummary}. Review in Gmail Drafts, then tell me to send it or send it yourself.`,
185
142
  );
186
143
  }
187
144
 
188
- // Without attachments: use standard createDraft
189
145
  const draft = await createDraft(
190
- token,
191
- conversationId,
192
- subject ?? "",
146
+ gmailConn,
147
+ toList.join(", "),
148
+ replySubject,
193
149
  text,
194
- inReplyTo,
195
- undefined,
150
+ messageIdHeader || undefined,
151
+ ccList.length > 0 ? ccList.join(", ") : undefined,
196
152
  undefined,
197
153
  threadId,
198
154
  );
155
+
156
+ const recipientSummary =
157
+ ccList.length > 0
158
+ ? `To: ${toList.join(", ")}; Cc: ${ccList.join(", ")}`
159
+ : `To: ${toList.join(", ")}`;
199
160
  return ok(
200
- `Gmail draft created (ID: ${draft.id}). Review it in your Gmail Drafts, then tell me to send it or send it yourself from Gmail.`,
161
+ `Gmail draft created (ID: ${draft.id}). ${recipientSummary}. Review in Gmail Drafts, then tell me to send it or send it yourself.`,
162
+ );
163
+ }
164
+
165
+ // With attachments: build multipart MIME and use createDraftRaw
166
+ if (attachmentPaths?.length) {
167
+ const attachments = await Promise.all(
168
+ attachmentPaths.map(async (filePath) => {
169
+ const data = await readFile(filePath);
170
+ const filename = basename(filePath);
171
+ const mimeType = guessMimeType(filePath);
172
+ return { filename, mimeType, data };
173
+ }),
201
174
  );
202
- });
203
- }
204
175
 
205
- // Non-Gmail platforms
206
- return withProviderToken(provider, async (token) => {
207
- const result = await provider.sendMessage(token, conversationId, text, {
208
- subject,
176
+ const raw = buildMultipartMime({
177
+ to: conversationId,
178
+ subject: subject ?? "",
179
+ body: text,
180
+ inReplyTo,
181
+ attachments,
182
+ });
183
+ const draft = await createDraftRaw(gmailConn, raw, threadId);
184
+
185
+ const filenames = attachments.map((a) => a.filename).join(", ");
186
+ return ok(
187
+ `Gmail draft created with ${attachments.length} attachment(s): ${filenames} (Draft ID: ${draft.id}). Review in Gmail Drafts, then tell me to send it or send it yourself.`,
188
+ );
189
+ }
190
+
191
+ // Without attachments: use standard createDraft
192
+ const draft = await createDraft(
193
+ gmailConn,
194
+ conversationId,
195
+ subject ?? "",
196
+ text,
209
197
  inReplyTo,
198
+ undefined,
199
+ undefined,
210
200
  threadId,
211
- assistantId: context.assistantId,
212
- });
201
+ );
202
+ return ok(
203
+ `Gmail draft created (ID: ${draft.id}). Review it in your Gmail Drafts, then tell me to send it or send it yourself from Gmail.`,
204
+ );
205
+ }
213
206
 
214
- const threadSuffix = result.threadId
215
- ? `, "thread_id": "${result.threadId}"`
216
- : "";
217
- return ok(`Message sent (ID: ${result.id}${threadSuffix}).`);
207
+ // Non-Gmail platforms
208
+ const result = await provider.sendMessage(conn, conversationId, text, {
209
+ subject,
210
+ inReplyTo,
211
+ threadId,
212
+ assistantId: context.assistantId,
218
213
  });
214
+
215
+ const threadSuffix = result.threadId
216
+ ? `, "thread_id": "${result.threadId}"`
217
+ : "";
218
+ return ok(`Message sent (ID: ${result.id}${threadSuffix}).`);
219
219
  } catch (e) {
220
220
  return err(e instanceof Error ? e.message : String(e));
221
221
  }