@agenticmail/core 0.9.5 → 0.9.7

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
@@ -200,6 +200,20 @@ interface AgenticMailConfig {
200
200
  provider?: 'google_voice';
201
201
  configuredAt?: string;
202
202
  };
203
+ /**
204
+ * Operator's notification address. Used by the dispatcher's
205
+ * bridge-escalation path: when sub-agents mail a bridge inbox AND
206
+ * no fresh host session is available for a headless resume, the
207
+ * API forwards a digest to this address so the operator gets a
208
+ * phone push (via Gmail / Apple Mail / whichever app handles
209
+ * their address). Set during install — the host agent (claude /
210
+ * codex) collects it via the `setup_operator_email` MCP tool
211
+ * during bootstrap and persists it here.
212
+ *
213
+ * Optional. When unset, escalations are still recorded as a
214
+ * system event (visible in the web UI) but no email is sent.
215
+ */
216
+ operatorEmail?: string;
203
217
  masterKey: string;
204
218
  dataDir: string;
205
219
  }
@@ -1493,6 +1507,28 @@ declare class SmsManager {
1493
1507
  getSmsConfig(agentId: string): SmsConfig | null;
1494
1508
  /** Save SMS config to agent metadata */
1495
1509
  saveSmsConfig(agentId: string, config: SmsConfig): void;
1510
+ /**
1511
+ * Resolve the operator's "where do I get pinged" address from an
1512
+ * agent's SMS config. Used by the dispatcher's bridge-escalation
1513
+ * path: when sub-agents mail a bridge with no fresh host session
1514
+ * available, we email the operator a digest at this address. Their
1515
+ * phone's Gmail push notification surfaces it within seconds —
1516
+ * effectively a free, programmatic alert channel.
1517
+ *
1518
+ * Returns the configured `forwardingEmail` (the same Gmail Google
1519
+ * Voice forwards inbound SMS to, which the operator already has
1520
+ * push notifications enabled for) when SMS is configured AND
1521
+ * enabled. Returns null otherwise — caller falls through to a
1522
+ * silent log + system event.
1523
+ *
1524
+ * Why we don't try real-SMS delivery yet: Google Voice's
1525
+ * `<number>@txt.voice.google.com` email-to-SMS gateway was
1526
+ * deprecated by Google years ago. A future `carrier` field on
1527
+ * SmsConfig (Verizon vtext.com / AT&T txt.att.net / etc) will let
1528
+ * the operator opt into actual SMS, but that's a follow-up — the
1529
+ * email path already gets the operator a phone notification.
1530
+ */
1531
+ getAlertEmail(agentId: string): string | null;
1496
1532
  /** Remove SMS config from agent metadata */
1497
1533
  removeSmsConfig(agentId: string): void;
1498
1534
  /** Record an inbound SMS (parsed from email) */
@@ -1764,6 +1800,161 @@ declare function redactSecret(value: unknown): string;
1764
1800
  */
1765
1801
  declare function redactObject<T>(input: T, _depth?: number): T;
1766
1802
 
1803
+ /**
1804
+ * Operator preferences — small per-installation knobs that don't fit
1805
+ * cleanly in the bootstrap-managed `~/.agenticmail/config.json`.
1806
+ *
1807
+ * Currently just `operatorEmail` for bridge-escalation alerts. The
1808
+ * intent is a tiny mutable surface the host agent (claudecode /
1809
+ * codex) can update via MCP without touching the read-only-after-
1810
+ * bootstrap config blob.
1811
+ *
1812
+ * # Storage
1813
+ *
1814
+ * `~/.agenticmail/operator-prefs.json`:
1815
+ *
1816
+ * ```json
1817
+ * { "version": 1, "operatorEmail": "ope@gmail.com" }
1818
+ * ```
1819
+ *
1820
+ * Atomic writes (tmp + rename), tolerant of missing / corrupt
1821
+ * files (returns null and lets the caller decide). Lazy path
1822
+ * resolution so tests can override `homedir()` per-test.
1823
+ */
1824
+ /** Returns the operator's escalation email, or null if not set. */
1825
+ declare function getOperatorEmail(): string | null;
1826
+ /**
1827
+ * Set (or clear) the operator's escalation email. Pass `null` /
1828
+ * empty string to clear. Returns the canonical stored value (trimmed,
1829
+ * lowercased local-part preserved as-is per RFC 5321).
1830
+ */
1831
+ declare function setOperatorEmail(email: string | null): string | null;
1832
+ /** Exposed for tests + diagnostic CLI commands. */
1833
+ declare function operatorPrefsStoragePath(): string;
1834
+
1835
+ /**
1836
+ * Persisted host-session registry.
1837
+ *
1838
+ * # What this is for
1839
+ *
1840
+ * When a sub-agent replies into the host bridge inbox
1841
+ * (`claudecode@localhost` / `codex@localhost`), the dispatcher
1842
+ * historically had no way to react: bridges are skipped by
1843
+ * `shouldWatch` because they belong to the human operator's host CLI,
1844
+ * not to an automated worker. The mail would sit unread until the
1845
+ * operator opened their CLI again — sometimes hours later, sometimes
1846
+ * never that day.
1847
+ *
1848
+ * This module is the missing link. Every time the host's mail-hook
1849
+ * fires (on `SessionStart` / `UserPromptSubmit` / `Stop`), it captures
1850
+ * the host CLI's current `session_id` and persists it here. The
1851
+ * dispatcher can then check "what session was running last?" and
1852
+ * attempt a headless resume against it when bridge mail arrives —
1853
+ * the same way Telegram bridges (Fola, etc.) keep a session alive
1854
+ * between bursts of activity.
1855
+ *
1856
+ * # The file
1857
+ *
1858
+ * `~/.agenticmail/host-sessions.json`:
1859
+ *
1860
+ * ```json
1861
+ * {
1862
+ * "version": 1,
1863
+ * "sessions": {
1864
+ * "claudecode": {
1865
+ * "sessionId": "01a2b3c4-…",
1866
+ * "workspace": "/Users/ope/Desktop/facebook-project",
1867
+ * "lastSeenMs": 1778905200000,
1868
+ * "model": "claude-sonnet-4-5"
1869
+ * },
1870
+ * "codex": {
1871
+ * "sessionId": "019a2b3c-…",
1872
+ * "workspace": "/Users/ope/Desktop/facebook-project",
1873
+ * "lastSeenMs": 1778905100000
1874
+ * }
1875
+ * }
1876
+ * }
1877
+ * ```
1878
+ *
1879
+ * Per host we keep ONE session — the most recent. If the operator
1880
+ * runs `claude` twice (e.g. one window for "general", one for "build
1881
+ * the LinkedIn clone"), we only track the last-active one. The
1882
+ * bridge resume always targets whichever was active most recently;
1883
+ * the operator's intuition matches that ("I just left my Claude
1884
+ * Code session a minute ago and the bridge ought to be able to
1885
+ * resume it").
1886
+ *
1887
+ * # Freshness semantics
1888
+ *
1889
+ * A session is considered "resumable" if `lastSeenMs` is within the
1890
+ * last 24 hours. Older than that, both Anthropic and OpenAI tend to
1891
+ * have evicted the session from their resume cache (cost-driven
1892
+ * decision; observed empirically). The dispatcher uses
1893
+ * `isSessionFresh(session, maxAgeMs)` to gate the resume attempt and
1894
+ * fall through to SMS / persistent storage when stale.
1895
+ *
1896
+ * # Atomic writes
1897
+ *
1898
+ * The mail-hook can fire concurrently with another hook on a
1899
+ * different host CLI window. The write path goes through a tmp file
1900
+ * + rename so a torn write can never leave a half-valid JSON file
1901
+ * that crashes the next reader. Same shape as `dispatcher-state.ts`.
1902
+ */
1903
+ /** Canonical names for the host integrations that own bridge inboxes. */
1904
+ type HostName = 'claudecode' | 'codex';
1905
+ /**
1906
+ * A snapshot of one host CLI's last-known session. Persisted to disk
1907
+ * by the mail-hook on every fire; loaded by the dispatcher when
1908
+ * bridge mail arrives so a resume can be attempted.
1909
+ */
1910
+ interface HostSession {
1911
+ /** Stable session_id from the host CLI (Claude Code or Codex). */
1912
+ sessionId: string;
1913
+ /** Wall-clock timestamp of the last hook fire on this session. */
1914
+ lastSeenMs: number;
1915
+ /** Optional: project cwd the host CLI was opened in. Used by
1916
+ * resume to spawn the headless turn in the right directory. */
1917
+ workspace?: string;
1918
+ /** Optional: model name the host session was using, surfaced
1919
+ * in logs for diagnostic context. */
1920
+ model?: string;
1921
+ }
1922
+ /** Default freshness window — sessions older than this are skipped. */
1923
+ declare const DEFAULT_SESSION_MAX_AGE_MS: number;
1924
+ /**
1925
+ * Record the current host session. Called from the mail-hook on
1926
+ * every fire with the `session_id` the host passed in. Updates the
1927
+ * existing record for that host or creates one — last write wins.
1928
+ *
1929
+ * Failures are swallowed so the mail-hook never crashes the host CLI
1930
+ * over a disk write. The bridge-wake path tolerates a missing record
1931
+ * (falls through to SMS).
1932
+ */
1933
+ declare function saveHostSession(host: HostName, session: Omit<HostSession, 'lastSeenMs'>): void;
1934
+ /**
1935
+ * Look up the most-recent session for a host. Returns null when no
1936
+ * record exists OR the record is older than `maxAgeMs` (default 24h).
1937
+ *
1938
+ * The age gate exists because both providers expire resume tokens
1939
+ * after roughly a day; attempting resume against a stale token is
1940
+ * almost always slower than starting fresh.
1941
+ */
1942
+ declare function loadHostSession(host: HostName, maxAgeMs?: number): HostSession | null;
1943
+ /**
1944
+ * Pure freshness predicate — exported for tests + for callers that
1945
+ * want to read the raw record (e.g. to print "last seen 2 hours ago"
1946
+ * in a diagnostic command).
1947
+ */
1948
+ declare function isSessionFresh(session: HostSession, maxAgeMs?: number): boolean;
1949
+ /**
1950
+ * Clear a host's recorded session. Called when the operator runs
1951
+ * `agenticmail-<host> uninstall` so the next install doesn't try to
1952
+ * resume a session that no longer exists.
1953
+ */
1954
+ declare function forgetHostSession(host: HostName): void;
1955
+ /** Exposed for tests + the `agenticmail status` diagnostic command. */
1956
+ declare function hostSessionStoragePath(): string;
1957
+
1767
1958
  /**
1768
1959
  * SSRF-safe URL validation for the AgenticMail API base URL.
1769
1960
  *
@@ -2371,4 +2562,4 @@ declare class AgentMemoryStore {
2371
2562
  renderForPrompt(memory: AgentMemoryRead | null): string;
2372
2563
  }
2373
2564
 
2374
- 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, PathTraversalError, type PurchasedDomain, REDACTED, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SafeJoinOptions, 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, UnsafeApiUrlError, WARNING_THRESHOLD, type WatcherOptions, assertWithinBase, buildApiUrl, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizeAddress, normalizePhoneNumber, normalizeSubject, parseEmail, parseGoogleVoiceSms, recordToolCall, redactObject, redactSecret, resolveConfig, safeJoin, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge, threadIdFor, tryJoin, validateApiUrl };
2565
+ 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, DEFAULT_SESSION_MAX_AGE_MS, 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 HostName, type HostSession, 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, PathTraversalError, type PurchasedDomain, REDACTED, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SafeJoinOptions, 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, UnsafeApiUrlError, WARNING_THRESHOLD, type WatcherOptions, assertWithinBase, buildApiUrl, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, forgetHostSession, getDatabase, getOperatorEmail, hostSessionStoragePath, isInternalEmail, isSessionFresh, isValidPhoneNumber, loadHostSession, normalizeAddress, normalizePhoneNumber, normalizeSubject, operatorPrefsStoragePath, parseEmail, parseGoogleVoiceSms, recordToolCall, redactObject, redactSecret, resolveConfig, safeJoin, sanitizeEmail, saveConfig, saveHostSession, scanOutboundEmail, scoreEmail, setOperatorEmail, setTelemetryVersion, startRelayBridge, threadIdFor, tryJoin, validateApiUrl };
package/dist/index.d.ts CHANGED
@@ -200,6 +200,20 @@ interface AgenticMailConfig {
200
200
  provider?: 'google_voice';
201
201
  configuredAt?: string;
202
202
  };
203
+ /**
204
+ * Operator's notification address. Used by the dispatcher's
205
+ * bridge-escalation path: when sub-agents mail a bridge inbox AND
206
+ * no fresh host session is available for a headless resume, the
207
+ * API forwards a digest to this address so the operator gets a
208
+ * phone push (via Gmail / Apple Mail / whichever app handles
209
+ * their address). Set during install — the host agent (claude /
210
+ * codex) collects it via the `setup_operator_email` MCP tool
211
+ * during bootstrap and persists it here.
212
+ *
213
+ * Optional. When unset, escalations are still recorded as a
214
+ * system event (visible in the web UI) but no email is sent.
215
+ */
216
+ operatorEmail?: string;
203
217
  masterKey: string;
204
218
  dataDir: string;
205
219
  }
@@ -1493,6 +1507,28 @@ declare class SmsManager {
1493
1507
  getSmsConfig(agentId: string): SmsConfig | null;
1494
1508
  /** Save SMS config to agent metadata */
1495
1509
  saveSmsConfig(agentId: string, config: SmsConfig): void;
1510
+ /**
1511
+ * Resolve the operator's "where do I get pinged" address from an
1512
+ * agent's SMS config. Used by the dispatcher's bridge-escalation
1513
+ * path: when sub-agents mail a bridge with no fresh host session
1514
+ * available, we email the operator a digest at this address. Their
1515
+ * phone's Gmail push notification surfaces it within seconds —
1516
+ * effectively a free, programmatic alert channel.
1517
+ *
1518
+ * Returns the configured `forwardingEmail` (the same Gmail Google
1519
+ * Voice forwards inbound SMS to, which the operator already has
1520
+ * push notifications enabled for) when SMS is configured AND
1521
+ * enabled. Returns null otherwise — caller falls through to a
1522
+ * silent log + system event.
1523
+ *
1524
+ * Why we don't try real-SMS delivery yet: Google Voice's
1525
+ * `<number>@txt.voice.google.com` email-to-SMS gateway was
1526
+ * deprecated by Google years ago. A future `carrier` field on
1527
+ * SmsConfig (Verizon vtext.com / AT&T txt.att.net / etc) will let
1528
+ * the operator opt into actual SMS, but that's a follow-up — the
1529
+ * email path already gets the operator a phone notification.
1530
+ */
1531
+ getAlertEmail(agentId: string): string | null;
1496
1532
  /** Remove SMS config from agent metadata */
1497
1533
  removeSmsConfig(agentId: string): void;
1498
1534
  /** Record an inbound SMS (parsed from email) */
@@ -1764,6 +1800,161 @@ declare function redactSecret(value: unknown): string;
1764
1800
  */
1765
1801
  declare function redactObject<T>(input: T, _depth?: number): T;
1766
1802
 
1803
+ /**
1804
+ * Operator preferences — small per-installation knobs that don't fit
1805
+ * cleanly in the bootstrap-managed `~/.agenticmail/config.json`.
1806
+ *
1807
+ * Currently just `operatorEmail` for bridge-escalation alerts. The
1808
+ * intent is a tiny mutable surface the host agent (claudecode /
1809
+ * codex) can update via MCP without touching the read-only-after-
1810
+ * bootstrap config blob.
1811
+ *
1812
+ * # Storage
1813
+ *
1814
+ * `~/.agenticmail/operator-prefs.json`:
1815
+ *
1816
+ * ```json
1817
+ * { "version": 1, "operatorEmail": "ope@gmail.com" }
1818
+ * ```
1819
+ *
1820
+ * Atomic writes (tmp + rename), tolerant of missing / corrupt
1821
+ * files (returns null and lets the caller decide). Lazy path
1822
+ * resolution so tests can override `homedir()` per-test.
1823
+ */
1824
+ /** Returns the operator's escalation email, or null if not set. */
1825
+ declare function getOperatorEmail(): string | null;
1826
+ /**
1827
+ * Set (or clear) the operator's escalation email. Pass `null` /
1828
+ * empty string to clear. Returns the canonical stored value (trimmed,
1829
+ * lowercased local-part preserved as-is per RFC 5321).
1830
+ */
1831
+ declare function setOperatorEmail(email: string | null): string | null;
1832
+ /** Exposed for tests + diagnostic CLI commands. */
1833
+ declare function operatorPrefsStoragePath(): string;
1834
+
1835
+ /**
1836
+ * Persisted host-session registry.
1837
+ *
1838
+ * # What this is for
1839
+ *
1840
+ * When a sub-agent replies into the host bridge inbox
1841
+ * (`claudecode@localhost` / `codex@localhost`), the dispatcher
1842
+ * historically had no way to react: bridges are skipped by
1843
+ * `shouldWatch` because they belong to the human operator's host CLI,
1844
+ * not to an automated worker. The mail would sit unread until the
1845
+ * operator opened their CLI again — sometimes hours later, sometimes
1846
+ * never that day.
1847
+ *
1848
+ * This module is the missing link. Every time the host's mail-hook
1849
+ * fires (on `SessionStart` / `UserPromptSubmit` / `Stop`), it captures
1850
+ * the host CLI's current `session_id` and persists it here. The
1851
+ * dispatcher can then check "what session was running last?" and
1852
+ * attempt a headless resume against it when bridge mail arrives —
1853
+ * the same way Telegram bridges (Fola, etc.) keep a session alive
1854
+ * between bursts of activity.
1855
+ *
1856
+ * # The file
1857
+ *
1858
+ * `~/.agenticmail/host-sessions.json`:
1859
+ *
1860
+ * ```json
1861
+ * {
1862
+ * "version": 1,
1863
+ * "sessions": {
1864
+ * "claudecode": {
1865
+ * "sessionId": "01a2b3c4-…",
1866
+ * "workspace": "/Users/ope/Desktop/facebook-project",
1867
+ * "lastSeenMs": 1778905200000,
1868
+ * "model": "claude-sonnet-4-5"
1869
+ * },
1870
+ * "codex": {
1871
+ * "sessionId": "019a2b3c-…",
1872
+ * "workspace": "/Users/ope/Desktop/facebook-project",
1873
+ * "lastSeenMs": 1778905100000
1874
+ * }
1875
+ * }
1876
+ * }
1877
+ * ```
1878
+ *
1879
+ * Per host we keep ONE session — the most recent. If the operator
1880
+ * runs `claude` twice (e.g. one window for "general", one for "build
1881
+ * the LinkedIn clone"), we only track the last-active one. The
1882
+ * bridge resume always targets whichever was active most recently;
1883
+ * the operator's intuition matches that ("I just left my Claude
1884
+ * Code session a minute ago and the bridge ought to be able to
1885
+ * resume it").
1886
+ *
1887
+ * # Freshness semantics
1888
+ *
1889
+ * A session is considered "resumable" if `lastSeenMs` is within the
1890
+ * last 24 hours. Older than that, both Anthropic and OpenAI tend to
1891
+ * have evicted the session from their resume cache (cost-driven
1892
+ * decision; observed empirically). The dispatcher uses
1893
+ * `isSessionFresh(session, maxAgeMs)` to gate the resume attempt and
1894
+ * fall through to SMS / persistent storage when stale.
1895
+ *
1896
+ * # Atomic writes
1897
+ *
1898
+ * The mail-hook can fire concurrently with another hook on a
1899
+ * different host CLI window. The write path goes through a tmp file
1900
+ * + rename so a torn write can never leave a half-valid JSON file
1901
+ * that crashes the next reader. Same shape as `dispatcher-state.ts`.
1902
+ */
1903
+ /** Canonical names for the host integrations that own bridge inboxes. */
1904
+ type HostName = 'claudecode' | 'codex';
1905
+ /**
1906
+ * A snapshot of one host CLI's last-known session. Persisted to disk
1907
+ * by the mail-hook on every fire; loaded by the dispatcher when
1908
+ * bridge mail arrives so a resume can be attempted.
1909
+ */
1910
+ interface HostSession {
1911
+ /** Stable session_id from the host CLI (Claude Code or Codex). */
1912
+ sessionId: string;
1913
+ /** Wall-clock timestamp of the last hook fire on this session. */
1914
+ lastSeenMs: number;
1915
+ /** Optional: project cwd the host CLI was opened in. Used by
1916
+ * resume to spawn the headless turn in the right directory. */
1917
+ workspace?: string;
1918
+ /** Optional: model name the host session was using, surfaced
1919
+ * in logs for diagnostic context. */
1920
+ model?: string;
1921
+ }
1922
+ /** Default freshness window — sessions older than this are skipped. */
1923
+ declare const DEFAULT_SESSION_MAX_AGE_MS: number;
1924
+ /**
1925
+ * Record the current host session. Called from the mail-hook on
1926
+ * every fire with the `session_id` the host passed in. Updates the
1927
+ * existing record for that host or creates one — last write wins.
1928
+ *
1929
+ * Failures are swallowed so the mail-hook never crashes the host CLI
1930
+ * over a disk write. The bridge-wake path tolerates a missing record
1931
+ * (falls through to SMS).
1932
+ */
1933
+ declare function saveHostSession(host: HostName, session: Omit<HostSession, 'lastSeenMs'>): void;
1934
+ /**
1935
+ * Look up the most-recent session for a host. Returns null when no
1936
+ * record exists OR the record is older than `maxAgeMs` (default 24h).
1937
+ *
1938
+ * The age gate exists because both providers expire resume tokens
1939
+ * after roughly a day; attempting resume against a stale token is
1940
+ * almost always slower than starting fresh.
1941
+ */
1942
+ declare function loadHostSession(host: HostName, maxAgeMs?: number): HostSession | null;
1943
+ /**
1944
+ * Pure freshness predicate — exported for tests + for callers that
1945
+ * want to read the raw record (e.g. to print "last seen 2 hours ago"
1946
+ * in a diagnostic command).
1947
+ */
1948
+ declare function isSessionFresh(session: HostSession, maxAgeMs?: number): boolean;
1949
+ /**
1950
+ * Clear a host's recorded session. Called when the operator runs
1951
+ * `agenticmail-<host> uninstall` so the next install doesn't try to
1952
+ * resume a session that no longer exists.
1953
+ */
1954
+ declare function forgetHostSession(host: HostName): void;
1955
+ /** Exposed for tests + the `agenticmail status` diagnostic command. */
1956
+ declare function hostSessionStoragePath(): string;
1957
+
1767
1958
  /**
1768
1959
  * SSRF-safe URL validation for the AgenticMail API base URL.
1769
1960
  *
@@ -2371,4 +2562,4 @@ declare class AgentMemoryStore {
2371
2562
  renderForPrompt(memory: AgentMemoryRead | null): string;
2372
2563
  }
2373
2564
 
2374
- 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, PathTraversalError, type PurchasedDomain, REDACTED, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SafeJoinOptions, 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, UnsafeApiUrlError, WARNING_THRESHOLD, type WatcherOptions, assertWithinBase, buildApiUrl, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizeAddress, normalizePhoneNumber, normalizeSubject, parseEmail, parseGoogleVoiceSms, recordToolCall, redactObject, redactSecret, resolveConfig, safeJoin, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge, threadIdFor, tryJoin, validateApiUrl };
2565
+ 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, DEFAULT_SESSION_MAX_AGE_MS, 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 HostName, type HostSession, 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, PathTraversalError, type PurchasedDomain, REDACTED, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SafeJoinOptions, 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, UnsafeApiUrlError, WARNING_THRESHOLD, type WatcherOptions, assertWithinBase, buildApiUrl, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, forgetHostSession, getDatabase, getOperatorEmail, hostSessionStoragePath, isInternalEmail, isSessionFresh, isValidPhoneNumber, loadHostSession, normalizeAddress, normalizePhoneNumber, normalizeSubject, operatorPrefsStoragePath, parseEmail, parseGoogleVoiceSms, recordToolCall, redactObject, redactSecret, resolveConfig, safeJoin, sanitizeEmail, saveConfig, saveHostSession, scanOutboundEmail, scoreEmail, setOperatorEmail, setTelemetryVersion, startRelayBridge, threadIdFor, tryJoin, validateApiUrl };