@agenticmail/core 0.7.6 → 0.9.1
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 +10 -1
- package/dist/index.cjs +323 -26
- package/dist/index.d.cts +258 -1
- package/dist/index.d.ts +258 -1
- package/dist/index.js +333 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1087,14 +1087,14 @@ var StalwartAdmin = class {
|
|
|
1087
1087
|
if (!isValidDomain(domain)) {
|
|
1088
1088
|
throw new Error(`Invalid domain format: "${domain}"`);
|
|
1089
1089
|
}
|
|
1090
|
-
const { readFileSync:
|
|
1091
|
-
const { homedir:
|
|
1092
|
-
const { join:
|
|
1093
|
-
const configPath =
|
|
1090
|
+
const { readFileSync: readFileSync7, writeFileSync: writeFileSync8 } = await import("fs");
|
|
1091
|
+
const { homedir: homedir11 } = await import("os");
|
|
1092
|
+
const { join: join12 } = await import("path");
|
|
1093
|
+
const configPath = join12(homedir11(), ".agenticmail", "stalwart.toml");
|
|
1094
1094
|
try {
|
|
1095
|
-
let config =
|
|
1095
|
+
let config = readFileSync7(configPath, "utf-8");
|
|
1096
1096
|
config = config.replace(/^hostname\s*=\s*"[^"]*"/m, `hostname = "${escapeTomlString(domain)}"`);
|
|
1097
|
-
|
|
1097
|
+
writeFileSync8(configPath, config);
|
|
1098
1098
|
console.log(`[Stalwart] Updated hostname to "${domain}" in stalwart.toml`);
|
|
1099
1099
|
} catch (err) {
|
|
1100
1100
|
throw new Error(`Failed to set config server.hostname=${domain}`);
|
|
@@ -1103,15 +1103,15 @@ var StalwartAdmin = class {
|
|
|
1103
1103
|
// --- DKIM ---
|
|
1104
1104
|
/** Path to the host-side stalwart.toml (mounted read-only into container) */
|
|
1105
1105
|
get configPath() {
|
|
1106
|
-
const { homedir:
|
|
1107
|
-
const { join:
|
|
1108
|
-
return
|
|
1106
|
+
const { homedir: homedir11 } = __require("os");
|
|
1107
|
+
const { join: join12 } = __require("path");
|
|
1108
|
+
return join12(homedir11(), ".agenticmail", "stalwart.toml");
|
|
1109
1109
|
}
|
|
1110
1110
|
/** Path to host-side DKIM key directory */
|
|
1111
1111
|
get dkimDir() {
|
|
1112
|
-
const { homedir:
|
|
1113
|
-
const { join:
|
|
1114
|
-
return
|
|
1112
|
+
const { homedir: homedir11 } = __require("os");
|
|
1113
|
+
const { join: join12 } = __require("path");
|
|
1114
|
+
return join12(homedir11(), ".agenticmail");
|
|
1115
1115
|
}
|
|
1116
1116
|
/**
|
|
1117
1117
|
* Create/reuse a DKIM signing key for a domain.
|
|
@@ -1212,12 +1212,12 @@ var StalwartAdmin = class {
|
|
|
1212
1212
|
* This bypasses the need for a PTR record on the sending IP.
|
|
1213
1213
|
*/
|
|
1214
1214
|
async configureOutboundRelay(config) {
|
|
1215
|
-
const { readFileSync:
|
|
1216
|
-
const { homedir:
|
|
1217
|
-
const { join:
|
|
1215
|
+
const { readFileSync: readFileSync7, writeFileSync: writeFileSync8 } = await import("fs");
|
|
1216
|
+
const { homedir: homedir11 } = await import("os");
|
|
1217
|
+
const { join: join12 } = await import("path");
|
|
1218
1218
|
const routeName = config.routeName ?? "gmail";
|
|
1219
|
-
const tomlPath =
|
|
1220
|
-
let toml =
|
|
1219
|
+
const tomlPath = join12(homedir11(), ".agenticmail", "stalwart.toml");
|
|
1220
|
+
let toml = readFileSync7(tomlPath, "utf-8");
|
|
1221
1221
|
toml = toml.replace(/\n\[queue\.route\.gmail\][\s\S]*?(?=\n\[|$)/, "");
|
|
1222
1222
|
toml = toml.replace(/\n\[queue\.strategy\][\s\S]*?(?=\n\[|$)/, "");
|
|
1223
1223
|
const safeRouteName = routeName.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
@@ -1237,7 +1237,7 @@ auth.secret = "${escapeTomlString(config.password)}"
|
|
|
1237
1237
|
route = [ { if = "is_local_domain('', rcpt_domain)", then = "'local'" },
|
|
1238
1238
|
{ else = "'${safeRouteName}'" } ]
|
|
1239
1239
|
`;
|
|
1240
|
-
|
|
1240
|
+
writeFileSync8(tomlPath, toml, "utf-8");
|
|
1241
1241
|
await this.restartContainer();
|
|
1242
1242
|
}
|
|
1243
1243
|
};
|
|
@@ -1275,7 +1275,11 @@ function rowToAgent(row) {
|
|
|
1275
1275
|
createdAt: row.created_at,
|
|
1276
1276
|
updatedAt: row.updated_at,
|
|
1277
1277
|
metadata,
|
|
1278
|
-
role: row.role || "secretary"
|
|
1278
|
+
role: row.role || "secretary",
|
|
1279
|
+
// Old rows (pre-migration-016) have undefined `wake_on_cc`;
|
|
1280
|
+
// treat that as the default-true (respect sender's wake list
|
|
1281
|
+
// as-is). Only explicit 0 disables CC wakes for this agent.
|
|
1282
|
+
wakeOnCc: row.wake_on_cc !== void 0 ? row.wake_on_cc !== 0 : true
|
|
1279
1283
|
};
|
|
1280
1284
|
}
|
|
1281
1285
|
var AccountManager = class {
|
|
@@ -2858,6 +2862,15 @@ ALTER TABLE agent_tasks ADD COLUMN output_schema TEXT;
|
|
|
2858
2862
|
-- stays snappy. NULL means "no attachments", fully back-compat
|
|
2859
2863
|
-- with rows from before this migration.
|
|
2860
2864
|
ALTER TABLE drafts ADD COLUMN attachments TEXT;
|
|
2865
|
+
`,
|
|
2866
|
+
"016_agent_wake_on_cc.sql": `
|
|
2867
|
+
-- Per-agent wake preference. When 0, the dispatcher SKIPS this
|
|
2868
|
+
-- agent on every CC-only delivery (the agent is on Cc/Bcc but
|
|
2869
|
+
-- not To), regardless of what the sender passed as the wake
|
|
2870
|
+
-- argument. This is the "coder agent, only wake me when
|
|
2871
|
+
-- explicitly named" preference from the wake-thrash feedback.
|
|
2872
|
+
-- Defaults to 1 (respect the senders wake list as-is).
|
|
2873
|
+
ALTER TABLE agents ADD COLUMN wake_on_cc INTEGER NOT NULL DEFAULT 1;
|
|
2861
2874
|
`
|
|
2862
2875
|
};
|
|
2863
2876
|
function runMigrations(database) {
|
|
@@ -5105,12 +5118,12 @@ var GatewayManager = class {
|
|
|
5105
5118
|
zone = await this.cfClient.createZone(domain);
|
|
5106
5119
|
}
|
|
5107
5120
|
const existingRecords = await this.cfClient.listDnsRecords(zone.id);
|
|
5108
|
-
const { homedir:
|
|
5109
|
-
const backupDir = join4(
|
|
5121
|
+
const { homedir: homedir11 } = await import("os");
|
|
5122
|
+
const backupDir = join4(homedir11(), ".agenticmail");
|
|
5110
5123
|
const backupPath = join4(backupDir, `dns-backup-${domain}-${Date.now()}.json`);
|
|
5111
|
-
const { writeFileSync:
|
|
5112
|
-
|
|
5113
|
-
|
|
5124
|
+
const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync9 } = await import("fs");
|
|
5125
|
+
mkdirSync9(backupDir, { recursive: true });
|
|
5126
|
+
writeFileSync8(backupPath, JSON.stringify({
|
|
5114
5127
|
domain,
|
|
5115
5128
|
zoneId: zone.id,
|
|
5116
5129
|
backedUpAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7292,10 +7305,301 @@ secret = "${password}"
|
|
|
7292
7305
|
return existsSync7(configPath);
|
|
7293
7306
|
}
|
|
7294
7307
|
};
|
|
7308
|
+
|
|
7309
|
+
// src/threading/thread-id.ts
|
|
7310
|
+
import { createHash as createHash2 } from "crypto";
|
|
7311
|
+
function stripReplyPrefixes(subject) {
|
|
7312
|
+
let s = subject;
|
|
7313
|
+
for (; ; ) {
|
|
7314
|
+
const next = s.replace(/^\s*(?:re|fwd?|fw)\s*(?:\[\d+\])?\s*:\s*/i, "");
|
|
7315
|
+
if (next === s) break;
|
|
7316
|
+
s = next;
|
|
7317
|
+
}
|
|
7318
|
+
return s;
|
|
7319
|
+
}
|
|
7320
|
+
function stripCoordinationMarkers(subject) {
|
|
7321
|
+
return subject.replace(/\[\s*(?:final|done|closed|wrap)\s*\]/gi, " ");
|
|
7322
|
+
}
|
|
7323
|
+
function normalizeSubject(subject) {
|
|
7324
|
+
if (!subject) return "(no subject)";
|
|
7325
|
+
let s = stripReplyPrefixes(subject);
|
|
7326
|
+
s = stripCoordinationMarkers(s);
|
|
7327
|
+
s = s.replace(/\s+/g, " ").trim().toLowerCase();
|
|
7328
|
+
return s || "(no subject)";
|
|
7329
|
+
}
|
|
7330
|
+
function normalizeAddress(addr) {
|
|
7331
|
+
if (!addr) return "(unknown)";
|
|
7332
|
+
const m = addr.match(/<([^>]+)>/);
|
|
7333
|
+
const raw = m ? m[1] : addr;
|
|
7334
|
+
return raw.trim().toLowerCase();
|
|
7335
|
+
}
|
|
7336
|
+
function threadIdFor(input) {
|
|
7337
|
+
const subject = normalizeSubject(input.subject);
|
|
7338
|
+
return createHash2("sha256").update(subject).digest("base64url").slice(0, 16);
|
|
7339
|
+
}
|
|
7340
|
+
|
|
7341
|
+
// src/threading/thread-cache.ts
|
|
7342
|
+
import {
|
|
7343
|
+
existsSync as existsSync8,
|
|
7344
|
+
mkdirSync as mkdirSync7,
|
|
7345
|
+
readFileSync as readFileSync5,
|
|
7346
|
+
writeFileSync as writeFileSync6,
|
|
7347
|
+
readdirSync,
|
|
7348
|
+
statSync,
|
|
7349
|
+
rmSync,
|
|
7350
|
+
renameSync
|
|
7351
|
+
} from "fs";
|
|
7352
|
+
import { homedir as homedir9 } from "os";
|
|
7353
|
+
import { join as join10 } from "path";
|
|
7354
|
+
var CACHE_DIR_DEFAULT = join10(homedir9(), ".agenticmail", "thread-cache");
|
|
7355
|
+
var DEFAULT_K_MESSAGES = 10;
|
|
7356
|
+
var DEFAULT_LRU_CAP = 5e3;
|
|
7357
|
+
var PREVIEW_MAX_CHARS = 240;
|
|
7358
|
+
var ThreadCache = class {
|
|
7359
|
+
dir;
|
|
7360
|
+
k;
|
|
7361
|
+
lruCap;
|
|
7362
|
+
constructor(opts = {}) {
|
|
7363
|
+
this.dir = opts.cacheDir ?? CACHE_DIR_DEFAULT;
|
|
7364
|
+
this.k = opts.k ?? DEFAULT_K_MESSAGES;
|
|
7365
|
+
this.lruCap = opts.lruCap ?? DEFAULT_LRU_CAP;
|
|
7366
|
+
try {
|
|
7367
|
+
mkdirSync7(this.dir, { recursive: true });
|
|
7368
|
+
} catch {
|
|
7369
|
+
}
|
|
7370
|
+
}
|
|
7371
|
+
pathFor(threadId) {
|
|
7372
|
+
return join10(this.dir, `${threadId}.json`);
|
|
7373
|
+
}
|
|
7374
|
+
read(threadId) {
|
|
7375
|
+
const p = this.pathFor(threadId);
|
|
7376
|
+
if (!existsSync8(p)) return null;
|
|
7377
|
+
try {
|
|
7378
|
+
const raw = readFileSync5(p, "utf-8");
|
|
7379
|
+
return JSON.parse(raw);
|
|
7380
|
+
} catch {
|
|
7381
|
+
try {
|
|
7382
|
+
rmSync(p, { force: true });
|
|
7383
|
+
} catch {
|
|
7384
|
+
}
|
|
7385
|
+
return null;
|
|
7386
|
+
}
|
|
7387
|
+
}
|
|
7388
|
+
/**
|
|
7389
|
+
* Append a message to the thread's cache, pruning to the K
|
|
7390
|
+
* newest entries. Creates the cache entry on first write.
|
|
7391
|
+
*
|
|
7392
|
+
* `rootFromAddr` is the sender of the ROOT message on the
|
|
7393
|
+
* thread; on a brand-new thread this is just `env.fromAddr`,
|
|
7394
|
+
* on a reply it's read off the existing cache entry (callers
|
|
7395
|
+
* should pass the existing entry's rootFromAddr when known).
|
|
7396
|
+
*/
|
|
7397
|
+
pushMessage(threadId, env, meta) {
|
|
7398
|
+
const existing = this.read(threadId);
|
|
7399
|
+
const entry = existing ? {
|
|
7400
|
+
...existing,
|
|
7401
|
+
// We re-affirm subject + rootFromAddr to existing values —
|
|
7402
|
+
// the first-seen values win. Reply messages carry the
|
|
7403
|
+
// replier's `from`, not the original sender's, so we'd
|
|
7404
|
+
// corrupt the thread root if we overwrote here.
|
|
7405
|
+
subject: existing.subject,
|
|
7406
|
+
rootFromAddr: existing.rootFromAddr,
|
|
7407
|
+
lastUpdated: Date.now(),
|
|
7408
|
+
messages: dedupAndCap([env, ...existing.messages], this.k)
|
|
7409
|
+
} : {
|
|
7410
|
+
threadId,
|
|
7411
|
+
subject: meta.subject,
|
|
7412
|
+
rootFromAddr: meta.rootFromAddr,
|
|
7413
|
+
lastUpdated: Date.now(),
|
|
7414
|
+
messages: [env]
|
|
7415
|
+
};
|
|
7416
|
+
this.writeAtomic(threadId, entry);
|
|
7417
|
+
this.maybeEvict();
|
|
7418
|
+
return entry;
|
|
7419
|
+
}
|
|
7420
|
+
/** Permanently remove a thread's cache (called on [FINAL] / [DONE] / [CLOSED] / [WRAP]). */
|
|
7421
|
+
delete(threadId) {
|
|
7422
|
+
try {
|
|
7423
|
+
rmSync(this.pathFor(threadId), { force: true });
|
|
7424
|
+
} catch {
|
|
7425
|
+
}
|
|
7426
|
+
}
|
|
7427
|
+
/**
|
|
7428
|
+
* Render the cache as a compact text block for the wake prompt.
|
|
7429
|
+
* One line per message, newest first. Empty string when the
|
|
7430
|
+
* cache is empty — caller decides whether to suppress the
|
|
7431
|
+
* header in that case.
|
|
7432
|
+
*/
|
|
7433
|
+
renderForPrompt(entry) {
|
|
7434
|
+
if (!entry || entry.messages.length === 0) return "";
|
|
7435
|
+
return entry.messages.map((m) => {
|
|
7436
|
+
const preview = m.preview.replace(/\s+/g, " ").slice(0, PREVIEW_MAX_CHARS);
|
|
7437
|
+
return `- UID ${m.uid} \xB7 ${m.from} \xB7 ${m.date} \xB7 "${m.subject}" \xB7 ${preview}`;
|
|
7438
|
+
}).join("\n");
|
|
7439
|
+
}
|
|
7440
|
+
writeAtomic(threadId, entry) {
|
|
7441
|
+
const p = this.pathFor(threadId);
|
|
7442
|
+
const tmp = `${p}.tmp`;
|
|
7443
|
+
writeFileSync6(tmp, JSON.stringify(entry), "utf-8");
|
|
7444
|
+
renameSync(tmp, p);
|
|
7445
|
+
}
|
|
7446
|
+
/**
|
|
7447
|
+
* Best-effort LRU eviction. Runs at most every 256 writes (we
|
|
7448
|
+
* don't track a precise counter — `Math.random()` sampling keeps
|
|
7449
|
+
* the write path cheap). When the directory has more files than
|
|
7450
|
+
* `lruCap`, sort by mtime ascending and delete the oldest 10%.
|
|
7451
|
+
*/
|
|
7452
|
+
maybeEvict() {
|
|
7453
|
+
if (Math.random() > 1 / 256) return;
|
|
7454
|
+
let files;
|
|
7455
|
+
try {
|
|
7456
|
+
files = readdirSync(this.dir).filter((f) => f.endsWith(".json"));
|
|
7457
|
+
} catch {
|
|
7458
|
+
return;
|
|
7459
|
+
}
|
|
7460
|
+
if (files.length <= this.lruCap) return;
|
|
7461
|
+
const stats = files.map((f) => {
|
|
7462
|
+
const p = join10(this.dir, f);
|
|
7463
|
+
try {
|
|
7464
|
+
return { p, mtime: statSync(p).mtimeMs };
|
|
7465
|
+
} catch {
|
|
7466
|
+
return { p, mtime: 0 };
|
|
7467
|
+
}
|
|
7468
|
+
});
|
|
7469
|
+
stats.sort((a, b) => a.mtime - b.mtime);
|
|
7470
|
+
const dropCount = Math.max(1, Math.floor(this.lruCap * 0.1));
|
|
7471
|
+
for (let i = 0; i < dropCount; i++) {
|
|
7472
|
+
try {
|
|
7473
|
+
rmSync(stats[i].p, { force: true });
|
|
7474
|
+
} catch {
|
|
7475
|
+
}
|
|
7476
|
+
}
|
|
7477
|
+
}
|
|
7478
|
+
};
|
|
7479
|
+
function dedupAndCap(messages, k) {
|
|
7480
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7481
|
+
const out = [];
|
|
7482
|
+
for (const m of messages) {
|
|
7483
|
+
if (seen.has(m.uid)) continue;
|
|
7484
|
+
seen.add(m.uid);
|
|
7485
|
+
out.push(m);
|
|
7486
|
+
if (out.length >= k) break;
|
|
7487
|
+
}
|
|
7488
|
+
return out;
|
|
7489
|
+
}
|
|
7490
|
+
|
|
7491
|
+
// src/threading/agent-memory.ts
|
|
7492
|
+
import {
|
|
7493
|
+
existsSync as existsSync9,
|
|
7494
|
+
mkdirSync as mkdirSync8,
|
|
7495
|
+
readFileSync as readFileSync6,
|
|
7496
|
+
writeFileSync as writeFileSync7,
|
|
7497
|
+
rmSync as rmSync2,
|
|
7498
|
+
renameSync as renameSync2
|
|
7499
|
+
} from "fs";
|
|
7500
|
+
import { homedir as homedir10 } from "os";
|
|
7501
|
+
import { join as join11 } from "path";
|
|
7502
|
+
var MEMORY_DIR_DEFAULT = join11(homedir10(), ".agenticmail", "agent-memory");
|
|
7503
|
+
var AgentMemoryStore = class {
|
|
7504
|
+
dir;
|
|
7505
|
+
constructor(opts = {}) {
|
|
7506
|
+
this.dir = opts.memoryDir ?? MEMORY_DIR_DEFAULT;
|
|
7507
|
+
try {
|
|
7508
|
+
mkdirSync8(this.dir, { recursive: true });
|
|
7509
|
+
} catch {
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
7512
|
+
dirFor(agentId) {
|
|
7513
|
+
return join11(this.dir, sanitizeId(agentId));
|
|
7514
|
+
}
|
|
7515
|
+
pathFor(agentId, threadId) {
|
|
7516
|
+
return join11(this.dirFor(agentId), `${sanitizeId(threadId)}.md`);
|
|
7517
|
+
}
|
|
7518
|
+
read(agentId, threadId) {
|
|
7519
|
+
const p = this.pathFor(agentId, threadId);
|
|
7520
|
+
if (!existsSync9(p)) return null;
|
|
7521
|
+
try {
|
|
7522
|
+
const raw = readFileSync6(p, "utf-8");
|
|
7523
|
+
const parsed = parse(raw);
|
|
7524
|
+
return { ...parsed, raw };
|
|
7525
|
+
} catch {
|
|
7526
|
+
return null;
|
|
7527
|
+
}
|
|
7528
|
+
}
|
|
7529
|
+
write(agentId, threadId, fields) {
|
|
7530
|
+
const agentDir = this.dirFor(agentId);
|
|
7531
|
+
try {
|
|
7532
|
+
mkdirSync8(agentDir, { recursive: true });
|
|
7533
|
+
} catch {
|
|
7534
|
+
}
|
|
7535
|
+
const body = render({ ...fields, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
7536
|
+
const p = this.pathFor(agentId, threadId);
|
|
7537
|
+
const tmp = `${p}.tmp`;
|
|
7538
|
+
writeFileSync7(tmp, body, "utf-8");
|
|
7539
|
+
renameSync2(tmp, p);
|
|
7540
|
+
}
|
|
7541
|
+
delete(agentId, threadId) {
|
|
7542
|
+
try {
|
|
7543
|
+
rmSync2(this.pathFor(agentId, threadId), { force: true });
|
|
7544
|
+
} catch {
|
|
7545
|
+
}
|
|
7546
|
+
}
|
|
7547
|
+
/** Render an agent's memory for injection into a wake prompt.
|
|
7548
|
+
* Returns the raw markdown if present; empty string when there's
|
|
7549
|
+
* no prior memory (the caller decides whether to suppress the
|
|
7550
|
+
* whole "Your own memory" block). */
|
|
7551
|
+
renderForPrompt(memory) {
|
|
7552
|
+
if (!memory) return "";
|
|
7553
|
+
return memory.raw;
|
|
7554
|
+
}
|
|
7555
|
+
};
|
|
7556
|
+
function sanitizeId(id) {
|
|
7557
|
+
return id.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
7558
|
+
}
|
|
7559
|
+
function render(fields) {
|
|
7560
|
+
const fm = ["---"];
|
|
7561
|
+
fm.push(`updated_at: ${fields.updatedAt}`);
|
|
7562
|
+
if (typeof fields.lastUid === "number") fm.push(`last_uid: ${fields.lastUid}`);
|
|
7563
|
+
fm.push("---", "");
|
|
7564
|
+
const sections = [];
|
|
7565
|
+
if (fields.summary && fields.summary.trim()) {
|
|
7566
|
+
sections.push(fields.summary.trim());
|
|
7567
|
+
}
|
|
7568
|
+
if (fields.commitments && fields.commitments.length > 0) {
|
|
7569
|
+
sections.push(`### Commitments
|
|
7570
|
+
${fields.commitments.map((c) => `- ${c}`).join("\n")}`);
|
|
7571
|
+
}
|
|
7572
|
+
if (fields.openQuestions && fields.openQuestions.length > 0) {
|
|
7573
|
+
sections.push(`### Open
|
|
7574
|
+
${fields.openQuestions.map((q) => `- ${q}`).join("\n")}`);
|
|
7575
|
+
}
|
|
7576
|
+
if (fields.lastAction && fields.lastAction.trim()) {
|
|
7577
|
+
sections.push(`### Last action
|
|
7578
|
+
${fields.lastAction.trim()}`);
|
|
7579
|
+
}
|
|
7580
|
+
return fm.join("\n") + sections.join("\n\n") + "\n";
|
|
7581
|
+
}
|
|
7582
|
+
function parse(raw) {
|
|
7583
|
+
const out = {};
|
|
7584
|
+
const m = raw.match(/^---\n([\s\S]*?)\n---\n/);
|
|
7585
|
+
if (m) {
|
|
7586
|
+
for (const line of m[1].split("\n")) {
|
|
7587
|
+
const kv = line.match(/^(\w+):\s*(.*)$/);
|
|
7588
|
+
if (!kv) continue;
|
|
7589
|
+
if (kv[1] === "updated_at") out.updatedAt = kv[2].trim();
|
|
7590
|
+
else if (kv[1] === "last_uid") {
|
|
7591
|
+
const n = parseInt(kv[2], 10);
|
|
7592
|
+
if (!isNaN(n)) out.lastUid = n;
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
7595
|
+
}
|
|
7596
|
+
return out;
|
|
7597
|
+
}
|
|
7295
7598
|
export {
|
|
7296
7599
|
AGENT_ROLES,
|
|
7297
7600
|
AccountManager,
|
|
7298
7601
|
AgentDeletionService,
|
|
7602
|
+
AgentMemoryStore,
|
|
7299
7603
|
AgenticMailClient,
|
|
7300
7604
|
CloudflareClient,
|
|
7301
7605
|
DEFAULT_AGENT_NAME,
|
|
@@ -7319,6 +7623,7 @@ export {
|
|
|
7319
7623
|
SmsManager,
|
|
7320
7624
|
SmsPoller,
|
|
7321
7625
|
StalwartAdmin,
|
|
7626
|
+
ThreadCache,
|
|
7322
7627
|
TunnelManager,
|
|
7323
7628
|
WARNING_THRESHOLD,
|
|
7324
7629
|
buildInboundSecurityAdvisory,
|
|
@@ -7333,7 +7638,9 @@ export {
|
|
|
7333
7638
|
getDatabase,
|
|
7334
7639
|
isInternalEmail,
|
|
7335
7640
|
isValidPhoneNumber,
|
|
7641
|
+
normalizeAddress,
|
|
7336
7642
|
normalizePhoneNumber,
|
|
7643
|
+
normalizeSubject,
|
|
7337
7644
|
parseEmail,
|
|
7338
7645
|
parseGoogleVoiceSms,
|
|
7339
7646
|
recordToolCall,
|
|
@@ -7343,5 +7650,6 @@ export {
|
|
|
7343
7650
|
scanOutboundEmail,
|
|
7344
7651
|
scoreEmail,
|
|
7345
7652
|
setTelemetryVersion,
|
|
7346
|
-
startRelayBridge
|
|
7653
|
+
startRelayBridge,
|
|
7654
|
+
threadIdFor
|
|
7347
7655
|
};
|