@agenticmail/core 0.7.6 → 0.9.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 +10 -1
- package/dist/index.cjs +309 -25
- package/dist/index.d.cts +251 -1
- package/dist/index.d.ts +251 -1
- package/dist/index.js +319 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,16 @@ This is the foundation layer that everything else builds on. If the API server,
|
|
|
10
10
|
|
|
11
11
|
Every other AgenticMail package depends on this one.
|
|
12
12
|
|
|
13
|
-
## ✨ What's new in 0.
|
|
13
|
+
## ✨ What's new in 0.9.0
|
|
14
|
+
|
|
15
|
+
- **🧠 New `threading/` module.** Three exported primitives for the dispatcher's wake-context layer:
|
|
16
|
+
- **`threadIdFor({subject})`** — stable, subject-only thread id (replies collapse into the same thread regardless of sender). SHA-256 base64url truncated to 16 chars. Strips chained `Re:`/`Fwd:` prefixes + `[FINAL]/[DONE]/[CLOSED]/[WRAP]` markers + collapses whitespace + lower-cases.
|
|
17
|
+
- **`ThreadCache`** — per-thread ring buffer of the last K message envelopes + previews on disk at `~/.agenticmail/thread-cache/<t>.json`. Configurable K, LRU-bounded directory eviction (default cap 5000 threads ≈ 25 MB). Atomic writes via rename.
|
|
18
|
+
- **`AgentMemoryStore`** — per-`(agent, thread)` markdown files at `~/.agenticmail/agent-memory/<agentId>/<t>.md`. Agent writes structured fields (summary, commitments, openQuestions, lastAction, lastUid) at end-of-wake; on the next wake the dispatcher reads the raw markdown back into the prompt. Per-agent isolation — one agent's memory is invisible to another sharing the same thread.
|
|
19
|
+
|
|
20
|
+
23 new unit tests covering normalization, hash stability, push/dedup/cap/delete, write/read round-trip, and per-agent isolation.
|
|
21
|
+
|
|
22
|
+
## ✨ Earlier — 0.7.4
|
|
14
23
|
|
|
15
24
|
- **⭐ `MailReceiver.setStarred(uid, starred, mailbox?)`** — toggles IMAP's `\Flagged` flag via `messageFlagsAdd` / `messageFlagsRemove`. Same on-disk bit Gmail's star uses. Underpins the new `POST /mail/messages/:uid/star` route in `@agenticmail/api`.
|
|
16
25
|
- **📐 Task `output_schema` column** — migration `014_task_output_schema.sql` adds an optional `output_schema TEXT` column to `agent_tasks`. Stores the assigner-supplied JSON Schema for typed task contracts. `NULL` means "no schema, accept anything", fully back-compat with the v0.8.x task model.
|
package/dist/index.cjs
CHANGED
|
@@ -711,6 +711,7 @@ __export(index_exports, {
|
|
|
711
711
|
AGENT_ROLES: () => AGENT_ROLES,
|
|
712
712
|
AccountManager: () => AccountManager,
|
|
713
713
|
AgentDeletionService: () => AgentDeletionService,
|
|
714
|
+
AgentMemoryStore: () => AgentMemoryStore,
|
|
714
715
|
AgenticMailClient: () => AgenticMailClient,
|
|
715
716
|
CloudflareClient: () => CloudflareClient,
|
|
716
717
|
DEFAULT_AGENT_NAME: () => DEFAULT_AGENT_NAME,
|
|
@@ -734,6 +735,7 @@ __export(index_exports, {
|
|
|
734
735
|
SmsManager: () => SmsManager,
|
|
735
736
|
SmsPoller: () => SmsPoller,
|
|
736
737
|
StalwartAdmin: () => StalwartAdmin,
|
|
738
|
+
ThreadCache: () => ThreadCache,
|
|
737
739
|
TunnelManager: () => TunnelManager,
|
|
738
740
|
WARNING_THRESHOLD: () => WARNING_THRESHOLD,
|
|
739
741
|
buildInboundSecurityAdvisory: () => buildInboundSecurityAdvisory,
|
|
@@ -748,7 +750,9 @@ __export(index_exports, {
|
|
|
748
750
|
getDatabase: () => getDatabase,
|
|
749
751
|
isInternalEmail: () => isInternalEmail,
|
|
750
752
|
isValidPhoneNumber: () => isValidPhoneNumber,
|
|
753
|
+
normalizeAddress: () => normalizeAddress,
|
|
751
754
|
normalizePhoneNumber: () => normalizePhoneNumber,
|
|
755
|
+
normalizeSubject: () => normalizeSubject,
|
|
752
756
|
parseEmail: () => parseEmail,
|
|
753
757
|
parseGoogleVoiceSms: () => parseGoogleVoiceSms,
|
|
754
758
|
recordToolCall: () => recordToolCall,
|
|
@@ -758,7 +762,8 @@ __export(index_exports, {
|
|
|
758
762
|
scanOutboundEmail: () => scanOutboundEmail,
|
|
759
763
|
scoreEmail: () => scoreEmail,
|
|
760
764
|
setTelemetryVersion: () => setTelemetryVersion,
|
|
761
|
-
startRelayBridge: () => startRelayBridge
|
|
765
|
+
startRelayBridge: () => startRelayBridge,
|
|
766
|
+
threadIdFor: () => threadIdFor
|
|
762
767
|
});
|
|
763
768
|
module.exports = __toCommonJS(index_exports);
|
|
764
769
|
|
|
@@ -1841,14 +1846,14 @@ var StalwartAdmin = class {
|
|
|
1841
1846
|
if (!isValidDomain(domain)) {
|
|
1842
1847
|
throw new Error(`Invalid domain format: "${domain}"`);
|
|
1843
1848
|
}
|
|
1844
|
-
const { readFileSync:
|
|
1845
|
-
const { homedir:
|
|
1846
|
-
const { join:
|
|
1847
|
-
const configPath =
|
|
1849
|
+
const { readFileSync: readFileSync7, writeFileSync: writeFileSync8 } = await import("fs");
|
|
1850
|
+
const { homedir: homedir11 } = await import("os");
|
|
1851
|
+
const { join: join12 } = await import("path");
|
|
1852
|
+
const configPath = join12(homedir11(), ".agenticmail", "stalwart.toml");
|
|
1848
1853
|
try {
|
|
1849
|
-
let config =
|
|
1854
|
+
let config = readFileSync7(configPath, "utf-8");
|
|
1850
1855
|
config = config.replace(/^hostname\s*=\s*"[^"]*"/m, `hostname = "${escapeTomlString(domain)}"`);
|
|
1851
|
-
|
|
1856
|
+
writeFileSync8(configPath, config);
|
|
1852
1857
|
console.log(`[Stalwart] Updated hostname to "${domain}" in stalwart.toml`);
|
|
1853
1858
|
} catch (err) {
|
|
1854
1859
|
throw new Error(`Failed to set config server.hostname=${domain}`);
|
|
@@ -1857,15 +1862,15 @@ var StalwartAdmin = class {
|
|
|
1857
1862
|
// --- DKIM ---
|
|
1858
1863
|
/** Path to the host-side stalwart.toml (mounted read-only into container) */
|
|
1859
1864
|
get configPath() {
|
|
1860
|
-
const { homedir:
|
|
1861
|
-
const { join:
|
|
1862
|
-
return
|
|
1865
|
+
const { homedir: homedir11 } = require("os");
|
|
1866
|
+
const { join: join12 } = require("path");
|
|
1867
|
+
return join12(homedir11(), ".agenticmail", "stalwart.toml");
|
|
1863
1868
|
}
|
|
1864
1869
|
/** Path to host-side DKIM key directory */
|
|
1865
1870
|
get dkimDir() {
|
|
1866
|
-
const { homedir:
|
|
1867
|
-
const { join:
|
|
1868
|
-
return
|
|
1871
|
+
const { homedir: homedir11 } = require("os");
|
|
1872
|
+
const { join: join12 } = require("path");
|
|
1873
|
+
return join12(homedir11(), ".agenticmail");
|
|
1869
1874
|
}
|
|
1870
1875
|
/**
|
|
1871
1876
|
* Create/reuse a DKIM signing key for a domain.
|
|
@@ -1966,12 +1971,12 @@ var StalwartAdmin = class {
|
|
|
1966
1971
|
* This bypasses the need for a PTR record on the sending IP.
|
|
1967
1972
|
*/
|
|
1968
1973
|
async configureOutboundRelay(config) {
|
|
1969
|
-
const { readFileSync:
|
|
1970
|
-
const { homedir:
|
|
1971
|
-
const { join:
|
|
1974
|
+
const { readFileSync: readFileSync7, writeFileSync: writeFileSync8 } = await import("fs");
|
|
1975
|
+
const { homedir: homedir11 } = await import("os");
|
|
1976
|
+
const { join: join12 } = await import("path");
|
|
1972
1977
|
const routeName = config.routeName ?? "gmail";
|
|
1973
|
-
const tomlPath =
|
|
1974
|
-
let toml =
|
|
1978
|
+
const tomlPath = join12(homedir11(), ".agenticmail", "stalwart.toml");
|
|
1979
|
+
let toml = readFileSync7(tomlPath, "utf-8");
|
|
1975
1980
|
toml = toml.replace(/\n\[queue\.route\.gmail\][\s\S]*?(?=\n\[|$)/, "");
|
|
1976
1981
|
toml = toml.replace(/\n\[queue\.strategy\][\s\S]*?(?=\n\[|$)/, "");
|
|
1977
1982
|
const safeRouteName = routeName.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
@@ -1991,7 +1996,7 @@ auth.secret = "${escapeTomlString(config.password)}"
|
|
|
1991
1996
|
route = [ { if = "is_local_domain('', rcpt_domain)", then = "'local'" },
|
|
1992
1997
|
{ else = "'${safeRouteName}'" } ]
|
|
1993
1998
|
`;
|
|
1994
|
-
|
|
1999
|
+
writeFileSync8(tomlPath, toml, "utf-8");
|
|
1995
2000
|
await this.restartContainer();
|
|
1996
2001
|
}
|
|
1997
2002
|
};
|
|
@@ -5864,12 +5869,12 @@ var GatewayManager = class {
|
|
|
5864
5869
|
zone = await this.cfClient.createZone(domain);
|
|
5865
5870
|
}
|
|
5866
5871
|
const existingRecords = await this.cfClient.listDnsRecords(zone.id);
|
|
5867
|
-
const { homedir:
|
|
5868
|
-
const backupDir = (0, import_node_path4.join)(
|
|
5872
|
+
const { homedir: homedir11 } = await import("os");
|
|
5873
|
+
const backupDir = (0, import_node_path4.join)(homedir11(), ".agenticmail");
|
|
5869
5874
|
const backupPath = (0, import_node_path4.join)(backupDir, `dns-backup-${domain}-${Date.now()}.json`);
|
|
5870
|
-
const { writeFileSync:
|
|
5871
|
-
|
|
5872
|
-
|
|
5875
|
+
const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync9 } = await import("fs");
|
|
5876
|
+
mkdirSync9(backupDir, { recursive: true });
|
|
5877
|
+
writeFileSync8(backupPath, JSON.stringify({
|
|
5873
5878
|
domain,
|
|
5874
5879
|
zoneId: zone.id,
|
|
5875
5880
|
backedUpAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8052,11 +8057,286 @@ secret = "${password}"
|
|
|
8052
8057
|
return (0, import_node_fs7.existsSync)(configPath);
|
|
8053
8058
|
}
|
|
8054
8059
|
};
|
|
8060
|
+
|
|
8061
|
+
// src/threading/thread-id.ts
|
|
8062
|
+
var import_node_crypto4 = require("crypto");
|
|
8063
|
+
function stripReplyPrefixes(subject) {
|
|
8064
|
+
let s = subject;
|
|
8065
|
+
for (; ; ) {
|
|
8066
|
+
const next = s.replace(/^\s*(?:re|fwd?|fw)\s*(?:\[\d+\])?\s*:\s*/i, "");
|
|
8067
|
+
if (next === s) break;
|
|
8068
|
+
s = next;
|
|
8069
|
+
}
|
|
8070
|
+
return s;
|
|
8071
|
+
}
|
|
8072
|
+
function stripCoordinationMarkers(subject) {
|
|
8073
|
+
return subject.replace(/\[\s*(?:final|done|closed|wrap)\s*\]/gi, " ");
|
|
8074
|
+
}
|
|
8075
|
+
function normalizeSubject(subject) {
|
|
8076
|
+
if (!subject) return "(no subject)";
|
|
8077
|
+
let s = stripReplyPrefixes(subject);
|
|
8078
|
+
s = stripCoordinationMarkers(s);
|
|
8079
|
+
s = s.replace(/\s+/g, " ").trim().toLowerCase();
|
|
8080
|
+
return s || "(no subject)";
|
|
8081
|
+
}
|
|
8082
|
+
function normalizeAddress(addr) {
|
|
8083
|
+
if (!addr) return "(unknown)";
|
|
8084
|
+
const m = addr.match(/<([^>]+)>/);
|
|
8085
|
+
const raw = m ? m[1] : addr;
|
|
8086
|
+
return raw.trim().toLowerCase();
|
|
8087
|
+
}
|
|
8088
|
+
function threadIdFor(input) {
|
|
8089
|
+
const subject = normalizeSubject(input.subject);
|
|
8090
|
+
return (0, import_node_crypto4.createHash)("sha256").update(subject).digest("base64url").slice(0, 16);
|
|
8091
|
+
}
|
|
8092
|
+
|
|
8093
|
+
// src/threading/thread-cache.ts
|
|
8094
|
+
var import_node_fs8 = require("fs");
|
|
8095
|
+
var import_node_os8 = require("os");
|
|
8096
|
+
var import_node_path9 = require("path");
|
|
8097
|
+
var CACHE_DIR_DEFAULT = (0, import_node_path9.join)((0, import_node_os8.homedir)(), ".agenticmail", "thread-cache");
|
|
8098
|
+
var DEFAULT_K_MESSAGES = 10;
|
|
8099
|
+
var DEFAULT_LRU_CAP = 5e3;
|
|
8100
|
+
var PREVIEW_MAX_CHARS = 240;
|
|
8101
|
+
var ThreadCache = class {
|
|
8102
|
+
dir;
|
|
8103
|
+
k;
|
|
8104
|
+
lruCap;
|
|
8105
|
+
constructor(opts = {}) {
|
|
8106
|
+
this.dir = opts.cacheDir ?? CACHE_DIR_DEFAULT;
|
|
8107
|
+
this.k = opts.k ?? DEFAULT_K_MESSAGES;
|
|
8108
|
+
this.lruCap = opts.lruCap ?? DEFAULT_LRU_CAP;
|
|
8109
|
+
try {
|
|
8110
|
+
(0, import_node_fs8.mkdirSync)(this.dir, { recursive: true });
|
|
8111
|
+
} catch {
|
|
8112
|
+
}
|
|
8113
|
+
}
|
|
8114
|
+
pathFor(threadId) {
|
|
8115
|
+
return (0, import_node_path9.join)(this.dir, `${threadId}.json`);
|
|
8116
|
+
}
|
|
8117
|
+
read(threadId) {
|
|
8118
|
+
const p = this.pathFor(threadId);
|
|
8119
|
+
if (!(0, import_node_fs8.existsSync)(p)) return null;
|
|
8120
|
+
try {
|
|
8121
|
+
const raw = (0, import_node_fs8.readFileSync)(p, "utf-8");
|
|
8122
|
+
return JSON.parse(raw);
|
|
8123
|
+
} catch {
|
|
8124
|
+
try {
|
|
8125
|
+
(0, import_node_fs8.rmSync)(p, { force: true });
|
|
8126
|
+
} catch {
|
|
8127
|
+
}
|
|
8128
|
+
return null;
|
|
8129
|
+
}
|
|
8130
|
+
}
|
|
8131
|
+
/**
|
|
8132
|
+
* Append a message to the thread's cache, pruning to the K
|
|
8133
|
+
* newest entries. Creates the cache entry on first write.
|
|
8134
|
+
*
|
|
8135
|
+
* `rootFromAddr` is the sender of the ROOT message on the
|
|
8136
|
+
* thread; on a brand-new thread this is just `env.fromAddr`,
|
|
8137
|
+
* on a reply it's read off the existing cache entry (callers
|
|
8138
|
+
* should pass the existing entry's rootFromAddr when known).
|
|
8139
|
+
*/
|
|
8140
|
+
pushMessage(threadId, env, meta) {
|
|
8141
|
+
const existing = this.read(threadId);
|
|
8142
|
+
const entry = existing ? {
|
|
8143
|
+
...existing,
|
|
8144
|
+
// We re-affirm subject + rootFromAddr to existing values —
|
|
8145
|
+
// the first-seen values win. Reply messages carry the
|
|
8146
|
+
// replier's `from`, not the original sender's, so we'd
|
|
8147
|
+
// corrupt the thread root if we overwrote here.
|
|
8148
|
+
subject: existing.subject,
|
|
8149
|
+
rootFromAddr: existing.rootFromAddr,
|
|
8150
|
+
lastUpdated: Date.now(),
|
|
8151
|
+
messages: dedupAndCap([env, ...existing.messages], this.k)
|
|
8152
|
+
} : {
|
|
8153
|
+
threadId,
|
|
8154
|
+
subject: meta.subject,
|
|
8155
|
+
rootFromAddr: meta.rootFromAddr,
|
|
8156
|
+
lastUpdated: Date.now(),
|
|
8157
|
+
messages: [env]
|
|
8158
|
+
};
|
|
8159
|
+
this.writeAtomic(threadId, entry);
|
|
8160
|
+
this.maybeEvict();
|
|
8161
|
+
return entry;
|
|
8162
|
+
}
|
|
8163
|
+
/** Permanently remove a thread's cache (called on [FINAL] / [DONE] / [CLOSED] / [WRAP]). */
|
|
8164
|
+
delete(threadId) {
|
|
8165
|
+
try {
|
|
8166
|
+
(0, import_node_fs8.rmSync)(this.pathFor(threadId), { force: true });
|
|
8167
|
+
} catch {
|
|
8168
|
+
}
|
|
8169
|
+
}
|
|
8170
|
+
/**
|
|
8171
|
+
* Render the cache as a compact text block for the wake prompt.
|
|
8172
|
+
* One line per message, newest first. Empty string when the
|
|
8173
|
+
* cache is empty — caller decides whether to suppress the
|
|
8174
|
+
* header in that case.
|
|
8175
|
+
*/
|
|
8176
|
+
renderForPrompt(entry) {
|
|
8177
|
+
if (!entry || entry.messages.length === 0) return "";
|
|
8178
|
+
return entry.messages.map((m) => {
|
|
8179
|
+
const preview = m.preview.replace(/\s+/g, " ").slice(0, PREVIEW_MAX_CHARS);
|
|
8180
|
+
return `- UID ${m.uid} \xB7 ${m.from} \xB7 ${m.date} \xB7 "${m.subject}" \xB7 ${preview}`;
|
|
8181
|
+
}).join("\n");
|
|
8182
|
+
}
|
|
8183
|
+
writeAtomic(threadId, entry) {
|
|
8184
|
+
const p = this.pathFor(threadId);
|
|
8185
|
+
const tmp = `${p}.tmp`;
|
|
8186
|
+
(0, import_node_fs8.writeFileSync)(tmp, JSON.stringify(entry), "utf-8");
|
|
8187
|
+
(0, import_node_fs8.renameSync)(tmp, p);
|
|
8188
|
+
}
|
|
8189
|
+
/**
|
|
8190
|
+
* Best-effort LRU eviction. Runs at most every 256 writes (we
|
|
8191
|
+
* don't track a precise counter — `Math.random()` sampling keeps
|
|
8192
|
+
* the write path cheap). When the directory has more files than
|
|
8193
|
+
* `lruCap`, sort by mtime ascending and delete the oldest 10%.
|
|
8194
|
+
*/
|
|
8195
|
+
maybeEvict() {
|
|
8196
|
+
if (Math.random() > 1 / 256) return;
|
|
8197
|
+
let files;
|
|
8198
|
+
try {
|
|
8199
|
+
files = (0, import_node_fs8.readdirSync)(this.dir).filter((f) => f.endsWith(".json"));
|
|
8200
|
+
} catch {
|
|
8201
|
+
return;
|
|
8202
|
+
}
|
|
8203
|
+
if (files.length <= this.lruCap) return;
|
|
8204
|
+
const stats = files.map((f) => {
|
|
8205
|
+
const p = (0, import_node_path9.join)(this.dir, f);
|
|
8206
|
+
try {
|
|
8207
|
+
return { p, mtime: (0, import_node_fs8.statSync)(p).mtimeMs };
|
|
8208
|
+
} catch {
|
|
8209
|
+
return { p, mtime: 0 };
|
|
8210
|
+
}
|
|
8211
|
+
});
|
|
8212
|
+
stats.sort((a, b) => a.mtime - b.mtime);
|
|
8213
|
+
const dropCount = Math.max(1, Math.floor(this.lruCap * 0.1));
|
|
8214
|
+
for (let i = 0; i < dropCount; i++) {
|
|
8215
|
+
try {
|
|
8216
|
+
(0, import_node_fs8.rmSync)(stats[i].p, { force: true });
|
|
8217
|
+
} catch {
|
|
8218
|
+
}
|
|
8219
|
+
}
|
|
8220
|
+
}
|
|
8221
|
+
};
|
|
8222
|
+
function dedupAndCap(messages, k) {
|
|
8223
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8224
|
+
const out = [];
|
|
8225
|
+
for (const m of messages) {
|
|
8226
|
+
if (seen.has(m.uid)) continue;
|
|
8227
|
+
seen.add(m.uid);
|
|
8228
|
+
out.push(m);
|
|
8229
|
+
if (out.length >= k) break;
|
|
8230
|
+
}
|
|
8231
|
+
return out;
|
|
8232
|
+
}
|
|
8233
|
+
|
|
8234
|
+
// src/threading/agent-memory.ts
|
|
8235
|
+
var import_node_fs9 = require("fs");
|
|
8236
|
+
var import_node_os9 = require("os");
|
|
8237
|
+
var import_node_path10 = require("path");
|
|
8238
|
+
var MEMORY_DIR_DEFAULT = (0, import_node_path10.join)((0, import_node_os9.homedir)(), ".agenticmail", "agent-memory");
|
|
8239
|
+
var AgentMemoryStore = class {
|
|
8240
|
+
dir;
|
|
8241
|
+
constructor(opts = {}) {
|
|
8242
|
+
this.dir = opts.memoryDir ?? MEMORY_DIR_DEFAULT;
|
|
8243
|
+
try {
|
|
8244
|
+
(0, import_node_fs9.mkdirSync)(this.dir, { recursive: true });
|
|
8245
|
+
} catch {
|
|
8246
|
+
}
|
|
8247
|
+
}
|
|
8248
|
+
dirFor(agentId) {
|
|
8249
|
+
return (0, import_node_path10.join)(this.dir, sanitizeId(agentId));
|
|
8250
|
+
}
|
|
8251
|
+
pathFor(agentId, threadId) {
|
|
8252
|
+
return (0, import_node_path10.join)(this.dirFor(agentId), `${sanitizeId(threadId)}.md`);
|
|
8253
|
+
}
|
|
8254
|
+
read(agentId, threadId) {
|
|
8255
|
+
const p = this.pathFor(agentId, threadId);
|
|
8256
|
+
if (!(0, import_node_fs9.existsSync)(p)) return null;
|
|
8257
|
+
try {
|
|
8258
|
+
const raw = (0, import_node_fs9.readFileSync)(p, "utf-8");
|
|
8259
|
+
const parsed = parse(raw);
|
|
8260
|
+
return { ...parsed, raw };
|
|
8261
|
+
} catch {
|
|
8262
|
+
return null;
|
|
8263
|
+
}
|
|
8264
|
+
}
|
|
8265
|
+
write(agentId, threadId, fields) {
|
|
8266
|
+
const agentDir = this.dirFor(agentId);
|
|
8267
|
+
try {
|
|
8268
|
+
(0, import_node_fs9.mkdirSync)(agentDir, { recursive: true });
|
|
8269
|
+
} catch {
|
|
8270
|
+
}
|
|
8271
|
+
const body = render({ ...fields, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8272
|
+
const p = this.pathFor(agentId, threadId);
|
|
8273
|
+
const tmp = `${p}.tmp`;
|
|
8274
|
+
(0, import_node_fs9.writeFileSync)(tmp, body, "utf-8");
|
|
8275
|
+
(0, import_node_fs9.renameSync)(tmp, p);
|
|
8276
|
+
}
|
|
8277
|
+
delete(agentId, threadId) {
|
|
8278
|
+
try {
|
|
8279
|
+
(0, import_node_fs9.rmSync)(this.pathFor(agentId, threadId), { force: true });
|
|
8280
|
+
} catch {
|
|
8281
|
+
}
|
|
8282
|
+
}
|
|
8283
|
+
/** Render an agent's memory for injection into a wake prompt.
|
|
8284
|
+
* Returns the raw markdown if present; empty string when there's
|
|
8285
|
+
* no prior memory (the caller decides whether to suppress the
|
|
8286
|
+
* whole "Your own memory" block). */
|
|
8287
|
+
renderForPrompt(memory) {
|
|
8288
|
+
if (!memory) return "";
|
|
8289
|
+
return memory.raw;
|
|
8290
|
+
}
|
|
8291
|
+
};
|
|
8292
|
+
function sanitizeId(id) {
|
|
8293
|
+
return id.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
8294
|
+
}
|
|
8295
|
+
function render(fields) {
|
|
8296
|
+
const fm = ["---"];
|
|
8297
|
+
fm.push(`updated_at: ${fields.updatedAt}`);
|
|
8298
|
+
if (typeof fields.lastUid === "number") fm.push(`last_uid: ${fields.lastUid}`);
|
|
8299
|
+
fm.push("---", "");
|
|
8300
|
+
const sections = [];
|
|
8301
|
+
if (fields.summary && fields.summary.trim()) {
|
|
8302
|
+
sections.push(fields.summary.trim());
|
|
8303
|
+
}
|
|
8304
|
+
if (fields.commitments && fields.commitments.length > 0) {
|
|
8305
|
+
sections.push(`### Commitments
|
|
8306
|
+
${fields.commitments.map((c) => `- ${c}`).join("\n")}`);
|
|
8307
|
+
}
|
|
8308
|
+
if (fields.openQuestions && fields.openQuestions.length > 0) {
|
|
8309
|
+
sections.push(`### Open
|
|
8310
|
+
${fields.openQuestions.map((q) => `- ${q}`).join("\n")}`);
|
|
8311
|
+
}
|
|
8312
|
+
if (fields.lastAction && fields.lastAction.trim()) {
|
|
8313
|
+
sections.push(`### Last action
|
|
8314
|
+
${fields.lastAction.trim()}`);
|
|
8315
|
+
}
|
|
8316
|
+
return fm.join("\n") + sections.join("\n\n") + "\n";
|
|
8317
|
+
}
|
|
8318
|
+
function parse(raw) {
|
|
8319
|
+
const out = {};
|
|
8320
|
+
const m = raw.match(/^---\n([\s\S]*?)\n---\n/);
|
|
8321
|
+
if (m) {
|
|
8322
|
+
for (const line of m[1].split("\n")) {
|
|
8323
|
+
const kv = line.match(/^(\w+):\s*(.*)$/);
|
|
8324
|
+
if (!kv) continue;
|
|
8325
|
+
if (kv[1] === "updated_at") out.updatedAt = kv[2].trim();
|
|
8326
|
+
else if (kv[1] === "last_uid") {
|
|
8327
|
+
const n = parseInt(kv[2], 10);
|
|
8328
|
+
if (!isNaN(n)) out.lastUid = n;
|
|
8329
|
+
}
|
|
8330
|
+
}
|
|
8331
|
+
}
|
|
8332
|
+
return out;
|
|
8333
|
+
}
|
|
8055
8334
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8056
8335
|
0 && (module.exports = {
|
|
8057
8336
|
AGENT_ROLES,
|
|
8058
8337
|
AccountManager,
|
|
8059
8338
|
AgentDeletionService,
|
|
8339
|
+
AgentMemoryStore,
|
|
8060
8340
|
AgenticMailClient,
|
|
8061
8341
|
CloudflareClient,
|
|
8062
8342
|
DEFAULT_AGENT_NAME,
|
|
@@ -8080,6 +8360,7 @@ secret = "${password}"
|
|
|
8080
8360
|
SmsManager,
|
|
8081
8361
|
SmsPoller,
|
|
8082
8362
|
StalwartAdmin,
|
|
8363
|
+
ThreadCache,
|
|
8083
8364
|
TunnelManager,
|
|
8084
8365
|
WARNING_THRESHOLD,
|
|
8085
8366
|
buildInboundSecurityAdvisory,
|
|
@@ -8094,7 +8375,9 @@ secret = "${password}"
|
|
|
8094
8375
|
getDatabase,
|
|
8095
8376
|
isInternalEmail,
|
|
8096
8377
|
isValidPhoneNumber,
|
|
8378
|
+
normalizeAddress,
|
|
8097
8379
|
normalizePhoneNumber,
|
|
8380
|
+
normalizeSubject,
|
|
8098
8381
|
parseEmail,
|
|
8099
8382
|
parseGoogleVoiceSms,
|
|
8100
8383
|
recordToolCall,
|
|
@@ -8104,5 +8387,6 @@ secret = "${password}"
|
|
|
8104
8387
|
scanOutboundEmail,
|
|
8105
8388
|
scoreEmail,
|
|
8106
8389
|
setTelemetryVersion,
|
|
8107
|
-
startRelayBridge
|
|
8390
|
+
startRelayBridge,
|
|
8391
|
+
threadIdFor
|
|
8108
8392
|
});
|