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