@agenticmail/core 0.9.4 → 0.9.6
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/dist/index.cjs +472 -211
- package/dist/index.d.cts +372 -1
- package/dist/index.d.ts +372 -1
- package/dist/index.js +445 -200
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -78,7 +78,7 @@ var MailSender = class {
|
|
|
78
78
|
const code = err?.responseCode ?? err?.code;
|
|
79
79
|
const isTransient = typeof code === "number" && code >= 400 && code < 500 || code === "ECONNRESET" || code === "ETIMEDOUT" || code === "ESOCKET";
|
|
80
80
|
if (!isTransient || attempt === MAX_RETRIES) throw err;
|
|
81
|
-
await new Promise((
|
|
81
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1e3 * (attempt + 1)));
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
throw lastError;
|
|
@@ -1010,20 +1010,20 @@ var StalwartAdmin = class {
|
|
|
1010
1010
|
}
|
|
1011
1011
|
try {
|
|
1012
1012
|
const net = await import("net");
|
|
1013
|
-
return await new Promise((
|
|
1013
|
+
return await new Promise((resolve2) => {
|
|
1014
1014
|
const smtpPort = parseInt(process.env.SMTP_PORT || "25", 10);
|
|
1015
1015
|
const smtpHost = process.env.SMTP_HOST || "localhost";
|
|
1016
1016
|
const socket = net.createConnection({ host: smtpHost, port: smtpPort, timeout: 3e3 }, () => {
|
|
1017
1017
|
socket.destroy();
|
|
1018
|
-
|
|
1018
|
+
resolve2(true);
|
|
1019
1019
|
});
|
|
1020
1020
|
socket.on("error", () => {
|
|
1021
1021
|
socket.destroy();
|
|
1022
|
-
|
|
1022
|
+
resolve2(false);
|
|
1023
1023
|
});
|
|
1024
1024
|
socket.on("timeout", () => {
|
|
1025
1025
|
socket.destroy();
|
|
1026
|
-
|
|
1026
|
+
resolve2(false);
|
|
1027
1027
|
});
|
|
1028
1028
|
});
|
|
1029
1029
|
} catch {
|
|
@@ -1093,14 +1093,14 @@ var StalwartAdmin = class {
|
|
|
1093
1093
|
if (!isValidDomain(domain)) {
|
|
1094
1094
|
throw new Error(`Invalid domain format: "${domain}"`);
|
|
1095
1095
|
}
|
|
1096
|
-
const { readFileSync:
|
|
1097
|
-
const { homedir:
|
|
1098
|
-
const { join:
|
|
1099
|
-
const configPath =
|
|
1096
|
+
const { readFileSync: readFileSync8, writeFileSync: writeFileSync9 } = await import("fs");
|
|
1097
|
+
const { homedir: homedir12 } = await import("os");
|
|
1098
|
+
const { join: join14 } = await import("path");
|
|
1099
|
+
const configPath = join14(homedir12(), ".agenticmail", "stalwart.toml");
|
|
1100
1100
|
try {
|
|
1101
|
-
let config =
|
|
1101
|
+
let config = readFileSync8(configPath, "utf-8");
|
|
1102
1102
|
config = config.replace(/^hostname\s*=\s*"[^"]*"/m, `hostname = "${escapeTomlString(domain)}"`);
|
|
1103
|
-
|
|
1103
|
+
writeFileSync9(configPath, config);
|
|
1104
1104
|
console.log(`[Stalwart] Updated hostname to "${domain}" in stalwart.toml`);
|
|
1105
1105
|
} catch (err) {
|
|
1106
1106
|
throw new Error(`Failed to set config server.hostname=${domain}`);
|
|
@@ -1109,15 +1109,15 @@ var StalwartAdmin = class {
|
|
|
1109
1109
|
// --- DKIM ---
|
|
1110
1110
|
/** Path to the host-side stalwart.toml (mounted read-only into container) */
|
|
1111
1111
|
get configPath() {
|
|
1112
|
-
const { homedir:
|
|
1113
|
-
const { join:
|
|
1114
|
-
return
|
|
1112
|
+
const { homedir: homedir12 } = __require("os");
|
|
1113
|
+
const { join: join14 } = __require("path");
|
|
1114
|
+
return join14(homedir12(), ".agenticmail", "stalwart.toml");
|
|
1115
1115
|
}
|
|
1116
1116
|
/** Path to host-side DKIM key directory */
|
|
1117
1117
|
get dkimDir() {
|
|
1118
|
-
const { homedir:
|
|
1119
|
-
const { join:
|
|
1120
|
-
return
|
|
1118
|
+
const { homedir: homedir12 } = __require("os");
|
|
1119
|
+
const { join: join14 } = __require("path");
|
|
1120
|
+
return join14(homedir12(), ".agenticmail");
|
|
1121
1121
|
}
|
|
1122
1122
|
/**
|
|
1123
1123
|
* Create/reuse a DKIM signing key for a domain.
|
|
@@ -1218,12 +1218,12 @@ var StalwartAdmin = class {
|
|
|
1218
1218
|
* This bypasses the need for a PTR record on the sending IP.
|
|
1219
1219
|
*/
|
|
1220
1220
|
async configureOutboundRelay(config) {
|
|
1221
|
-
const { readFileSync:
|
|
1222
|
-
const { homedir:
|
|
1223
|
-
const { join:
|
|
1221
|
+
const { readFileSync: readFileSync8, writeFileSync: writeFileSync9 } = await import("fs");
|
|
1222
|
+
const { homedir: homedir12 } = await import("os");
|
|
1223
|
+
const { join: join14 } = await import("path");
|
|
1224
1224
|
const routeName = config.routeName ?? "gmail";
|
|
1225
|
-
const tomlPath =
|
|
1226
|
-
let toml =
|
|
1225
|
+
const tomlPath = join14(homedir12(), ".agenticmail", "stalwart.toml");
|
|
1226
|
+
let toml = readFileSync8(tomlPath, "utf-8");
|
|
1227
1227
|
toml = toml.replace(/\n\[queue\.route\.gmail\][\s\S]*?(?=\n\[|$)/, "");
|
|
1228
1228
|
toml = toml.replace(/\n\[queue\.strategy\][\s\S]*?(?=\n\[|$)/, "");
|
|
1229
1229
|
const safeRouteName = routeName.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
@@ -1243,7 +1243,7 @@ auth.secret = "${escapeTomlString(config.password)}"
|
|
|
1243
1243
|
route = [ { if = "is_local_domain('', rcpt_domain)", then = "'local'" },
|
|
1244
1244
|
{ else = "'${safeRouteName}'" } ]
|
|
1245
1245
|
`;
|
|
1246
|
-
|
|
1246
|
+
writeFileSync9(tomlPath, toml, "utf-8");
|
|
1247
1247
|
await this.restartContainer();
|
|
1248
1248
|
}
|
|
1249
1249
|
};
|
|
@@ -2327,7 +2327,8 @@ var OUTBOUND_TEXT_RULES = [
|
|
|
2327
2327
|
var HIGH_RISK_EXTENSIONS = /* @__PURE__ */ new Set([".pem", ".key", ".p12", ".pfx", ".env", ".credentials", ".keystore", ".jks", ".p8"]);
|
|
2328
2328
|
var MEDIUM_RISK_EXTENSIONS = /* @__PURE__ */ new Set([".db", ".sqlite", ".sqlite3", ".sql", ".csv", ".tsv", ".json", ".yml", ".yaml", ".conf", ".config", ".ini"]);
|
|
2329
2329
|
function stripHtmlTags(html) {
|
|
2330
|
-
|
|
2330
|
+
const bounded = html.length > 1048576 ? html.slice(0, 1048576) : html;
|
|
2331
|
+
return bounded.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/�?39;/g, "'").replace(/ /g, " ").replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(parseInt(dec, 10)));
|
|
2331
2332
|
}
|
|
2332
2333
|
var TEXT_SCANNABLE_TYPES = /* @__PURE__ */ new Set([
|
|
2333
2334
|
"text/plain",
|
|
@@ -3859,7 +3860,7 @@ var CloudflareClient = class {
|
|
|
3859
3860
|
// --- Workers methods ---
|
|
3860
3861
|
/** Deploy an Email Worker script (ES module format) */
|
|
3861
3862
|
async deployEmailWorker(scriptName, scriptContent, envVars = {}) {
|
|
3862
|
-
const url = `${CF_API_BASE}/accounts/${this.accountId}/workers/scripts/${scriptName}`;
|
|
3863
|
+
const url = `${CF_API_BASE}/accounts/${encodeURIComponent(this.accountId)}/workers/scripts/${encodeURIComponent(scriptName)}`;
|
|
3863
3864
|
const bindings = Object.entries(envVars).map(([name, text2]) => ({
|
|
3864
3865
|
type: "plain_text",
|
|
3865
3866
|
name,
|
|
@@ -4239,9 +4240,9 @@ var TunnelManager = class {
|
|
|
4239
4240
|
throw new Error(`Failed to download cloudflared: ${response.statusText}`);
|
|
4240
4241
|
}
|
|
4241
4242
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
4242
|
-
const { writeFile:
|
|
4243
|
+
const { writeFile: writeFile3, rename: rename2 } = await import("fs/promises");
|
|
4243
4244
|
const tmpPath = this.binPath + ".tmp";
|
|
4244
|
-
await
|
|
4245
|
+
await writeFile3(tmpPath, buffer);
|
|
4245
4246
|
await chmod(tmpPath, 493);
|
|
4246
4247
|
await rename2(tmpPath, this.binPath);
|
|
4247
4248
|
return this.binPath;
|
|
@@ -4277,7 +4278,7 @@ var TunnelManager = class {
|
|
|
4277
4278
|
detached: false,
|
|
4278
4279
|
env: { ...process.env, TUNNEL_TOKEN: tunnelToken }
|
|
4279
4280
|
});
|
|
4280
|
-
await new Promise((
|
|
4281
|
+
await new Promise((resolve2, reject) => {
|
|
4281
4282
|
let resolved = false;
|
|
4282
4283
|
const timeout = setTimeout(() => {
|
|
4283
4284
|
if (!resolved) {
|
|
@@ -4291,7 +4292,7 @@ var TunnelManager = class {
|
|
|
4291
4292
|
resolved = true;
|
|
4292
4293
|
clearTimeout(timeout);
|
|
4293
4294
|
this.running = true;
|
|
4294
|
-
|
|
4295
|
+
resolve2();
|
|
4295
4296
|
}
|
|
4296
4297
|
};
|
|
4297
4298
|
this.process.stderr?.on("data", onData);
|
|
@@ -4330,8 +4331,8 @@ var TunnelManager = class {
|
|
|
4330
4331
|
this.process = null;
|
|
4331
4332
|
p.kill("SIGTERM");
|
|
4332
4333
|
await Promise.race([
|
|
4333
|
-
new Promise((
|
|
4334
|
-
new Promise((
|
|
4334
|
+
new Promise((resolve2) => p.on("exit", () => resolve2())),
|
|
4335
|
+
new Promise((resolve2) => setTimeout(resolve2, 5e3))
|
|
4335
4336
|
]);
|
|
4336
4337
|
this.running = false;
|
|
4337
4338
|
}
|
|
@@ -4386,7 +4387,10 @@ function parseGoogleVoiceSms(emailBody, emailFrom) {
|
|
|
4386
4387
|
if (!emailBody || typeof emailBody !== "string") return null;
|
|
4387
4388
|
if (!emailFrom || typeof emailFrom !== "string") return null;
|
|
4388
4389
|
const fromLower = emailFrom.toLowerCase();
|
|
4389
|
-
const
|
|
4390
|
+
const atIdx = fromLower.lastIndexOf("@");
|
|
4391
|
+
const domain = atIdx >= 0 ? fromLower.slice(atIdx + 1).replace(/[>"'\s].*$/, "") : "";
|
|
4392
|
+
const isGoogleDomain = domain === "google.com" || domain.endsWith(".google.com");
|
|
4393
|
+
const isGoogleVoice = isGoogleDomain && (fromLower.startsWith("voice-noreply@") || domain === "txt.voice.google.com" || domain === "voice.google.com" || domain.endsWith(".voice.google.com") || fromLower.includes("voice"));
|
|
4390
4394
|
if (!isGoogleVoice) return null;
|
|
4391
4395
|
let text = emailBody.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<\/div>/gi, "\n").replace(/<[^>]+>/g, "").replace(/ /gi, " ").replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/'/gi, "'").trim();
|
|
4392
4396
|
let from = "";
|
|
@@ -5145,12 +5149,12 @@ var GatewayManager = class {
|
|
|
5145
5149
|
zone = await this.cfClient.createZone(domain);
|
|
5146
5150
|
}
|
|
5147
5151
|
const existingRecords = await this.cfClient.listDnsRecords(zone.id);
|
|
5148
|
-
const { homedir:
|
|
5149
|
-
const backupDir = join4(
|
|
5152
|
+
const { homedir: homedir12 } = await import("os");
|
|
5153
|
+
const backupDir = join4(homedir12(), ".agenticmail");
|
|
5150
5154
|
const backupPath = join4(backupDir, `dns-backup-${domain}-${Date.now()}.json`);
|
|
5151
|
-
const { writeFileSync:
|
|
5152
|
-
|
|
5153
|
-
|
|
5155
|
+
const { writeFileSync: writeFileSync9, mkdirSync: mkdirSync10 } = await import("fs");
|
|
5156
|
+
mkdirSync10(backupDir, { recursive: true });
|
|
5157
|
+
writeFileSync9(backupPath, JSON.stringify({
|
|
5154
5158
|
domain,
|
|
5155
5159
|
zoneId: zone.id,
|
|
5156
5160
|
backedUpAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5417,6 +5421,15 @@ var GatewayManager = class {
|
|
|
5417
5421
|
bcc: mail.bcc ? Array.isArray(mail.bcc) ? mail.bcc.join(", ") : mail.bcc : void 0,
|
|
5418
5422
|
subject: mail.subject,
|
|
5419
5423
|
text: mail.text || void 0,
|
|
5424
|
+
// The `html` field is the literal HTML body of the outbound
|
|
5425
|
+
// mail — by design it is whatever the sender chose to compose.
|
|
5426
|
+
// CodeQL `js/xss` flags this because the value flows from user
|
|
5427
|
+
// input, but nodemailer is the SMTP serializer, not an HTML
|
|
5428
|
+
// renderer; XSS would only occur if the recipient's MUA
|
|
5429
|
+
// executed the body, which is outside our trust boundary.
|
|
5430
|
+
// The outbound-guard (packages/core/src/mail/outbound-guard.ts)
|
|
5431
|
+
// already scores HTML bodies for suspicious patterns at the
|
|
5432
|
+
// pre-send step. lgtm[js/xss]
|
|
5420
5433
|
html: mail.html || void 0,
|
|
5421
5434
|
replyTo: mail.replyTo || from,
|
|
5422
5435
|
inReplyTo: mail.inReplyTo || void 0,
|
|
@@ -5778,11 +5791,11 @@ var RelayBridge = class {
|
|
|
5778
5791
|
this.options = options;
|
|
5779
5792
|
}
|
|
5780
5793
|
async start() {
|
|
5781
|
-
return new Promise((
|
|
5794
|
+
return new Promise((resolve2, reject) => {
|
|
5782
5795
|
this.server = createServer((req, res) => this.handleRequest(req, res));
|
|
5783
5796
|
this.server.listen(this.options.port, "127.0.0.1", () => {
|
|
5784
5797
|
console.log(`[RelayBridge] Listening on 127.0.0.1:${this.options.port}`);
|
|
5785
|
-
|
|
5798
|
+
resolve2();
|
|
5786
5799
|
});
|
|
5787
5800
|
this.server.on("error", reject);
|
|
5788
5801
|
});
|
|
@@ -5984,17 +5997,232 @@ try {
|
|
|
5984
5997
|
} catch {
|
|
5985
5998
|
}
|
|
5986
5999
|
|
|
6000
|
+
// src/util/safe-path.ts
|
|
6001
|
+
import { isAbsolute, join as join6, resolve, sep } from "path";
|
|
6002
|
+
var PathTraversalError = class extends Error {
|
|
6003
|
+
constructor(baseDir, parts) {
|
|
6004
|
+
super(
|
|
6005
|
+
`path traversal attempt: ${JSON.stringify(parts)} resolves outside ${baseDir}`
|
|
6006
|
+
);
|
|
6007
|
+
this.baseDir = baseDir;
|
|
6008
|
+
this.parts = parts;
|
|
6009
|
+
this.name = "PathTraversalError";
|
|
6010
|
+
}
|
|
6011
|
+
};
|
|
6012
|
+
function safeJoin(baseDir, ...partsAndOpts) {
|
|
6013
|
+
let opts = {};
|
|
6014
|
+
const parts = [];
|
|
6015
|
+
for (const p of partsAndOpts) {
|
|
6016
|
+
if (typeof p === "string") {
|
|
6017
|
+
parts.push(p);
|
|
6018
|
+
} else if (p && typeof p === "object") {
|
|
6019
|
+
opts = p;
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
6022
|
+
if (!opts.allowAbsolute) {
|
|
6023
|
+
for (const part of parts) {
|
|
6024
|
+
if (isAbsolute(part)) {
|
|
6025
|
+
throw new PathTraversalError(baseDir, parts);
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
const resolvedBase = resolve(baseDir);
|
|
6030
|
+
const resolved = resolve(resolvedBase, ...parts);
|
|
6031
|
+
if (resolved !== resolvedBase && !resolved.startsWith(resolvedBase + sep)) {
|
|
6032
|
+
throw new PathTraversalError(baseDir, parts);
|
|
6033
|
+
}
|
|
6034
|
+
return resolved;
|
|
6035
|
+
}
|
|
6036
|
+
function tryJoin(baseDir, ...parts) {
|
|
6037
|
+
try {
|
|
6038
|
+
return safeJoin(baseDir, ...parts);
|
|
6039
|
+
} catch (err) {
|
|
6040
|
+
if (err instanceof PathTraversalError) return null;
|
|
6041
|
+
throw err;
|
|
6042
|
+
}
|
|
6043
|
+
}
|
|
6044
|
+
function assertWithinBase(baseDir, candidate) {
|
|
6045
|
+
const resolvedBase = resolve(baseDir);
|
|
6046
|
+
const resolvedCandidate = resolve(candidate);
|
|
6047
|
+
if (resolvedCandidate !== resolvedBase && !resolvedCandidate.startsWith(resolvedBase + sep)) {
|
|
6048
|
+
throw new PathTraversalError(baseDir, [candidate]);
|
|
6049
|
+
}
|
|
6050
|
+
return resolvedCandidate;
|
|
6051
|
+
}
|
|
6052
|
+
|
|
6053
|
+
// src/util/redact.ts
|
|
6054
|
+
var REDACTED = "***";
|
|
6055
|
+
function redactSecret(value) {
|
|
6056
|
+
if (typeof value !== "string") return REDACTED;
|
|
6057
|
+
if (value.length === 0) return REDACTED;
|
|
6058
|
+
for (const prefix of ["mk_", "ak_", "sk-", "pk_", "tok_"]) {
|
|
6059
|
+
if (value.startsWith(prefix)) return `${prefix}${REDACTED}`;
|
|
6060
|
+
}
|
|
6061
|
+
return REDACTED;
|
|
6062
|
+
}
|
|
6063
|
+
var SENSITIVE_KEY_PATTERNS = [
|
|
6064
|
+
/key$/i,
|
|
6065
|
+
/secret/i,
|
|
6066
|
+
/password/i,
|
|
6067
|
+
/passwd/i,
|
|
6068
|
+
/token/i,
|
|
6069
|
+
/authorization/i,
|
|
6070
|
+
/bearer/i,
|
|
6071
|
+
/\bapikey\b/i,
|
|
6072
|
+
/\bapi_key\b/i,
|
|
6073
|
+
/\bmasterkey\b/i,
|
|
6074
|
+
/\bmaster_key\b/i
|
|
6075
|
+
];
|
|
6076
|
+
function looksSensitive(key) {
|
|
6077
|
+
return SENSITIVE_KEY_PATTERNS.some((p) => p.test(key));
|
|
6078
|
+
}
|
|
6079
|
+
function redactObject(input, _depth = 0) {
|
|
6080
|
+
if (_depth > 12) return input;
|
|
6081
|
+
if (input === null || input === void 0) return input;
|
|
6082
|
+
if (typeof input !== "object") return input;
|
|
6083
|
+
if (Array.isArray(input)) {
|
|
6084
|
+
return input.map((v) => redactObject(v, _depth + 1));
|
|
6085
|
+
}
|
|
6086
|
+
const proto = Object.getPrototypeOf(input);
|
|
6087
|
+
if (proto !== Object.prototype && proto !== null) return input;
|
|
6088
|
+
const out = {};
|
|
6089
|
+
for (const [k, v] of Object.entries(input)) {
|
|
6090
|
+
if (looksSensitive(k)) {
|
|
6091
|
+
out[k] = typeof v === "string" ? redactSecret(v) : REDACTED;
|
|
6092
|
+
} else {
|
|
6093
|
+
out[k] = redactObject(v, _depth + 1);
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
return out;
|
|
6097
|
+
}
|
|
6098
|
+
|
|
6099
|
+
// src/host-sessions.ts
|
|
6100
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync4 } from "fs";
|
|
6101
|
+
import { join as join7 } from "path";
|
|
6102
|
+
import { homedir as homedir5 } from "os";
|
|
6103
|
+
function storageDir() {
|
|
6104
|
+
return join7(homedir5(), ".agenticmail");
|
|
6105
|
+
}
|
|
6106
|
+
function storagePath() {
|
|
6107
|
+
return join7(storageDir(), "host-sessions.json");
|
|
6108
|
+
}
|
|
6109
|
+
var DEFAULT_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
6110
|
+
function readFile() {
|
|
6111
|
+
const p = storagePath();
|
|
6112
|
+
if (!existsSync4(p)) return { version: 1, sessions: {} };
|
|
6113
|
+
try {
|
|
6114
|
+
const raw = readFileSync3(p, "utf-8");
|
|
6115
|
+
if (!raw.trim()) return { version: 1, sessions: {} };
|
|
6116
|
+
const parsed = JSON.parse(raw);
|
|
6117
|
+
return {
|
|
6118
|
+
version: 1,
|
|
6119
|
+
sessions: parsed.sessions && typeof parsed.sessions === "object" ? parsed.sessions : {}
|
|
6120
|
+
};
|
|
6121
|
+
} catch {
|
|
6122
|
+
return { version: 1, sessions: {} };
|
|
6123
|
+
}
|
|
6124
|
+
}
|
|
6125
|
+
function writeFile(shape) {
|
|
6126
|
+
const dir = storageDir();
|
|
6127
|
+
const p = storagePath();
|
|
6128
|
+
if (!existsSync4(dir)) mkdirSync4(dir, { recursive: true });
|
|
6129
|
+
const tmp = `${p}.agenticmail-tmp-${process.pid}`;
|
|
6130
|
+
writeFileSync4(tmp, JSON.stringify(shape, null, 2), "utf-8");
|
|
6131
|
+
renameSync(tmp, p);
|
|
6132
|
+
}
|
|
6133
|
+
function saveHostSession(host, session) {
|
|
6134
|
+
if (!session.sessionId) return;
|
|
6135
|
+
try {
|
|
6136
|
+
const shape = readFile();
|
|
6137
|
+
shape.sessions[host] = {
|
|
6138
|
+
...session,
|
|
6139
|
+
lastSeenMs: Date.now()
|
|
6140
|
+
};
|
|
6141
|
+
writeFile(shape);
|
|
6142
|
+
} catch {
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
function loadHostSession(host, maxAgeMs = DEFAULT_SESSION_MAX_AGE_MS) {
|
|
6146
|
+
const shape = readFile();
|
|
6147
|
+
const record = shape.sessions[host];
|
|
6148
|
+
if (!record) return null;
|
|
6149
|
+
if (!isSessionFresh(record, maxAgeMs)) return null;
|
|
6150
|
+
return record;
|
|
6151
|
+
}
|
|
6152
|
+
function isSessionFresh(session, maxAgeMs = DEFAULT_SESSION_MAX_AGE_MS) {
|
|
6153
|
+
if (!session || !Number.isFinite(session.lastSeenMs)) return false;
|
|
6154
|
+
return Date.now() - session.lastSeenMs <= maxAgeMs;
|
|
6155
|
+
}
|
|
6156
|
+
function forgetHostSession(host) {
|
|
6157
|
+
try {
|
|
6158
|
+
const shape = readFile();
|
|
6159
|
+
if (!shape.sessions[host]) return;
|
|
6160
|
+
delete shape.sessions[host];
|
|
6161
|
+
writeFile(shape);
|
|
6162
|
+
} catch {
|
|
6163
|
+
}
|
|
6164
|
+
}
|
|
6165
|
+
function hostSessionStoragePath() {
|
|
6166
|
+
return storagePath();
|
|
6167
|
+
}
|
|
6168
|
+
|
|
6169
|
+
// src/util/safe-url.ts
|
|
6170
|
+
var UnsafeApiUrlError = class extends Error {
|
|
6171
|
+
constructor(raw, reason) {
|
|
6172
|
+
super(`unsafe AgenticMail API URL ${JSON.stringify(raw)}: ${reason}`);
|
|
6173
|
+
this.raw = raw;
|
|
6174
|
+
this.reason = reason;
|
|
6175
|
+
this.name = "UnsafeApiUrlError";
|
|
6176
|
+
}
|
|
6177
|
+
};
|
|
6178
|
+
var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
|
|
6179
|
+
"169.254.169.254",
|
|
6180
|
+
// AWS / Azure / GCP IPv4 metadata
|
|
6181
|
+
"fd00:ec2::254",
|
|
6182
|
+
// AWS IPv6 metadata
|
|
6183
|
+
"metadata.google.internal",
|
|
6184
|
+
// GCP DNS
|
|
6185
|
+
"metadata.azure.internal"
|
|
6186
|
+
// Azure DNS
|
|
6187
|
+
]);
|
|
6188
|
+
function validateApiUrl(raw) {
|
|
6189
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
6190
|
+
throw new UnsafeApiUrlError(String(raw), "must be a non-empty string");
|
|
6191
|
+
}
|
|
6192
|
+
let parsed;
|
|
6193
|
+
try {
|
|
6194
|
+
parsed = new URL(raw);
|
|
6195
|
+
} catch (err) {
|
|
6196
|
+
throw new UnsafeApiUrlError(raw, `unparseable: ${err.message}`);
|
|
6197
|
+
}
|
|
6198
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
6199
|
+
throw new UnsafeApiUrlError(raw, `unsupported protocol: ${parsed.protocol}`);
|
|
6200
|
+
}
|
|
6201
|
+
const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
|
|
6202
|
+
if (BLOCKED_HOSTS.has(host)) {
|
|
6203
|
+
throw new UnsafeApiUrlError(raw, `blocked metadata host: ${host}`);
|
|
6204
|
+
}
|
|
6205
|
+
if (parsed.username || parsed.password) {
|
|
6206
|
+
throw new UnsafeApiUrlError(raw, "embedded credentials are not supported");
|
|
6207
|
+
}
|
|
6208
|
+
return parsed.origin;
|
|
6209
|
+
}
|
|
6210
|
+
function buildApiUrl(baseOrigin, pathAndQuery) {
|
|
6211
|
+
const path = pathAndQuery.startsWith("/") ? pathAndQuery : `/${pathAndQuery}`;
|
|
6212
|
+
return new URL(path, baseOrigin + "/").toString();
|
|
6213
|
+
}
|
|
6214
|
+
|
|
5987
6215
|
// src/setup/index.ts
|
|
5988
6216
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
5989
|
-
import { existsSync as
|
|
5990
|
-
import { join as
|
|
5991
|
-
import { homedir as
|
|
6217
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, chmodSync as chmodSync2 } from "fs";
|
|
6218
|
+
import { join as join11 } from "path";
|
|
6219
|
+
import { homedir as homedir9 } from "os";
|
|
5992
6220
|
|
|
5993
6221
|
// src/setup/deps.ts
|
|
5994
6222
|
import { execFileSync } from "child_process";
|
|
5995
|
-
import { existsSync as
|
|
5996
|
-
import { join as
|
|
5997
|
-
import { homedir as
|
|
6223
|
+
import { existsSync as existsSync5 } from "fs";
|
|
6224
|
+
import { join as join8 } from "path";
|
|
6225
|
+
import { homedir as homedir6 } from "os";
|
|
5998
6226
|
var DependencyChecker = class {
|
|
5999
6227
|
async checkAll() {
|
|
6000
6228
|
return Promise.all([
|
|
@@ -6044,8 +6272,8 @@ var DependencyChecker = class {
|
|
|
6044
6272
|
}
|
|
6045
6273
|
}
|
|
6046
6274
|
async checkCloudflared() {
|
|
6047
|
-
const binPath =
|
|
6048
|
-
if (
|
|
6275
|
+
const binPath = join8(homedir6(), ".agenticmail", "bin", "cloudflared");
|
|
6276
|
+
if (existsSync5(binPath)) {
|
|
6049
6277
|
let version;
|
|
6050
6278
|
try {
|
|
6051
6279
|
const output = execFileSync(binPath, ["--version"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
@@ -6067,14 +6295,14 @@ var DependencyChecker = class {
|
|
|
6067
6295
|
|
|
6068
6296
|
// src/setup/installer.ts
|
|
6069
6297
|
import { execFileSync as execFileSync2, execSync, spawn as spawnChild } from "child_process";
|
|
6070
|
-
import { existsSync as
|
|
6071
|
-
import { writeFile, rename, chmod as chmod2, mkdir as mkdir2, unlink } from "fs/promises";
|
|
6072
|
-
import { join as
|
|
6073
|
-
import { homedir as
|
|
6298
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, symlinkSync } from "fs";
|
|
6299
|
+
import { writeFile as writeFile2, rename, chmod as chmod2, mkdir as mkdir2, unlink } from "fs/promises";
|
|
6300
|
+
import { join as join9 } from "path";
|
|
6301
|
+
import { homedir as homedir7, platform as platform3, arch as arch2 } from "os";
|
|
6074
6302
|
function runShellWithRollingOutput(cmd, opts = {}) {
|
|
6075
6303
|
const maxLines = opts.maxLines ?? 20;
|
|
6076
6304
|
const timeout = opts.timeout ?? 3e5;
|
|
6077
|
-
return new Promise((
|
|
6305
|
+
return new Promise((resolve2, reject) => {
|
|
6078
6306
|
const child = spawnChild("sh", ["-c", cmd], {
|
|
6079
6307
|
stdio: ["ignore", "pipe", "pipe"],
|
|
6080
6308
|
timeout
|
|
@@ -6116,7 +6344,7 @@ function runShellWithRollingOutput(cmd, opts = {}) {
|
|
|
6116
6344
|
}
|
|
6117
6345
|
process.stdout.write(`\x1B[${displayedCount}A`);
|
|
6118
6346
|
}
|
|
6119
|
-
|
|
6347
|
+
resolve2({ exitCode: code ?? 1, fullOutput });
|
|
6120
6348
|
});
|
|
6121
6349
|
child.on("error", (err) => {
|
|
6122
6350
|
if (displayedCount > 0) {
|
|
@@ -6132,7 +6360,7 @@ function runShellWithRollingOutput(cmd, opts = {}) {
|
|
|
6132
6360
|
}
|
|
6133
6361
|
function runSilent(command, args, opts = {}) {
|
|
6134
6362
|
const timeout = opts.timeout ?? 3e5;
|
|
6135
|
-
return new Promise((
|
|
6363
|
+
return new Promise((resolve2, reject) => {
|
|
6136
6364
|
const child = spawnChild(command, args, {
|
|
6137
6365
|
stdio: ["ignore", "pipe", "pipe"],
|
|
6138
6366
|
timeout
|
|
@@ -6144,7 +6372,7 @@ function runSilent(command, args, opts = {}) {
|
|
|
6144
6372
|
child.stderr?.on("data", (d) => {
|
|
6145
6373
|
fullOutput += d.toString();
|
|
6146
6374
|
});
|
|
6147
|
-
child.on("close", (code) =>
|
|
6375
|
+
child.on("close", (code) => resolve2({ exitCode: code ?? 1, fullOutput }));
|
|
6148
6376
|
child.on("error", reject);
|
|
6149
6377
|
});
|
|
6150
6378
|
}
|
|
@@ -6246,11 +6474,11 @@ var DependencyInstaller = class {
|
|
|
6246
6474
|
try {
|
|
6247
6475
|
const composeBin = execFileSync2("which", ["docker-compose"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
6248
6476
|
if (!composeBin) return;
|
|
6249
|
-
const pluginDir =
|
|
6250
|
-
const pluginPath =
|
|
6251
|
-
if (
|
|
6477
|
+
const pluginDir = join9(homedir7(), ".docker", "cli-plugins");
|
|
6478
|
+
const pluginPath = join9(pluginDir, "docker-compose");
|
|
6479
|
+
if (existsSync6(pluginPath)) return;
|
|
6252
6480
|
try {
|
|
6253
|
-
|
|
6481
|
+
mkdirSync5(pluginDir, { recursive: true });
|
|
6254
6482
|
} catch {
|
|
6255
6483
|
}
|
|
6256
6484
|
try {
|
|
@@ -6313,9 +6541,9 @@ var DependencyInstaller = class {
|
|
|
6313
6541
|
return;
|
|
6314
6542
|
}
|
|
6315
6543
|
this.onProgress("__progress__:5:Installing Docker Engine...");
|
|
6316
|
-
const tmpDir =
|
|
6544
|
+
const tmpDir = join9(homedir7(), ".agenticmail", "tmp");
|
|
6317
6545
|
await mkdir2(tmpDir, { recursive: true });
|
|
6318
|
-
const scriptPath =
|
|
6546
|
+
const scriptPath = join9(tmpDir, "install-docker.sh");
|
|
6319
6547
|
const dlResult = await runShellWithRollingOutput(
|
|
6320
6548
|
`curl -fsSL https://get.docker.com -o "${scriptPath}" && sudo sh "${scriptPath}"`,
|
|
6321
6549
|
{ timeout: 3e5 }
|
|
@@ -6383,8 +6611,8 @@ var DependencyInstaller = class {
|
|
|
6383
6611
|
}
|
|
6384
6612
|
try {
|
|
6385
6613
|
const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
|
|
6386
|
-
const dockerExe =
|
|
6387
|
-
if (
|
|
6614
|
+
const dockerExe = join9(programFiles, "Docker", "Docker", "Docker Desktop.exe");
|
|
6615
|
+
if (existsSync6(dockerExe)) {
|
|
6388
6616
|
execSync(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
|
|
6389
6617
|
}
|
|
6390
6618
|
} catch {
|
|
@@ -6434,8 +6662,8 @@ var DependencyInstaller = class {
|
|
|
6434
6662
|
this.onProgress("__progress__:70:Docker Desktop installed. Starting...");
|
|
6435
6663
|
try {
|
|
6436
6664
|
const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
|
|
6437
|
-
const dockerExe =
|
|
6438
|
-
if (
|
|
6665
|
+
const dockerExe = join9(programFiles, "Docker", "Docker", "Docker Desktop.exe");
|
|
6666
|
+
if (existsSync6(dockerExe)) {
|
|
6439
6667
|
execSync(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
|
|
6440
6668
|
}
|
|
6441
6669
|
} catch {
|
|
@@ -6472,7 +6700,7 @@ var DependencyInstaller = class {
|
|
|
6472
6700
|
* Start the Stalwart mail server Docker container.
|
|
6473
6701
|
*/
|
|
6474
6702
|
async startStalwart(composePath) {
|
|
6475
|
-
if (!
|
|
6703
|
+
if (!existsSync6(composePath)) {
|
|
6476
6704
|
throw new Error(`docker-compose.yml not found at: ${composePath}`);
|
|
6477
6705
|
}
|
|
6478
6706
|
if (!this.isDockerReady()) {
|
|
@@ -6531,16 +6759,16 @@ var DependencyInstaller = class {
|
|
|
6531
6759
|
*/
|
|
6532
6760
|
async installCloudflared() {
|
|
6533
6761
|
const os = platform3();
|
|
6534
|
-
const binDir =
|
|
6762
|
+
const binDir = join9(homedir7(), ".agenticmail", "bin");
|
|
6535
6763
|
const binName = os === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
6536
|
-
const binPath =
|
|
6537
|
-
if (
|
|
6764
|
+
const binPath = join9(binDir, binName);
|
|
6765
|
+
if (existsSync6(binPath)) {
|
|
6538
6766
|
return binPath;
|
|
6539
6767
|
}
|
|
6540
6768
|
try {
|
|
6541
6769
|
const whichCmd = os === "win32" ? "where" : "which";
|
|
6542
6770
|
const sysPath = execFileSync2(whichCmd, ["cloudflared"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim().split("\n")[0];
|
|
6543
|
-
if (sysPath &&
|
|
6771
|
+
if (sysPath && existsSync6(sysPath)) return sysPath;
|
|
6544
6772
|
} catch {
|
|
6545
6773
|
}
|
|
6546
6774
|
this.onProgress("Downloading cloudflared...");
|
|
@@ -6563,8 +6791,8 @@ var DependencyInstaller = class {
|
|
|
6563
6791
|
}
|
|
6564
6792
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
6565
6793
|
if (os === "darwin") {
|
|
6566
|
-
const tgzPath =
|
|
6567
|
-
await
|
|
6794
|
+
const tgzPath = join9(binDir, "cloudflared.tgz");
|
|
6795
|
+
await writeFile2(tgzPath, buffer);
|
|
6568
6796
|
try {
|
|
6569
6797
|
execFileSync2("tar", ["-xzf", tgzPath, "-C", binDir, "cloudflared"], { timeout: 15e3, stdio: "ignore" });
|
|
6570
6798
|
await chmod2(binPath, 493);
|
|
@@ -6576,11 +6804,11 @@ var DependencyInstaller = class {
|
|
|
6576
6804
|
}
|
|
6577
6805
|
} else {
|
|
6578
6806
|
const tmpPath = binPath + ".tmp";
|
|
6579
|
-
await
|
|
6807
|
+
await writeFile2(tmpPath, buffer);
|
|
6580
6808
|
if (os !== "win32") await chmod2(tmpPath, 493);
|
|
6581
6809
|
await rename(tmpPath, binPath);
|
|
6582
6810
|
}
|
|
6583
|
-
if (!
|
|
6811
|
+
if (!existsSync6(binPath)) {
|
|
6584
6812
|
throw new Error("cloudflared download succeeded but binary not found after extraction");
|
|
6585
6813
|
}
|
|
6586
6814
|
this.onProgress("cloudflared installed");
|
|
@@ -6598,9 +6826,9 @@ var DependencyInstaller = class {
|
|
|
6598
6826
|
|
|
6599
6827
|
// src/setup/service.ts
|
|
6600
6828
|
import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
|
|
6601
|
-
import { existsSync as
|
|
6602
|
-
import { join as
|
|
6603
|
-
import { homedir as
|
|
6829
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync5, unlinkSync, mkdirSync as mkdirSync6, chmodSync } from "fs";
|
|
6830
|
+
import { join as join10 } from "path";
|
|
6831
|
+
import { homedir as homedir8, platform as platform4 } from "os";
|
|
6604
6832
|
import { createRequire as createRequire2 } from "module";
|
|
6605
6833
|
var PLIST_LABEL = "com.agenticmail.server";
|
|
6606
6834
|
var SYSTEMD_UNIT = "agenticmail.service";
|
|
@@ -6611,9 +6839,9 @@ var ServiceManager = class {
|
|
|
6611
6839
|
*/
|
|
6612
6840
|
getServicePath() {
|
|
6613
6841
|
if (this.os === "darwin") {
|
|
6614
|
-
return
|
|
6842
|
+
return join10(homedir8(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
6615
6843
|
} else {
|
|
6616
|
-
return
|
|
6844
|
+
return join10(homedir8(), ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
6617
6845
|
}
|
|
6618
6846
|
}
|
|
6619
6847
|
/**
|
|
@@ -6647,40 +6875,40 @@ var ServiceManager = class {
|
|
|
6647
6875
|
try {
|
|
6648
6876
|
const req = createRequire2(import.meta.url);
|
|
6649
6877
|
const resolved = req.resolve("@agenticmail/api");
|
|
6650
|
-
if (
|
|
6878
|
+
if (existsSync7(resolved)) return resolved;
|
|
6651
6879
|
} catch {
|
|
6652
6880
|
}
|
|
6653
6881
|
const parentPackages = [
|
|
6654
|
-
|
|
6882
|
+
join10("@agenticmail", "cli"),
|
|
6655
6883
|
// current scoped package
|
|
6656
6884
|
"agenticmail"
|
|
6657
6885
|
// legacy unscoped package
|
|
6658
6886
|
];
|
|
6659
6887
|
const baseDirs = [
|
|
6660
6888
|
// user-local install
|
|
6661
|
-
|
|
6889
|
+
join10(homedir8(), "node_modules")
|
|
6662
6890
|
];
|
|
6663
6891
|
try {
|
|
6664
6892
|
const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
6665
|
-
baseDirs.push(
|
|
6666
|
-
baseDirs.push(
|
|
6893
|
+
baseDirs.push(join10(prefix, "lib", "node_modules"));
|
|
6894
|
+
baseDirs.push(join10(prefix, "node_modules"));
|
|
6667
6895
|
} catch {
|
|
6668
6896
|
}
|
|
6669
6897
|
baseDirs.push("/opt/homebrew/lib/node_modules");
|
|
6670
6898
|
baseDirs.push("/usr/local/lib/node_modules");
|
|
6671
6899
|
for (const base of baseDirs) {
|
|
6672
|
-
const sibling =
|
|
6673
|
-
if (
|
|
6900
|
+
const sibling = join10(base, "@agenticmail", "api", "dist", "index.js");
|
|
6901
|
+
if (existsSync7(sibling)) return sibling;
|
|
6674
6902
|
for (const parent of parentPackages) {
|
|
6675
|
-
const nested =
|
|
6676
|
-
if (
|
|
6903
|
+
const nested = join10(base, parent, "node_modules", "@agenticmail", "api", "dist", "index.js");
|
|
6904
|
+
if (existsSync7(nested)) return nested;
|
|
6677
6905
|
}
|
|
6678
6906
|
}
|
|
6679
|
-
const dataDir =
|
|
6680
|
-
const entryCache =
|
|
6681
|
-
if (
|
|
6682
|
-
const cached =
|
|
6683
|
-
if (cached &&
|
|
6907
|
+
const dataDir = join10(homedir8(), ".agenticmail");
|
|
6908
|
+
const entryCache = join10(dataDir, "api-entry.path");
|
|
6909
|
+
if (existsSync7(entryCache)) {
|
|
6910
|
+
const cached = readFileSync4(entryCache, "utf-8").trim();
|
|
6911
|
+
if (cached && existsSync7(cached)) return cached;
|
|
6684
6912
|
}
|
|
6685
6913
|
throw new Error("Could not find @agenticmail/api entry point. Run `agenticmail start` first to populate the cache.");
|
|
6686
6914
|
}
|
|
@@ -6688,9 +6916,9 @@ var ServiceManager = class {
|
|
|
6688
6916
|
* Cache the API entry path so the service can find it later.
|
|
6689
6917
|
*/
|
|
6690
6918
|
cacheApiEntryPath(entryPath) {
|
|
6691
|
-
const dataDir =
|
|
6692
|
-
if (!
|
|
6693
|
-
|
|
6919
|
+
const dataDir = join10(homedir8(), ".agenticmail");
|
|
6920
|
+
if (!existsSync7(dataDir)) mkdirSync6(dataDir, { recursive: true });
|
|
6921
|
+
writeFileSync5(join10(dataDir, "api-entry.path"), entryPath);
|
|
6694
6922
|
}
|
|
6695
6923
|
/**
|
|
6696
6924
|
* Get the current package version.
|
|
@@ -6703,36 +6931,36 @@ var ServiceManager = class {
|
|
|
6703
6931
|
try {
|
|
6704
6932
|
const req = createRequire2(import.meta.url);
|
|
6705
6933
|
const pkgJson = req.resolve("@agenticmail/cli/package.json");
|
|
6706
|
-
if (
|
|
6707
|
-
const pkg = JSON.parse(
|
|
6934
|
+
if (existsSync7(pkgJson)) {
|
|
6935
|
+
const pkg = JSON.parse(readFileSync4(pkgJson, "utf-8"));
|
|
6708
6936
|
if (pkg.version) return pkg.version;
|
|
6709
6937
|
}
|
|
6710
6938
|
} catch {
|
|
6711
6939
|
}
|
|
6712
6940
|
try {
|
|
6713
6941
|
const apiEntry = this.getApiEntryPath();
|
|
6714
|
-
const apiPkg =
|
|
6715
|
-
if (
|
|
6716
|
-
const pkg = JSON.parse(
|
|
6942
|
+
const apiPkg = join10(apiEntry, "..", "..", "package.json");
|
|
6943
|
+
if (existsSync7(apiPkg)) {
|
|
6944
|
+
const pkg = JSON.parse(readFileSync4(apiPkg, "utf-8"));
|
|
6717
6945
|
if (pkg.version) return pkg.version;
|
|
6718
6946
|
}
|
|
6719
6947
|
} catch {
|
|
6720
6948
|
}
|
|
6721
6949
|
const candidates = [
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6950
|
+
join10(homedir8(), "node_modules", "@agenticmail", "cli", "package.json"),
|
|
6951
|
+
join10(homedir8(), "node_modules", "agenticmail", "package.json"),
|
|
6952
|
+
join10(homedir8(), ".agenticmail", "package-version.json")
|
|
6725
6953
|
];
|
|
6726
6954
|
try {
|
|
6727
6955
|
const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
6728
|
-
candidates.push(
|
|
6729
|
-
candidates.push(
|
|
6956
|
+
candidates.push(join10(prefix, "lib", "node_modules", "@agenticmail", "cli", "package.json"));
|
|
6957
|
+
candidates.push(join10(prefix, "lib", "node_modules", "agenticmail", "package.json"));
|
|
6730
6958
|
} catch {
|
|
6731
6959
|
}
|
|
6732
6960
|
for (const p of candidates) {
|
|
6733
6961
|
try {
|
|
6734
|
-
if (
|
|
6735
|
-
const pkg = JSON.parse(
|
|
6962
|
+
if (existsSync7(p)) {
|
|
6963
|
+
const pkg = JSON.parse(readFileSync4(p, "utf-8"));
|
|
6736
6964
|
if (pkg.version) return pkg.version;
|
|
6737
6965
|
}
|
|
6738
6966
|
} catch {
|
|
@@ -6745,9 +6973,9 @@ var ServiceManager = class {
|
|
|
6745
6973
|
* This ensures AgenticMail doesn't fail on boot when Docker is still loading.
|
|
6746
6974
|
*/
|
|
6747
6975
|
generateStartScript(nodePath, apiEntry) {
|
|
6748
|
-
const scriptPath =
|
|
6749
|
-
const scriptDir =
|
|
6750
|
-
if (!
|
|
6976
|
+
const scriptPath = join10(homedir8(), ".agenticmail", "bin", "start-server.sh");
|
|
6977
|
+
const scriptDir = join10(homedir8(), ".agenticmail", "bin");
|
|
6978
|
+
if (!existsSync7(scriptDir)) mkdirSync6(scriptDir, { recursive: true });
|
|
6751
6979
|
const script = [
|
|
6752
6980
|
"#!/bin/bash",
|
|
6753
6981
|
"# AgenticMail auto-start script",
|
|
@@ -6798,7 +7026,7 @@ var ServiceManager = class {
|
|
|
6798
7026
|
`log "Starting API server: ${nodePath} ${apiEntry}"`,
|
|
6799
7027
|
`exec "${nodePath}" "${apiEntry}"`
|
|
6800
7028
|
].join("\n") + "\n";
|
|
6801
|
-
|
|
7029
|
+
writeFileSync5(scriptPath, script, { mode: 493 });
|
|
6802
7030
|
return scriptPath;
|
|
6803
7031
|
}
|
|
6804
7032
|
/**
|
|
@@ -6811,9 +7039,9 @@ var ServiceManager = class {
|
|
|
6811
7039
|
* - Service version tracking in env vars
|
|
6812
7040
|
*/
|
|
6813
7041
|
generatePlist(nodePath, apiEntry, configPath) {
|
|
6814
|
-
const config = JSON.parse(
|
|
6815
|
-
const logDir =
|
|
6816
|
-
if (!
|
|
7042
|
+
const config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
7043
|
+
const logDir = join10(homedir8(), ".agenticmail", "logs");
|
|
7044
|
+
if (!existsSync7(logDir)) mkdirSync6(logDir, { recursive: true });
|
|
6817
7045
|
const version = this.getVersion();
|
|
6818
7046
|
const startScript = this.generateStartScript(nodePath, apiEntry);
|
|
6819
7047
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -6834,9 +7062,9 @@ var ServiceManager = class {
|
|
|
6834
7062
|
<key>EnvironmentVariables</key>
|
|
6835
7063
|
<dict>
|
|
6836
7064
|
<key>HOME</key>
|
|
6837
|
-
<string>${
|
|
7065
|
+
<string>${homedir8()}</string>
|
|
6838
7066
|
<key>AGENTICMAIL_DATA_DIR</key>
|
|
6839
|
-
<string>${config.dataDir ||
|
|
7067
|
+
<string>${config.dataDir || join10(homedir8(), ".agenticmail")}</string>
|
|
6840
7068
|
<key>PATH</key>
|
|
6841
7069
|
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
6842
7070
|
<key>AGENTICMAIL_SERVICE_VERSION</key>
|
|
@@ -6889,8 +7117,8 @@ var ServiceManager = class {
|
|
|
6889
7117
|
* - Proper dependency ordering
|
|
6890
7118
|
*/
|
|
6891
7119
|
generateSystemdUnit(nodePath, apiEntry, configPath) {
|
|
6892
|
-
const config = JSON.parse(
|
|
6893
|
-
const dataDir = config.dataDir ||
|
|
7120
|
+
const config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
7121
|
+
const dataDir = config.dataDir || join10(homedir8(), ".agenticmail");
|
|
6894
7122
|
const version = this.getVersion();
|
|
6895
7123
|
const startScript = this.generateStartScript(nodePath, apiEntry);
|
|
6896
7124
|
return `[Unit]
|
|
@@ -6907,7 +7135,7 @@ Restart=always
|
|
|
6907
7135
|
RestartSec=15
|
|
6908
7136
|
TimeoutStartSec=660
|
|
6909
7137
|
LimitNOFILE=8192
|
|
6910
|
-
Environment=HOME=${
|
|
7138
|
+
Environment=HOME=${homedir8()}
|
|
6911
7139
|
Environment=AGENTICMAIL_DATA_DIR=${dataDir}
|
|
6912
7140
|
Environment=PATH=/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin
|
|
6913
7141
|
Environment=AGENTICMAIL_SERVICE_VERSION=${version}
|
|
@@ -6920,8 +7148,8 @@ WantedBy=default.target
|
|
|
6920
7148
|
* Install the auto-start service.
|
|
6921
7149
|
*/
|
|
6922
7150
|
install() {
|
|
6923
|
-
const configPath =
|
|
6924
|
-
if (!
|
|
7151
|
+
const configPath = join10(homedir8(), ".agenticmail", "config.json");
|
|
7152
|
+
if (!existsSync7(configPath)) {
|
|
6925
7153
|
return { installed: false, message: "Config not found. Run agenticmail setup first." };
|
|
6926
7154
|
}
|
|
6927
7155
|
const nodePath = this.getNodePath();
|
|
@@ -6933,16 +7161,16 @@ WantedBy=default.target
|
|
|
6933
7161
|
}
|
|
6934
7162
|
const servicePath = this.getServicePath();
|
|
6935
7163
|
if (this.os === "darwin") {
|
|
6936
|
-
const dir =
|
|
6937
|
-
if (!
|
|
6938
|
-
if (
|
|
7164
|
+
const dir = join10(homedir8(), "Library", "LaunchAgents");
|
|
7165
|
+
if (!existsSync7(dir)) mkdirSync6(dir, { recursive: true });
|
|
7166
|
+
if (existsSync7(servicePath)) {
|
|
6939
7167
|
try {
|
|
6940
7168
|
execFileSync3("launchctl", ["unload", servicePath], { timeout: 1e4, stdio: "ignore" });
|
|
6941
7169
|
} catch {
|
|
6942
7170
|
}
|
|
6943
7171
|
}
|
|
6944
7172
|
const plist = this.generatePlist(nodePath, apiEntry, configPath);
|
|
6945
|
-
|
|
7173
|
+
writeFileSync5(servicePath, plist);
|
|
6946
7174
|
chmodSync(servicePath, 384);
|
|
6947
7175
|
try {
|
|
6948
7176
|
execFileSync3("launchctl", ["load", servicePath], { timeout: 1e4, stdio: "ignore" });
|
|
@@ -6951,10 +7179,10 @@ WantedBy=default.target
|
|
|
6951
7179
|
}
|
|
6952
7180
|
return { installed: true, message: `Service installed at ${servicePath}` };
|
|
6953
7181
|
} else if (this.os === "linux") {
|
|
6954
|
-
const dir =
|
|
6955
|
-
if (!
|
|
7182
|
+
const dir = join10(homedir8(), ".config", "systemd", "user");
|
|
7183
|
+
if (!existsSync7(dir)) mkdirSync6(dir, { recursive: true });
|
|
6956
7184
|
const unit = this.generateSystemdUnit(nodePath, apiEntry, configPath);
|
|
6957
|
-
|
|
7185
|
+
writeFileSync5(servicePath, unit);
|
|
6958
7186
|
chmodSync(servicePath, 384);
|
|
6959
7187
|
try {
|
|
6960
7188
|
execFileSync3("systemctl", ["--user", "daemon-reload"], { timeout: 1e4, stdio: "ignore" });
|
|
@@ -6977,7 +7205,7 @@ WantedBy=default.target
|
|
|
6977
7205
|
*/
|
|
6978
7206
|
uninstall() {
|
|
6979
7207
|
const servicePath = this.getServicePath();
|
|
6980
|
-
if (!
|
|
7208
|
+
if (!existsSync7(servicePath)) {
|
|
6981
7209
|
return { removed: false, message: "Service is not installed." };
|
|
6982
7210
|
}
|
|
6983
7211
|
if (this.os === "darwin") {
|
|
@@ -7015,7 +7243,7 @@ WantedBy=default.target
|
|
|
7015
7243
|
status() {
|
|
7016
7244
|
const servicePath = this.getServicePath();
|
|
7017
7245
|
const plat = this.os === "darwin" ? "launchd" : this.os === "linux" ? "systemd" : "unsupported";
|
|
7018
|
-
const installed =
|
|
7246
|
+
const installed = existsSync7(servicePath);
|
|
7019
7247
|
let running = false;
|
|
7020
7248
|
if (installed) {
|
|
7021
7249
|
if (this.os === "darwin") {
|
|
@@ -7070,34 +7298,34 @@ WantedBy=default.target
|
|
|
7070
7298
|
needsRepair() {
|
|
7071
7299
|
if (this.os !== "darwin" && this.os !== "linux") return null;
|
|
7072
7300
|
const servicePath = this.getServicePath();
|
|
7073
|
-
if (!
|
|
7301
|
+
if (!existsSync7(servicePath)) return null;
|
|
7074
7302
|
let serviceContent = "";
|
|
7075
7303
|
try {
|
|
7076
|
-
serviceContent =
|
|
7304
|
+
serviceContent = readFileSync4(servicePath, "utf-8");
|
|
7077
7305
|
} catch {
|
|
7078
7306
|
return { reason: "Service file unreadable" };
|
|
7079
7307
|
}
|
|
7080
|
-
const startScript =
|
|
7308
|
+
const startScript = join10(homedir8(), ".agenticmail", "bin", "start-server.sh");
|
|
7081
7309
|
if (serviceContent.includes(startScript)) {
|
|
7082
|
-
if (!
|
|
7310
|
+
if (!existsSync7(startScript)) {
|
|
7083
7311
|
return { reason: "start-server.sh is missing" };
|
|
7084
7312
|
}
|
|
7085
7313
|
let scriptContent = "";
|
|
7086
7314
|
try {
|
|
7087
|
-
scriptContent =
|
|
7315
|
+
scriptContent = readFileSync4(startScript, "utf-8");
|
|
7088
7316
|
} catch {
|
|
7089
7317
|
return { reason: "start-server.sh unreadable" };
|
|
7090
7318
|
}
|
|
7091
7319
|
const apiPathMatch = scriptContent.match(/(\/[^"\s]+@agenticmail\/api\/dist\/index\.js)/);
|
|
7092
7320
|
if (apiPathMatch) {
|
|
7093
7321
|
const referenced = apiPathMatch[1];
|
|
7094
|
-
if (!
|
|
7322
|
+
if (!existsSync7(referenced)) {
|
|
7095
7323
|
return { reason: `start-server.sh references missing path: ${referenced}` };
|
|
7096
7324
|
}
|
|
7097
7325
|
}
|
|
7098
7326
|
if (/node_modules\/agenticmail\/(?!.*@agenticmail\/cli)/.test(scriptContent)) {
|
|
7099
7327
|
const stale = /(\S*node_modules\/agenticmail\/\S*)/.exec(scriptContent)?.[1];
|
|
7100
|
-
if (stale && !
|
|
7328
|
+
if (stale && !existsSync7(stale)) {
|
|
7101
7329
|
return { reason: `start-server.sh references legacy unscoped path: ${stale}` };
|
|
7102
7330
|
}
|
|
7103
7331
|
}
|
|
@@ -7110,11 +7338,11 @@ WantedBy=default.target
|
|
|
7110
7338
|
return { reason: `Service version drift (current CLI is v${currentVersion})` };
|
|
7111
7339
|
}
|
|
7112
7340
|
}
|
|
7113
|
-
const entryCache =
|
|
7114
|
-
if (
|
|
7341
|
+
const entryCache = join10(homedir8(), ".agenticmail", "api-entry.path");
|
|
7342
|
+
if (existsSync7(entryCache)) {
|
|
7115
7343
|
try {
|
|
7116
|
-
const cached =
|
|
7117
|
-
if (cached && !
|
|
7344
|
+
const cached = readFileSync4(entryCache, "utf-8").trim();
|
|
7345
|
+
if (cached && !existsSync7(cached)) {
|
|
7118
7346
|
return { reason: `Cached API entry path no longer exists: ${cached}` };
|
|
7119
7347
|
}
|
|
7120
7348
|
} catch {
|
|
@@ -7165,13 +7393,13 @@ var SetupManager = class {
|
|
|
7165
7393
|
* falls back to monorepo location.
|
|
7166
7394
|
*/
|
|
7167
7395
|
getComposePath() {
|
|
7168
|
-
const standalonePath =
|
|
7169
|
-
if (
|
|
7396
|
+
const standalonePath = join11(homedir9(), ".agenticmail", "docker-compose.yml");
|
|
7397
|
+
if (existsSync8(standalonePath)) return standalonePath;
|
|
7170
7398
|
const cwd = process.cwd();
|
|
7171
|
-
const candidates = [cwd,
|
|
7399
|
+
const candidates = [cwd, join11(cwd, "..")];
|
|
7172
7400
|
for (const dir of candidates) {
|
|
7173
|
-
const p =
|
|
7174
|
-
if (
|
|
7401
|
+
const p = join11(dir, "docker-compose.yml");
|
|
7402
|
+
if (existsSync8(p)) return p;
|
|
7175
7403
|
}
|
|
7176
7404
|
return standalonePath;
|
|
7177
7405
|
}
|
|
@@ -7181,19 +7409,19 @@ var SetupManager = class {
|
|
|
7181
7409
|
* Always regenerates Docker files to keep passwords in sync.
|
|
7182
7410
|
*/
|
|
7183
7411
|
initConfig() {
|
|
7184
|
-
const dataDir =
|
|
7185
|
-
const configPath =
|
|
7186
|
-
const envPath =
|
|
7187
|
-
if (
|
|
7412
|
+
const dataDir = join11(homedir9(), ".agenticmail");
|
|
7413
|
+
const configPath = join11(dataDir, "config.json");
|
|
7414
|
+
const envPath = join11(dataDir, ".env");
|
|
7415
|
+
if (existsSync8(configPath)) {
|
|
7188
7416
|
try {
|
|
7189
|
-
const existing = JSON.parse(
|
|
7417
|
+
const existing = JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
7190
7418
|
this.generateDockerFiles(existing);
|
|
7191
7419
|
return { configPath, envPath, config: existing, isNew: false };
|
|
7192
7420
|
} catch {
|
|
7193
7421
|
}
|
|
7194
7422
|
}
|
|
7195
|
-
if (!
|
|
7196
|
-
|
|
7423
|
+
if (!existsSync8(dataDir)) {
|
|
7424
|
+
mkdirSync7(dataDir, { recursive: true });
|
|
7197
7425
|
}
|
|
7198
7426
|
const masterKey = `mk_${randomBytes3(24).toString("hex")}`;
|
|
7199
7427
|
const stalwartPassword = randomBytes3(16).toString("hex");
|
|
@@ -7209,7 +7437,7 @@ var SetupManager = class {
|
|
|
7209
7437
|
api: { port: 3829, host: "127.0.0.1" },
|
|
7210
7438
|
dataDir
|
|
7211
7439
|
};
|
|
7212
|
-
|
|
7440
|
+
writeFileSync6(configPath, JSON.stringify(config, null, 2));
|
|
7213
7441
|
chmodSync2(configPath, 384);
|
|
7214
7442
|
const envContent = `# Auto-generated by agenticmail setup
|
|
7215
7443
|
STALWART_ADMIN_USER=admin
|
|
@@ -7225,7 +7453,7 @@ SMTP_PORT=587
|
|
|
7225
7453
|
IMAP_HOST=localhost
|
|
7226
7454
|
IMAP_PORT=143
|
|
7227
7455
|
`;
|
|
7228
|
-
|
|
7456
|
+
writeFileSync6(envPath, envContent);
|
|
7229
7457
|
chmodSync2(envPath, 384);
|
|
7230
7458
|
this.generateDockerFiles(config);
|
|
7231
7459
|
return { configPath, envPath, config, isNew: true };
|
|
@@ -7235,13 +7463,13 @@ IMAP_PORT=143
|
|
|
7235
7463
|
* with the correct admin password from config.
|
|
7236
7464
|
*/
|
|
7237
7465
|
generateDockerFiles(config) {
|
|
7238
|
-
const dataDir = config.dataDir ||
|
|
7239
|
-
if (!
|
|
7240
|
-
|
|
7466
|
+
const dataDir = config.dataDir || join11(homedir9(), ".agenticmail");
|
|
7467
|
+
if (!existsSync8(dataDir)) {
|
|
7468
|
+
mkdirSync7(dataDir, { recursive: true });
|
|
7241
7469
|
}
|
|
7242
7470
|
const password = config.stalwart?.adminPassword || "changeme";
|
|
7243
|
-
const composePath =
|
|
7244
|
-
|
|
7471
|
+
const composePath = join11(dataDir, "docker-compose.yml");
|
|
7472
|
+
writeFileSync6(composePath, `services:
|
|
7245
7473
|
stalwart:
|
|
7246
7474
|
# Pinned to v0.15.5 \u2014 Stalwart 0.16+ moved its config to JSON
|
|
7247
7475
|
# at /etc/stalwart/config.json (hardcoded into the container
|
|
@@ -7271,8 +7499,8 @@ volumes:
|
|
|
7271
7499
|
stalwart-data:
|
|
7272
7500
|
`);
|
|
7273
7501
|
chmodSync2(composePath, 384);
|
|
7274
|
-
const tomlPath =
|
|
7275
|
-
|
|
7502
|
+
const tomlPath = join11(dataDir, "stalwart.toml");
|
|
7503
|
+
writeFileSync6(tomlPath, `# Stalwart Mail Server \u2014 AgenticMail Configuration
|
|
7276
7504
|
|
|
7277
7505
|
[server]
|
|
7278
7506
|
hostname = "localhost"
|
|
@@ -7328,15 +7556,15 @@ secret = "${password}"
|
|
|
7328
7556
|
* Check if config has already been initialized.
|
|
7329
7557
|
*/
|
|
7330
7558
|
isInitialized() {
|
|
7331
|
-
const configPath =
|
|
7332
|
-
return
|
|
7559
|
+
const configPath = join11(homedir9(), ".agenticmail", "config.json");
|
|
7560
|
+
return existsSync8(configPath);
|
|
7333
7561
|
}
|
|
7334
7562
|
};
|
|
7335
7563
|
|
|
7336
7564
|
// src/threading/thread-id.ts
|
|
7337
7565
|
import { createHash as createHash2 } from "crypto";
|
|
7338
7566
|
function stripReplyPrefixes(subject) {
|
|
7339
|
-
let s = subject;
|
|
7567
|
+
let s = subject.length > 1e3 ? subject.slice(0, 1e3) : subject;
|
|
7340
7568
|
for (; ; ) {
|
|
7341
7569
|
const next = s.replace(/^\s*(?:re|fwd?|fw)\s*(?:\[\d+\])?\s*:\s*/i, "");
|
|
7342
7570
|
if (next === s) break;
|
|
@@ -7356,8 +7584,9 @@ function normalizeSubject(subject) {
|
|
|
7356
7584
|
}
|
|
7357
7585
|
function normalizeAddress(addr) {
|
|
7358
7586
|
if (!addr) return "(unknown)";
|
|
7359
|
-
const
|
|
7360
|
-
const
|
|
7587
|
+
const bounded = addr.length > 500 ? addr.slice(0, 500) : addr;
|
|
7588
|
+
const m = bounded.match(/<([^>]+)>/);
|
|
7589
|
+
const raw = m ? m[1] : bounded;
|
|
7361
7590
|
return raw.trim().toLowerCase();
|
|
7362
7591
|
}
|
|
7363
7592
|
function threadIdFor(input) {
|
|
@@ -7367,18 +7596,18 @@ function threadIdFor(input) {
|
|
|
7367
7596
|
|
|
7368
7597
|
// src/threading/thread-cache.ts
|
|
7369
7598
|
import {
|
|
7370
|
-
existsSync as
|
|
7371
|
-
mkdirSync as
|
|
7372
|
-
readFileSync as
|
|
7373
|
-
writeFileSync as
|
|
7599
|
+
existsSync as existsSync9,
|
|
7600
|
+
mkdirSync as mkdirSync8,
|
|
7601
|
+
readFileSync as readFileSync6,
|
|
7602
|
+
writeFileSync as writeFileSync7,
|
|
7374
7603
|
readdirSync,
|
|
7375
7604
|
statSync,
|
|
7376
7605
|
rmSync,
|
|
7377
|
-
renameSync
|
|
7606
|
+
renameSync as renameSync2
|
|
7378
7607
|
} from "fs";
|
|
7379
|
-
import { homedir as
|
|
7380
|
-
import { join as
|
|
7381
|
-
var CACHE_DIR_DEFAULT =
|
|
7608
|
+
import { homedir as homedir10 } from "os";
|
|
7609
|
+
import { join as join12 } from "path";
|
|
7610
|
+
var CACHE_DIR_DEFAULT = join12(homedir10(), ".agenticmail", "thread-cache");
|
|
7382
7611
|
var DEFAULT_K_MESSAGES = 10;
|
|
7383
7612
|
var DEFAULT_LRU_CAP = 5e3;
|
|
7384
7613
|
var PREVIEW_MAX_CHARS = 240;
|
|
@@ -7391,18 +7620,18 @@ var ThreadCache = class {
|
|
|
7391
7620
|
this.k = opts.k ?? DEFAULT_K_MESSAGES;
|
|
7392
7621
|
this.lruCap = opts.lruCap ?? DEFAULT_LRU_CAP;
|
|
7393
7622
|
try {
|
|
7394
|
-
|
|
7623
|
+
mkdirSync8(this.dir, { recursive: true });
|
|
7395
7624
|
} catch {
|
|
7396
7625
|
}
|
|
7397
7626
|
}
|
|
7398
7627
|
pathFor(threadId) {
|
|
7399
|
-
return
|
|
7628
|
+
return join12(this.dir, `${threadId}.json`);
|
|
7400
7629
|
}
|
|
7401
7630
|
read(threadId) {
|
|
7402
7631
|
const p = this.pathFor(threadId);
|
|
7403
|
-
if (!
|
|
7632
|
+
if (!existsSync9(p)) return null;
|
|
7404
7633
|
try {
|
|
7405
|
-
const raw =
|
|
7634
|
+
const raw = readFileSync6(p, "utf-8");
|
|
7406
7635
|
return JSON.parse(raw);
|
|
7407
7636
|
} catch {
|
|
7408
7637
|
try {
|
|
@@ -7467,8 +7696,8 @@ var ThreadCache = class {
|
|
|
7467
7696
|
writeAtomic(threadId, entry) {
|
|
7468
7697
|
const p = this.pathFor(threadId);
|
|
7469
7698
|
const tmp = `${p}.tmp`;
|
|
7470
|
-
|
|
7471
|
-
|
|
7699
|
+
writeFileSync7(tmp, JSON.stringify(entry), "utf-8");
|
|
7700
|
+
renameSync2(tmp, p);
|
|
7472
7701
|
}
|
|
7473
7702
|
/**
|
|
7474
7703
|
* Best-effort LRU eviction. Runs at most every 256 writes (we
|
|
@@ -7486,7 +7715,7 @@ var ThreadCache = class {
|
|
|
7486
7715
|
}
|
|
7487
7716
|
if (files.length <= this.lruCap) return;
|
|
7488
7717
|
const stats = files.map((f) => {
|
|
7489
|
-
const p =
|
|
7718
|
+
const p = join12(this.dir, f);
|
|
7490
7719
|
try {
|
|
7491
7720
|
return { p, mtime: statSync(p).mtimeMs };
|
|
7492
7721
|
} catch {
|
|
@@ -7517,36 +7746,36 @@ function dedupAndCap(messages, k) {
|
|
|
7517
7746
|
|
|
7518
7747
|
// src/threading/agent-memory.ts
|
|
7519
7748
|
import {
|
|
7520
|
-
existsSync as
|
|
7521
|
-
mkdirSync as
|
|
7522
|
-
readFileSync as
|
|
7523
|
-
writeFileSync as
|
|
7749
|
+
existsSync as existsSync10,
|
|
7750
|
+
mkdirSync as mkdirSync9,
|
|
7751
|
+
readFileSync as readFileSync7,
|
|
7752
|
+
writeFileSync as writeFileSync8,
|
|
7524
7753
|
rmSync as rmSync2,
|
|
7525
|
-
renameSync as
|
|
7754
|
+
renameSync as renameSync3
|
|
7526
7755
|
} from "fs";
|
|
7527
|
-
import { homedir as
|
|
7528
|
-
import { join as
|
|
7529
|
-
var MEMORY_DIR_DEFAULT =
|
|
7756
|
+
import { homedir as homedir11 } from "os";
|
|
7757
|
+
import { join as join13 } from "path";
|
|
7758
|
+
var MEMORY_DIR_DEFAULT = join13(homedir11(), ".agenticmail", "agent-memory");
|
|
7530
7759
|
var AgentMemoryStore = class {
|
|
7531
7760
|
dir;
|
|
7532
7761
|
constructor(opts = {}) {
|
|
7533
7762
|
this.dir = opts.memoryDir ?? MEMORY_DIR_DEFAULT;
|
|
7534
7763
|
try {
|
|
7535
|
-
|
|
7764
|
+
mkdirSync9(this.dir, { recursive: true });
|
|
7536
7765
|
} catch {
|
|
7537
7766
|
}
|
|
7538
7767
|
}
|
|
7539
7768
|
dirFor(agentId) {
|
|
7540
|
-
return
|
|
7769
|
+
return join13(this.dir, sanitizeId(agentId));
|
|
7541
7770
|
}
|
|
7542
7771
|
pathFor(agentId, threadId) {
|
|
7543
|
-
return
|
|
7772
|
+
return join13(this.dirFor(agentId), `${sanitizeId(threadId)}.md`);
|
|
7544
7773
|
}
|
|
7545
7774
|
read(agentId, threadId) {
|
|
7546
7775
|
const p = this.pathFor(agentId, threadId);
|
|
7547
|
-
if (!
|
|
7776
|
+
if (!existsSync10(p)) return null;
|
|
7548
7777
|
try {
|
|
7549
|
-
const raw =
|
|
7778
|
+
const raw = readFileSync7(p, "utf-8");
|
|
7550
7779
|
const parsed = parse(raw);
|
|
7551
7780
|
return { ...parsed, raw };
|
|
7552
7781
|
} catch {
|
|
@@ -7556,14 +7785,14 @@ var AgentMemoryStore = class {
|
|
|
7556
7785
|
write(agentId, threadId, fields) {
|
|
7557
7786
|
const agentDir = this.dirFor(agentId);
|
|
7558
7787
|
try {
|
|
7559
|
-
|
|
7788
|
+
mkdirSync9(agentDir, { recursive: true });
|
|
7560
7789
|
} catch {
|
|
7561
7790
|
}
|
|
7562
7791
|
const body = render({ ...fields, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
7563
7792
|
const p = this.pathFor(agentId, threadId);
|
|
7564
7793
|
const tmp = `${p}.tmp`;
|
|
7565
|
-
|
|
7566
|
-
|
|
7794
|
+
writeFileSync8(tmp, body, "utf-8");
|
|
7795
|
+
renameSync3(tmp, p);
|
|
7567
7796
|
}
|
|
7568
7797
|
delete(agentId, threadId) {
|
|
7569
7798
|
try {
|
|
@@ -7631,6 +7860,7 @@ export {
|
|
|
7631
7860
|
CloudflareClient,
|
|
7632
7861
|
DEFAULT_AGENT_NAME,
|
|
7633
7862
|
DEFAULT_AGENT_ROLE,
|
|
7863
|
+
DEFAULT_SESSION_MAX_AGE_MS,
|
|
7634
7864
|
DNSConfigurator,
|
|
7635
7865
|
DependencyChecker,
|
|
7636
7866
|
DependencyInstaller,
|
|
@@ -7641,6 +7871,8 @@ export {
|
|
|
7641
7871
|
InboxWatcher,
|
|
7642
7872
|
MailReceiver,
|
|
7643
7873
|
MailSender,
|
|
7874
|
+
PathTraversalError,
|
|
7875
|
+
REDACTED,
|
|
7644
7876
|
RELAY_PRESETS,
|
|
7645
7877
|
RelayBridge,
|
|
7646
7878
|
RelayGateway,
|
|
@@ -7652,7 +7884,10 @@ export {
|
|
|
7652
7884
|
StalwartAdmin,
|
|
7653
7885
|
ThreadCache,
|
|
7654
7886
|
TunnelManager,
|
|
7887
|
+
UnsafeApiUrlError,
|
|
7655
7888
|
WARNING_THRESHOLD,
|
|
7889
|
+
assertWithinBase,
|
|
7890
|
+
buildApiUrl,
|
|
7656
7891
|
buildInboundSecurityAdvisory,
|
|
7657
7892
|
classifyEmailRoute,
|
|
7658
7893
|
closeDatabase,
|
|
@@ -7662,21 +7897,31 @@ export {
|
|
|
7662
7897
|
ensureDataDir,
|
|
7663
7898
|
extractVerificationCode,
|
|
7664
7899
|
flushTelemetry,
|
|
7900
|
+
forgetHostSession,
|
|
7665
7901
|
getDatabase,
|
|
7902
|
+
hostSessionStoragePath,
|
|
7666
7903
|
isInternalEmail,
|
|
7904
|
+
isSessionFresh,
|
|
7667
7905
|
isValidPhoneNumber,
|
|
7906
|
+
loadHostSession,
|
|
7668
7907
|
normalizeAddress,
|
|
7669
7908
|
normalizePhoneNumber,
|
|
7670
7909
|
normalizeSubject,
|
|
7671
7910
|
parseEmail,
|
|
7672
7911
|
parseGoogleVoiceSms,
|
|
7673
7912
|
recordToolCall,
|
|
7913
|
+
redactObject,
|
|
7914
|
+
redactSecret,
|
|
7674
7915
|
resolveConfig,
|
|
7916
|
+
safeJoin,
|
|
7675
7917
|
sanitizeEmail,
|
|
7676
7918
|
saveConfig,
|
|
7919
|
+
saveHostSession,
|
|
7677
7920
|
scanOutboundEmail,
|
|
7678
7921
|
scoreEmail,
|
|
7679
7922
|
setTelemetryVersion,
|
|
7680
7923
|
startRelayBridge,
|
|
7681
|
-
threadIdFor
|
|
7924
|
+
threadIdFor,
|
|
7925
|
+
tryJoin,
|
|
7926
|
+
validateApiUrl
|
|
7682
7927
|
};
|