@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.
- package/README.md +8 -1
- package/dist/{ask-D8iYqDAr.js → ask-CDysGnRg.js} +2 -2
- package/dist/{ask-D8iYqDAr.js.map → ask-CDysGnRg.js.map} +1 -1
- package/dist/attachments-CX2GAtsw.cjs +517 -0
- package/dist/attachments-CX2GAtsw.cjs.map +1 -0
- package/dist/attachments-D207gXfN.js +514 -0
- package/dist/attachments-D207gXfN.js.map +1 -0
- package/dist/attachments-rLa96rOK.js +514 -0
- package/dist/attachments-rLa96rOK.js.map +1 -0
- package/dist/chunk-BfDYWZQ8.cjs +32 -0
- package/dist/chunk-BfDYWZQ8.cjs.map +1 -0
- package/dist/chunk-BhUZmQg5.js +32 -0
- package/dist/chunk-BhUZmQg5.js.map +1 -0
- package/dist/chunk-ChC83jai.js +2 -0
- package/dist/chunk-e_w8qqtP.js +32 -0
- package/dist/chunk-e_w8qqtP.js.map +1 -0
- package/dist/cli.js +20 -18
- package/dist/cli.js.map +1 -1
- package/dist/daemon/worker.js +3 -3
- package/dist/{doctor-C14-vnJ1.js → doctor-BFeelnq8.js} +2 -2
- package/dist/{doctor-C14-vnJ1.js.map → doctor-BFeelnq8.js.map} +1 -1
- package/dist/doctor-CYDaNmFn.js +2 -0
- package/dist/email-body-BFSRa0AW.cjs +42 -0
- package/dist/email-body-BFSRa0AW.cjs.map +1 -0
- package/dist/email-body-BOd7U-D2.js +42 -0
- package/dist/email-body-BOd7U-D2.js.map +1 -0
- package/dist/{gmail-sync-DueE6tl5.js → gmail-sync-B4Iu3AQb.js} +45 -15
- package/dist/gmail-sync-B4Iu3AQb.js.map +1 -0
- package/dist/{gmail-sync-GEy3oVvw.cjs → gmail-sync-BpSVESSe.cjs} +45 -15
- package/dist/gmail-sync-BpSVESSe.cjs.map +1 -0
- package/dist/{gmail-sync-C-NmibzS.js → gmail-sync-DIbrPnTK.js} +45 -15
- package/dist/gmail-sync-DIbrPnTK.js.map +1 -0
- package/dist/{gmail-webhook-handler-kGKpbY9h.js → gmail-webhook-handler-BzOFbvgh.js} +2 -2
- package/dist/{gmail-webhook-handler-kGKpbY9h.js.map → gmail-webhook-handler-BzOFbvgh.js.map} +1 -1
- package/dist/{gmail-webhook-handler-B26COilD.js → gmail-webhook-handler-CvSDW_Js.js} +1 -1
- package/dist/{google-drive-sync-D1n7WKZn.js → google-drive-sync-B_I1d54Y.js} +2 -2
- package/dist/{google-drive-sync-D1n7WKZn.js.map → google-drive-sync-B_I1d54Y.js.map} +1 -1
- package/dist/html-BaeOCZKE.js +36 -0
- package/dist/html-BaeOCZKE.js.map +1 -0
- package/dist/html-CmOku6jS.cjs +47 -0
- package/dist/html-CmOku6jS.cjs.map +1 -0
- package/dist/{import-hubspot-DB4n89jy.js → import-hubspot-CTId9IGV.js} +2 -2
- package/dist/{import-hubspot-DB4n89jy.js.map → import-hubspot-CTId9IGV.js.map} +1 -1
- package/dist/{index-pY7tYXwH.d.cts → index-CLUKKfGb.d.cts} +12 -8
- package/dist/index-CLUKKfGb.d.cts.map +1 -0
- package/dist/{index-B0IMMrp_.d.ts → index-D8jJ1VIt.d.ts} +14 -10
- package/dist/index-D8jJ1VIt.d.ts.map +1 -0
- package/dist/index.d.cts +12 -8
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +14 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/{interactions-writer-RJB8SWf2.js → interactions-writer-B2y-73lh.js} +1 -1
- package/dist/{interactions-writer-DbSyI2rt.js → interactions-writer-B8XAzdqR.js} +3 -2
- package/dist/interactions-writer-B8XAzdqR.js.map +1 -0
- package/dist/{interactions-writer-a2yzBd7T.cjs → interactions-writer-BRJNrefF.cjs} +3 -2
- package/dist/interactions-writer-BRJNrefF.cjs.map +1 -0
- package/dist/{interactions-writer-BZzUIgJd.js → interactions-writer-ZQcpFOh9.js} +3 -2
- package/dist/interactions-writer-ZQcpFOh9.js.map +1 -0
- package/dist/{knowledge-base-DHNc4hVj.js → knowledge-base-Bx2PKQR2.js} +10 -7
- package/dist/knowledge-base-Bx2PKQR2.js.map +1 -0
- package/dist/mcp-CdTJWTJf.d.cts.map +1 -1
- package/dist/mcp-CdTJWTJf.d.ts.map +1 -1
- package/dist/mcp.cjs +308 -150
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.d.cts.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +308 -150
- package/dist/mcp.js.map +1 -1
- package/dist/{microsoft-calendar-jIu9K5zX.js → microsoft-calendar-BgVR8GDv.js} +3 -3
- package/dist/{microsoft-calendar-jIu9K5zX.js.map → microsoft-calendar-BgVR8GDv.js.map} +1 -1
- package/dist/{microsoft-sync-R_r8HL-B.js → microsoft-sync-D30_XksI.js} +3 -3
- package/dist/{microsoft-sync-R_r8HL-B.js.map → microsoft-sync-D30_XksI.js.map} +1 -1
- package/dist/{nba-mTJ4yEqD.js → nba-DwdfM93s.js} +2 -2
- package/dist/{nba-mTJ4yEqD.js.map → nba-DwdfM93s.js.map} +1 -1
- package/dist/{server-DqSMYhSA.js → server-BhNLrnAD.js} +201 -145
- package/dist/server-BhNLrnAD.js.map +1 -0
- package/dist/{transcript-watcher-0mh2ZhmH.js → transcript-watcher-BoClrJAz.js} +2 -2
- package/dist/{transcript-watcher-0mh2ZhmH.js.map → transcript-watcher-BoClrJAz.js.map} +1 -1
- package/package.json +13 -2
- package/dist/gmail-sync-C-NmibzS.js.map +0 -1
- package/dist/gmail-sync-DueE6tl5.js.map +0 -1
- package/dist/gmail-sync-GEy3oVvw.cjs.map +0 -1
- package/dist/index-B0IMMrp_.d.ts.map +0 -1
- package/dist/index-pY7tYXwH.d.cts.map +0 -1
- package/dist/interactions-writer-BZzUIgJd.js.map +0 -1
- package/dist/interactions-writer-DbSyI2rt.js.map +0 -1
- package/dist/interactions-writer-a2yzBd7T.cjs.map +0 -1
- package/dist/knowledge-base-DHNc4hVj.js.map +0 -1
- package/dist/server-DqSMYhSA.js.map +0 -1
package/dist/daemon/worker.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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 {
|
|
101
|
+
export { runDiagnostics as n, cleanupTempFiles as t };
|
|
102
102
|
|
|
103
|
-
//# sourceMappingURL=doctor-
|
|
103
|
+
//# sourceMappingURL=doctor-BFeelnq8.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-
|
|
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,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-
|
|
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: "
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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,
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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-
|
|
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":";;;;;;;;;;AAwBA,SAAS,gBAAgB,SAAiB,MAAsB;CAC9D,OAAO,KAAK,KAAK,SAAS,YAAY,UAAU,GAAG,KAAK,YAAY;AACtE;AAEA,SAAS,gBAAgB,SAAiB,MAAkC;CAC1E,MAAM,IAAI,gBAAgB,SAAS,IAAI;CACvC,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO;CAC9B,IAAI;EACF,MAAM,MAAM,KAAK,MAAM,GAAG,aAAa,GAAG,OAAO,CAAW;EAC5D,MAAM,SAAS,kBAAkB,UAAU,GAAG;EAC9C,OAAO,OAAO,UAAU,OAAO,OAAO;CACxC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,cAAc,SAAiB,MAAc,QAA2B;CAC/E,MAAM,IAAI,gBAAgB,SAAS,IAAI;CACvC,IAAI;EAEF,cAAc,GAAG;GADc,GAAG;GAAQ,2BAAU,IAAI,KAAK,GAAE,YAAY;EACpD,CAAC;CAC1B,QAAQ,CAER;AACF;AAIA,SAAS,oBAAoB,OAAe,QAAgB,MAA6B;CACvF,MAAM,OAAO,KAAK,UAAU;EAAE,SAAS;EAAQ;EAAM,YAAY;CAAW,CAAC;CAC7E,OAAO,IAAI,SAAe,SAAS,WAAW;EAC5C,MAAM,MAAM,MAAM,QAChB,+BAA+B,MAAM,eACrC;GACE,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,kBAAkB,OAAO,WAAW,IAAI;GAC1C;EACF,IACC,QAAQ;GACP,IAAI,OAAO;GACX,QAAQ;EACV,CACF;EACA,IAAI,GAAG,SAAS,MAAM;EACtB,IAAI,MAAM,IAAI;EACd,IAAI,IAAI;CACV,CAAC;AACH;AAIA,SAAS,iBACP,MACA,SACA,SACA,WACQ;CAER,OACE,uBAAuB,KAAK,MAAM,QAAQ,IACvC,QAAQ,2BAHW,UAAU,MAAM;AAM1C;;;;;;;;;;;AAcA,eAAsB,gBACpB,SACA,MACA,SACe;CACf,IAAI;EAEF,MAAM,SAAS,gBAAgB,SAAS,IAAI;EAC5C,IAAI,CAAC,QAAQ;EAGb,MAAM,QAAQ,QAAQ,IAAI;EAC1B,IAAI,CAAC,OAAO;EAGZ,MAAM,SAAS,OAAO,kBAAkB,QAAQ,IAAI;EACpD,IAAI,CAAC,QAAQ;EAGb,MAAM,eAAe,MAAM,eAAe,QAAQ,SAAS,QAAQ,SAAS,QAAQ,IAAI;EASxF,MAAM,oBAAoB,OAAO,QANpB,iBACX,MACA,QAAQ,SACR,aAAa,SACb,aAAa,SAE6B,CAAC;EAG7C,cAAc,SAAS,MAAM,MAAM;CACrC,QAAQ,CAER;AACF;;;;;;;AChHA,eAAsB,iBAAoB,IAAsB,aAAa,GAAe;CAC1F,IAAI,UAAU;CACd,OAAO,MACL,IAAI;EACF,OAAO,MAAM,GAAG;CAClB,SAAS,KAAK;EACZ,IAAI,WAAW,YAAY,MAAM;EAEjC,MAAM,MADU,MAAO,KAAK,IAAI,GAAG,OAAO,CACvB;EACnB;CACF;AAEJ;AAEA,eAAsB,UAAU,MAAiE;CAC/F,MAAMA,UAAQC,MAAS;EAAE,SAAS;EAAM,MAAM,KAAK;CAAK,CAAC;CACzD,MAAM,WAAW,KAAK,YAAY;CAClC,MAAM,qBAAqB,KAAK,sBAAsB;CAEtD,IAAI,IAAI,KAAK;CACb,IAAI,KAAK,OAAO;EACd,MAAM,QAAQ,KAAK,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAI;EACpD,KAAK,UAAU;CACjB;CAGA,MAAM,cAAuE,CAAC;CAC9E,IAAI,YAAgC,KAAA;CACpC,IAAI,eAAe;CAEnB,GAAG;EACD,MAAM,WACJ,MAAMD,QAAM,MAAM,SAAS,KAAK;GAC9B,QAAQ;GACR;GACA,YAAY;GACZ,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;EACnC,CAAC;EACH,MAAM,eAAe,SAAS,KAAK,YAAY,CAAC;EAChD,YAAY,KAAK,GAAG,YAAY;EAChC,YAAY,SAAS,KAAK,iBAAiB,KAAA;EAC3C;CACF,SAAS,aAAa,eAAe;CAGrC,IAAI,kBAAkB,MAAM,iBAAiB,KAAK,SAAS,KAAK,IAAI;CAEpE,IAAI,SAAS;CACb,IAAI,UAAU;CAEd,KAAK,MAAM,OAAO,aAAa;EAC7B,IAAI,CAAC,IAAI,IAAI;EAEb,MAAM,SAAS,kBAAkB,IAAI,YAAY,IAAI;EAErD,IAAI,gBAAgB,SAAS,MAAM,GAAG;GACpC;GACA;EACF;EAGA,MAAM,MAAM,GAAG;EAGf,IAAI;EACJ,IAAI;GAYF,WAAU,MAXW,uBACnBA,QAAM,MAAM,SAAS,IAAI;IACvB,QAAQ;IACR,IAAI,IAAI;IAGR,GAAI,qBACA,EAAE,QAAQ,OAAO,IACjB;KAAE,QAAQ;KAAY,iBAAiB;MAAC;MAAW;MAAQ;KAAM;IAAE;GACzE,CAAC,CACH,GACiB;EACnB,SAAS,KAAK;GACZ,OAAO,KAAK,cAAc,kCAAkC;IAC1D,WAAW,IAAI;IACf,OAAQ,IAAc;GACxB,CAAC;GACD;GACA;EACF;EAEA,MAAM,UAAU,QAAQ,SAAS,WAAW,CAAC;EAC7C,MAAM,UAAU,QAAQ,MAAM,MAAM,EAAE,SAAS,SAAS,GAAG,SAAS;EACpE,MAAM,OAAO,QAAQ,MAAM,MAAM,EAAE,SAAS,MAAM,GAAG,SAAS;EAC9D,MAAM,UAAU,QAAQ,MAAM,MAAM,EAAE,SAAS,MAAM,GAAG;EACxD,MAAM,OAAO,UACT,IAAI,KAAK,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE,qBAC3C,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;EACxC,MAAM,UAAU,QAAQ,WAAW;EAInC,MAAM,EAAE,6BAA6B,MAAM,OAAO;EAClD,MAAM,OAAQ,MAAM,yBAAyB,QAAQ,WAAW,KAAA,CAAS,KAAM;EAG/E,MAAM,EAAE,mBAAmB,MAAM,OAAO;EACxC,MAAM,eAAe,MAAM,eAAe,SAAS,MAAM,IAAI;EAI7D,IAAI,kBAA4B,CAAC;EACjC,IAAI,oBACF,IAAI;GACF,MAAM,EAAE,8BAA8B,MAAM,OAAO;GAanD,mBAAkB,MAZE,0BAA0B;IAC5C,OAAA;IACA,SAAS,KAAK;IACd,MAAM,KAAK;IACX,WAAW,IAAI;IACf;IACA,SAAS,QAAQ,WAAW,KAAA;IAC5B;IACA,GAAI,KAAK,uBAAuB,KAAA,IAC5B,EAAE,UAAU,KAAK,mBAAmB,IACpC,CAAC;GACP,CAAC,GACuB,KAAK,MAAM,EAAE,YAAY;EACnD,SAAS,KAAK;GACZ,OAAO,KAAK,cAAc,gCAAgC;IACxD,WAAW,IAAI;IACf,OAAQ,IAAc;GACxB,CAAC;EACH;EAGF,MAAM,kBAAkB,KAAK,SAAS,KAAK,MAAM;GAC/C;GACA,MAAM;GACN,WAAW,gBAAgB,IAAI;GAC/B,MAAM;GACN;GACA,SAAS,aAAa;GACtB,WAAW,aAAa;GACxB,GAAI,gBAAgB,SAAS,IAAI,EAAE,aAAa,gBAAgB,IAAI,CAAC;GACrE,WAAW;GACX,yBAAQ,IAAI,KAAK,GAAE,YAAY;EACjC,CAAC;EAGD,mBAAmB;EAInB,MAAM,EAAE,mBAAmB,MAAM,OAAO;EACxC,MAAM,EAAE,cAAc,MAAM,OAAO;EACnC,MAAM,aAAa,UAAU,GAAG,QAAQ,IAAI,MAAM;EAClD,KAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;GAC1C,MAAM,MAAM,MAAM,IAAI,SAAS,GAAG,OAAO,GAAG;GAC5C,MAAM,eAAe,KAAK,SAAS,KAAK,MAAM,WAAW,IAAK,KAAK;IACjE;IACA,MAAM;GACR,CAAC,EAAE,OAAO,QAAiB;IACzB,OAAO,MAAM,cAAc,wBAAwB,EAAE,OAAQ,IAAc,QAAQ,CAAC;GACtF,CAAC;EACH;EAGA,IAAI,kBAAkB,KAAK,SAAS,KAAK,IAAI,GAC3C,gBAAgB,KAAK,SAAS,KAAK,MAAM;GACvC,SAAS;GACT;GACA;GACA;EACF,CAAC,EAAE,YAAY,CAEf,CAAC;EAGH;CACF;CAEA,OAAO;EAAE;EAAQ;CAAQ;AAC3B;AAEA,SAAS,kBAAkB,SAAiB,MAAuB;CACjE,MAAM,aAAa,KAAK,KAAK,SAAS,YAAY,UAAU,GAAG,KAAK,YAAY;CAChF,OAAO,GAAG,WAAW,UAAU;AACjC;AAEA,SAAS,gBAAgB,OAAuC;CAC9D,OAAO;AACT;AAEA,SAAS,MAAM,IAA2B;CACxC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD"}
|
|
@@ -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-
|
|
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: "
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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,
|
|
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
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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-
|
|
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-
|
|
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: "
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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,
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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-
|
|
248
|
+
//# sourceMappingURL=gmail-sync-DIbrPnTK.js.map
|