@datasynx/agentic-crm 1.0.0 → 1.2.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 (89) hide show
  1. package/README.md +8 -1
  2. package/dist/{ask-D8iYqDAr.js → ask-CDysGnRg.js} +2 -2
  3. package/dist/{ask-D8iYqDAr.js.map → ask-CDysGnRg.js.map} +1 -1
  4. package/dist/attachments-CX2GAtsw.cjs +517 -0
  5. package/dist/attachments-CX2GAtsw.cjs.map +1 -0
  6. package/dist/attachments-D207gXfN.js +514 -0
  7. package/dist/attachments-D207gXfN.js.map +1 -0
  8. package/dist/attachments-rLa96rOK.js +514 -0
  9. package/dist/attachments-rLa96rOK.js.map +1 -0
  10. package/dist/chunk-BfDYWZQ8.cjs +32 -0
  11. package/dist/chunk-BfDYWZQ8.cjs.map +1 -0
  12. package/dist/chunk-BhUZmQg5.js +32 -0
  13. package/dist/chunk-BhUZmQg5.js.map +1 -0
  14. package/dist/chunk-ChC83jai.js +2 -0
  15. package/dist/chunk-e_w8qqtP.js +32 -0
  16. package/dist/chunk-e_w8qqtP.js.map +1 -0
  17. package/dist/cli.js +20 -18
  18. package/dist/cli.js.map +1 -1
  19. package/dist/daemon/worker.js +3 -3
  20. package/dist/{doctor-C14-vnJ1.js → doctor-BFeelnq8.js} +2 -2
  21. package/dist/{doctor-C14-vnJ1.js.map → doctor-BFeelnq8.js.map} +1 -1
  22. package/dist/doctor-CYDaNmFn.js +2 -0
  23. package/dist/email-body-BFSRa0AW.cjs +42 -0
  24. package/dist/email-body-BFSRa0AW.cjs.map +1 -0
  25. package/dist/email-body-BOd7U-D2.js +42 -0
  26. package/dist/email-body-BOd7U-D2.js.map +1 -0
  27. package/dist/{gmail-sync-DueE6tl5.js → gmail-sync-B4Iu3AQb.js} +45 -15
  28. package/dist/gmail-sync-B4Iu3AQb.js.map +1 -0
  29. package/dist/{gmail-sync-GEy3oVvw.cjs → gmail-sync-BpSVESSe.cjs} +45 -15
  30. package/dist/gmail-sync-BpSVESSe.cjs.map +1 -0
  31. package/dist/{gmail-sync-C-NmibzS.js → gmail-sync-DIbrPnTK.js} +45 -15
  32. package/dist/gmail-sync-DIbrPnTK.js.map +1 -0
  33. package/dist/{gmail-webhook-handler-kGKpbY9h.js → gmail-webhook-handler-BzOFbvgh.js} +2 -2
  34. package/dist/{gmail-webhook-handler-kGKpbY9h.js.map → gmail-webhook-handler-BzOFbvgh.js.map} +1 -1
  35. package/dist/{gmail-webhook-handler-B26COilD.js → gmail-webhook-handler-CvSDW_Js.js} +1 -1
  36. package/dist/{google-drive-sync-D1n7WKZn.js → google-drive-sync-B_I1d54Y.js} +2 -2
  37. package/dist/{google-drive-sync-D1n7WKZn.js.map → google-drive-sync-B_I1d54Y.js.map} +1 -1
  38. package/dist/html-BaeOCZKE.js +36 -0
  39. package/dist/html-BaeOCZKE.js.map +1 -0
  40. package/dist/html-CmOku6jS.cjs +47 -0
  41. package/dist/html-CmOku6jS.cjs.map +1 -0
  42. package/dist/{import-hubspot-DB4n89jy.js → import-hubspot-CTId9IGV.js} +2 -2
  43. package/dist/{import-hubspot-DB4n89jy.js.map → import-hubspot-CTId9IGV.js.map} +1 -1
  44. package/dist/{index-pY7tYXwH.d.cts → index-CLUKKfGb.d.cts} +12 -8
  45. package/dist/index-CLUKKfGb.d.cts.map +1 -0
  46. package/dist/{index-B0IMMrp_.d.ts → index-D8jJ1VIt.d.ts} +14 -10
  47. package/dist/index-D8jJ1VIt.d.ts.map +1 -0
  48. package/dist/index.d.cts +12 -8
  49. package/dist/index.d.cts.map +1 -1
  50. package/dist/index.d.ts +14 -10
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/{interactions-writer-RJB8SWf2.js → interactions-writer-B2y-73lh.js} +1 -1
  53. package/dist/{interactions-writer-DbSyI2rt.js → interactions-writer-B8XAzdqR.js} +3 -2
  54. package/dist/interactions-writer-B8XAzdqR.js.map +1 -0
  55. package/dist/{interactions-writer-a2yzBd7T.cjs → interactions-writer-BRJNrefF.cjs} +3 -2
  56. package/dist/interactions-writer-BRJNrefF.cjs.map +1 -0
  57. package/dist/{interactions-writer-BZzUIgJd.js → interactions-writer-ZQcpFOh9.js} +3 -2
  58. package/dist/interactions-writer-ZQcpFOh9.js.map +1 -0
  59. package/dist/{knowledge-base-DHNc4hVj.js → knowledge-base-Bx2PKQR2.js} +10 -7
  60. package/dist/knowledge-base-Bx2PKQR2.js.map +1 -0
  61. package/dist/mcp-CdTJWTJf.d.cts.map +1 -1
  62. package/dist/mcp-CdTJWTJf.d.ts.map +1 -1
  63. package/dist/mcp.cjs +308 -150
  64. package/dist/mcp.cjs.map +1 -1
  65. package/dist/mcp.d.cts.map +1 -1
  66. package/dist/mcp.d.ts.map +1 -1
  67. package/dist/mcp.js +308 -150
  68. package/dist/mcp.js.map +1 -1
  69. package/dist/{microsoft-calendar-jIu9K5zX.js → microsoft-calendar-BgVR8GDv.js} +3 -3
  70. package/dist/{microsoft-calendar-jIu9K5zX.js.map → microsoft-calendar-BgVR8GDv.js.map} +1 -1
  71. package/dist/{microsoft-sync-R_r8HL-B.js → microsoft-sync-D30_XksI.js} +3 -3
  72. package/dist/{microsoft-sync-R_r8HL-B.js.map → microsoft-sync-D30_XksI.js.map} +1 -1
  73. package/dist/{nba-mTJ4yEqD.js → nba-DwdfM93s.js} +2 -2
  74. package/dist/{nba-mTJ4yEqD.js.map → nba-DwdfM93s.js.map} +1 -1
  75. package/dist/{server-DqSMYhSA.js → server-BhNLrnAD.js} +201 -145
  76. package/dist/server-BhNLrnAD.js.map +1 -0
  77. package/dist/{transcript-watcher-0mh2ZhmH.js → transcript-watcher-BoClrJAz.js} +2 -2
  78. package/dist/{transcript-watcher-0mh2ZhmH.js.map → transcript-watcher-BoClrJAz.js.map} +1 -1
  79. package/package.json +13 -2
  80. package/dist/gmail-sync-C-NmibzS.js.map +0 -1
  81. package/dist/gmail-sync-DueE6tl5.js.map +0 -1
  82. package/dist/gmail-sync-GEy3oVvw.cjs.map +0 -1
  83. package/dist/index-B0IMMrp_.d.ts.map +0 -1
  84. package/dist/index-pY7tYXwH.d.cts.map +0 -1
  85. package/dist/interactions-writer-BZzUIgJd.js.map +0 -1
  86. package/dist/interactions-writer-DbSyI2rt.js.map +0 -1
  87. package/dist/interactions-writer-a2yzBd7T.cjs.map +0 -1
  88. package/dist/knowledge-base-DHNc4hVj.js.map +0 -1
  89. package/dist/server-DqSMYhSA.js.map +0 -1
@@ -39,7 +39,7 @@ async function syncAllCustomers() {
39
39
  const credPath = path.join(DATA_DIR, ".agentic", "gmail-credentials.json");
40
40
  if (fs.existsSync(tokenPath) && fs.existsSync(credPath)) {
41
41
  const { getGmailAuth } = await import("../gmail-auth-OComS92L.js");
42
- const { syncGmail } = await import("../gmail-sync-DueE6tl5.js");
42
+ const { syncGmail } = await import("../gmail-sync-B4Iu3AQb.js");
43
43
  const auth = await getGmailAuth(credPath, tokenPath);
44
44
  await syncWithBackoff(async () => {
45
45
  const result = await syncGmail({
@@ -72,7 +72,7 @@ async function startWatcher() {
72
72
  try {
73
73
  const sources = JSON.parse(fs.readFileSync(agenticSourcesPath, "utf-8"));
74
74
  if (sources.transcripts?.enabled && sources.transcripts.paths?.length) {
75
- const { watchTranscripts, processTranscriptFileAutoMatch } = await import("../transcript-watcher-0mh2ZhmH.js");
75
+ const { watchTranscripts, processTranscriptFileAutoMatch } = await import("../transcript-watcher-BoClrJAz.js");
76
76
  watchTranscripts({
77
77
  paths: sources.transcripts.paths,
78
78
  extensions: sources.transcripts.extensions ?? [".txt", ".vtt"],
@@ -158,7 +158,7 @@ new CronJob("*/60 * * * *", async () => {
158
158
  new CronJob("0 6 * * *", async () => {
159
159
  try {
160
160
  const { renewExpiringSubscriptions } = await import("../push-manager-BXM-IHfP.js");
161
- const { buildGmailRenewFn } = await import("../gmail-webhook-handler-B26COilD.js");
161
+ const { buildGmailRenewFn } = await import("../gmail-webhook-handler-CvSDW_Js.js");
162
162
  const tokenPath = path.join(DATA_DIR, ".agentic", "gmail-token.json");
163
163
  const credPath = path.join(DATA_DIR, ".agentic", "gmail-credentials.json");
164
164
  const { readSubscriptions } = await import("../push-manager-BXM-IHfP.js");
@@ -98,6 +98,6 @@ async function runDiagnostics(dataDir) {
98
98
  };
99
99
  }
100
100
  //#endregion
101
- export { cleanupTempFiles, runDiagnostics };
101
+ export { runDiagnostics as n, cleanupTempFiles as t };
102
102
 
103
- //# sourceMappingURL=doctor-C14-vnJ1.js.map
103
+ //# sourceMappingURL=doctor-BFeelnq8.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"doctor-C14-vnJ1.js","names":[],"sources":["../src/core/doctor.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { listCustomerSlugs, readMainFacts } from \"../fs/customer-dir.js\";\nimport { summarizeLogs } from \"./logger.js\";\n\n/**\n * Self-diagnostic (`dxcrm doctor`). Ties together the integrity, observability\n * and validation work into a single operator-facing health check: is the data\n * directory sound, is customer data valid, are there orphaned atomic-write temp\n * files (a crash signature), recent log errors, or a stale backup?\n */\nexport type CheckStatus = \"ok\" | \"warn\" | \"fail\";\n\nexport interface DiagnosticCheck {\n name: string;\n status: CheckStatus;\n detail: string;\n}\n\nexport interface DiagnosticReport {\n ok: boolean; // false if any check failed\n checks: DiagnosticCheck[];\n}\n\n/** Recursively collect files whose name matches the atomic-write temp pattern. */\nfunction findOrphanedTempFiles(dir: string, depth = 0): string[] {\n if (depth > 3 || !fs.existsSync(dir)) return [];\n const out: string[] = [];\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n return [];\n }\n for (const entry of entries) {\n const full = path.join(dir, entry);\n let isDir = false;\n try {\n isDir = fs.statSync(full).isDirectory();\n } catch {\n continue;\n }\n if (isDir) {\n out.push(...findOrphanedTempFiles(full, depth + 1));\n } else if (/\\.\\d+\\.[0-9a-f]+\\.tmp$/.test(entry)) {\n out.push(full);\n }\n }\n return out;\n}\n\n/** Delete orphaned atomic-write temp files; returns the paths removed. */\nexport function cleanupTempFiles(dataDir: string): string[] {\n const temps = [\n ...findOrphanedTempFiles(path.join(dataDir, \".agentic\")),\n ...findOrphanedTempFiles(path.join(dataDir, \"customers\")),\n ];\n const removed: string[] = [];\n for (const f of temps) {\n try {\n fs.rmSync(f, { force: true });\n removed.push(f);\n } catch {\n /* leave it; reported but not removable */\n }\n }\n return removed;\n}\n\nexport async function runDiagnostics(dataDir: string): Promise<DiagnosticReport> {\n const checks: DiagnosticCheck[] = [];\n\n // 1. Data directory structure\n const agenticDir = path.join(dataDir, \".agentic\");\n const customersDir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(agenticDir) && !fs.existsSync(customersDir)) {\n checks.push({\n name: \"data directory\",\n status: \"fail\",\n detail: `Neither .agentic/ nor customers/ found under ${dataDir} — run 'dxcrm init'`,\n });\n } else {\n checks.push({\n name: \"data directory\",\n status: \"ok\",\n detail: dataDir,\n });\n }\n\n // 2. Customer data validity\n const slugs = listCustomerSlugs(dataDir);\n const invalid: string[] = [];\n for (const slug of slugs) {\n try {\n await readMainFacts(dataDir, slug);\n } catch {\n invalid.push(slug);\n }\n }\n checks.push({\n name: \"customer data\",\n status: invalid.length > 0 ? \"fail\" : \"ok\",\n detail:\n invalid.length > 0\n ? `${invalid.length} of ${slugs.length} invalid: ${invalid.slice(0, 5).join(\", \")}`\n : `${slugs.length} customer(s) valid`,\n });\n\n // 3. Orphaned atomic-write temp files (crash signature)\n const temps = [...findOrphanedTempFiles(agenticDir), ...findOrphanedTempFiles(customersDir)];\n checks.push({\n name: \"temp files\",\n status: temps.length > 0 ? \"warn\" : \"ok\",\n detail:\n temps.length > 0\n ? `${temps.length} orphaned temp file(s) from interrupted writes — safe to delete`\n : \"no orphaned temp files\",\n });\n\n // 4. Recent log errors\n const summary = summarizeLogs(dataDir);\n const errorCount = summary.byLevel.error;\n checks.push({\n name: \"logs\",\n status: errorCount > 0 ? \"warn\" : \"ok\",\n detail:\n errorCount > 0\n ? `${errorCount} error entr${errorCount === 1 ? \"y\" : \"ies\"} in the log (dxcrm logs --level error)`\n : `${summary.total} log entr${summary.total === 1 ? \"y\" : \"ies\"}, no errors`,\n });\n\n // 5. Backup freshness\n const backupLogPath = path.join(agenticDir, \"backup-log.json\");\n if (fs.existsSync(backupLogPath)) {\n try {\n const entries = JSON.parse(fs.readFileSync(backupLogPath, \"utf-8\") as string) as Array<{\n createdAt?: string;\n }>;\n const last = entries[entries.length - 1]?.createdAt;\n const ageDays = last\n ? Math.floor((Date.now() - new Date(last).getTime()) / 86_400_000)\n : Infinity;\n checks.push({\n name: \"backups\",\n status: ageDays > 7 ? \"warn\" : \"ok\",\n detail: last ? `last backup ${ageDays}d ago` : \"no backups recorded\",\n });\n } catch {\n checks.push({ name: \"backups\", status: \"warn\", detail: \"backup log unreadable\" });\n }\n }\n\n return { ok: !checks.some((c) => c.status === \"fail\"), checks };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"doctor-BFeelnq8.js","names":[],"sources":["../src/core/doctor.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { listCustomerSlugs, readMainFacts } from \"../fs/customer-dir.js\";\nimport { summarizeLogs } from \"./logger.js\";\n\n/**\n * Self-diagnostic (`dxcrm doctor`). Ties together the integrity, observability\n * and validation work into a single operator-facing health check: is the data\n * directory sound, is customer data valid, are there orphaned atomic-write temp\n * files (a crash signature), recent log errors, or a stale backup?\n */\nexport type CheckStatus = \"ok\" | \"warn\" | \"fail\";\n\nexport interface DiagnosticCheck {\n name: string;\n status: CheckStatus;\n detail: string;\n}\n\nexport interface DiagnosticReport {\n ok: boolean; // false if any check failed\n checks: DiagnosticCheck[];\n}\n\n/** Recursively collect files whose name matches the atomic-write temp pattern. */\nfunction findOrphanedTempFiles(dir: string, depth = 0): string[] {\n if (depth > 3 || !fs.existsSync(dir)) return [];\n const out: string[] = [];\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n return [];\n }\n for (const entry of entries) {\n const full = path.join(dir, entry);\n let isDir = false;\n try {\n isDir = fs.statSync(full).isDirectory();\n } catch {\n continue;\n }\n if (isDir) {\n out.push(...findOrphanedTempFiles(full, depth + 1));\n } else if (/\\.\\d+\\.[0-9a-f]+\\.tmp$/.test(entry)) {\n out.push(full);\n }\n }\n return out;\n}\n\n/** Delete orphaned atomic-write temp files; returns the paths removed. */\nexport function cleanupTempFiles(dataDir: string): string[] {\n const temps = [\n ...findOrphanedTempFiles(path.join(dataDir, \".agentic\")),\n ...findOrphanedTempFiles(path.join(dataDir, \"customers\")),\n ];\n const removed: string[] = [];\n for (const f of temps) {\n try {\n fs.rmSync(f, { force: true });\n removed.push(f);\n } catch {\n /* leave it; reported but not removable */\n }\n }\n return removed;\n}\n\nexport async function runDiagnostics(dataDir: string): Promise<DiagnosticReport> {\n const checks: DiagnosticCheck[] = [];\n\n // 1. Data directory structure\n const agenticDir = path.join(dataDir, \".agentic\");\n const customersDir = path.join(dataDir, \"customers\");\n if (!fs.existsSync(agenticDir) && !fs.existsSync(customersDir)) {\n checks.push({\n name: \"data directory\",\n status: \"fail\",\n detail: `Neither .agentic/ nor customers/ found under ${dataDir} — run 'dxcrm init'`,\n });\n } else {\n checks.push({\n name: \"data directory\",\n status: \"ok\",\n detail: dataDir,\n });\n }\n\n // 2. Customer data validity\n const slugs = listCustomerSlugs(dataDir);\n const invalid: string[] = [];\n for (const slug of slugs) {\n try {\n await readMainFacts(dataDir, slug);\n } catch {\n invalid.push(slug);\n }\n }\n checks.push({\n name: \"customer data\",\n status: invalid.length > 0 ? \"fail\" : \"ok\",\n detail:\n invalid.length > 0\n ? `${invalid.length} of ${slugs.length} invalid: ${invalid.slice(0, 5).join(\", \")}`\n : `${slugs.length} customer(s) valid`,\n });\n\n // 3. Orphaned atomic-write temp files (crash signature)\n const temps = [...findOrphanedTempFiles(agenticDir), ...findOrphanedTempFiles(customersDir)];\n checks.push({\n name: \"temp files\",\n status: temps.length > 0 ? \"warn\" : \"ok\",\n detail:\n temps.length > 0\n ? `${temps.length} orphaned temp file(s) from interrupted writes — safe to delete`\n : \"no orphaned temp files\",\n });\n\n // 4. Recent log errors\n const summary = summarizeLogs(dataDir);\n const errorCount = summary.byLevel.error;\n checks.push({\n name: \"logs\",\n status: errorCount > 0 ? \"warn\" : \"ok\",\n detail:\n errorCount > 0\n ? `${errorCount} error entr${errorCount === 1 ? \"y\" : \"ies\"} in the log (dxcrm logs --level error)`\n : `${summary.total} log entr${summary.total === 1 ? \"y\" : \"ies\"}, no errors`,\n });\n\n // 5. Backup freshness\n const backupLogPath = path.join(agenticDir, \"backup-log.json\");\n if (fs.existsSync(backupLogPath)) {\n try {\n const entries = JSON.parse(fs.readFileSync(backupLogPath, \"utf-8\") as string) as Array<{\n createdAt?: string;\n }>;\n const last = entries[entries.length - 1]?.createdAt;\n const ageDays = last\n ? Math.floor((Date.now() - new Date(last).getTime()) / 86_400_000)\n : Infinity;\n checks.push({\n name: \"backups\",\n status: ageDays > 7 ? \"warn\" : \"ok\",\n detail: last ? `last backup ${ageDays}d ago` : \"no backups recorded\",\n });\n } catch {\n checks.push({ name: \"backups\", status: \"warn\", detail: \"backup log unreadable\" });\n }\n }\n\n return { ok: !checks.some((c) => c.status === \"fail\"), checks };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { n as runDiagnostics, t as cleanupTempFiles } from "./doctor-BFeelnq8.js";
2
+ export { cleanupTempFiles, runDiagnostics };
@@ -0,0 +1,42 @@
1
+ const require_html = require("./html-CmOku6jS.cjs");
2
+ //#region src/sync/email-body.ts
3
+ /** Decode a Gmail part body (base64url) to a UTF-8 string. */
4
+ function decodeBody(data) {
5
+ if (!data) return "";
6
+ return Buffer.from(data, "base64url").toString("utf-8");
7
+ }
8
+ /**
9
+ * Recursively collect the text/plain and text/html bodies from a Gmail message
10
+ * payload. Attachment parts (those with a filename) are ignored — only inline
11
+ * body parts are considered. The first body of each type wins (the top-level
12
+ * alternative), so signatures appended in nested forwards don't clobber it.
13
+ */
14
+ function collectBodyParts(payload) {
15
+ const result = {};
16
+ const walk = (part) => {
17
+ if (!part) return;
18
+ const mime = part.mimeType ?? "";
19
+ if (!(Boolean(part.filename) || Boolean(part.body?.attachmentId)) && part.body?.data) {
20
+ if (mime === "text/plain" && result.plain === void 0) result.plain = decodeBody(part.body.data);
21
+ else if (mime === "text/html" && result.html === void 0) result.html = decodeBody(part.body.data);
22
+ }
23
+ for (const child of part.parts ?? []) walk(child);
24
+ };
25
+ walk(payload);
26
+ return result;
27
+ }
28
+ /**
29
+ * Extract the email body as Markdown: prefers the plain-text part verbatim,
30
+ * otherwise converts the HTML part via Turndown. Returns an empty string when
31
+ * the message carries no inline body (e.g. attachment-only messages).
32
+ */
33
+ async function extractEmailBodyMarkdown(payload) {
34
+ const { plain, html } = collectBodyParts(payload);
35
+ if (plain && plain.trim()) return plain.trim();
36
+ if (html && html.trim()) return (await require_html.htmlToMarkdown(html)).trim();
37
+ return "";
38
+ }
39
+ //#endregion
40
+ exports.extractEmailBodyMarkdown = extractEmailBodyMarkdown;
41
+
42
+ //# sourceMappingURL=email-body-BFSRa0AW.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-body-BFSRa0AW.cjs","names":["htmlToMarkdown"],"sources":["../src/sync/email-body.ts"],"sourcesContent":["// src/sync/email-body.ts\nimport type { gmail_v1 } from \"@googleapis/gmail\";\nimport { htmlToMarkdown } from \"./converters/html.js\";\n\nexport interface EmailBodyParts {\n plain?: string;\n html?: string;\n}\n\n/** Decode a Gmail part body (base64url) to a UTF-8 string. */\nfunction decodeBody(data: string | null | undefined): string {\n if (!data) return \"\";\n return Buffer.from(data, \"base64url\").toString(\"utf-8\");\n}\n\n/**\n * Recursively collect the text/plain and text/html bodies from a Gmail message\n * payload. Attachment parts (those with a filename) are ignored — only inline\n * body parts are considered. The first body of each type wins (the top-level\n * alternative), so signatures appended in nested forwards don't clobber it.\n */\nexport function collectBodyParts(\n payload: gmail_v1.Schema$MessagePart | undefined\n): EmailBodyParts {\n const result: EmailBodyParts = {};\n const walk = (part?: gmail_v1.Schema$MessagePart): void => {\n if (!part) return;\n const mime = part.mimeType ?? \"\";\n const isAttachment = Boolean(part.filename) || Boolean(part.body?.attachmentId);\n if (!isAttachment && part.body?.data) {\n if (mime === \"text/plain\" && result.plain === undefined) {\n result.plain = decodeBody(part.body.data);\n } else if (mime === \"text/html\" && result.html === undefined) {\n result.html = decodeBody(part.body.data);\n }\n }\n for (const child of part.parts ?? []) walk(child);\n };\n walk(payload);\n return result;\n}\n\n/**\n * Extract the email body as Markdown: prefers the plain-text part verbatim,\n * otherwise converts the HTML part via Turndown. Returns an empty string when\n * the message carries no inline body (e.g. attachment-only messages).\n */\nexport async function extractEmailBodyMarkdown(\n payload: gmail_v1.Schema$MessagePart | undefined\n): Promise<string> {\n const { plain, html } = collectBodyParts(payload);\n if (plain && plain.trim()) return plain.trim();\n if (html && html.trim()) return (await htmlToMarkdown(html)).trim();\n return \"\";\n}\n"],"mappings":";;;AAUA,SAAS,WAAW,MAAyC;CAC3D,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO,OAAO,KAAK,MAAM,WAAW,EAAE,SAAS,OAAO;AACxD;;;;;;;AAQA,SAAgB,iBACd,SACgB;CAChB,MAAM,SAAyB,CAAC;CAChC,MAAM,QAAQ,SAA6C;EACzD,IAAI,CAAC,MAAM;EACX,MAAM,OAAO,KAAK,YAAY;EAE9B,IAAI,EADiB,QAAQ,KAAK,QAAQ,KAAK,QAAQ,KAAK,MAAM,YAAY,MACzD,KAAK,MAAM;OAC1B,SAAS,gBAAgB,OAAO,UAAU,KAAA,GAC5C,OAAO,QAAQ,WAAW,KAAK,KAAK,IAAI;QACnC,IAAI,SAAS,eAAe,OAAO,SAAS,KAAA,GACjD,OAAO,OAAO,WAAW,KAAK,KAAK,IAAI;EAAA;EAG3C,KAAK,MAAM,SAAS,KAAK,SAAS,CAAC,GAAG,KAAK,KAAK;CAClD;CACA,KAAK,OAAO;CACZ,OAAO;AACT;;;;;;AAOA,eAAsB,yBACpB,SACiB;CACjB,MAAM,EAAE,OAAO,SAAS,iBAAiB,OAAO;CAChD,IAAI,SAAS,MAAM,KAAK,GAAG,OAAO,MAAM,KAAK;CAC7C,IAAI,QAAQ,KAAK,KAAK,GAAG,QAAQ,MAAMA,aAAAA,eAAe,IAAI,GAAG,KAAK;CAClE,OAAO;AACT"}
@@ -0,0 +1,42 @@
1
+ import { n as htmlToMarkdown } from "./html-BaeOCZKE.js";
2
+ //#region src/sync/email-body.ts
3
+ /** Decode a Gmail part body (base64url) to a UTF-8 string. */
4
+ function decodeBody(data) {
5
+ if (!data) return "";
6
+ return Buffer.from(data, "base64url").toString("utf-8");
7
+ }
8
+ /**
9
+ * Recursively collect the text/plain and text/html bodies from a Gmail message
10
+ * payload. Attachment parts (those with a filename) are ignored — only inline
11
+ * body parts are considered. The first body of each type wins (the top-level
12
+ * alternative), so signatures appended in nested forwards don't clobber it.
13
+ */
14
+ function collectBodyParts(payload) {
15
+ const result = {};
16
+ const walk = (part) => {
17
+ if (!part) return;
18
+ const mime = part.mimeType ?? "";
19
+ if (!(Boolean(part.filename) || Boolean(part.body?.attachmentId)) && part.body?.data) {
20
+ if (mime === "text/plain" && result.plain === void 0) result.plain = decodeBody(part.body.data);
21
+ else if (mime === "text/html" && result.html === void 0) result.html = decodeBody(part.body.data);
22
+ }
23
+ for (const child of part.parts ?? []) walk(child);
24
+ };
25
+ walk(payload);
26
+ return result;
27
+ }
28
+ /**
29
+ * Extract the email body as Markdown: prefers the plain-text part verbatim,
30
+ * otherwise converts the HTML part via Turndown. Returns an empty string when
31
+ * the message carries no inline body (e.g. attachment-only messages).
32
+ */
33
+ async function extractEmailBodyMarkdown(payload) {
34
+ const { plain, html } = collectBodyParts(payload);
35
+ if (plain && plain.trim()) return plain.trim();
36
+ if (html && html.trim()) return (await htmlToMarkdown(html)).trim();
37
+ return "";
38
+ }
39
+ //#endregion
40
+ export { extractEmailBodyMarkdown };
41
+
42
+ //# sourceMappingURL=email-body-BOd7U-D2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-body-BOd7U-D2.js","names":[],"sources":["../src/sync/email-body.ts"],"sourcesContent":["// src/sync/email-body.ts\nimport type { gmail_v1 } from \"@googleapis/gmail\";\nimport { htmlToMarkdown } from \"./converters/html.js\";\n\nexport interface EmailBodyParts {\n plain?: string;\n html?: string;\n}\n\n/** Decode a Gmail part body (base64url) to a UTF-8 string. */\nfunction decodeBody(data: string | null | undefined): string {\n if (!data) return \"\";\n return Buffer.from(data, \"base64url\").toString(\"utf-8\");\n}\n\n/**\n * Recursively collect the text/plain and text/html bodies from a Gmail message\n * payload. Attachment parts (those with a filename) are ignored — only inline\n * body parts are considered. The first body of each type wins (the top-level\n * alternative), so signatures appended in nested forwards don't clobber it.\n */\nexport function collectBodyParts(\n payload: gmail_v1.Schema$MessagePart | undefined\n): EmailBodyParts {\n const result: EmailBodyParts = {};\n const walk = (part?: gmail_v1.Schema$MessagePart): void => {\n if (!part) return;\n const mime = part.mimeType ?? \"\";\n const isAttachment = Boolean(part.filename) || Boolean(part.body?.attachmentId);\n if (!isAttachment && part.body?.data) {\n if (mime === \"text/plain\" && result.plain === undefined) {\n result.plain = decodeBody(part.body.data);\n } else if (mime === \"text/html\" && result.html === undefined) {\n result.html = decodeBody(part.body.data);\n }\n }\n for (const child of part.parts ?? []) walk(child);\n };\n walk(payload);\n return result;\n}\n\n/**\n * Extract the email body as Markdown: prefers the plain-text part verbatim,\n * otherwise converts the HTML part via Turndown. Returns an empty string when\n * the message carries no inline body (e.g. attachment-only messages).\n */\nexport async function extractEmailBodyMarkdown(\n payload: gmail_v1.Schema$MessagePart | undefined\n): Promise<string> {\n const { plain, html } = collectBodyParts(payload);\n if (plain && plain.trim()) return plain.trim();\n if (html && html.trim()) return (await htmlToMarkdown(html)).trim();\n return \"\";\n}\n"],"mappings":";;;AAUA,SAAS,WAAW,MAAyC;CAC3D,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO,OAAO,KAAK,MAAM,WAAW,EAAE,SAAS,OAAO;AACxD;;;;;;;AAQA,SAAgB,iBACd,SACgB;CAChB,MAAM,SAAyB,CAAC;CAChC,MAAM,QAAQ,SAA6C;EACzD,IAAI,CAAC,MAAM;EACX,MAAM,OAAO,KAAK,YAAY;EAE9B,IAAI,EADiB,QAAQ,KAAK,QAAQ,KAAK,QAAQ,KAAK,MAAM,YAAY,MACzD,KAAK,MAAM;OAC1B,SAAS,gBAAgB,OAAO,UAAU,KAAA,GAC5C,OAAO,QAAQ,WAAW,KAAK,KAAK,IAAI;QACnC,IAAI,SAAS,eAAe,OAAO,SAAS,KAAA,GACjD,OAAO,OAAO,WAAW,KAAK,KAAK,IAAI;EAAA;EAG3C,KAAK,MAAM,SAAS,KAAK,SAAS,CAAC,GAAG,KAAK,KAAK;CAClD;CACA,KAAK,OAAO;CACZ,OAAO;AACT;;;;;;AAOA,eAAsB,yBACpB,SACiB;CACjB,MAAM,EAAE,OAAO,SAAS,iBAAiB,OAAO;CAChD,IAAI,SAAS,MAAM,KAAK,GAAG,OAAO,MAAM,KAAK;CAC7C,IAAI,QAAQ,KAAK,KAAK,GAAG,QAAQ,MAAM,eAAe,IAAI,GAAG,KAAK;CAClE,OAAO;AACT"}
@@ -1,6 +1,6 @@
1
1
  import { i as writeJsonFile } from "./json-store-WWsFzXub.js";
2
2
  import { t as AgentConfigSchema } from "./agent-config-zPvcqu07.js";
3
- import { i as readInteractions, n as appendInteraction } from "./interactions-writer-DbSyI2rt.js";
3
+ import { i as readInteractions, n as appendInteraction } from "./interactions-writer-B8XAzdqR.js";
4
4
  import { n as logger } from "./logger-Dyl4VcLO.js";
5
5
  import { a as summarizeEmail } from "./llm-PZzgPphl.js";
6
6
  import path from "path";
@@ -101,6 +101,7 @@ async function syncGmail(opts) {
101
101
  auth: opts.auth
102
102
  });
103
103
  const maxPages = opts.maxPages ?? 5;
104
+ const includeAttachments = opts.includeAttachments ?? true;
104
105
  let q = opts.query;
105
106
  if (opts.since) {
106
107
  const after = Math.floor(opts.since.getTime() / 1e3);
@@ -137,12 +138,14 @@ async function syncGmail(opts) {
137
138
  msgData = (await retryWithBackoff(() => gmail$1.users.messages.get({
138
139
  userId: "me",
139
140
  id: msg.id,
140
- format: "metadata",
141
- metadataHeaders: [
142
- "Subject",
143
- "From",
144
- "Date"
145
- ]
141
+ ...includeAttachments ? { format: "full" } : {
142
+ format: "metadata",
143
+ metadataHeaders: [
144
+ "Subject",
145
+ "From",
146
+ "Date"
147
+ ]
148
+ }
146
149
  }))).data;
147
150
  } catch (err) {
148
151
  logger.warn("gmail-sync", "skipping message after retries", {
@@ -158,8 +161,29 @@ async function syncGmail(opts) {
158
161
  const dateStr = headers.find((h) => h.name === "Date")?.value;
159
162
  const date = dateStr ? new Date(dateStr).toISOString().slice(0, 10) : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
160
163
  const snippet = msgData.snippet ?? "";
164
+ const { extractEmailBodyMarkdown } = await import("./email-body-BOd7U-D2.js");
165
+ const body = await extractEmailBodyMarkdown(msgData.payload ?? void 0) || snippet;
161
166
  const { summarizeEmail } = await import("./llm-DSX1-wFu.js");
162
- const emailSummary = await summarizeEmail(subject, snippet, from);
167
+ const emailSummary = await summarizeEmail(subject, body, from);
168
+ let attachmentLinks = [];
169
+ if (includeAttachments) try {
170
+ const { processMessageAttachments } = await import("./attachments-rLa96rOK.js");
171
+ attachmentLinks = (await processMessageAttachments({
172
+ gmail: gmail$1,
173
+ dataDir: opts.dataDir,
174
+ slug: opts.slug,
175
+ messageId: msg.id,
176
+ source,
177
+ payload: msgData.payload ?? void 0,
178
+ date,
179
+ ...opts.maxAttachmentBytes !== void 0 ? { maxBytes: opts.maxAttachmentBytes } : {}
180
+ })).map((a) => a.markdownName);
181
+ } catch (err) {
182
+ logger.warn("gmail-sync", "attachment processing failed", {
183
+ messageId: msg.id,
184
+ error: err.message
185
+ });
186
+ }
163
187
  await appendInteraction(opts.dataDir, opts.slug, {
164
188
  date,
165
189
  type: "Email",
@@ -168,17 +192,23 @@ async function syncGmail(opts) {
168
192
  subject,
169
193
  summary: emailSummary.summary,
170
194
  nextSteps: emailSummary.nextSteps,
195
+ ...attachmentLinks.length > 0 ? { attachments: attachmentLinks } : {},
171
196
  sourceRef: source,
172
197
  synced: (/* @__PURE__ */ new Date()).toISOString()
173
198
  });
174
199
  existingContent += source;
175
200
  const { indexInLanceDB } = await import("./lancedb-CswQEE5K.js");
176
- await indexInLanceDB(opts.dataDir, opts.slug, `${subject}\n${snippet}`, source, {
177
- date,
178
- type: "Email"
179
- }).catch((err) => {
180
- logger.error("gmail-sync", "LanceDB index failed", { error: err.message });
181
- });
201
+ const { chunkText } = await import("./chunk-ChC83jai.js");
202
+ const bodyChunks = chunkText(`${subject}\n${body}`);
203
+ for (let i = 0; i < bodyChunks.length; i++) {
204
+ const ref = i === 0 ? source : `${source}#${i}`;
205
+ await indexInLanceDB(opts.dataDir, opts.slug, bodyChunks[i], ref, {
206
+ date,
207
+ type: "Email"
208
+ }).catch((err) => {
209
+ logger.error("gmail-sync", "LanceDB index failed", { error: err.message });
210
+ });
211
+ }
182
212
  if (agentConfigExists(opts.dataDir, opts.slug)) notifyAgentWake(opts.dataDir, opts.slug, {
183
213
  trigger: "email",
184
214
  subject,
@@ -205,4 +235,4 @@ function sleep(ms) {
205
235
  //#endregion
206
236
  export { syncGmail };
207
237
 
208
- //# sourceMappingURL=gmail-sync-DueE6tl5.js.map
238
+ //# sourceMappingURL=gmail-sync-B4Iu3AQb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-sync-B4Iu3AQb.js","names":["gmail","gmailApi"],"sources":["../src/core/agent-notifier.ts","../src/sync/gmail-sync.ts"],"sourcesContent":["// src/core/agent-notifier.ts\n// Sends a Telegram wake notification when a new inbound email from a customer\n// domain is detected and an agent config exists for that customer slug.\n// All errors are swallowed — this is a notification feature and must never\n// crash the core loop.\n\nimport fs from \"fs\";\nimport https from \"https\";\nimport path from \"path\";\nimport { writeJsonFile } from \"../fs/json-store.js\";\nimport { AgentConfigSchema, type AgentConfig } from \"../schemas/agent-config.js\";\nimport { summarizeEmail } from \"./llm.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface WakeContext {\n trigger: \"email\" | \"calendar\";\n subject: string;\n from: string;\n snippet: string;\n}\n\n// ─── Agent config helpers ─────────────────────────────────────────────────────\n\nfunction agentConfigPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n}\n\nfunction readAgentConfig(dataDir: string, slug: string): AgentConfig | null {\n const p = agentConfigPath(dataDir, slug);\n if (!fs.existsSync(p)) return null;\n try {\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as unknown;\n const result = AgentConfigSchema.safeParse(raw);\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\nfunction writeLastWake(dataDir: string, slug: string, config: AgentConfig): void {\n const p = agentConfigPath(dataDir, slug);\n try {\n const updated: AgentConfig = { ...config, lastWake: new Date().toISOString() };\n writeJsonFile(p, updated);\n } catch {\n // non-fatal — just a housekeeping write\n }\n}\n\n// ─── Telegram transport ───────────────────────────────────────────────────────\n\nfunction sendTelegramMessage(token: string, chatId: string, text: string): Promise<void> {\n const body = JSON.stringify({ chat_id: chatId, text, parse_mode: \"Markdown\" });\n return new Promise<void>((resolve, reject) => {\n const req = https.request(\n `https://api.telegram.org/bot${token}/sendMessage`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(body),\n },\n },\n (res) => {\n res.resume();\n resolve();\n }\n );\n req.on(\"error\", reject);\n req.write(body);\n req.end();\n });\n}\n\n// ─── Message builder ──────────────────────────────────────────────────────────\n\nfunction buildWakeMessage(\n slug: string,\n subject: string,\n summary: string,\n nextSteps: string[]\n): string {\n const suggestedAction = nextSteps[0] ?? \"Follow up within 24h\";\n return (\n `📧 New email from **${slug}**: ${subject}\\n` +\n `${summary}\\n\\n` +\n `💡 Suggested action: ${suggestedAction}`\n );\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget notification: reads the agent config for `slug`, summarises\n * the inbound email with the LLM, and sends a Telegram message.\n *\n * Silently returns (no throw) when:\n * - no agent config exists for the slug\n * - TELEGRAM_BOT_TOKEN env var is not set\n * - no chat id is available (neither in config nor in TELEGRAM_CHAT_ID env var)\n * - any HTTPS / LLM error occurs\n */\nexport async function notifyAgentWake(\n dataDir: string,\n slug: string,\n context: WakeContext\n): Promise<void> {\n try {\n // 1. Read agent config — bail silently if not found\n const config = readAgentConfig(dataDir, slug);\n if (!config) return;\n\n // 2. Check for Telegram token — bail silently if absent\n const token = process.env[\"TELEGRAM_BOT_TOKEN\"];\n if (!token) return;\n\n // 3. Determine chat id — config takes precedence, fallback to env var\n const chatId = config.telegramChatId ?? process.env[\"TELEGRAM_CHAT_ID\"];\n if (!chatId) return;\n\n // 4. Summarise the email (LLM, with fallback built into summarizeEmail itself)\n const emailSummary = await summarizeEmail(context.subject, context.snippet, context.from);\n\n // 5. Build and send the Telegram message\n const text = buildWakeMessage(\n slug,\n context.subject,\n emailSummary.summary,\n emailSummary.nextSteps\n );\n await sendTelegramMessage(token, chatId, text);\n\n // 6. Update lastWake on success\n writeLastWake(dataDir, slug, config);\n } catch {\n // Swallow all errors — this is a notification feature, never crashes core loop\n }\n}\n","// src/sync/gmail-sync.ts\nimport fs from \"fs\";\nimport path from \"path\";\nimport { gmail as gmailApi, type gmail_v1 } from \"@googleapis/gmail\";\nimport type { OAuth2Client } from \"google-auth-library\";\nimport { readInteractions, appendInteraction } from \"../fs/interactions-writer.js\";\nimport { notifyAgentWake } from \"../core/agent-notifier.js\";\nimport { logger } from \"../core/logger.js\";\n\ninterface SyncOptions {\n slug: string;\n dataDir: string;\n auth: OAuth2Client;\n query: string;\n since?: Date;\n maxPages?: number;\n /** Download, convert and index email attachments (default true). */\n includeAttachments?: boolean;\n /** Per-attachment size cap in bytes. */\n maxAttachmentBytes?: number;\n}\n\n/**\n * Retry a function with exponential backoff on any error.\n * Delays: 1s, 2s, 4s, 8s … (2^attempt seconds), up to maxRetries retries.\n */\nexport async function retryWithBackoff<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {\n let attempt = 0;\n while (true) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n const delayMs = 1000 * Math.pow(2, attempt);\n await sleep(delayMs);\n attempt++;\n }\n }\n}\n\nexport async function syncGmail(opts: SyncOptions): Promise<{ synced: number; skipped: number }> {\n const gmail = gmailApi({ version: \"v1\", auth: opts.auth });\n const maxPages = opts.maxPages ?? 5;\n const includeAttachments = opts.includeAttachments ?? true;\n\n let q = opts.query;\n if (opts.since) {\n const after = Math.floor(opts.since.getTime() / 1000);\n q += ` after:${after}`;\n }\n\n // Collect all message stubs across pages (Task A — pagination)\n const allMessages: Array<{ id?: string | null; threadId?: string | null }> = [];\n let pageToken: string | undefined = undefined;\n let pagesFetched = 0;\n\n do {\n const listResp: { data: gmail_v1.Schema$ListMessagesResponse } =\n await gmail.users.messages.list({\n userId: \"me\",\n q,\n maxResults: 200,\n ...(pageToken ? { pageToken } : {}),\n });\n const pageMessages = listResp.data.messages ?? [];\n allMessages.push(...pageMessages);\n pageToken = listResp.data.nextPageToken ?? undefined;\n pagesFetched++;\n } while (pageToken && pagesFetched < maxPages);\n\n // Read existing interactions once before the loop — avoids O(messages) file reads\n let existingContent = await readInteractions(opts.dataDir, opts.slug);\n\n let synced = 0;\n let skipped = 0;\n\n for (const msg of allMessages) {\n if (!msg.id) continue;\n\n const source = `gmail://thread/${msg.threadId ?? msg.id}`;\n\n if (existingContent.includes(source)) {\n skipped++;\n continue;\n }\n\n // Rate limiting ~10 req/s\n await sleep(100);\n\n // Task B — exponential backoff retry on any error\n let msgData: gmail_v1.Schema$Message;\n try {\n const detail = await retryWithBackoff(() =>\n gmail.users.messages.get({\n userId: \"me\",\n id: msg.id!,\n // \"full\" exposes payload.parts so attachments can be downloaded;\n // fall back to lighter \"metadata\" when attachment sync is disabled.\n ...(includeAttachments\n ? { format: \"full\" }\n : { format: \"metadata\", metadataHeaders: [\"Subject\", \"From\", \"Date\"] }),\n })\n );\n msgData = detail.data;\n } catch (err) {\n logger.warn(\"gmail-sync\", \"skipping message after retries\", {\n messageId: msg.id,\n error: (err as Error).message,\n });\n skipped++;\n continue;\n }\n\n const headers = msgData.payload?.headers ?? [];\n const subject = headers.find((h) => h.name === \"Subject\")?.value ?? \"(no subject)\";\n const from = headers.find((h) => h.name === \"From\")?.value ?? \"\";\n const dateStr = headers.find((h) => h.name === \"Date\")?.value;\n const date = dateStr\n ? new Date(dateStr).toISOString().slice(0, 10)\n : new Date().toISOString().slice(0, 10);\n const snippet = msgData.snippet ?? \"\";\n\n // Extract the full inline body (plain preferred, else HTML->Markdown) so\n // summaries and search cover the whole message, not just the snippet.\n const { extractEmailBodyMarkdown } = await import(\"./email-body.js\");\n const body = (await extractEmailBodyMarkdown(msgData.payload ?? undefined)) || snippet;\n\n // LLM summary — non-blocking fallback to raw body/snippet if no API key or error\n const { summarizeEmail } = await import(\"../core/llm.js\");\n const emailSummary = await summarizeEmail(subject, body, from);\n\n // Download, convert and index attachments before logging the interaction so\n // the entry can link to the generated Markdown. Failures here are swallowed.\n let attachmentLinks: string[] = [];\n if (includeAttachments) {\n try {\n const { processMessageAttachments } = await import(\"./attachments.js\");\n const saved = await processMessageAttachments({\n gmail,\n dataDir: opts.dataDir,\n slug: opts.slug,\n messageId: msg.id,\n source,\n payload: msgData.payload ?? undefined,\n date,\n ...(opts.maxAttachmentBytes !== undefined\n ? { maxBytes: opts.maxAttachmentBytes }\n : {}),\n });\n attachmentLinks = saved.map((a) => a.markdownName);\n } catch (err) {\n logger.warn(\"gmail-sync\", \"attachment processing failed\", {\n messageId: msg.id,\n error: (err as Error).message,\n });\n }\n }\n\n await appendInteraction(opts.dataDir, opts.slug, {\n date,\n type: \"Email\",\n direction: detectDirection(from),\n with: from,\n subject,\n summary: emailSummary.summary,\n nextSteps: emailSummary.nextSteps,\n ...(attachmentLinks.length > 0 ? { attachments: attachmentLinks } : {}),\n sourceRef: source,\n synced: new Date().toISOString(),\n });\n\n // Append to in-memory string so within-batch duplicates are detected\n existingContent += source;\n\n // Index the full email (subject + body) into LanceDB for semantic search,\n // chunked so long threads stay searchable (non-blocking).\n const { indexInLanceDB } = await import(\"../core/lancedb.js\");\n const { chunkText } = await import(\"../core/chunk.js\");\n const bodyChunks = chunkText(`${subject}\\n${body}`);\n for (let i = 0; i < bodyChunks.length; i++) {\n const ref = i === 0 ? source : `${source}#${i}`;\n await indexInLanceDB(opts.dataDir, opts.slug, bodyChunks[i]!, ref, {\n date,\n type: \"Email\",\n }).catch((err: unknown) => {\n logger.error(\"gmail-sync\", \"LanceDB index failed\", { error: (err as Error).message });\n });\n }\n\n // Agent wake: notify if an agent config exists for this customer (fire-and-forget)\n if (agentConfigExists(opts.dataDir, opts.slug)) {\n notifyAgentWake(opts.dataDir, opts.slug, {\n trigger: \"email\",\n subject,\n from,\n snippet,\n }).catch(() => {\n // Notification is non-blocking; swallow all errors\n });\n }\n\n synced++;\n }\n\n return { synced, skipped };\n}\n\nfunction agentConfigExists(dataDir: string, slug: string): boolean {\n const configPath = path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n return fs.existsSync(configPath);\n}\n\nfunction detectDirection(_from: string): \"inbound\" | \"outbound\" {\n return \"inbound\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  const require_chunk = require("./chunk-DakpK96I.cjs");
2
2
  const require_session_store = require("./session-store-yfwnj0OC.cjs");
3
- const require_interactions_writer = require("./interactions-writer-a2yzBd7T.cjs");
3
+ const require_interactions_writer = require("./interactions-writer-BRJNrefF.cjs");
4
4
  const require_logger = require("./logger-BkInaGoV.cjs");
5
5
  const require_llm = require("./llm-CXycmEl9.cjs");
6
6
  let path = require("path");
@@ -115,6 +115,7 @@ async function syncGmail(opts) {
115
115
  auth: opts.auth
116
116
  });
117
117
  const maxPages = opts.maxPages ?? 5;
118
+ const includeAttachments = opts.includeAttachments ?? true;
118
119
  let q = opts.query;
119
120
  if (opts.since) {
120
121
  const after = Math.floor(opts.since.getTime() / 1e3);
@@ -151,12 +152,14 @@ async function syncGmail(opts) {
151
152
  msgData = (await retryWithBackoff(() => gmail.users.messages.get({
152
153
  userId: "me",
153
154
  id: msg.id,
154
- format: "metadata",
155
- metadataHeaders: [
156
- "Subject",
157
- "From",
158
- "Date"
159
- ]
155
+ ...includeAttachments ? { format: "full" } : {
156
+ format: "metadata",
157
+ metadataHeaders: [
158
+ "Subject",
159
+ "From",
160
+ "Date"
161
+ ]
162
+ }
160
163
  }))).data;
161
164
  } catch (err) {
162
165
  require_logger.logger.warn("gmail-sync", "skipping message after retries", {
@@ -172,8 +175,29 @@ async function syncGmail(opts) {
172
175
  const dateStr = headers.find((h) => h.name === "Date")?.value;
173
176
  const date = dateStr ? new Date(dateStr).toISOString().slice(0, 10) : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
174
177
  const snippet = msgData.snippet ?? "";
178
+ const { extractEmailBodyMarkdown } = await Promise.resolve().then(() => require("./email-body-BFSRa0AW.cjs"));
179
+ const body = await extractEmailBodyMarkdown(msgData.payload ?? void 0) || snippet;
175
180
  const { summarizeEmail } = await Promise.resolve().then(() => require("./llm-CXycmEl9.cjs")).then((n) => n.llm_exports);
176
- const emailSummary = await summarizeEmail(subject, snippet, from);
181
+ const emailSummary = await summarizeEmail(subject, body, from);
182
+ let attachmentLinks = [];
183
+ if (includeAttachments) try {
184
+ const { processMessageAttachments } = await Promise.resolve().then(() => require("./attachments-CX2GAtsw.cjs"));
185
+ attachmentLinks = (await processMessageAttachments({
186
+ gmail,
187
+ dataDir: opts.dataDir,
188
+ slug: opts.slug,
189
+ messageId: msg.id,
190
+ source,
191
+ payload: msgData.payload ?? void 0,
192
+ date,
193
+ ...opts.maxAttachmentBytes !== void 0 ? { maxBytes: opts.maxAttachmentBytes } : {}
194
+ })).map((a) => a.markdownName);
195
+ } catch (err) {
196
+ require_logger.logger.warn("gmail-sync", "attachment processing failed", {
197
+ messageId: msg.id,
198
+ error: err.message
199
+ });
200
+ }
177
201
  await require_interactions_writer.appendInteraction(opts.dataDir, opts.slug, {
178
202
  date,
179
203
  type: "Email",
@@ -182,17 +206,23 @@ async function syncGmail(opts) {
182
206
  subject,
183
207
  summary: emailSummary.summary,
184
208
  nextSteps: emailSummary.nextSteps,
209
+ ...attachmentLinks.length > 0 ? { attachments: attachmentLinks } : {},
185
210
  sourceRef: source,
186
211
  synced: (/* @__PURE__ */ new Date()).toISOString()
187
212
  });
188
213
  existingContent += source;
189
214
  const { indexInLanceDB } = await Promise.resolve().then(() => require("./mcp.cjs")).then((n) => n.lancedb_exports);
190
- await indexInLanceDB(opts.dataDir, opts.slug, `${subject}\n${snippet}`, source, {
191
- date,
192
- type: "Email"
193
- }).catch((err) => {
194
- require_logger.logger.error("gmail-sync", "LanceDB index failed", { error: err.message });
195
- });
215
+ const { chunkText } = await Promise.resolve().then(() => require("./chunk-BfDYWZQ8.cjs"));
216
+ const bodyChunks = chunkText(`${subject}\n${body}`);
217
+ for (let i = 0; i < bodyChunks.length; i++) {
218
+ const ref = i === 0 ? source : `${source}#${i}`;
219
+ await indexInLanceDB(opts.dataDir, opts.slug, bodyChunks[i], ref, {
220
+ date,
221
+ type: "Email"
222
+ }).catch((err) => {
223
+ require_logger.logger.error("gmail-sync", "LanceDB index failed", { error: err.message });
224
+ });
225
+ }
196
226
  if (agentConfigExists(opts.dataDir, opts.slug)) notifyAgentWake(opts.dataDir, opts.slug, {
197
227
  trigger: "email",
198
228
  subject,
@@ -219,4 +249,4 @@ function sleep(ms) {
219
249
  //#endregion
220
250
  exports.syncGmail = syncGmail;
221
251
 
222
- //# sourceMappingURL=gmail-sync-GEy3oVvw.cjs.map
252
+ //# sourceMappingURL=gmail-sync-BpSVESSe.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-sync-BpSVESSe.cjs","names":["z","summarizeEmail","readInteractions","appendInteraction"],"sources":["../src/schemas/agent-config.ts","../src/core/agent-notifier.ts","../src/sync/gmail-sync.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const AgentConfigSchema = z.object({\n slug: z.string().min(1),\n channel: z.enum([\"telegram\"]),\n wakeOn: z.array(z.enum([\"email\", \"calendar\"])).default([\"email\"]),\n createdAt: z.string(),\n lastWake: z.string().nullable().default(null),\n telegramChatId: z.string().optional(),\n});\n\nexport type AgentConfig = z.infer<typeof AgentConfigSchema>;\n","// src/core/agent-notifier.ts\n// Sends a Telegram wake notification when a new inbound email from a customer\n// domain is detected and an agent config exists for that customer slug.\n// All errors are swallowed — this is a notification feature and must never\n// crash the core loop.\n\nimport fs from \"fs\";\nimport https from \"https\";\nimport path from \"path\";\nimport { writeJsonFile } from \"../fs/json-store.js\";\nimport { AgentConfigSchema, type AgentConfig } from \"../schemas/agent-config.js\";\nimport { summarizeEmail } from \"./llm.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface WakeContext {\n trigger: \"email\" | \"calendar\";\n subject: string;\n from: string;\n snippet: string;\n}\n\n// ─── Agent config helpers ─────────────────────────────────────────────────────\n\nfunction agentConfigPath(dataDir: string, slug: string): string {\n return path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n}\n\nfunction readAgentConfig(dataDir: string, slug: string): AgentConfig | null {\n const p = agentConfigPath(dataDir, slug);\n if (!fs.existsSync(p)) return null;\n try {\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\") as string) as unknown;\n const result = AgentConfigSchema.safeParse(raw);\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\nfunction writeLastWake(dataDir: string, slug: string, config: AgentConfig): void {\n const p = agentConfigPath(dataDir, slug);\n try {\n const updated: AgentConfig = { ...config, lastWake: new Date().toISOString() };\n writeJsonFile(p, updated);\n } catch {\n // non-fatal — just a housekeeping write\n }\n}\n\n// ─── Telegram transport ───────────────────────────────────────────────────────\n\nfunction sendTelegramMessage(token: string, chatId: string, text: string): Promise<void> {\n const body = JSON.stringify({ chat_id: chatId, text, parse_mode: \"Markdown\" });\n return new Promise<void>((resolve, reject) => {\n const req = https.request(\n `https://api.telegram.org/bot${token}/sendMessage`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(body),\n },\n },\n (res) => {\n res.resume();\n resolve();\n }\n );\n req.on(\"error\", reject);\n req.write(body);\n req.end();\n });\n}\n\n// ─── Message builder ──────────────────────────────────────────────────────────\n\nfunction buildWakeMessage(\n slug: string,\n subject: string,\n summary: string,\n nextSteps: string[]\n): string {\n const suggestedAction = nextSteps[0] ?? \"Follow up within 24h\";\n return (\n `📧 New email from **${slug}**: ${subject}\\n` +\n `${summary}\\n\\n` +\n `💡 Suggested action: ${suggestedAction}`\n );\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget notification: reads the agent config for `slug`, summarises\n * the inbound email with the LLM, and sends a Telegram message.\n *\n * Silently returns (no throw) when:\n * - no agent config exists for the slug\n * - TELEGRAM_BOT_TOKEN env var is not set\n * - no chat id is available (neither in config nor in TELEGRAM_CHAT_ID env var)\n * - any HTTPS / LLM error occurs\n */\nexport async function notifyAgentWake(\n dataDir: string,\n slug: string,\n context: WakeContext\n): Promise<void> {\n try {\n // 1. Read agent config — bail silently if not found\n const config = readAgentConfig(dataDir, slug);\n if (!config) return;\n\n // 2. Check for Telegram token — bail silently if absent\n const token = process.env[\"TELEGRAM_BOT_TOKEN\"];\n if (!token) return;\n\n // 3. Determine chat id — config takes precedence, fallback to env var\n const chatId = config.telegramChatId ?? process.env[\"TELEGRAM_CHAT_ID\"];\n if (!chatId) return;\n\n // 4. Summarise the email (LLM, with fallback built into summarizeEmail itself)\n const emailSummary = await summarizeEmail(context.subject, context.snippet, context.from);\n\n // 5. Build and send the Telegram message\n const text = buildWakeMessage(\n slug,\n context.subject,\n emailSummary.summary,\n emailSummary.nextSteps\n );\n await sendTelegramMessage(token, chatId, text);\n\n // 6. Update lastWake on success\n writeLastWake(dataDir, slug, config);\n } catch {\n // Swallow all errors — this is a notification feature, never crashes core loop\n }\n}\n","// src/sync/gmail-sync.ts\nimport fs from \"fs\";\nimport path from \"path\";\nimport { gmail as gmailApi, type gmail_v1 } from \"@googleapis/gmail\";\nimport type { OAuth2Client } from \"google-auth-library\";\nimport { readInteractions, appendInteraction } from \"../fs/interactions-writer.js\";\nimport { notifyAgentWake } from \"../core/agent-notifier.js\";\nimport { logger } from \"../core/logger.js\";\n\ninterface SyncOptions {\n slug: string;\n dataDir: string;\n auth: OAuth2Client;\n query: string;\n since?: Date;\n maxPages?: number;\n /** Download, convert and index email attachments (default true). */\n includeAttachments?: boolean;\n /** Per-attachment size cap in bytes. */\n maxAttachmentBytes?: number;\n}\n\n/**\n * Retry a function with exponential backoff on any error.\n * Delays: 1s, 2s, 4s, 8s … (2^attempt seconds), up to maxRetries retries.\n */\nexport async function retryWithBackoff<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {\n let attempt = 0;\n while (true) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= maxRetries) throw err;\n const delayMs = 1000 * Math.pow(2, attempt);\n await sleep(delayMs);\n attempt++;\n }\n }\n}\n\nexport async function syncGmail(opts: SyncOptions): Promise<{ synced: number; skipped: number }> {\n const gmail = gmailApi({ version: \"v1\", auth: opts.auth });\n const maxPages = opts.maxPages ?? 5;\n const includeAttachments = opts.includeAttachments ?? true;\n\n let q = opts.query;\n if (opts.since) {\n const after = Math.floor(opts.since.getTime() / 1000);\n q += ` after:${after}`;\n }\n\n // Collect all message stubs across pages (Task A — pagination)\n const allMessages: Array<{ id?: string | null; threadId?: string | null }> = [];\n let pageToken: string | undefined = undefined;\n let pagesFetched = 0;\n\n do {\n const listResp: { data: gmail_v1.Schema$ListMessagesResponse } =\n await gmail.users.messages.list({\n userId: \"me\",\n q,\n maxResults: 200,\n ...(pageToken ? { pageToken } : {}),\n });\n const pageMessages = listResp.data.messages ?? [];\n allMessages.push(...pageMessages);\n pageToken = listResp.data.nextPageToken ?? undefined;\n pagesFetched++;\n } while (pageToken && pagesFetched < maxPages);\n\n // Read existing interactions once before the loop — avoids O(messages) file reads\n let existingContent = await readInteractions(opts.dataDir, opts.slug);\n\n let synced = 0;\n let skipped = 0;\n\n for (const msg of allMessages) {\n if (!msg.id) continue;\n\n const source = `gmail://thread/${msg.threadId ?? msg.id}`;\n\n if (existingContent.includes(source)) {\n skipped++;\n continue;\n }\n\n // Rate limiting ~10 req/s\n await sleep(100);\n\n // Task B — exponential backoff retry on any error\n let msgData: gmail_v1.Schema$Message;\n try {\n const detail = await retryWithBackoff(() =>\n gmail.users.messages.get({\n userId: \"me\",\n id: msg.id!,\n // \"full\" exposes payload.parts so attachments can be downloaded;\n // fall back to lighter \"metadata\" when attachment sync is disabled.\n ...(includeAttachments\n ? { format: \"full\" }\n : { format: \"metadata\", metadataHeaders: [\"Subject\", \"From\", \"Date\"] }),\n })\n );\n msgData = detail.data;\n } catch (err) {\n logger.warn(\"gmail-sync\", \"skipping message after retries\", {\n messageId: msg.id,\n error: (err as Error).message,\n });\n skipped++;\n continue;\n }\n\n const headers = msgData.payload?.headers ?? [];\n const subject = headers.find((h) => h.name === \"Subject\")?.value ?? \"(no subject)\";\n const from = headers.find((h) => h.name === \"From\")?.value ?? \"\";\n const dateStr = headers.find((h) => h.name === \"Date\")?.value;\n const date = dateStr\n ? new Date(dateStr).toISOString().slice(0, 10)\n : new Date().toISOString().slice(0, 10);\n const snippet = msgData.snippet ?? \"\";\n\n // Extract the full inline body (plain preferred, else HTML->Markdown) so\n // summaries and search cover the whole message, not just the snippet.\n const { extractEmailBodyMarkdown } = await import(\"./email-body.js\");\n const body = (await extractEmailBodyMarkdown(msgData.payload ?? undefined)) || snippet;\n\n // LLM summary — non-blocking fallback to raw body/snippet if no API key or error\n const { summarizeEmail } = await import(\"../core/llm.js\");\n const emailSummary = await summarizeEmail(subject, body, from);\n\n // Download, convert and index attachments before logging the interaction so\n // the entry can link to the generated Markdown. Failures here are swallowed.\n let attachmentLinks: string[] = [];\n if (includeAttachments) {\n try {\n const { processMessageAttachments } = await import(\"./attachments.js\");\n const saved = await processMessageAttachments({\n gmail,\n dataDir: opts.dataDir,\n slug: opts.slug,\n messageId: msg.id,\n source,\n payload: msgData.payload ?? undefined,\n date,\n ...(opts.maxAttachmentBytes !== undefined\n ? { maxBytes: opts.maxAttachmentBytes }\n : {}),\n });\n attachmentLinks = saved.map((a) => a.markdownName);\n } catch (err) {\n logger.warn(\"gmail-sync\", \"attachment processing failed\", {\n messageId: msg.id,\n error: (err as Error).message,\n });\n }\n }\n\n await appendInteraction(opts.dataDir, opts.slug, {\n date,\n type: \"Email\",\n direction: detectDirection(from),\n with: from,\n subject,\n summary: emailSummary.summary,\n nextSteps: emailSummary.nextSteps,\n ...(attachmentLinks.length > 0 ? { attachments: attachmentLinks } : {}),\n sourceRef: source,\n synced: new Date().toISOString(),\n });\n\n // Append to in-memory string so within-batch duplicates are detected\n existingContent += source;\n\n // Index the full email (subject + body) into LanceDB for semantic search,\n // chunked so long threads stay searchable (non-blocking).\n const { indexInLanceDB } = await import(\"../core/lancedb.js\");\n const { chunkText } = await import(\"../core/chunk.js\");\n const bodyChunks = chunkText(`${subject}\\n${body}`);\n for (let i = 0; i < bodyChunks.length; i++) {\n const ref = i === 0 ? source : `${source}#${i}`;\n await indexInLanceDB(opts.dataDir, opts.slug, bodyChunks[i]!, ref, {\n date,\n type: \"Email\",\n }).catch((err: unknown) => {\n logger.error(\"gmail-sync\", \"LanceDB index failed\", { error: (err as Error).message });\n });\n }\n\n // Agent wake: notify if an agent config exists for this customer (fire-and-forget)\n if (agentConfigExists(opts.dataDir, opts.slug)) {\n notifyAgentWake(opts.dataDir, opts.slug, {\n trigger: \"email\",\n subject,\n from,\n snippet,\n }).catch(() => {\n // Notification is non-blocking; swallow all errors\n });\n }\n\n synced++;\n }\n\n return { synced, skipped };\n}\n\nfunction agentConfigExists(dataDir: string, slug: string): boolean {\n const configPath = path.join(dataDir, \".agentic\", \"agents\", `${slug}.agent.json`);\n return fs.existsSync(configPath);\n}\n\nfunction detectDirection(_from: string): \"inbound\" | \"outbound\" {\n return \"inbound\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { w as writeJsonFile } from "./session-store-DWxJ5Pof.js";
2
- import { i as readInteractions, t as appendInteraction } from "./interactions-writer-BZzUIgJd.js";
2
+ import { i as readInteractions, t as appendInteraction } from "./interactions-writer-ZQcpFOh9.js";
3
3
  import { t as logger } from "./logger-UaF5p9d1.js";
4
4
  import { r as summarizeEmail } from "./llm-BnSUBisu.js";
5
5
  import path from "path";
@@ -111,6 +111,7 @@ async function syncGmail(opts) {
111
111
  auth: opts.auth
112
112
  });
113
113
  const maxPages = opts.maxPages ?? 5;
114
+ const includeAttachments = opts.includeAttachments ?? true;
114
115
  let q = opts.query;
115
116
  if (opts.since) {
116
117
  const after = Math.floor(opts.since.getTime() / 1e3);
@@ -147,12 +148,14 @@ async function syncGmail(opts) {
147
148
  msgData = (await retryWithBackoff(() => gmail$1.users.messages.get({
148
149
  userId: "me",
149
150
  id: msg.id,
150
- format: "metadata",
151
- metadataHeaders: [
152
- "Subject",
153
- "From",
154
- "Date"
155
- ]
151
+ ...includeAttachments ? { format: "full" } : {
152
+ format: "metadata",
153
+ metadataHeaders: [
154
+ "Subject",
155
+ "From",
156
+ "Date"
157
+ ]
158
+ }
156
159
  }))).data;
157
160
  } catch (err) {
158
161
  logger.warn("gmail-sync", "skipping message after retries", {
@@ -168,8 +171,29 @@ async function syncGmail(opts) {
168
171
  const dateStr = headers.find((h) => h.name === "Date")?.value;
169
172
  const date = dateStr ? new Date(dateStr).toISOString().slice(0, 10) : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
170
173
  const snippet = msgData.snippet ?? "";
174
+ const { extractEmailBodyMarkdown } = await import("./email-body-BOd7U-D2.js");
175
+ const body = await extractEmailBodyMarkdown(msgData.payload ?? void 0) || snippet;
171
176
  const { summarizeEmail } = await import("./llm-BnSUBisu.js").then((n) => n.n);
172
- const emailSummary = await summarizeEmail(subject, snippet, from);
177
+ const emailSummary = await summarizeEmail(subject, body, from);
178
+ let attachmentLinks = [];
179
+ if (includeAttachments) try {
180
+ const { processMessageAttachments } = await import("./attachments-D207gXfN.js");
181
+ attachmentLinks = (await processMessageAttachments({
182
+ gmail: gmail$1,
183
+ dataDir: opts.dataDir,
184
+ slug: opts.slug,
185
+ messageId: msg.id,
186
+ source,
187
+ payload: msgData.payload ?? void 0,
188
+ date,
189
+ ...opts.maxAttachmentBytes !== void 0 ? { maxBytes: opts.maxAttachmentBytes } : {}
190
+ })).map((a) => a.markdownName);
191
+ } catch (err) {
192
+ logger.warn("gmail-sync", "attachment processing failed", {
193
+ messageId: msg.id,
194
+ error: err.message
195
+ });
196
+ }
173
197
  await appendInteraction(opts.dataDir, opts.slug, {
174
198
  date,
175
199
  type: "Email",
@@ -178,17 +202,23 @@ async function syncGmail(opts) {
178
202
  subject,
179
203
  summary: emailSummary.summary,
180
204
  nextSteps: emailSummary.nextSteps,
205
+ ...attachmentLinks.length > 0 ? { attachments: attachmentLinks } : {},
181
206
  sourceRef: source,
182
207
  synced: (/* @__PURE__ */ new Date()).toISOString()
183
208
  });
184
209
  existingContent += source;
185
210
  const { indexInLanceDB } = await import("./mcp.js").then((n) => n.t);
186
- await indexInLanceDB(opts.dataDir, opts.slug, `${subject}\n${snippet}`, source, {
187
- date,
188
- type: "Email"
189
- }).catch((err) => {
190
- logger.error("gmail-sync", "LanceDB index failed", { error: err.message });
191
- });
211
+ const { chunkText } = await import("./chunk-e_w8qqtP.js");
212
+ const bodyChunks = chunkText(`${subject}\n${body}`);
213
+ for (let i = 0; i < bodyChunks.length; i++) {
214
+ const ref = i === 0 ? source : `${source}#${i}`;
215
+ await indexInLanceDB(opts.dataDir, opts.slug, bodyChunks[i], ref, {
216
+ date,
217
+ type: "Email"
218
+ }).catch((err) => {
219
+ logger.error("gmail-sync", "LanceDB index failed", { error: err.message });
220
+ });
221
+ }
192
222
  if (agentConfigExists(opts.dataDir, opts.slug)) notifyAgentWake(opts.dataDir, opts.slug, {
193
223
  trigger: "email",
194
224
  subject,
@@ -215,4 +245,4 @@ function sleep(ms) {
215
245
  //#endregion
216
246
  export { syncGmail };
217
247
 
218
- //# sourceMappingURL=gmail-sync-C-NmibzS.js.map
248
+ //# sourceMappingURL=gmail-sync-DIbrPnTK.js.map