@aaronsb/google-workspace-mcp 2.7.1 → 4.0.0

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 (273) hide show
  1. package/LICENSE +201 -21
  2. package/LICENSE-MIT +21 -0
  3. package/NOTICE +24 -0
  4. package/README.md +87 -57
  5. package/build/accounts/auth.d.ts +1 -1
  6. package/build/accounts/auth.js +1 -1
  7. package/build/accounts/oauth.js +3 -3
  8. package/build/accounts/oauth.js.map +1 -1
  9. package/build/accounts/token-service.js +1 -1
  10. package/build/accounts/token-service.js.map +1 -1
  11. package/build/coverage/analyze.d.ts +1 -1
  12. package/build/coverage/analyze.js +6 -4
  13. package/build/coverage/analyze.js.map +1 -1
  14. package/build/coverage/baseline.js +37 -20
  15. package/build/coverage/baseline.js.map +1 -1
  16. package/build/coverage/compare.d.ts +1 -1
  17. package/build/coverage/compare.js +19 -10
  18. package/build/coverage/compare.js.map +1 -1
  19. package/build/coverage/discover.d.ts +5 -5
  20. package/build/coverage/discover.js +71 -155
  21. package/build/coverage/discover.js.map +1 -1
  22. package/build/coverage/report.js +3 -3
  23. package/build/coverage/report.js.map +1 -1
  24. package/build/coverage/types.d.ts +19 -4
  25. package/build/executor/index.d.ts +0 -3
  26. package/build/executor/index.js +1 -2
  27. package/build/executor/index.js.map +1 -1
  28. package/build/factory/defaults.js +30 -4
  29. package/build/factory/defaults.js.map +1 -1
  30. package/build/factory/generator.d.ts +10 -4
  31. package/build/factory/generator.js +86 -96
  32. package/build/factory/generator.js.map +1 -1
  33. package/build/factory/manifest/README.md +4 -3
  34. package/build/factory/manifest/calendar.yaml +1 -11
  35. package/build/factory/manifest/docs.yaml +15 -6
  36. package/build/factory/manifest/drive.yaml +1 -5
  37. package/build/factory/manifest/gmail.yaml +32 -25
  38. package/build/factory/manifest/meet.yaml +1 -1
  39. package/build/factory/manifest/sheets.yaml +2 -5
  40. package/build/factory/manifest/tasks.yaml +40 -2
  41. package/build/factory/patches.js +2 -0
  42. package/build/factory/patches.js.map +1 -1
  43. package/build/factory/registry.d.ts +2 -3
  44. package/build/factory/registry.js +3 -9
  45. package/build/factory/registry.js.map +1 -1
  46. package/build/factory/safety.js +4 -4
  47. package/build/factory/safety.js.map +1 -1
  48. package/build/factory/types.d.ts +16 -15
  49. package/build/google/client.d.ts +45 -0
  50. package/build/google/client.js +232 -0
  51. package/build/google/client.js.map +1 -0
  52. package/build/google/descriptor.d.ts +40 -0
  53. package/build/google/descriptor.js +37 -0
  54. package/build/google/descriptor.js.map +1 -0
  55. package/build/google/descriptor.json +5271 -0
  56. package/build/google/errors.d.ts +45 -0
  57. package/build/google/errors.js +44 -0
  58. package/build/google/errors.js.map +1 -0
  59. package/build/google/methods.d.ts +37 -0
  60. package/build/google/methods.js +13 -0
  61. package/build/google/methods.js.map +1 -0
  62. package/build/index.js +30 -4
  63. package/build/index.js.map +1 -1
  64. package/build/node-floor.d.ts +16 -0
  65. package/build/node-floor.js +73 -0
  66. package/build/node-floor.js.map +1 -0
  67. package/build/server/formatting/markdown.d.ts +1 -1
  68. package/build/server/formatting/markdown.js +1 -1
  69. package/build/server/formatting/next-steps.js +1 -1
  70. package/build/server/formatting/next-steps.js.map +1 -1
  71. package/build/server/handlers/accounts.js +2 -2
  72. package/build/server/handlers/accounts.js.map +1 -1
  73. package/build/server/scratchpad/adapters/import-doc.js +15 -13
  74. package/build/server/scratchpad/adapters/import-doc.js.map +1 -1
  75. package/build/server/scratchpad/adapters/import-drive.js +16 -11
  76. package/build/server/scratchpad/adapters/import-drive.js.map +1 -1
  77. package/build/server/scratchpad/adapters/import-email.js +3 -15
  78. package/build/server/scratchpad/adapters/import-email.js.map +1 -1
  79. package/build/server/scratchpad/adapters/import-meet.js +5 -17
  80. package/build/server/scratchpad/adapters/import-meet.js.map +1 -1
  81. package/build/server/scratchpad/adapters/import-sheet.js +5 -6
  82. package/build/server/scratchpad/adapters/import-sheet.js.map +1 -1
  83. package/build/server/scratchpad/adapters/send-calendar.js +18 -13
  84. package/build/server/scratchpad/adapters/send-calendar.js.map +1 -1
  85. package/build/server/scratchpad/adapters/send-doc.js +22 -17
  86. package/build/server/scratchpad/adapters/send-doc.js.map +1 -1
  87. package/build/server/scratchpad/adapters/send-email-draft.d.ts +1 -1
  88. package/build/server/scratchpad/adapters/send-email-draft.js +17 -25
  89. package/build/server/scratchpad/adapters/send-email-draft.js.map +1 -1
  90. package/build/server/scratchpad/adapters/send-email.d.ts +2 -2
  91. package/build/server/scratchpad/adapters/send-email.js +21 -25
  92. package/build/server/scratchpad/adapters/send-email.js.map +1 -1
  93. package/build/server/scratchpad/adapters/send-sheet.js +10 -10
  94. package/build/server/scratchpad/adapters/send-sheet.js.map +1 -1
  95. package/build/server/scratchpad/adapters/send-task.js +9 -8
  96. package/build/server/scratchpad/adapters/send-task.js.map +1 -1
  97. package/build/server/scratchpad/handler.js +22 -28
  98. package/build/server/scratchpad/handler.js.map +1 -1
  99. package/build/server/scratchpad/manager.d.ts +1 -1
  100. package/build/server/server.js +13 -10
  101. package/build/server/server.js.map +1 -1
  102. package/build/server/session/tracker.js +21 -25
  103. package/build/server/session/tracker.js.map +1 -1
  104. package/build/server/tools.d.ts +1 -1
  105. package/build/server/tools.js +3 -3
  106. package/build/server/tools.js.map +1 -1
  107. package/build/services/calendar/patch.js +178 -68
  108. package/build/services/calendar/patch.js.map +1 -1
  109. package/build/services/docs/patch.d.ts +5 -3
  110. package/build/services/docs/patch.js +89 -19
  111. package/build/services/docs/patch.js.map +1 -1
  112. package/build/services/drive/patch.d.ts +1 -1
  113. package/build/services/drive/patch.js +115 -143
  114. package/build/services/drive/patch.js.map +1 -1
  115. package/build/services/gmail/attachments.js +10 -15
  116. package/build/services/gmail/attachments.js.map +1 -1
  117. package/build/services/gmail/mail.d.ts +34 -0
  118. package/build/services/gmail/mail.js +245 -0
  119. package/build/services/gmail/mail.js.map +1 -0
  120. package/build/services/gmail/mime.d.ts +45 -4
  121. package/build/services/gmail/mime.js +138 -4
  122. package/build/services/gmail/mime.js.map +1 -1
  123. package/build/services/gmail/patch.js +103 -53
  124. package/build/services/gmail/patch.js.map +1 -1
  125. package/build/services/meet/patch.js +24 -31
  126. package/build/services/meet/patch.js.map +1 -1
  127. package/build/services/sheets/patch.js +30 -35
  128. package/build/services/sheets/patch.js.map +1 -1
  129. package/build/services/tasks/patch.d.ts +18 -0
  130. package/build/services/tasks/patch.js +37 -0
  131. package/build/services/tasks/patch.js.map +1 -0
  132. package/build/version.d.ts +1 -1
  133. package/build/version.js +2 -2
  134. package/build/version.js.map +1 -1
  135. package/package.json +23 -12
  136. package/build/__helpers__/testSetup.d.ts +0 -1
  137. package/build/__helpers__/testSetup.js +0 -6
  138. package/build/__helpers__/testSetup.js.map +0 -1
  139. package/build/__tests__/accounts/credentials.test.d.ts +0 -1
  140. package/build/__tests__/accounts/credentials.test.js +0 -127
  141. package/build/__tests__/accounts/credentials.test.js.map +0 -1
  142. package/build/__tests__/accounts/oauth.test.d.ts +0 -1
  143. package/build/__tests__/accounts/oauth.test.js +0 -71
  144. package/build/__tests__/accounts/oauth.test.js.map +0 -1
  145. package/build/__tests__/accounts/registry.test.d.ts +0 -1
  146. package/build/__tests__/accounts/registry.test.js +0 -74
  147. package/build/__tests__/accounts/registry.test.js.map +0 -1
  148. package/build/__tests__/accounts/token-service.test.d.ts +0 -1
  149. package/build/__tests__/accounts/token-service.test.js +0 -126
  150. package/build/__tests__/accounts/token-service.test.js.map +0 -1
  151. package/build/__tests__/executor/errors.test.d.ts +0 -1
  152. package/build/__tests__/executor/errors.test.js +0 -42
  153. package/build/__tests__/executor/errors.test.js.map +0 -1
  154. package/build/__tests__/executor/gws.test.d.ts +0 -1
  155. package/build/__tests__/executor/gws.test.js +0 -187
  156. package/build/__tests__/executor/gws.test.js.map +0 -1
  157. package/build/__tests__/executor/paths.test.d.ts +0 -1
  158. package/build/__tests__/executor/paths.test.js +0 -60
  159. package/build/__tests__/executor/paths.test.js.map +0 -1
  160. package/build/__tests__/executor/resolve-binary.test.d.ts +0 -1
  161. package/build/__tests__/executor/resolve-binary.test.js +0 -58
  162. package/build/__tests__/executor/resolve-binary.test.js.map +0 -1
  163. package/build/__tests__/executor/workspace.test.d.ts +0 -1
  164. package/build/__tests__/executor/workspace.test.js +0 -143
  165. package/build/__tests__/executor/workspace.test.js.map +0 -1
  166. package/build/__tests__/factory/calendar-patch.test.d.ts +0 -5
  167. package/build/__tests__/factory/calendar-patch.test.js +0 -269
  168. package/build/__tests__/factory/calendar-patch.test.js.map +0 -1
  169. package/build/__tests__/factory/drive-patch.test.d.ts +0 -5
  170. package/build/__tests__/factory/drive-patch.test.js +0 -344
  171. package/build/__tests__/factory/drive-patch.test.js.map +0 -1
  172. package/build/__tests__/factory/generator.test.d.ts +0 -1
  173. package/build/__tests__/factory/generator.test.js +0 -240
  174. package/build/__tests__/factory/generator.test.js.map +0 -1
  175. package/build/__tests__/factory/patch-coverage.test.d.ts +0 -10
  176. package/build/__tests__/factory/patch-coverage.test.js +0 -182
  177. package/build/__tests__/factory/patch-coverage.test.js.map +0 -1
  178. package/build/__tests__/factory/safety.test.d.ts +0 -1
  179. package/build/__tests__/factory/safety.test.js +0 -107
  180. package/build/__tests__/factory/safety.test.js.map +0 -1
  181. package/build/__tests__/factory/sheets-patch.test.d.ts +0 -6
  182. package/build/__tests__/factory/sheets-patch.test.js +0 -498
  183. package/build/__tests__/factory/sheets-patch.test.js.map +0 -1
  184. package/build/__tests__/integration/executor.test.d.ts +0 -5
  185. package/build/__tests__/integration/executor.test.js +0 -38
  186. package/build/__tests__/integration/executor.test.js.map +0 -1
  187. package/build/__tests__/integration/handlers.test.d.ts +0 -6
  188. package/build/__tests__/integration/handlers.test.js +0 -95
  189. package/build/__tests__/integration/handlers.test.js.map +0 -1
  190. package/build/__tests__/integration/setup.d.ts +0 -19
  191. package/build/__tests__/integration/setup.js +0 -61
  192. package/build/__tests__/integration/setup.js.map +0 -1
  193. package/build/__tests__/server/formatting/html-sanitize.test.d.ts +0 -7
  194. package/build/__tests__/server/formatting/html-sanitize.test.js +0 -174
  195. package/build/__tests__/server/formatting/html-sanitize.test.js.map +0 -1
  196. package/build/__tests__/server/formatting/markdown.test.d.ts +0 -1
  197. package/build/__tests__/server/formatting/markdown.test.js +0 -259
  198. package/build/__tests__/server/formatting/markdown.test.js.map +0 -1
  199. package/build/__tests__/server/formatting/next-steps.test.d.ts +0 -1
  200. package/build/__tests__/server/formatting/next-steps.test.js +0 -42
  201. package/build/__tests__/server/formatting/next-steps.test.js.map +0 -1
  202. package/build/__tests__/server/handler.test.d.ts +0 -1
  203. package/build/__tests__/server/handler.test.js +0 -97
  204. package/build/__tests__/server/handler.test.js.map +0 -1
  205. package/build/__tests__/server/handlers/__mocks__/executor.d.ts +0 -173
  206. package/build/__tests__/server/handlers/__mocks__/executor.js +0 -135
  207. package/build/__tests__/server/handlers/__mocks__/executor.js.map +0 -1
  208. package/build/__tests__/server/handlers/accounts.test.d.ts +0 -1
  209. package/build/__tests__/server/handlers/accounts.test.js +0 -129
  210. package/build/__tests__/server/handlers/accounts.test.js.map +0 -1
  211. package/build/__tests__/server/handlers/calendar.test.d.ts +0 -1
  212. package/build/__tests__/server/handlers/calendar.test.js +0 -96
  213. package/build/__tests__/server/handlers/calendar.test.js.map +0 -1
  214. package/build/__tests__/server/handlers/drive.test.d.ts +0 -1
  215. package/build/__tests__/server/handlers/drive.test.js +0 -68
  216. package/build/__tests__/server/handlers/drive.test.js.map +0 -1
  217. package/build/__tests__/server/handlers/email.test.d.ts +0 -1
  218. package/build/__tests__/server/handlers/email.test.js +0 -99
  219. package/build/__tests__/server/handlers/email.test.js.map +0 -1
  220. package/build/__tests__/server/handlers/validate.test.d.ts +0 -1
  221. package/build/__tests__/server/handlers/validate.test.js +0 -88
  222. package/build/__tests__/server/handlers/validate.test.js.map +0 -1
  223. package/build/__tests__/server/queue.test.d.ts +0 -1
  224. package/build/__tests__/server/queue.test.js +0 -199
  225. package/build/__tests__/server/queue.test.js.map +0 -1
  226. package/build/__tests__/server/server.test.d.ts +0 -7
  227. package/build/__tests__/server/server.test.js +0 -135
  228. package/build/__tests__/server/server.test.js.map +0 -1
  229. package/build/__tests__/server/session/context.test.d.ts +0 -1
  230. package/build/__tests__/server/session/context.test.js +0 -101
  231. package/build/__tests__/server/session/context.test.js.map +0 -1
  232. package/build/__tests__/server/session/tracker.test.d.ts +0 -1
  233. package/build/__tests__/server/session/tracker.test.js +0 -146
  234. package/build/__tests__/server/session/tracker.test.js.map +0 -1
  235. package/build/__tests__/server/tools.test.d.ts +0 -1
  236. package/build/__tests__/server/tools.test.js +0 -91
  237. package/build/__tests__/server/tools.test.js.map +0 -1
  238. package/build/__tests__/services/gmail/mime.test.d.ts +0 -1
  239. package/build/__tests__/services/gmail/mime.test.js +0 -23
  240. package/build/__tests__/services/gmail/mime.test.js.map +0 -1
  241. package/build/__tests__/services/meet/patch.test.d.ts +0 -4
  242. package/build/__tests__/services/meet/patch.test.js +0 -374
  243. package/build/__tests__/services/meet/patch.test.js.map +0 -1
  244. package/build/executor/errors.d.ts +0 -15
  245. package/build/executor/errors.js +0 -37
  246. package/build/executor/errors.js.map +0 -1
  247. package/build/executor/gws.d.ts +0 -26
  248. package/build/executor/gws.js +0 -197
  249. package/build/executor/gws.js.map +0 -1
  250. package/build/server/handlers/calendar.d.ts +0 -2
  251. package/build/server/handlers/calendar.js +0 -97
  252. package/build/server/handlers/calendar.js.map +0 -1
  253. package/build/server/handlers/drive.d.ts +0 -2
  254. package/build/server/handlers/drive.js +0 -58
  255. package/build/server/handlers/drive.js.map +0 -1
  256. package/build/server/handlers/email.d.ts +0 -2
  257. package/build/server/handlers/email.js +0 -119
  258. package/build/server/handlers/email.js.map +0 -1
  259. package/build/server/scratchpad/__tests__/docs-sync-integration.test.d.ts +0 -11
  260. package/build/server/scratchpad/__tests__/docs-sync-integration.test.js +0 -187
  261. package/build/server/scratchpad/__tests__/docs-sync-integration.test.js.map +0 -1
  262. package/build/server/scratchpad/__tests__/docs-sync.test.d.ts +0 -16
  263. package/build/server/scratchpad/__tests__/docs-sync.test.js +0 -213
  264. package/build/server/scratchpad/__tests__/docs-sync.test.js.map +0 -1
  265. package/build/server/scratchpad/__tests__/json-path.test.d.ts +0 -4
  266. package/build/server/scratchpad/__tests__/json-path.test.js +0 -121
  267. package/build/server/scratchpad/__tests__/json-path.test.js.map +0 -1
  268. package/build/server/scratchpad/__tests__/manager.test.d.ts +0 -5
  269. package/build/server/scratchpad/__tests__/manager.test.js +0 -886
  270. package/build/server/scratchpad/__tests__/manager.test.js.map +0 -1
  271. package/build/server/scratchpad/__tests__/validate.test.d.ts +0 -4
  272. package/build/server/scratchpad/__tests__/validate.test.js +0 -112
  273. package/build/server/scratchpad/__tests__/validate.test.js.map +0 -1
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Outbound mail: send, reply, reply-all, forward.
3
+ *
4
+ * The recipient logic below looks fussy. It is fussy because getting it wrong is
5
+ * silent and embarrassing: a reply-all that mails the sender their own reply, or
6
+ * that drops the one person who needed to see it. The rules are spelled out in
7
+ * docs/design-notes/adr-103-helper-semantics.md — read them before changing any
8
+ * of it.
9
+ */
10
+ import { readFile } from 'node:fs/promises';
11
+ import { basename } from 'node:path';
12
+ import { call, upload } from '../../google/client.js';
13
+ import { resolveWorkspacePath, verifyPathSafety } from '../../executor/workspace.js';
14
+ import { buildMimeMessage, lookupMimeType } from './mime.js';
15
+ /**
16
+ * Gmail's declared ceiling is 36,700,160 bytes for the ENCODED message. Base64
17
+ * inflates by 4/3, so we cap the RAW attachment bytes well under it and let the
18
+ * client's own descriptor-driven check (which knows the real declared maxSize) be
19
+ * the backstop. Refusing here gives the caller a sentence instead of a 400.
20
+ */
21
+ const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
22
+ /** Read workspace attachments, fenced by our own path safety check. */
23
+ async function loadAttachments(names) {
24
+ const parts = await Promise.all(names.map(async (name) => {
25
+ const filePath = resolveWorkspacePath(name);
26
+ await verifyPathSafety(filePath); // the workspace fence — attachments cannot escape it
27
+ const data = await readFile(filePath);
28
+ if (data.length === 0) {
29
+ throw new Error(`Attachment "${name}" is empty (0 bytes). Gmail rejects empty parts.`);
30
+ }
31
+ return { filename: basename(filePath), contentType: lookupMimeType(filePath), data };
32
+ }));
33
+ const total = parts.reduce((n, p) => n + p.data.length, 0);
34
+ if (total > MAX_ATTACHMENT_BYTES) {
35
+ throw new Error(`Attachments total ${(total / 1e6).toFixed(1)} MB; the limit is ` +
36
+ `${(MAX_ATTACHMENT_BYTES / 1e6).toFixed(0)} MB of raw bytes (base64 inflates by ~4/3 ` +
37
+ `and Gmail's encoded ceiling is 35 MB).`);
38
+ }
39
+ return parts;
40
+ }
41
+ /**
42
+ * Deliver a built message.
43
+ *
44
+ * Uses the RESUMABLE upload endpoint, always — never `uploadType=multipart`, which
45
+ * has no chunking and degrades at size. Resumable is verified (ADR-103 item 4) to
46
+ * carry a 25 MB attachment — 34.2 MB encoded, 93% of Google's declared cap — and to
47
+ * round-trip byte-for-byte.
48
+ */
49
+ async function deliver(message, account, draft, threadId) {
50
+ const resource = draft ? 'users.drafts.create' : 'users.messages.send';
51
+ const metadata = threadId
52
+ ? (draft ? { message: { threadId } } : { threadId })
53
+ : {};
54
+ return await upload('gmail', resource, { userId: 'me' }, {
55
+ account,
56
+ media: message,
57
+ contentType: 'message/rfc822',
58
+ metadata,
59
+ });
60
+ }
61
+ export async function sendMail(account, opts) {
62
+ const attachments = opts.attachments?.length ? await loadAttachments(opts.attachments) : [];
63
+ const message = buildMimeMessage({
64
+ to: opts.to, subject: opts.subject, body: opts.body,
65
+ from: opts.from, cc: opts.cc, bcc: opts.bcc, html: opts.html,
66
+ attachments,
67
+ });
68
+ return deliver(message, account, opts.draft === true);
69
+ }
70
+ const headerOf = (headers, name) => headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? '';
71
+ /** Walk the payload for the first text/plain body. */
72
+ function plainBody(payload) {
73
+ const mime = String(payload.mimeType ?? '');
74
+ const body = payload.body;
75
+ if (mime === 'text/plain' && body?.data) {
76
+ return Buffer.from(body.data, 'base64url').toString('utf-8');
77
+ }
78
+ for (const part of (payload.parts ?? [])) {
79
+ const found = plainBody(part);
80
+ if (found)
81
+ return found;
82
+ }
83
+ return '';
84
+ }
85
+ async function fetchOriginal(account, messageId) {
86
+ const msg = await call('gmail', 'users.messages.get', { userId: 'me', id: messageId, format: 'full' }, { account });
87
+ const payload = msg.payload;
88
+ const headers = (payload.headers ?? []);
89
+ return {
90
+ id: String(msg.id),
91
+ threadId: String(msg.threadId),
92
+ messageId: headerOf(headers, 'Message-ID') || undefined,
93
+ references: headerOf(headers, 'References') || undefined,
94
+ subject: headerOf(headers, 'Subject'),
95
+ from: headerOf(headers, 'From'),
96
+ to: headerOf(headers, 'To'),
97
+ cc: headerOf(headers, 'Cc'),
98
+ replyTo: headerOf(headers, 'Reply-To') || undefined,
99
+ date: headerOf(headers, 'Date'),
100
+ bodyText: plainBody(payload),
101
+ };
102
+ }
103
+ /** `a@b.com`, `"Name" <a@b.com>` -> `a@b.com`, lowercased. The identity we compare on. */
104
+ function emailOf(address) {
105
+ const angled = address.match(/<([^>]+)>/);
106
+ return (angled ? angled[1] : address).trim().toLowerCase();
107
+ }
108
+ const splitAddresses = (list) => list.split(',').map((a) => a.trim()).filter(Boolean);
109
+ /**
110
+ * Enforce To > Cc > Bcc precedence and drop excluded/duplicate addresses.
111
+ *
112
+ * An address that appears in two fields survives only in the highest — otherwise
113
+ * a recipient is mailed twice, and Gmail shows them in both lines.
114
+ */
115
+ function dedupeRecipients(to, cc, excluded) {
116
+ const seen = new Set();
117
+ const keep = (list) => list.filter((a) => {
118
+ const key = emailOf(a);
119
+ if (!key || excluded.has(key) || seen.has(key))
120
+ return false;
121
+ seen.add(key);
122
+ return true;
123
+ });
124
+ return { to: keep(to), cc: keep(cc) };
125
+ }
126
+ /** `Re: ` unless it is already a reply. Case-insensitive, like Gmail. */
127
+ const rePrefix = (subject) => /^re:/i.test(subject.trim()) ? subject : `Re: ${subject}`;
128
+ const fwdPrefix = (subject) => /^fwd:/i.test(subject.trim()) ? subject : `Fwd: ${subject}`;
129
+ /** Quote the original, plain-text style. */
130
+ function quotePlain(o) {
131
+ const attribution = `On ${o.date}, ${o.from} wrote:`;
132
+ const quoted = o.bodyText.split('\n').map((l) => `> ${l}`).join('\n');
133
+ return `${attribution}\n${quoted}`;
134
+ }
135
+ export async function replyMail(account, opts) {
136
+ const original = await fetchOriginal(account, opts.messageId);
137
+ // Who gets it. `Reply-To` wins over `From` when the sender asked for it.
138
+ const primaryTo = original.replyTo || original.from;
139
+ let to = splitAddresses(primaryTo);
140
+ let cc = opts.cc ? splitAddresses(opts.cc) : [];
141
+ const excluded = new Set();
142
+ if (opts.all) {
143
+ // Never reply-all to yourself. The account's own address is excluded, or you
144
+ // mail yourself a copy of your own reply — visibly, in the To line.
145
+ excluded.add(emailOf(account));
146
+ const iSentIt = emailOf(original.from) === emailOf(account);
147
+ if (iSentIt) {
148
+ // Replying-all to your OWN message: the people to reach are the ones you
149
+ // originally wrote to, not yourself. Matches Gmail's web behaviour, and
150
+ // Reply-To is ignored here on purpose.
151
+ to = splitAddresses(original.to);
152
+ cc = [...splitAddresses(original.cc), ...cc];
153
+ }
154
+ else {
155
+ // Everyone else on the thread moves to Cc; the sender stays in To.
156
+ cc = [...splitAddresses(original.to), ...splitAddresses(original.cc), ...cc];
157
+ }
158
+ }
159
+ const recipients = dedupeRecipients(to, cc, excluded);
160
+ if (recipients.to.length === 0) {
161
+ throw new Error('No recipient remains after removing your own address. Nobody would receive this reply.');
162
+ }
163
+ const quoted = opts.html
164
+ ? `${opts.body}<br><blockquote>${original.bodyText}</blockquote>`
165
+ : `${opts.body}\n\n${quotePlain(original)}`;
166
+ // Threading. In-Reply-To points at the parent; References carries the chain so
167
+ // clients that do not know threadId can still build the tree.
168
+ const references = [original.references, original.messageId].filter(Boolean).join(' ');
169
+ const message = buildMimeMessage({
170
+ to: recipients.to.join(', '),
171
+ cc: recipients.cc.length ? recipients.cc.join(', ') : undefined,
172
+ subject: rePrefix(original.subject),
173
+ body: quoted,
174
+ html: opts.html,
175
+ attachments: opts.attachments?.length ? await loadAttachments(opts.attachments) : [],
176
+ inReplyTo: original.messageId,
177
+ references: references || undefined,
178
+ });
179
+ return deliver(message, account, opts.draft === true, original.threadId);
180
+ }
181
+ /** Pull the original's real attachment bytes back out of Gmail. */
182
+ async function originalAttachments(account, original) {
183
+ const msg = await call('gmail', 'users.messages.get', { userId: 'me', id: original.id, format: 'full' }, { account });
184
+ const found = [];
185
+ const walk = (part) => {
186
+ const body = part.body;
187
+ if (part.filename && body?.attachmentId) {
188
+ found.push({
189
+ filename: String(part.filename),
190
+ contentType: String(part.mimeType ?? 'application/octet-stream'),
191
+ attachmentId: body.attachmentId,
192
+ });
193
+ }
194
+ for (const p of (part.parts ?? []))
195
+ walk(p);
196
+ };
197
+ walk(msg.payload);
198
+ return Promise.all(found.map(async (f) => {
199
+ const att = await call('gmail', 'users.messages.attachments.get', { userId: 'me', messageId: original.id, id: f.attachmentId }, { account });
200
+ return {
201
+ filename: f.filename,
202
+ contentType: f.contentType,
203
+ data: Buffer.from(att.data, 'base64url'),
204
+ };
205
+ }));
206
+ }
207
+ export async function forwardMail(account, opts) {
208
+ const original = await fetchOriginal(account, opts.messageId);
209
+ const attachments = opts.includeAttachments === false
210
+ ? []
211
+ : await originalAttachments(account, original);
212
+ const header = `---------- Forwarded message ---------\n` +
213
+ `From: ${original.from}\n` +
214
+ (original.date ? `Date: ${original.date}\n` : '') +
215
+ `Subject: ${original.subject}\n` +
216
+ `To: ${original.to}\n` +
217
+ (original.cc ? `Cc: ${original.cc}\n` : '') +
218
+ `\n`;
219
+ const body = `${opts.body ? `${opts.body}\n\n` : ''}${header}${original.bodyText}`;
220
+ // A forward THREADS. It is a continuation of the message's identity, not a new
221
+ // conversation, and this is what every mail client actually does.
222
+ //
223
+ // Measured, not assumed — an earlier version of this function did NOT thread,
224
+ // on the confident but INVENTED belief that Gmail's web client starts a new
225
+ // thread on forward. It does not. Checking real mail settled it: every forward
226
+ // sent from this account carries In-Reply-To + References and shares the
227
+ // original's threadId, and 20/20 forwards RECEIVED from other clients — Gmail,
228
+ // Outlook/Exchange, Yahoo — carry the same headers. RFC 5322 §3.6.4 is the
229
+ // mechanism; universal practice is the evidence.
230
+ //
231
+ // The forward lives in the sender's own thread, in the sender's own mailbox. The
232
+ // original recipients never see it.
233
+ const references = [original.references, original.messageId].filter(Boolean).join(' ');
234
+ const message = buildMimeMessage({
235
+ to: opts.to,
236
+ subject: fwdPrefix(original.subject),
237
+ body,
238
+ html: opts.html,
239
+ attachments,
240
+ inReplyTo: original.messageId,
241
+ references: references || undefined,
242
+ });
243
+ return deliver(message, account, opts.draft === true, original.threadId);
244
+ }
245
+ //# sourceMappingURL=mail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail.js","sourceRoot":"","sources":["../../../src/services/gmail/mail.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,50 @@
1
1
  /**
2
- * MIME type lookup utility.
2
+ * MIME: type lookup, and the RFC 5322 message builder.
3
3
  *
4
- * The MIME message builder that used to live here was removed when gws 0.18+
5
- * added native --attach support to mail helpers. MIME construction is now
6
- * handled by the gws CLI directly.
4
+ * Two constraints worth stating, because getting either wrong is silent:
5
+ *
6
+ * 1. **Header injection.** A `To:` or `Subject:` carrying CR or LF splits the
7
+ * header block and lets a caller forge headers (Bcc, From) or inject a body.
8
+ * Every header value is stripped of control characters. This is not
9
+ * theoretical: the values come from an LLM, through a tool call.
10
+ *
11
+ * 2. **Inline images belong in multipart/related, not multipart/mixed.** Gmail
12
+ * rewrites `Content-Disposition: inline` to `attachment` when a CID part sits
13
+ * in a `mixed` container, so an inline image silently becomes a dangling
14
+ * attachment.
7
15
  */
8
16
  /** Look up MIME type by filename extension. */
9
17
  export declare function lookupMimeType(filename: string): string;
18
+ export interface MimeAttachment {
19
+ filename: string;
20
+ contentType: string;
21
+ data: Buffer;
22
+ /** Present for inline images. Forces a multipart/related container — see the header note. */
23
+ contentId?: string;
24
+ }
25
+ export interface MimeMessage {
26
+ to: string;
27
+ subject: string;
28
+ body: string;
29
+ from?: string;
30
+ cc?: string;
31
+ bcc?: string;
32
+ html?: boolean;
33
+ attachments?: MimeAttachment[];
34
+ /** Threading (reply/forward). */
35
+ inReplyTo?: string;
36
+ references?: string;
37
+ }
38
+ /**
39
+ * Build an RFC 5322 message, base64url-encoded for Gmail's `raw` field or for a
40
+ * `message/rfc822` media upload.
41
+ *
42
+ * Structure is chosen by content, not by habit:
43
+ * body only -> text/plain or text/html
44
+ * body + inline images -> multipart/related (NOT mixed — Gmail rewrites inline->attachment in mixed)
45
+ * body + attachments -> multipart/mixed
46
+ * body + inline + attachments-> multipart/mixed [ multipart/related [ body, inline… ], attachments… ]
47
+ */
48
+ export declare function buildMimeMessage(msg: MimeMessage): Buffer;
49
+ /** Gmail's `raw` field wants base64url with no padding. */
50
+ export declare function toRawField(message: Buffer): string;
@@ -1,9 +1,17 @@
1
1
  /**
2
- * MIME type lookup utility.
2
+ * MIME: type lookup, and the RFC 5322 message builder.
3
3
  *
4
- * The MIME message builder that used to live here was removed when gws 0.18+
5
- * added native --attach support to mail helpers. MIME construction is now
6
- * handled by the gws CLI directly.
4
+ * Two constraints worth stating, because getting either wrong is silent:
5
+ *
6
+ * 1. **Header injection.** A `To:` or `Subject:` carrying CR or LF splits the
7
+ * header block and lets a caller forge headers (Bcc, From) or inject a body.
8
+ * Every header value is stripped of control characters. This is not
9
+ * theoretical: the values come from an LLM, through a tool call.
10
+ *
11
+ * 2. **Inline images belong in multipart/related, not multipart/mixed.** Gmail
12
+ * rewrites `Content-Disposition: inline` to `attachment` when a CID part sits
13
+ * in a `mixed` container, so an inline image silently becomes a dangling
14
+ * attachment.
7
15
  */
8
16
  /** Common extension → MIME type map. Falls back to application/octet-stream. */
9
17
  const MIME_TYPES = {
@@ -46,4 +54,130 @@ export function lookupMimeType(filename) {
46
54
  const ext = filename.slice(dotIndex).toLowerCase();
47
55
  return MIME_TYPES[ext] ?? 'application/octet-stream';
48
56
  }
57
+ // --- RFC 5322 message construction ---
58
+ const CRLF = '\r\n';
59
+ /**
60
+ * Strip CR/LF and other control characters from a header value.
61
+ *
62
+ * A newline in a header value ENDS THE HEADER and starts a new one. A caller who
63
+ * gets `\r\nBcc: attacker@evil.com` into a subject has forged a Bcc. Values here
64
+ * originate from a model's tool call, so this is a real input, not a hypothetical.
65
+ */
66
+ function sanitizeHeader(value) {
67
+ // eslint-disable-next-line no-control-regex
68
+ return value.replace(/[\x00-\x1F\x7F]/g, '');
69
+ }
70
+ /**
71
+ * Encode a header value that may contain non-ASCII, per RFC 2047.
72
+ * A bare UTF-8 subject is not legal in a header; Gmail tolerates it, other
73
+ * receivers mangle it. Encode only when needed, so plain subjects stay readable.
74
+ */
75
+ function encodeHeaderValue(value) {
76
+ const clean = sanitizeHeader(value);
77
+ // eslint-disable-next-line no-control-regex
78
+ if (!/[^\x00-\x7F]/.test(clean))
79
+ return clean;
80
+ return `=?UTF-8?B?${Buffer.from(clean, 'utf-8').toString('base64')}?=`;
81
+ }
82
+ /** Wrap base64 at 76 chars, as RFC 2045 requires. */
83
+ function wrapBase64(data) {
84
+ return data.toString('base64').replace(/(.{76})/g, `$1${CRLF}`);
85
+ }
86
+ /**
87
+ * A boundary that cannot collide with the content it delimits.
88
+ *
89
+ * If a boundary string happens to occur inside an attachment's base64 (or inside
90
+ * a body), the message silently truncates at that point. Random suffix, and the
91
+ * marker is not something base64 can produce (it contains `=` only at the end and
92
+ * `_`), so a collision would have to be deliberate.
93
+ */
94
+ function makeBoundary(tag) {
95
+ return `----gwsmcp_${tag}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
96
+ }
97
+ /**
98
+ * Build an RFC 5322 message, base64url-encoded for Gmail's `raw` field or for a
99
+ * `message/rfc822` media upload.
100
+ *
101
+ * Structure is chosen by content, not by habit:
102
+ * body only -> text/plain or text/html
103
+ * body + inline images -> multipart/related (NOT mixed — Gmail rewrites inline->attachment in mixed)
104
+ * body + attachments -> multipart/mixed
105
+ * body + inline + attachments-> multipart/mixed [ multipart/related [ body, inline… ], attachments… ]
106
+ */
107
+ export function buildMimeMessage(msg) {
108
+ const headers = [];
109
+ const add = (name, value) => {
110
+ if (value)
111
+ headers.push(`${name}: ${encodeHeaderValue(value)}`);
112
+ };
113
+ add('From', msg.from);
114
+ add('To', msg.to);
115
+ add('Cc', msg.cc);
116
+ add('Bcc', msg.bcc);
117
+ add('Subject', msg.subject);
118
+ // Message-ID / References must NOT be RFC-2047 encoded — they are addr-spec
119
+ // tokens, not display text. Sanitize only.
120
+ if (msg.inReplyTo)
121
+ headers.push(`In-Reply-To: ${sanitizeHeader(msg.inReplyTo)}`);
122
+ if (msg.references)
123
+ headers.push(`References: ${sanitizeHeader(msg.references)}`);
124
+ headers.push('MIME-Version: 1.0');
125
+ const bodyType = msg.html ? 'text/html' : 'text/plain';
126
+ const bodyPart = () => `Content-Type: ${bodyType}; charset="UTF-8"${CRLF}` +
127
+ `Content-Transfer-Encoding: base64${CRLF}${CRLF}` +
128
+ wrapBase64(Buffer.from(msg.body, 'utf-8'));
129
+ const attachmentPart = (a) => `Content-Type: ${a.contentType}; name="${sanitizeHeader(a.filename)}"${CRLF}` +
130
+ (a.contentId
131
+ ? `Content-Disposition: inline; filename="${sanitizeHeader(a.filename)}"${CRLF}` +
132
+ `Content-ID: <${sanitizeHeader(a.contentId)}>${CRLF}`
133
+ : `Content-Disposition: attachment; filename="${sanitizeHeader(a.filename)}"${CRLF}`) +
134
+ `Content-Transfer-Encoding: base64${CRLF}${CRLF}` +
135
+ wrapBase64(a.data);
136
+ const all = msg.attachments ?? [];
137
+ const inline = all.filter((a) => a.contentId);
138
+ const files = all.filter((a) => !a.contentId);
139
+ let content;
140
+ if (all.length === 0) {
141
+ headers.push(`Content-Type: ${bodyType}; charset="UTF-8"`);
142
+ headers.push('Content-Transfer-Encoding: base64');
143
+ content = wrapBase64(Buffer.from(msg.body, 'utf-8'));
144
+ }
145
+ else {
146
+ // The body, possibly wrapped with its inline images in a `related` container.
147
+ let bodyBlock;
148
+ if (inline.length > 0) {
149
+ const rel = makeBoundary('rel');
150
+ bodyBlock =
151
+ `Content-Type: multipart/related; boundary="${rel}"${CRLF}${CRLF}` +
152
+ `--${rel}${CRLF}${bodyPart()}${CRLF}` +
153
+ inline.map((a) => `--${rel}${CRLF}${attachmentPart(a)}${CRLF}`).join('') +
154
+ `--${rel}--`;
155
+ }
156
+ else {
157
+ bodyBlock = bodyPart();
158
+ }
159
+ if (files.length === 0) {
160
+ // Inline images only: the related container IS the message.
161
+ const rel = makeBoundary('rel');
162
+ headers.push(`Content-Type: multipart/related; boundary="${rel}"`);
163
+ content =
164
+ `--${rel}${CRLF}${bodyPart()}${CRLF}` +
165
+ inline.map((a) => `--${rel}${CRLF}${attachmentPart(a)}${CRLF}`).join('') +
166
+ `--${rel}--`;
167
+ }
168
+ else {
169
+ const mixed = makeBoundary('mix');
170
+ headers.push(`Content-Type: multipart/mixed; boundary="${mixed}"`);
171
+ content =
172
+ `--${mixed}${CRLF}${bodyBlock}${CRLF}` +
173
+ files.map((a) => `--${mixed}${CRLF}${attachmentPart(a)}${CRLF}`).join('') +
174
+ `--${mixed}--`;
175
+ }
176
+ }
177
+ return Buffer.from(headers.join(CRLF) + CRLF + CRLF + content + CRLF, 'utf-8');
178
+ }
179
+ /** Gmail's `raw` field wants base64url with no padding. */
180
+ export function toRawField(message) {
181
+ return message.toString('base64url');
182
+ }
49
183
  //# sourceMappingURL=mime.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mime.js","sourceRoot":"","sources":["../../../src/services/gmail/mime.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,gFAAgF;AAChF,MAAM,UAAU,GAA2B;IACzC,KAAK,EAAE,eAAe;IACtB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,WAAW;IACpB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,iBAAiB;IACzB,KAAK,EAAE,kBAAkB;IACzB,MAAM,EAAE,mBAAmB;IAC3B,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,eAAe;IACvB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,oBAAoB;IAC5B,OAAO,EAAE,yEAAyE;IAClF,MAAM,EAAE,0BAA0B;IAClC,OAAO,EAAE,mEAAmE;IAC5E,MAAM,EAAE,+BAA+B;IACvC,OAAO,EAAE,2EAA2E;IACpF,OAAO,EAAE,oBAAoB;IAC7B,MAAM,EAAE,oBAAoB;IAC5B,KAAK,EAAE,wBAAwB;IAC/B,KAAK,EAAE,mBAAmB;IAC1B,KAAK,EAAE,eAAe;IACtB,KAAK,EAAE,kBAAkB;CAC1B,CAAC;AAEF,+CAA+C;AAC/C,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,QAAQ,KAAK,CAAC,CAAC;QAAE,OAAO,0BAA0B,CAAC;IACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACnD,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;AACvD,CAAC"}
1
+ {"version":3,"file":"mime.js","sourceRoot":"","sources":["../../../src/services/gmail/mime.ts"],"names":[],"mappings":"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"}