@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/dist/index.d.cts
CHANGED
|
@@ -1843,4 +1843,254 @@ declare class SetupManager {
|
|
|
1843
1843
|
isInitialized(): boolean;
|
|
1844
1844
|
}
|
|
1845
1845
|
|
|
1846
|
-
|
|
1846
|
+
/**
|
|
1847
|
+
* Stable thread-id derivation.
|
|
1848
|
+
*
|
|
1849
|
+
* Two messages belong to the same thread if their normalized
|
|
1850
|
+
* `(subject, root-from)` tuple matches. Computing this on demand
|
|
1851
|
+
* from envelope data is cheap and avoids depending on the IMAP
|
|
1852
|
+
* `THREAD` extension (which Stalwart doesn't advertise) or on
|
|
1853
|
+
* reconstructing `In-Reply-To` / `References` chains (which agents
|
|
1854
|
+
* sometimes forge or strip).
|
|
1855
|
+
*
|
|
1856
|
+
* # Normalization rules
|
|
1857
|
+
*
|
|
1858
|
+
* - Strip every leading `Re:` / `Fwd:` / `Re[2]:` chain. Some
|
|
1859
|
+
* clients chain prefixes (`Re: Re: Fwd: Re: …`), which would
|
|
1860
|
+
* otherwise produce a different thread id for every hop.
|
|
1861
|
+
* - Collapse internal whitespace to single spaces.
|
|
1862
|
+
* - Trim leading + trailing whitespace.
|
|
1863
|
+
* - Lower-case for case-insensitive matching.
|
|
1864
|
+
* - Reply-on-thread coordination markers (`[FINAL]`, `[DONE]`,
|
|
1865
|
+
* `[CLOSED]`, `[WRAP]`) are stripped — a closing message
|
|
1866
|
+
* belongs to the SAME thread as the conversation it closes.
|
|
1867
|
+
*
|
|
1868
|
+
* # Identity hash
|
|
1869
|
+
*
|
|
1870
|
+
* SHA-256 of `<normalizedSubject>\n<rootFromLower>`, base64url
|
|
1871
|
+
* truncated to 16 chars (~12 bytes of entropy = ~10^28 distinct
|
|
1872
|
+
* threads; collision-free for any realistic deployment).
|
|
1873
|
+
*
|
|
1874
|
+
* The root sender is included so two unrelated conversations
|
|
1875
|
+
* that share a generic subject ("hello", "follow up") aren't
|
|
1876
|
+
* collapsed into one thread. We use the FIRST sender's address
|
|
1877
|
+
* on the thread — agents reading a reply pass their own
|
|
1878
|
+
* envelope's `from` value, but the thread id stays stable
|
|
1879
|
+
* because we re-derive `rootFromAddr` from the cache when
|
|
1880
|
+
* looking up an existing thread (see thread-cache.ts).
|
|
1881
|
+
*/
|
|
1882
|
+
declare function normalizeSubject(subject: string | undefined | null): string;
|
|
1883
|
+
declare function normalizeAddress(addr: string | undefined | null): string;
|
|
1884
|
+
interface ThreadIdInput {
|
|
1885
|
+
subject?: string | null;
|
|
1886
|
+
/** Optional. Kept as a field so call sites that previously
|
|
1887
|
+
* passed it keep working, but NOT used in the hash. The thread
|
|
1888
|
+
* id is intentionally subject-only so a reply from a different
|
|
1889
|
+
* sender (the replier, not the root) still maps to the same
|
|
1890
|
+
* thread without needing a cache lookup first. The dispatcher's
|
|
1891
|
+
* legacy `threadIdFromSubject` uses the same convention; this
|
|
1892
|
+
* function is its disk-safe + hashed equivalent. */
|
|
1893
|
+
rootFromAddr?: string | null;
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Subject-only stable thread id. Collisions between unrelated
|
|
1897
|
+
* conversations that genuinely share the same normalized subject
|
|
1898
|
+
* ("hello", "follow up") are accepted as the tradeoff for stable
|
|
1899
|
+
* threading across replies. In practice agents on different
|
|
1900
|
+
* threads use different participants, so the wake-budget +
|
|
1901
|
+
* thread-close logic disambiguates downstream.
|
|
1902
|
+
*/
|
|
1903
|
+
declare function threadIdFor(input: ThreadIdInput): string;
|
|
1904
|
+
|
|
1905
|
+
/**
|
|
1906
|
+
* Per-thread message cache — Layer 1 of the wake-context system.
|
|
1907
|
+
*
|
|
1908
|
+
* # What it stores
|
|
1909
|
+
*
|
|
1910
|
+
* Each thread (keyed by the stable `threadIdFor` hash) gets a JSON
|
|
1911
|
+
* file containing the last K message envelopes that were seen on
|
|
1912
|
+
* the thread:
|
|
1913
|
+
*
|
|
1914
|
+
* { threadId, subject, rootFromAddr, lastUpdated, messages: [
|
|
1915
|
+
* { uid, from, subject, preview, date }
|
|
1916
|
+
* ]}
|
|
1917
|
+
*
|
|
1918
|
+
* # What it does NOT store
|
|
1919
|
+
*
|
|
1920
|
+
* - Full message bodies. Storing the body would multiply the cache
|
|
1921
|
+
* size 20x and most agents don't need it on rehydration — they
|
|
1922
|
+
* need to see "who said what about what" at a glance, not the
|
|
1923
|
+
* exact prose. The dedicated `read_email(uid)` MCP tool serves
|
|
1924
|
+
* the body when the agent actually wants it.
|
|
1925
|
+
*
|
|
1926
|
+
* # Lifecycle
|
|
1927
|
+
*
|
|
1928
|
+
* - Built passively: the dispatcher calls `pushMessage(t, env)` on
|
|
1929
|
+
* every SSE new-mail event for the thread, even when no agent
|
|
1930
|
+
* actually wakes. Selective-wake skips, circuit-breaker mutes,
|
|
1931
|
+
* `[FINAL]` markers — none of them prevent the cache from being
|
|
1932
|
+
* populated. The cache is always up to date.
|
|
1933
|
+
*
|
|
1934
|
+
* - Read on every spawn: `readCache(t)` is called before the
|
|
1935
|
+
* dispatcher fires a worker, and the result is rendered into
|
|
1936
|
+
* the wake prompt.
|
|
1937
|
+
*
|
|
1938
|
+
* - Pruned on close: `cleanupThread(t)` is called when the
|
|
1939
|
+
* dispatcher detects a thread-close marker. Removes the file
|
|
1940
|
+
* immediately; 7-day grace via empty-tombstone is not needed
|
|
1941
|
+
* because the agent's own memory file handles "did this
|
|
1942
|
+
* thread actually close" semantics.
|
|
1943
|
+
*
|
|
1944
|
+
* - LRU-bounded: a directory-level LRU (default 5000 threads, ~25 MB)
|
|
1945
|
+
* runs at the head of every write to keep disk usage flat.
|
|
1946
|
+
*/
|
|
1947
|
+
interface CachedMessage {
|
|
1948
|
+
uid: number;
|
|
1949
|
+
/** Display name OR raw address — whichever was on the envelope. */
|
|
1950
|
+
from: string;
|
|
1951
|
+
/** Sender's bare email (post-normalization). Same value used to
|
|
1952
|
+
* derive the thread root, so agents can spot "did I write this?"
|
|
1953
|
+
* with one equality check. */
|
|
1954
|
+
fromAddr: string;
|
|
1955
|
+
subject: string;
|
|
1956
|
+
/** Up to ~240 chars of plain-text body. */
|
|
1957
|
+
preview: string;
|
|
1958
|
+
/** ISO string. */
|
|
1959
|
+
date: string;
|
|
1960
|
+
}
|
|
1961
|
+
interface ThreadCacheEntry {
|
|
1962
|
+
threadId: string;
|
|
1963
|
+
/** First-seen normalized subject — kept on the entry so the wake
|
|
1964
|
+
* prompt can show it without re-normalizing. */
|
|
1965
|
+
subject: string;
|
|
1966
|
+
/** First-seen root sender. Used to keep `threadIdFor` stable on
|
|
1967
|
+
* subsequent replies (which carry the replier's `from`, not
|
|
1968
|
+
* the original). */
|
|
1969
|
+
rootFromAddr: string;
|
|
1970
|
+
/** ms timestamp of most recent write. Drives LRU eviction. */
|
|
1971
|
+
lastUpdated: number;
|
|
1972
|
+
/** Newest-first. We cap at K — drop oldest on push. */
|
|
1973
|
+
messages: CachedMessage[];
|
|
1974
|
+
}
|
|
1975
|
+
interface ThreadCacheOptions {
|
|
1976
|
+
/** Override the on-disk root. Mainly for tests. */
|
|
1977
|
+
cacheDir?: string;
|
|
1978
|
+
/** Newest-N messages kept per thread. */
|
|
1979
|
+
k?: number;
|
|
1980
|
+
/** Max threads on disk before LRU eviction kicks in. */
|
|
1981
|
+
lruCap?: number;
|
|
1982
|
+
}
|
|
1983
|
+
declare class ThreadCache {
|
|
1984
|
+
private readonly dir;
|
|
1985
|
+
private readonly k;
|
|
1986
|
+
private readonly lruCap;
|
|
1987
|
+
constructor(opts?: ThreadCacheOptions);
|
|
1988
|
+
private pathFor;
|
|
1989
|
+
read(threadId: string): ThreadCacheEntry | null;
|
|
1990
|
+
/**
|
|
1991
|
+
* Append a message to the thread's cache, pruning to the K
|
|
1992
|
+
* newest entries. Creates the cache entry on first write.
|
|
1993
|
+
*
|
|
1994
|
+
* `rootFromAddr` is the sender of the ROOT message on the
|
|
1995
|
+
* thread; on a brand-new thread this is just `env.fromAddr`,
|
|
1996
|
+
* on a reply it's read off the existing cache entry (callers
|
|
1997
|
+
* should pass the existing entry's rootFromAddr when known).
|
|
1998
|
+
*/
|
|
1999
|
+
pushMessage(threadId: string, env: CachedMessage, meta: {
|
|
2000
|
+
subject: string;
|
|
2001
|
+
rootFromAddr: string;
|
|
2002
|
+
}): ThreadCacheEntry;
|
|
2003
|
+
/** Permanently remove a thread's cache (called on [FINAL] / [DONE] / [CLOSED] / [WRAP]). */
|
|
2004
|
+
delete(threadId: string): void;
|
|
2005
|
+
/**
|
|
2006
|
+
* Render the cache as a compact text block for the wake prompt.
|
|
2007
|
+
* One line per message, newest first. Empty string when the
|
|
2008
|
+
* cache is empty — caller decides whether to suppress the
|
|
2009
|
+
* header in that case.
|
|
2010
|
+
*/
|
|
2011
|
+
renderForPrompt(entry: ThreadCacheEntry | null): string;
|
|
2012
|
+
private writeAtomic;
|
|
2013
|
+
/**
|
|
2014
|
+
* Best-effort LRU eviction. Runs at most every 256 writes (we
|
|
2015
|
+
* don't track a precise counter — `Math.random()` sampling keeps
|
|
2016
|
+
* the write path cheap). When the directory has more files than
|
|
2017
|
+
* `lruCap`, sort by mtime ascending and delete the oldest 10%.
|
|
2018
|
+
*/
|
|
2019
|
+
private maybeEvict;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
/**
|
|
2023
|
+
* Per-agent thread memory — Layer 2 of the wake-context system.
|
|
2024
|
+
*
|
|
2025
|
+
* # What it stores
|
|
2026
|
+
*
|
|
2027
|
+
* Each `(agent, thread)` tuple gets a tiny markdown file
|
|
2028
|
+
* the AGENT writes at the end of its wake. The dispatcher
|
|
2029
|
+
* doesn't write this — it's the agent's own narrative about
|
|
2030
|
+
* what THEY committed to, what's open, and the last action
|
|
2031
|
+
* they took on the thread.
|
|
2032
|
+
*
|
|
2033
|
+
* # Why it's separate from the ThreadCache
|
|
2034
|
+
*
|
|
2035
|
+
* The cache is FACTS (whoever sent what when). The memory is
|
|
2036
|
+
* JUDGMENT (what does each agent intend to do about it). Two
|
|
2037
|
+
* agents on the same thread share the cache verbatim but each
|
|
2038
|
+
* has their own memory file; what Vesper thinks she committed
|
|
2039
|
+
* to is none of Orion's business.
|
|
2040
|
+
*
|
|
2041
|
+
* # Disk layout
|
|
2042
|
+
*
|
|
2043
|
+
* ~/.agenticmail/agent-memory/<agentId>/<threadId>.md
|
|
2044
|
+
*
|
|
2045
|
+
* The path is hierarchical (per-agent dir) so cleanup on agent
|
|
2046
|
+
* deletion is `rm -rf <agentDir>` and concurrent writers don't
|
|
2047
|
+
* step on each other across agents.
|
|
2048
|
+
*
|
|
2049
|
+
* # Format
|
|
2050
|
+
*
|
|
2051
|
+
* Tiny YAML frontmatter for structured fields the dispatcher
|
|
2052
|
+
* cares about (`updated_at`, `lastUid`); free-form markdown
|
|
2053
|
+
* body for the agent's prose. The agent passes these as
|
|
2054
|
+
* separate fields on the MCP tool and we render the file
|
|
2055
|
+
* deterministically — no Markdown-in-YAML parsing nightmare.
|
|
2056
|
+
*/
|
|
2057
|
+
interface AgentMemoryFields {
|
|
2058
|
+
/** One-paragraph narrative of where the thread stands. */
|
|
2059
|
+
summary?: string;
|
|
2060
|
+
/** Things THIS agent has committed to doing. */
|
|
2061
|
+
commitments?: string[];
|
|
2062
|
+
/** Things THIS agent is waiting on / open questions. */
|
|
2063
|
+
openQuestions?: string[];
|
|
2064
|
+
/** Last action this agent took on the thread (e.g. "replied UID 41 asking for the raw counts"). */
|
|
2065
|
+
lastAction?: string;
|
|
2066
|
+
/** Last message UID this agent has digested. Used as a cursor
|
|
2067
|
+
* to detect "memory is older than the cache" on the dispatcher
|
|
2068
|
+
* side. */
|
|
2069
|
+
lastUid?: number;
|
|
2070
|
+
}
|
|
2071
|
+
interface AgentMemoryRead extends AgentMemoryFields {
|
|
2072
|
+
/** ISO timestamp of the most recent write. */
|
|
2073
|
+
updatedAt?: string;
|
|
2074
|
+
/** Raw file contents — useful for the wake prompt; rendered
|
|
2075
|
+
* verbatim into the "Your own memory" block. */
|
|
2076
|
+
raw: string;
|
|
2077
|
+
}
|
|
2078
|
+
interface AgentMemoryOptions {
|
|
2079
|
+
memoryDir?: string;
|
|
2080
|
+
}
|
|
2081
|
+
declare class AgentMemoryStore {
|
|
2082
|
+
private readonly dir;
|
|
2083
|
+
constructor(opts?: AgentMemoryOptions);
|
|
2084
|
+
private dirFor;
|
|
2085
|
+
private pathFor;
|
|
2086
|
+
read(agentId: string, threadId: string): AgentMemoryRead | null;
|
|
2087
|
+
write(agentId: string, threadId: string, fields: AgentMemoryFields): void;
|
|
2088
|
+
delete(agentId: string, threadId: string): void;
|
|
2089
|
+
/** Render an agent's memory for injection into a wake prompt.
|
|
2090
|
+
* Returns the raw markdown if present; empty string when there's
|
|
2091
|
+
* no prior memory (the caller decides whether to suppress the
|
|
2092
|
+
* whole "Your own memory" block). */
|
|
2093
|
+
renderForPrompt(memory: AgentMemoryRead | null): string;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryFields, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type CachedMessage, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, type PurchasedDomain, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizeAddress, normalizePhoneNumber, normalizeSubject, parseEmail, parseGoogleVoiceSms, recordToolCall, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge, threadIdFor };
|
package/dist/index.d.ts
CHANGED
|
@@ -1843,4 +1843,254 @@ declare class SetupManager {
|
|
|
1843
1843
|
isInitialized(): boolean;
|
|
1844
1844
|
}
|
|
1845
1845
|
|
|
1846
|
-
|
|
1846
|
+
/**
|
|
1847
|
+
* Stable thread-id derivation.
|
|
1848
|
+
*
|
|
1849
|
+
* Two messages belong to the same thread if their normalized
|
|
1850
|
+
* `(subject, root-from)` tuple matches. Computing this on demand
|
|
1851
|
+
* from envelope data is cheap and avoids depending on the IMAP
|
|
1852
|
+
* `THREAD` extension (which Stalwart doesn't advertise) or on
|
|
1853
|
+
* reconstructing `In-Reply-To` / `References` chains (which agents
|
|
1854
|
+
* sometimes forge or strip).
|
|
1855
|
+
*
|
|
1856
|
+
* # Normalization rules
|
|
1857
|
+
*
|
|
1858
|
+
* - Strip every leading `Re:` / `Fwd:` / `Re[2]:` chain. Some
|
|
1859
|
+
* clients chain prefixes (`Re: Re: Fwd: Re: …`), which would
|
|
1860
|
+
* otherwise produce a different thread id for every hop.
|
|
1861
|
+
* - Collapse internal whitespace to single spaces.
|
|
1862
|
+
* - Trim leading + trailing whitespace.
|
|
1863
|
+
* - Lower-case for case-insensitive matching.
|
|
1864
|
+
* - Reply-on-thread coordination markers (`[FINAL]`, `[DONE]`,
|
|
1865
|
+
* `[CLOSED]`, `[WRAP]`) are stripped — a closing message
|
|
1866
|
+
* belongs to the SAME thread as the conversation it closes.
|
|
1867
|
+
*
|
|
1868
|
+
* # Identity hash
|
|
1869
|
+
*
|
|
1870
|
+
* SHA-256 of `<normalizedSubject>\n<rootFromLower>`, base64url
|
|
1871
|
+
* truncated to 16 chars (~12 bytes of entropy = ~10^28 distinct
|
|
1872
|
+
* threads; collision-free for any realistic deployment).
|
|
1873
|
+
*
|
|
1874
|
+
* The root sender is included so two unrelated conversations
|
|
1875
|
+
* that share a generic subject ("hello", "follow up") aren't
|
|
1876
|
+
* collapsed into one thread. We use the FIRST sender's address
|
|
1877
|
+
* on the thread — agents reading a reply pass their own
|
|
1878
|
+
* envelope's `from` value, but the thread id stays stable
|
|
1879
|
+
* because we re-derive `rootFromAddr` from the cache when
|
|
1880
|
+
* looking up an existing thread (see thread-cache.ts).
|
|
1881
|
+
*/
|
|
1882
|
+
declare function normalizeSubject(subject: string | undefined | null): string;
|
|
1883
|
+
declare function normalizeAddress(addr: string | undefined | null): string;
|
|
1884
|
+
interface ThreadIdInput {
|
|
1885
|
+
subject?: string | null;
|
|
1886
|
+
/** Optional. Kept as a field so call sites that previously
|
|
1887
|
+
* passed it keep working, but NOT used in the hash. The thread
|
|
1888
|
+
* id is intentionally subject-only so a reply from a different
|
|
1889
|
+
* sender (the replier, not the root) still maps to the same
|
|
1890
|
+
* thread without needing a cache lookup first. The dispatcher's
|
|
1891
|
+
* legacy `threadIdFromSubject` uses the same convention; this
|
|
1892
|
+
* function is its disk-safe + hashed equivalent. */
|
|
1893
|
+
rootFromAddr?: string | null;
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Subject-only stable thread id. Collisions between unrelated
|
|
1897
|
+
* conversations that genuinely share the same normalized subject
|
|
1898
|
+
* ("hello", "follow up") are accepted as the tradeoff for stable
|
|
1899
|
+
* threading across replies. In practice agents on different
|
|
1900
|
+
* threads use different participants, so the wake-budget +
|
|
1901
|
+
* thread-close logic disambiguates downstream.
|
|
1902
|
+
*/
|
|
1903
|
+
declare function threadIdFor(input: ThreadIdInput): string;
|
|
1904
|
+
|
|
1905
|
+
/**
|
|
1906
|
+
* Per-thread message cache — Layer 1 of the wake-context system.
|
|
1907
|
+
*
|
|
1908
|
+
* # What it stores
|
|
1909
|
+
*
|
|
1910
|
+
* Each thread (keyed by the stable `threadIdFor` hash) gets a JSON
|
|
1911
|
+
* file containing the last K message envelopes that were seen on
|
|
1912
|
+
* the thread:
|
|
1913
|
+
*
|
|
1914
|
+
* { threadId, subject, rootFromAddr, lastUpdated, messages: [
|
|
1915
|
+
* { uid, from, subject, preview, date }
|
|
1916
|
+
* ]}
|
|
1917
|
+
*
|
|
1918
|
+
* # What it does NOT store
|
|
1919
|
+
*
|
|
1920
|
+
* - Full message bodies. Storing the body would multiply the cache
|
|
1921
|
+
* size 20x and most agents don't need it on rehydration — they
|
|
1922
|
+
* need to see "who said what about what" at a glance, not the
|
|
1923
|
+
* exact prose. The dedicated `read_email(uid)` MCP tool serves
|
|
1924
|
+
* the body when the agent actually wants it.
|
|
1925
|
+
*
|
|
1926
|
+
* # Lifecycle
|
|
1927
|
+
*
|
|
1928
|
+
* - Built passively: the dispatcher calls `pushMessage(t, env)` on
|
|
1929
|
+
* every SSE new-mail event for the thread, even when no agent
|
|
1930
|
+
* actually wakes. Selective-wake skips, circuit-breaker mutes,
|
|
1931
|
+
* `[FINAL]` markers — none of them prevent the cache from being
|
|
1932
|
+
* populated. The cache is always up to date.
|
|
1933
|
+
*
|
|
1934
|
+
* - Read on every spawn: `readCache(t)` is called before the
|
|
1935
|
+
* dispatcher fires a worker, and the result is rendered into
|
|
1936
|
+
* the wake prompt.
|
|
1937
|
+
*
|
|
1938
|
+
* - Pruned on close: `cleanupThread(t)` is called when the
|
|
1939
|
+
* dispatcher detects a thread-close marker. Removes the file
|
|
1940
|
+
* immediately; 7-day grace via empty-tombstone is not needed
|
|
1941
|
+
* because the agent's own memory file handles "did this
|
|
1942
|
+
* thread actually close" semantics.
|
|
1943
|
+
*
|
|
1944
|
+
* - LRU-bounded: a directory-level LRU (default 5000 threads, ~25 MB)
|
|
1945
|
+
* runs at the head of every write to keep disk usage flat.
|
|
1946
|
+
*/
|
|
1947
|
+
interface CachedMessage {
|
|
1948
|
+
uid: number;
|
|
1949
|
+
/** Display name OR raw address — whichever was on the envelope. */
|
|
1950
|
+
from: string;
|
|
1951
|
+
/** Sender's bare email (post-normalization). Same value used to
|
|
1952
|
+
* derive the thread root, so agents can spot "did I write this?"
|
|
1953
|
+
* with one equality check. */
|
|
1954
|
+
fromAddr: string;
|
|
1955
|
+
subject: string;
|
|
1956
|
+
/** Up to ~240 chars of plain-text body. */
|
|
1957
|
+
preview: string;
|
|
1958
|
+
/** ISO string. */
|
|
1959
|
+
date: string;
|
|
1960
|
+
}
|
|
1961
|
+
interface ThreadCacheEntry {
|
|
1962
|
+
threadId: string;
|
|
1963
|
+
/** First-seen normalized subject — kept on the entry so the wake
|
|
1964
|
+
* prompt can show it without re-normalizing. */
|
|
1965
|
+
subject: string;
|
|
1966
|
+
/** First-seen root sender. Used to keep `threadIdFor` stable on
|
|
1967
|
+
* subsequent replies (which carry the replier's `from`, not
|
|
1968
|
+
* the original). */
|
|
1969
|
+
rootFromAddr: string;
|
|
1970
|
+
/** ms timestamp of most recent write. Drives LRU eviction. */
|
|
1971
|
+
lastUpdated: number;
|
|
1972
|
+
/** Newest-first. We cap at K — drop oldest on push. */
|
|
1973
|
+
messages: CachedMessage[];
|
|
1974
|
+
}
|
|
1975
|
+
interface ThreadCacheOptions {
|
|
1976
|
+
/** Override the on-disk root. Mainly for tests. */
|
|
1977
|
+
cacheDir?: string;
|
|
1978
|
+
/** Newest-N messages kept per thread. */
|
|
1979
|
+
k?: number;
|
|
1980
|
+
/** Max threads on disk before LRU eviction kicks in. */
|
|
1981
|
+
lruCap?: number;
|
|
1982
|
+
}
|
|
1983
|
+
declare class ThreadCache {
|
|
1984
|
+
private readonly dir;
|
|
1985
|
+
private readonly k;
|
|
1986
|
+
private readonly lruCap;
|
|
1987
|
+
constructor(opts?: ThreadCacheOptions);
|
|
1988
|
+
private pathFor;
|
|
1989
|
+
read(threadId: string): ThreadCacheEntry | null;
|
|
1990
|
+
/**
|
|
1991
|
+
* Append a message to the thread's cache, pruning to the K
|
|
1992
|
+
* newest entries. Creates the cache entry on first write.
|
|
1993
|
+
*
|
|
1994
|
+
* `rootFromAddr` is the sender of the ROOT message on the
|
|
1995
|
+
* thread; on a brand-new thread this is just `env.fromAddr`,
|
|
1996
|
+
* on a reply it's read off the existing cache entry (callers
|
|
1997
|
+
* should pass the existing entry's rootFromAddr when known).
|
|
1998
|
+
*/
|
|
1999
|
+
pushMessage(threadId: string, env: CachedMessage, meta: {
|
|
2000
|
+
subject: string;
|
|
2001
|
+
rootFromAddr: string;
|
|
2002
|
+
}): ThreadCacheEntry;
|
|
2003
|
+
/** Permanently remove a thread's cache (called on [FINAL] / [DONE] / [CLOSED] / [WRAP]). */
|
|
2004
|
+
delete(threadId: string): void;
|
|
2005
|
+
/**
|
|
2006
|
+
* Render the cache as a compact text block for the wake prompt.
|
|
2007
|
+
* One line per message, newest first. Empty string when the
|
|
2008
|
+
* cache is empty — caller decides whether to suppress the
|
|
2009
|
+
* header in that case.
|
|
2010
|
+
*/
|
|
2011
|
+
renderForPrompt(entry: ThreadCacheEntry | null): string;
|
|
2012
|
+
private writeAtomic;
|
|
2013
|
+
/**
|
|
2014
|
+
* Best-effort LRU eviction. Runs at most every 256 writes (we
|
|
2015
|
+
* don't track a precise counter — `Math.random()` sampling keeps
|
|
2016
|
+
* the write path cheap). When the directory has more files than
|
|
2017
|
+
* `lruCap`, sort by mtime ascending and delete the oldest 10%.
|
|
2018
|
+
*/
|
|
2019
|
+
private maybeEvict;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
/**
|
|
2023
|
+
* Per-agent thread memory — Layer 2 of the wake-context system.
|
|
2024
|
+
*
|
|
2025
|
+
* # What it stores
|
|
2026
|
+
*
|
|
2027
|
+
* Each `(agent, thread)` tuple gets a tiny markdown file
|
|
2028
|
+
* the AGENT writes at the end of its wake. The dispatcher
|
|
2029
|
+
* doesn't write this — it's the agent's own narrative about
|
|
2030
|
+
* what THEY committed to, what's open, and the last action
|
|
2031
|
+
* they took on the thread.
|
|
2032
|
+
*
|
|
2033
|
+
* # Why it's separate from the ThreadCache
|
|
2034
|
+
*
|
|
2035
|
+
* The cache is FACTS (whoever sent what when). The memory is
|
|
2036
|
+
* JUDGMENT (what does each agent intend to do about it). Two
|
|
2037
|
+
* agents on the same thread share the cache verbatim but each
|
|
2038
|
+
* has their own memory file; what Vesper thinks she committed
|
|
2039
|
+
* to is none of Orion's business.
|
|
2040
|
+
*
|
|
2041
|
+
* # Disk layout
|
|
2042
|
+
*
|
|
2043
|
+
* ~/.agenticmail/agent-memory/<agentId>/<threadId>.md
|
|
2044
|
+
*
|
|
2045
|
+
* The path is hierarchical (per-agent dir) so cleanup on agent
|
|
2046
|
+
* deletion is `rm -rf <agentDir>` and concurrent writers don't
|
|
2047
|
+
* step on each other across agents.
|
|
2048
|
+
*
|
|
2049
|
+
* # Format
|
|
2050
|
+
*
|
|
2051
|
+
* Tiny YAML frontmatter for structured fields the dispatcher
|
|
2052
|
+
* cares about (`updated_at`, `lastUid`); free-form markdown
|
|
2053
|
+
* body for the agent's prose. The agent passes these as
|
|
2054
|
+
* separate fields on the MCP tool and we render the file
|
|
2055
|
+
* deterministically — no Markdown-in-YAML parsing nightmare.
|
|
2056
|
+
*/
|
|
2057
|
+
interface AgentMemoryFields {
|
|
2058
|
+
/** One-paragraph narrative of where the thread stands. */
|
|
2059
|
+
summary?: string;
|
|
2060
|
+
/** Things THIS agent has committed to doing. */
|
|
2061
|
+
commitments?: string[];
|
|
2062
|
+
/** Things THIS agent is waiting on / open questions. */
|
|
2063
|
+
openQuestions?: string[];
|
|
2064
|
+
/** Last action this agent took on the thread (e.g. "replied UID 41 asking for the raw counts"). */
|
|
2065
|
+
lastAction?: string;
|
|
2066
|
+
/** Last message UID this agent has digested. Used as a cursor
|
|
2067
|
+
* to detect "memory is older than the cache" on the dispatcher
|
|
2068
|
+
* side. */
|
|
2069
|
+
lastUid?: number;
|
|
2070
|
+
}
|
|
2071
|
+
interface AgentMemoryRead extends AgentMemoryFields {
|
|
2072
|
+
/** ISO timestamp of the most recent write. */
|
|
2073
|
+
updatedAt?: string;
|
|
2074
|
+
/** Raw file contents — useful for the wake prompt; rendered
|
|
2075
|
+
* verbatim into the "Your own memory" block. */
|
|
2076
|
+
raw: string;
|
|
2077
|
+
}
|
|
2078
|
+
interface AgentMemoryOptions {
|
|
2079
|
+
memoryDir?: string;
|
|
2080
|
+
}
|
|
2081
|
+
declare class AgentMemoryStore {
|
|
2082
|
+
private readonly dir;
|
|
2083
|
+
constructor(opts?: AgentMemoryOptions);
|
|
2084
|
+
private dirFor;
|
|
2085
|
+
private pathFor;
|
|
2086
|
+
read(agentId: string, threadId: string): AgentMemoryRead | null;
|
|
2087
|
+
write(agentId: string, threadId: string, fields: AgentMemoryFields): void;
|
|
2088
|
+
delete(agentId: string, threadId: string): void;
|
|
2089
|
+
/** Render an agent's memory for injection into a wake prompt.
|
|
2090
|
+
* Returns the raw markdown if present; empty string when there's
|
|
2091
|
+
* no prior memory (the caller decides whether to suppress the
|
|
2092
|
+
* whole "Your own memory" block). */
|
|
2093
|
+
renderForPrompt(memory: AgentMemoryRead | null): string;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryFields, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type CachedMessage, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, type PurchasedDomain, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizeAddress, normalizePhoneNumber, normalizeSubject, parseEmail, parseGoogleVoiceSms, recordToolCall, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge, threadIdFor };
|