@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/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
|
};
|
|
@@ -2029,7 +2034,11 @@ function rowToAgent(row) {
|
|
|
2029
2034
|
createdAt: row.created_at,
|
|
2030
2035
|
updatedAt: row.updated_at,
|
|
2031
2036
|
metadata,
|
|
2032
|
-
role: row.role || "secretary"
|
|
2037
|
+
role: row.role || "secretary",
|
|
2038
|
+
// Old rows (pre-migration-016) have undefined `wake_on_cc`;
|
|
2039
|
+
// treat that as the default-true (respect sender's wake list
|
|
2040
|
+
// as-is). Only explicit 0 disables CC wakes for this agent.
|
|
2041
|
+
wakeOnCc: row.wake_on_cc !== void 0 ? row.wake_on_cc !== 0 : true
|
|
2033
2042
|
};
|
|
2034
2043
|
}
|
|
2035
2044
|
var AccountManager = class {
|
|
@@ -3616,6 +3625,15 @@ ALTER TABLE agent_tasks ADD COLUMN output_schema TEXT;
|
|
|
3616
3625
|
-- stays snappy. NULL means "no attachments", fully back-compat
|
|
3617
3626
|
-- with rows from before this migration.
|
|
3618
3627
|
ALTER TABLE drafts ADD COLUMN attachments TEXT;
|
|
3628
|
+
`,
|
|
3629
|
+
"016_agent_wake_on_cc.sql": `
|
|
3630
|
+
-- Per-agent wake preference. When 0, the dispatcher SKIPS this
|
|
3631
|
+
-- agent on every CC-only delivery (the agent is on Cc/Bcc but
|
|
3632
|
+
-- not To), regardless of what the sender passed as the wake
|
|
3633
|
+
-- argument. This is the "coder agent, only wake me when
|
|
3634
|
+
-- explicitly named" preference from the wake-thrash feedback.
|
|
3635
|
+
-- Defaults to 1 (respect the senders wake list as-is).
|
|
3636
|
+
ALTER TABLE agents ADD COLUMN wake_on_cc INTEGER NOT NULL DEFAULT 1;
|
|
3619
3637
|
`
|
|
3620
3638
|
};
|
|
3621
3639
|
function runMigrations(database) {
|
|
@@ -5864,12 +5882,12 @@ var GatewayManager = class {
|
|
|
5864
5882
|
zone = await this.cfClient.createZone(domain);
|
|
5865
5883
|
}
|
|
5866
5884
|
const existingRecords = await this.cfClient.listDnsRecords(zone.id);
|
|
5867
|
-
const { homedir:
|
|
5868
|
-
const backupDir = (0, import_node_path4.join)(
|
|
5885
|
+
const { homedir: homedir11 } = await import("os");
|
|
5886
|
+
const backupDir = (0, import_node_path4.join)(homedir11(), ".agenticmail");
|
|
5869
5887
|
const backupPath = (0, import_node_path4.join)(backupDir, `dns-backup-${domain}-${Date.now()}.json`);
|
|
5870
|
-
const { writeFileSync:
|
|
5871
|
-
|
|
5872
|
-
|
|
5888
|
+
const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync9 } = await import("fs");
|
|
5889
|
+
mkdirSync9(backupDir, { recursive: true });
|
|
5890
|
+
writeFileSync8(backupPath, JSON.stringify({
|
|
5873
5891
|
domain,
|
|
5874
5892
|
zoneId: zone.id,
|
|
5875
5893
|
backedUpAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8052,11 +8070,286 @@ secret = "${password}"
|
|
|
8052
8070
|
return (0, import_node_fs7.existsSync)(configPath);
|
|
8053
8071
|
}
|
|
8054
8072
|
};
|
|
8073
|
+
|
|
8074
|
+
// src/threading/thread-id.ts
|
|
8075
|
+
var import_node_crypto4 = require("crypto");
|
|
8076
|
+
function stripReplyPrefixes(subject) {
|
|
8077
|
+
let s = subject;
|
|
8078
|
+
for (; ; ) {
|
|
8079
|
+
const next = s.replace(/^\s*(?:re|fwd?|fw)\s*(?:\[\d+\])?\s*:\s*/i, "");
|
|
8080
|
+
if (next === s) break;
|
|
8081
|
+
s = next;
|
|
8082
|
+
}
|
|
8083
|
+
return s;
|
|
8084
|
+
}
|
|
8085
|
+
function stripCoordinationMarkers(subject) {
|
|
8086
|
+
return subject.replace(/\[\s*(?:final|done|closed|wrap)\s*\]/gi, " ");
|
|
8087
|
+
}
|
|
8088
|
+
function normalizeSubject(subject) {
|
|
8089
|
+
if (!subject) return "(no subject)";
|
|
8090
|
+
let s = stripReplyPrefixes(subject);
|
|
8091
|
+
s = stripCoordinationMarkers(s);
|
|
8092
|
+
s = s.replace(/\s+/g, " ").trim().toLowerCase();
|
|
8093
|
+
return s || "(no subject)";
|
|
8094
|
+
}
|
|
8095
|
+
function normalizeAddress(addr) {
|
|
8096
|
+
if (!addr) return "(unknown)";
|
|
8097
|
+
const m = addr.match(/<([^>]+)>/);
|
|
8098
|
+
const raw = m ? m[1] : addr;
|
|
8099
|
+
return raw.trim().toLowerCase();
|
|
8100
|
+
}
|
|
8101
|
+
function threadIdFor(input) {
|
|
8102
|
+
const subject = normalizeSubject(input.subject);
|
|
8103
|
+
return (0, import_node_crypto4.createHash)("sha256").update(subject).digest("base64url").slice(0, 16);
|
|
8104
|
+
}
|
|
8105
|
+
|
|
8106
|
+
// src/threading/thread-cache.ts
|
|
8107
|
+
var import_node_fs8 = require("fs");
|
|
8108
|
+
var import_node_os8 = require("os");
|
|
8109
|
+
var import_node_path9 = require("path");
|
|
8110
|
+
var CACHE_DIR_DEFAULT = (0, import_node_path9.join)((0, import_node_os8.homedir)(), ".agenticmail", "thread-cache");
|
|
8111
|
+
var DEFAULT_K_MESSAGES = 10;
|
|
8112
|
+
var DEFAULT_LRU_CAP = 5e3;
|
|
8113
|
+
var PREVIEW_MAX_CHARS = 240;
|
|
8114
|
+
var ThreadCache = class {
|
|
8115
|
+
dir;
|
|
8116
|
+
k;
|
|
8117
|
+
lruCap;
|
|
8118
|
+
constructor(opts = {}) {
|
|
8119
|
+
this.dir = opts.cacheDir ?? CACHE_DIR_DEFAULT;
|
|
8120
|
+
this.k = opts.k ?? DEFAULT_K_MESSAGES;
|
|
8121
|
+
this.lruCap = opts.lruCap ?? DEFAULT_LRU_CAP;
|
|
8122
|
+
try {
|
|
8123
|
+
(0, import_node_fs8.mkdirSync)(this.dir, { recursive: true });
|
|
8124
|
+
} catch {
|
|
8125
|
+
}
|
|
8126
|
+
}
|
|
8127
|
+
pathFor(threadId) {
|
|
8128
|
+
return (0, import_node_path9.join)(this.dir, `${threadId}.json`);
|
|
8129
|
+
}
|
|
8130
|
+
read(threadId) {
|
|
8131
|
+
const p = this.pathFor(threadId);
|
|
8132
|
+
if (!(0, import_node_fs8.existsSync)(p)) return null;
|
|
8133
|
+
try {
|
|
8134
|
+
const raw = (0, import_node_fs8.readFileSync)(p, "utf-8");
|
|
8135
|
+
return JSON.parse(raw);
|
|
8136
|
+
} catch {
|
|
8137
|
+
try {
|
|
8138
|
+
(0, import_node_fs8.rmSync)(p, { force: true });
|
|
8139
|
+
} catch {
|
|
8140
|
+
}
|
|
8141
|
+
return null;
|
|
8142
|
+
}
|
|
8143
|
+
}
|
|
8144
|
+
/**
|
|
8145
|
+
* Append a message to the thread's cache, pruning to the K
|
|
8146
|
+
* newest entries. Creates the cache entry on first write.
|
|
8147
|
+
*
|
|
8148
|
+
* `rootFromAddr` is the sender of the ROOT message on the
|
|
8149
|
+
* thread; on a brand-new thread this is just `env.fromAddr`,
|
|
8150
|
+
* on a reply it's read off the existing cache entry (callers
|
|
8151
|
+
* should pass the existing entry's rootFromAddr when known).
|
|
8152
|
+
*/
|
|
8153
|
+
pushMessage(threadId, env, meta) {
|
|
8154
|
+
const existing = this.read(threadId);
|
|
8155
|
+
const entry = existing ? {
|
|
8156
|
+
...existing,
|
|
8157
|
+
// We re-affirm subject + rootFromAddr to existing values —
|
|
8158
|
+
// the first-seen values win. Reply messages carry the
|
|
8159
|
+
// replier's `from`, not the original sender's, so we'd
|
|
8160
|
+
// corrupt the thread root if we overwrote here.
|
|
8161
|
+
subject: existing.subject,
|
|
8162
|
+
rootFromAddr: existing.rootFromAddr,
|
|
8163
|
+
lastUpdated: Date.now(),
|
|
8164
|
+
messages: dedupAndCap([env, ...existing.messages], this.k)
|
|
8165
|
+
} : {
|
|
8166
|
+
threadId,
|
|
8167
|
+
subject: meta.subject,
|
|
8168
|
+
rootFromAddr: meta.rootFromAddr,
|
|
8169
|
+
lastUpdated: Date.now(),
|
|
8170
|
+
messages: [env]
|
|
8171
|
+
};
|
|
8172
|
+
this.writeAtomic(threadId, entry);
|
|
8173
|
+
this.maybeEvict();
|
|
8174
|
+
return entry;
|
|
8175
|
+
}
|
|
8176
|
+
/** Permanently remove a thread's cache (called on [FINAL] / [DONE] / [CLOSED] / [WRAP]). */
|
|
8177
|
+
delete(threadId) {
|
|
8178
|
+
try {
|
|
8179
|
+
(0, import_node_fs8.rmSync)(this.pathFor(threadId), { force: true });
|
|
8180
|
+
} catch {
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
/**
|
|
8184
|
+
* Render the cache as a compact text block for the wake prompt.
|
|
8185
|
+
* One line per message, newest first. Empty string when the
|
|
8186
|
+
* cache is empty — caller decides whether to suppress the
|
|
8187
|
+
* header in that case.
|
|
8188
|
+
*/
|
|
8189
|
+
renderForPrompt(entry) {
|
|
8190
|
+
if (!entry || entry.messages.length === 0) return "";
|
|
8191
|
+
return entry.messages.map((m) => {
|
|
8192
|
+
const preview = m.preview.replace(/\s+/g, " ").slice(0, PREVIEW_MAX_CHARS);
|
|
8193
|
+
return `- UID ${m.uid} \xB7 ${m.from} \xB7 ${m.date} \xB7 "${m.subject}" \xB7 ${preview}`;
|
|
8194
|
+
}).join("\n");
|
|
8195
|
+
}
|
|
8196
|
+
writeAtomic(threadId, entry) {
|
|
8197
|
+
const p = this.pathFor(threadId);
|
|
8198
|
+
const tmp = `${p}.tmp`;
|
|
8199
|
+
(0, import_node_fs8.writeFileSync)(tmp, JSON.stringify(entry), "utf-8");
|
|
8200
|
+
(0, import_node_fs8.renameSync)(tmp, p);
|
|
8201
|
+
}
|
|
8202
|
+
/**
|
|
8203
|
+
* Best-effort LRU eviction. Runs at most every 256 writes (we
|
|
8204
|
+
* don't track a precise counter — `Math.random()` sampling keeps
|
|
8205
|
+
* the write path cheap). When the directory has more files than
|
|
8206
|
+
* `lruCap`, sort by mtime ascending and delete the oldest 10%.
|
|
8207
|
+
*/
|
|
8208
|
+
maybeEvict() {
|
|
8209
|
+
if (Math.random() > 1 / 256) return;
|
|
8210
|
+
let files;
|
|
8211
|
+
try {
|
|
8212
|
+
files = (0, import_node_fs8.readdirSync)(this.dir).filter((f) => f.endsWith(".json"));
|
|
8213
|
+
} catch {
|
|
8214
|
+
return;
|
|
8215
|
+
}
|
|
8216
|
+
if (files.length <= this.lruCap) return;
|
|
8217
|
+
const stats = files.map((f) => {
|
|
8218
|
+
const p = (0, import_node_path9.join)(this.dir, f);
|
|
8219
|
+
try {
|
|
8220
|
+
return { p, mtime: (0, import_node_fs8.statSync)(p).mtimeMs };
|
|
8221
|
+
} catch {
|
|
8222
|
+
return { p, mtime: 0 };
|
|
8223
|
+
}
|
|
8224
|
+
});
|
|
8225
|
+
stats.sort((a, b) => a.mtime - b.mtime);
|
|
8226
|
+
const dropCount = Math.max(1, Math.floor(this.lruCap * 0.1));
|
|
8227
|
+
for (let i = 0; i < dropCount; i++) {
|
|
8228
|
+
try {
|
|
8229
|
+
(0, import_node_fs8.rmSync)(stats[i].p, { force: true });
|
|
8230
|
+
} catch {
|
|
8231
|
+
}
|
|
8232
|
+
}
|
|
8233
|
+
}
|
|
8234
|
+
};
|
|
8235
|
+
function dedupAndCap(messages, k) {
|
|
8236
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8237
|
+
const out = [];
|
|
8238
|
+
for (const m of messages) {
|
|
8239
|
+
if (seen.has(m.uid)) continue;
|
|
8240
|
+
seen.add(m.uid);
|
|
8241
|
+
out.push(m);
|
|
8242
|
+
if (out.length >= k) break;
|
|
8243
|
+
}
|
|
8244
|
+
return out;
|
|
8245
|
+
}
|
|
8246
|
+
|
|
8247
|
+
// src/threading/agent-memory.ts
|
|
8248
|
+
var import_node_fs9 = require("fs");
|
|
8249
|
+
var import_node_os9 = require("os");
|
|
8250
|
+
var import_node_path10 = require("path");
|
|
8251
|
+
var MEMORY_DIR_DEFAULT = (0, import_node_path10.join)((0, import_node_os9.homedir)(), ".agenticmail", "agent-memory");
|
|
8252
|
+
var AgentMemoryStore = class {
|
|
8253
|
+
dir;
|
|
8254
|
+
constructor(opts = {}) {
|
|
8255
|
+
this.dir = opts.memoryDir ?? MEMORY_DIR_DEFAULT;
|
|
8256
|
+
try {
|
|
8257
|
+
(0, import_node_fs9.mkdirSync)(this.dir, { recursive: true });
|
|
8258
|
+
} catch {
|
|
8259
|
+
}
|
|
8260
|
+
}
|
|
8261
|
+
dirFor(agentId) {
|
|
8262
|
+
return (0, import_node_path10.join)(this.dir, sanitizeId(agentId));
|
|
8263
|
+
}
|
|
8264
|
+
pathFor(agentId, threadId) {
|
|
8265
|
+
return (0, import_node_path10.join)(this.dirFor(agentId), `${sanitizeId(threadId)}.md`);
|
|
8266
|
+
}
|
|
8267
|
+
read(agentId, threadId) {
|
|
8268
|
+
const p = this.pathFor(agentId, threadId);
|
|
8269
|
+
if (!(0, import_node_fs9.existsSync)(p)) return null;
|
|
8270
|
+
try {
|
|
8271
|
+
const raw = (0, import_node_fs9.readFileSync)(p, "utf-8");
|
|
8272
|
+
const parsed = parse(raw);
|
|
8273
|
+
return { ...parsed, raw };
|
|
8274
|
+
} catch {
|
|
8275
|
+
return null;
|
|
8276
|
+
}
|
|
8277
|
+
}
|
|
8278
|
+
write(agentId, threadId, fields) {
|
|
8279
|
+
const agentDir = this.dirFor(agentId);
|
|
8280
|
+
try {
|
|
8281
|
+
(0, import_node_fs9.mkdirSync)(agentDir, { recursive: true });
|
|
8282
|
+
} catch {
|
|
8283
|
+
}
|
|
8284
|
+
const body = render({ ...fields, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8285
|
+
const p = this.pathFor(agentId, threadId);
|
|
8286
|
+
const tmp = `${p}.tmp`;
|
|
8287
|
+
(0, import_node_fs9.writeFileSync)(tmp, body, "utf-8");
|
|
8288
|
+
(0, import_node_fs9.renameSync)(tmp, p);
|
|
8289
|
+
}
|
|
8290
|
+
delete(agentId, threadId) {
|
|
8291
|
+
try {
|
|
8292
|
+
(0, import_node_fs9.rmSync)(this.pathFor(agentId, threadId), { force: true });
|
|
8293
|
+
} catch {
|
|
8294
|
+
}
|
|
8295
|
+
}
|
|
8296
|
+
/** Render an agent's memory for injection into a wake prompt.
|
|
8297
|
+
* Returns the raw markdown if present; empty string when there's
|
|
8298
|
+
* no prior memory (the caller decides whether to suppress the
|
|
8299
|
+
* whole "Your own memory" block). */
|
|
8300
|
+
renderForPrompt(memory) {
|
|
8301
|
+
if (!memory) return "";
|
|
8302
|
+
return memory.raw;
|
|
8303
|
+
}
|
|
8304
|
+
};
|
|
8305
|
+
function sanitizeId(id) {
|
|
8306
|
+
return id.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
8307
|
+
}
|
|
8308
|
+
function render(fields) {
|
|
8309
|
+
const fm = ["---"];
|
|
8310
|
+
fm.push(`updated_at: ${fields.updatedAt}`);
|
|
8311
|
+
if (typeof fields.lastUid === "number") fm.push(`last_uid: ${fields.lastUid}`);
|
|
8312
|
+
fm.push("---", "");
|
|
8313
|
+
const sections = [];
|
|
8314
|
+
if (fields.summary && fields.summary.trim()) {
|
|
8315
|
+
sections.push(fields.summary.trim());
|
|
8316
|
+
}
|
|
8317
|
+
if (fields.commitments && fields.commitments.length > 0) {
|
|
8318
|
+
sections.push(`### Commitments
|
|
8319
|
+
${fields.commitments.map((c) => `- ${c}`).join("\n")}`);
|
|
8320
|
+
}
|
|
8321
|
+
if (fields.openQuestions && fields.openQuestions.length > 0) {
|
|
8322
|
+
sections.push(`### Open
|
|
8323
|
+
${fields.openQuestions.map((q) => `- ${q}`).join("\n")}`);
|
|
8324
|
+
}
|
|
8325
|
+
if (fields.lastAction && fields.lastAction.trim()) {
|
|
8326
|
+
sections.push(`### Last action
|
|
8327
|
+
${fields.lastAction.trim()}`);
|
|
8328
|
+
}
|
|
8329
|
+
return fm.join("\n") + sections.join("\n\n") + "\n";
|
|
8330
|
+
}
|
|
8331
|
+
function parse(raw) {
|
|
8332
|
+
const out = {};
|
|
8333
|
+
const m = raw.match(/^---\n([\s\S]*?)\n---\n/);
|
|
8334
|
+
if (m) {
|
|
8335
|
+
for (const line of m[1].split("\n")) {
|
|
8336
|
+
const kv = line.match(/^(\w+):\s*(.*)$/);
|
|
8337
|
+
if (!kv) continue;
|
|
8338
|
+
if (kv[1] === "updated_at") out.updatedAt = kv[2].trim();
|
|
8339
|
+
else if (kv[1] === "last_uid") {
|
|
8340
|
+
const n = parseInt(kv[2], 10);
|
|
8341
|
+
if (!isNaN(n)) out.lastUid = n;
|
|
8342
|
+
}
|
|
8343
|
+
}
|
|
8344
|
+
}
|
|
8345
|
+
return out;
|
|
8346
|
+
}
|
|
8055
8347
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8056
8348
|
0 && (module.exports = {
|
|
8057
8349
|
AGENT_ROLES,
|
|
8058
8350
|
AccountManager,
|
|
8059
8351
|
AgentDeletionService,
|
|
8352
|
+
AgentMemoryStore,
|
|
8060
8353
|
AgenticMailClient,
|
|
8061
8354
|
CloudflareClient,
|
|
8062
8355
|
DEFAULT_AGENT_NAME,
|
|
@@ -8080,6 +8373,7 @@ secret = "${password}"
|
|
|
8080
8373
|
SmsManager,
|
|
8081
8374
|
SmsPoller,
|
|
8082
8375
|
StalwartAdmin,
|
|
8376
|
+
ThreadCache,
|
|
8083
8377
|
TunnelManager,
|
|
8084
8378
|
WARNING_THRESHOLD,
|
|
8085
8379
|
buildInboundSecurityAdvisory,
|
|
@@ -8094,7 +8388,9 @@ secret = "${password}"
|
|
|
8094
8388
|
getDatabase,
|
|
8095
8389
|
isInternalEmail,
|
|
8096
8390
|
isValidPhoneNumber,
|
|
8391
|
+
normalizeAddress,
|
|
8097
8392
|
normalizePhoneNumber,
|
|
8393
|
+
normalizeSubject,
|
|
8098
8394
|
parseEmail,
|
|
8099
8395
|
parseGoogleVoiceSms,
|
|
8100
8396
|
recordToolCall,
|
|
@@ -8104,5 +8400,6 @@ secret = "${password}"
|
|
|
8104
8400
|
scanOutboundEmail,
|
|
8105
8401
|
scoreEmail,
|
|
8106
8402
|
setTelemetryVersion,
|
|
8107
|
-
startRelayBridge
|
|
8403
|
+
startRelayBridge,
|
|
8404
|
+
threadIdFor
|
|
8108
8405
|
});
|