@agenticmail/core 0.9.4 → 0.9.6

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.ts CHANGED
@@ -1583,6 +1583,377 @@ declare function flushTelemetry(): void;
1583
1583
  declare function debug(tag: string, message: string): void;
1584
1584
  declare function debugWarn(tag: string, message: string): void;
1585
1585
 
1586
+ /**
1587
+ * Path-traversal safe filesystem joiner.
1588
+ *
1589
+ * # Why this exists
1590
+ *
1591
+ * Every host-integration installer writes files into a directory
1592
+ * derived from operator config / env vars (`CODEX_HOME`,
1593
+ * `CLAUDE_CODE_AGENTS_DIR`, etc) AND builds filenames from values
1594
+ * returned by the AgenticMail API (account names, agent metadata).
1595
+ * Both inputs cross trust boundaries:
1596
+ *
1597
+ * - The operator's env vars are mostly trusted — but the operator
1598
+ * could fat-finger a relative path that resolves outside the
1599
+ * intended dir, or paste a path containing `..` segments.
1600
+ * - The AgenticMail API responses cross the master-key boundary.
1601
+ * A compromised MCP session or stolen master key could create an
1602
+ * account whose `name` contains `../../etc/something` to escape
1603
+ * the agents directory at install time.
1604
+ *
1605
+ * The existing `sanitizeSubagentName` helpers filter most malicious
1606
+ * names, but defence-in-depth dictates a second boundary check at
1607
+ * the actual `fs.writeFile` call. This module is that second check.
1608
+ *
1609
+ * # API
1610
+ *
1611
+ * `safeJoin(baseDir, ...parts)` resolves the parts under `baseDir`
1612
+ * and throws `PathTraversalError` if the resulting absolute path
1613
+ * escapes `baseDir`. Use it instead of `path.join(baseDir, ...)` in
1614
+ * every code path that mixes operator config + user-provided
1615
+ * filenames.
1616
+ *
1617
+ * # Why this idiom (resolve + boundary check)
1618
+ *
1619
+ * - `path.resolve` normalises `..` segments deterministically.
1620
+ * - The startsWith check rejects any normalised path that climbed
1621
+ * out of `baseDir`.
1622
+ * - CodeQL's `js/path-injection` query recognises this exact shape
1623
+ * as a sanitizer, so the static-analysis warning is resolved at
1624
+ * the same time the runtime risk is closed.
1625
+ *
1626
+ * # Edge cases handled
1627
+ *
1628
+ * - `parts` containing `..` segments → throws.
1629
+ * - `parts` containing an absolute path → the absolute path wins
1630
+ * over `baseDir` per POSIX rules, which is almost always wrong
1631
+ * when the caller meant "filename inside baseDir". We reject
1632
+ * absolute segments unless `allowAbsolute: true` is set.
1633
+ * - `baseDir` itself is not normalised before comparison: pass an
1634
+ * already-resolved absolute path. The helper asserts this.
1635
+ * - Symlinks are NOT resolved. If you need symlink-safe joining,
1636
+ * wrap with `fs.realpath` separately. For our installers we
1637
+ * don't follow symlinks at all (we always do explicit
1638
+ * `existsSync` + `writeFileSync`), so this is fine.
1639
+ *
1640
+ * # Examples
1641
+ *
1642
+ * ```ts
1643
+ * // Safe: resolves to /home/ope/.codex/agents/agenticmail-fola.toml
1644
+ * safeJoin('/home/ope/.codex/agents', 'agenticmail-fola.toml');
1645
+ *
1646
+ * // Throws: '../etc/passwd' resolves outside the base dir
1647
+ * safeJoin('/home/ope/.codex/agents', '../etc/passwd');
1648
+ *
1649
+ * // Throws: an absolute path bypasses the base dir
1650
+ * safeJoin('/home/ope/.codex/agents', '/etc/passwd');
1651
+ * ```
1652
+ */
1653
+
1654
+ /**
1655
+ * Thrown when a `safeJoin` call would resolve to a path outside the
1656
+ * provided base directory. Carries the offending inputs for logging
1657
+ * (with the resolved path elided so we don't leak filesystem layout
1658
+ * to a remote attacker via error messages).
1659
+ */
1660
+ declare class PathTraversalError extends Error {
1661
+ readonly baseDir: string;
1662
+ readonly parts: string[];
1663
+ constructor(baseDir: string, parts: string[]);
1664
+ }
1665
+ interface SafeJoinOptions {
1666
+ /**
1667
+ * Allow segments that are themselves absolute paths. Off by default
1668
+ * because passing an absolute path to `path.join` discards the
1669
+ * base directory — almost always a bug at our call sites.
1670
+ */
1671
+ allowAbsolute?: boolean;
1672
+ }
1673
+ /**
1674
+ * Resolve `parts` under `baseDir`, asserting the result stays inside
1675
+ * `baseDir`. See module docstring for the rationale.
1676
+ *
1677
+ * @throws `PathTraversalError` if any segment is absolute (and
1678
+ * `allowAbsolute` is not set), or if the resolved path escapes
1679
+ * `baseDir`.
1680
+ */
1681
+ declare function safeJoin(baseDir: string, ...partsAndOpts: (string | SafeJoinOptions)[]): string;
1682
+ /**
1683
+ * Convenience: same as `safeJoin` but returns `null` instead of
1684
+ * throwing on a traversal attempt. Use in code paths where a single
1685
+ * malicious filename should be skipped, not propagated up as an
1686
+ * exception (e.g. iterating `readdirSync` results during cleanup).
1687
+ */
1688
+ declare function tryJoin(baseDir: string, ...parts: string[]): string | null;
1689
+ /**
1690
+ * Validate that a candidate path is already absolute and stays inside
1691
+ * `baseDir`. Use when receiving a path from external config that the
1692
+ * caller wants to treat as already-canonical (e.g. an env var that
1693
+ * names a project directory) — the helper asserts the safety
1694
+ * properties without further joining.
1695
+ */
1696
+ declare function assertWithinBase(baseDir: string, candidate: string): string;
1697
+
1698
+ /**
1699
+ * Secret-redaction helpers for log lines and diagnostic output.
1700
+ *
1701
+ * # Why this exists
1702
+ *
1703
+ * AgenticMail keeps three kinds of long-lived secrets in memory:
1704
+ *
1705
+ * 1. The master key (`mk_…`) — full admin scope on the local API.
1706
+ * 2. Per-agent API keys (`ak_…`) — scoped to one agent's mailbox.
1707
+ * 3. Stalwart's admin password and per-agent IMAP/SMTP passwords.
1708
+ *
1709
+ * All three flow through config objects, install results, and the
1710
+ * MCP-server env block. Several of those objects get logged for
1711
+ * diagnostic purposes (install completion summary, dispatcher
1712
+ * startup, error stacks). CodeQL's `js/clear-text-logging` query
1713
+ * flagged eight call sites where a secret could plausibly reach a
1714
+ * log line. This module is the canonical sanitizer for those spots.
1715
+ *
1716
+ * # API
1717
+ *
1718
+ * - `redactSecret(value)` collapses a string secret to a
1719
+ * fixed-shape redaction marker (`mk_***`, `ak_***`, `***`)
1720
+ * while preserving the prefix so the log reader can still tell
1721
+ * the kind of secret it was.
1722
+ * - `redactObject(obj)` walks an object and replaces every value
1723
+ * under a "sensitive-looking" key with REDACTED.
1724
+ * - `REDACTED` constant for cases where the caller wants to
1725
+ * compose their own log line.
1726
+ *
1727
+ * # Conservative match
1728
+ *
1729
+ * The "sensitive-looking" key detection is deliberately broad —
1730
+ * it's better to over-redact a harmless `name` field that happens
1731
+ * to contain "key" than to leak a secret. The currently-matched
1732
+ * key names (case-insensitive substring): `key`, `secret`,
1733
+ * `password`, `token`, `apikey`, `masterkey`, `authorization`,
1734
+ * `bearer`.
1735
+ */
1736
+ /** Returned in place of any redacted secret. */
1737
+ declare const REDACTED = "***";
1738
+ /**
1739
+ * Redact a string secret to a fixed-shape marker. Preserves the
1740
+ * known prefixes (`mk_`, `ak_`) so log readers can still tell what
1741
+ * kind of secret was elided.
1742
+ *
1743
+ * Non-string inputs return REDACTED unchanged.
1744
+ */
1745
+ declare function redactSecret(value: unknown): string;
1746
+ /**
1747
+ * Walk an object recursively. Any value under a key that looks
1748
+ * sensitive (see SENSITIVE_KEY_PATTERNS above) is replaced with
1749
+ * `REDACTED`. Arrays are walked too. The original object is NOT
1750
+ * mutated — returns a shallow-cloned tree with the same shape.
1751
+ *
1752
+ * Use this when you want to pass a whole config object into a log
1753
+ * line via JSON.stringify, e.g.:
1754
+ *
1755
+ * ```ts
1756
+ * log.info('install complete', redactObject(result));
1757
+ * ```
1758
+ *
1759
+ * Edge cases:
1760
+ * - Cycles are NOT supported; pass tree-shaped data only.
1761
+ * - Non-plain objects (Map, Set, Buffer, Date, Error) are returned
1762
+ * by reference unchanged — redaction is for plain config dicts.
1763
+ * - Symbols are skipped.
1764
+ */
1765
+ declare function redactObject<T>(input: T, _depth?: number): T;
1766
+
1767
+ /**
1768
+ * Persisted host-session registry.
1769
+ *
1770
+ * # What this is for
1771
+ *
1772
+ * When a sub-agent replies into the host bridge inbox
1773
+ * (`claudecode@localhost` / `codex@localhost`), the dispatcher
1774
+ * historically had no way to react: bridges are skipped by
1775
+ * `shouldWatch` because they belong to the human operator's host CLI,
1776
+ * not to an automated worker. The mail would sit unread until the
1777
+ * operator opened their CLI again — sometimes hours later, sometimes
1778
+ * never that day.
1779
+ *
1780
+ * This module is the missing link. Every time the host's mail-hook
1781
+ * fires (on `SessionStart` / `UserPromptSubmit` / `Stop`), it captures
1782
+ * the host CLI's current `session_id` and persists it here. The
1783
+ * dispatcher can then check "what session was running last?" and
1784
+ * attempt a headless resume against it when bridge mail arrives —
1785
+ * the same way Telegram bridges (Fola, etc.) keep a session alive
1786
+ * between bursts of activity.
1787
+ *
1788
+ * # The file
1789
+ *
1790
+ * `~/.agenticmail/host-sessions.json`:
1791
+ *
1792
+ * ```json
1793
+ * {
1794
+ * "version": 1,
1795
+ * "sessions": {
1796
+ * "claudecode": {
1797
+ * "sessionId": "01a2b3c4-…",
1798
+ * "workspace": "/Users/ope/Desktop/facebook-project",
1799
+ * "lastSeenMs": 1778905200000,
1800
+ * "model": "claude-sonnet-4-5"
1801
+ * },
1802
+ * "codex": {
1803
+ * "sessionId": "019a2b3c-…",
1804
+ * "workspace": "/Users/ope/Desktop/facebook-project",
1805
+ * "lastSeenMs": 1778905100000
1806
+ * }
1807
+ * }
1808
+ * }
1809
+ * ```
1810
+ *
1811
+ * Per host we keep ONE session — the most recent. If the operator
1812
+ * runs `claude` twice (e.g. one window for "general", one for "build
1813
+ * the LinkedIn clone"), we only track the last-active one. The
1814
+ * bridge resume always targets whichever was active most recently;
1815
+ * the operator's intuition matches that ("I just left my Claude
1816
+ * Code session a minute ago and the bridge ought to be able to
1817
+ * resume it").
1818
+ *
1819
+ * # Freshness semantics
1820
+ *
1821
+ * A session is considered "resumable" if `lastSeenMs` is within the
1822
+ * last 24 hours. Older than that, both Anthropic and OpenAI tend to
1823
+ * have evicted the session from their resume cache (cost-driven
1824
+ * decision; observed empirically). The dispatcher uses
1825
+ * `isSessionFresh(session, maxAgeMs)` to gate the resume attempt and
1826
+ * fall through to SMS / persistent storage when stale.
1827
+ *
1828
+ * # Atomic writes
1829
+ *
1830
+ * The mail-hook can fire concurrently with another hook on a
1831
+ * different host CLI window. The write path goes through a tmp file
1832
+ * + rename so a torn write can never leave a half-valid JSON file
1833
+ * that crashes the next reader. Same shape as `dispatcher-state.ts`.
1834
+ */
1835
+ /** Canonical names for the host integrations that own bridge inboxes. */
1836
+ type HostName = 'claudecode' | 'codex';
1837
+ /**
1838
+ * A snapshot of one host CLI's last-known session. Persisted to disk
1839
+ * by the mail-hook on every fire; loaded by the dispatcher when
1840
+ * bridge mail arrives so a resume can be attempted.
1841
+ */
1842
+ interface HostSession {
1843
+ /** Stable session_id from the host CLI (Claude Code or Codex). */
1844
+ sessionId: string;
1845
+ /** Wall-clock timestamp of the last hook fire on this session. */
1846
+ lastSeenMs: number;
1847
+ /** Optional: project cwd the host CLI was opened in. Used by
1848
+ * resume to spawn the headless turn in the right directory. */
1849
+ workspace?: string;
1850
+ /** Optional: model name the host session was using, surfaced
1851
+ * in logs for diagnostic context. */
1852
+ model?: string;
1853
+ }
1854
+ /** Default freshness window — sessions older than this are skipped. */
1855
+ declare const DEFAULT_SESSION_MAX_AGE_MS: number;
1856
+ /**
1857
+ * Record the current host session. Called from the mail-hook on
1858
+ * every fire with the `session_id` the host passed in. Updates the
1859
+ * existing record for that host or creates one — last write wins.
1860
+ *
1861
+ * Failures are swallowed so the mail-hook never crashes the host CLI
1862
+ * over a disk write. The bridge-wake path tolerates a missing record
1863
+ * (falls through to SMS).
1864
+ */
1865
+ declare function saveHostSession(host: HostName, session: Omit<HostSession, 'lastSeenMs'>): void;
1866
+ /**
1867
+ * Look up the most-recent session for a host. Returns null when no
1868
+ * record exists OR the record is older than `maxAgeMs` (default 24h).
1869
+ *
1870
+ * The age gate exists because both providers expire resume tokens
1871
+ * after roughly a day; attempting resume against a stale token is
1872
+ * almost always slower than starting fresh.
1873
+ */
1874
+ declare function loadHostSession(host: HostName, maxAgeMs?: number): HostSession | null;
1875
+ /**
1876
+ * Pure freshness predicate — exported for tests + for callers that
1877
+ * want to read the raw record (e.g. to print "last seen 2 hours ago"
1878
+ * in a diagnostic command).
1879
+ */
1880
+ declare function isSessionFresh(session: HostSession, maxAgeMs?: number): boolean;
1881
+ /**
1882
+ * Clear a host's recorded session. Called when the operator runs
1883
+ * `agenticmail-<host> uninstall` so the next install doesn't try to
1884
+ * resume a session that no longer exists.
1885
+ */
1886
+ declare function forgetHostSession(host: HostName): void;
1887
+ /** Exposed for tests + the `agenticmail status` diagnostic command. */
1888
+ declare function hostSessionStoragePath(): string;
1889
+
1890
+ /**
1891
+ * SSRF-safe URL validation for the AgenticMail API base URL.
1892
+ *
1893
+ * # What this is for
1894
+ *
1895
+ * Every host integration (claudecode, codex, …) reads the master API
1896
+ * URL from `~/.agenticmail/config.json` and uses it to build fetch
1897
+ * requests. CodeQL's `js/request-forgery` query flags those fetch
1898
+ * calls because the URL is operator-controlled (via the config file
1899
+ * or env vars) and could theoretically be redirected to an internal
1900
+ * service the host process can reach but the operator didn't intend.
1901
+ *
1902
+ * In practice the operator controls their own config file — there's
1903
+ * no remote attacker controlling apiUrl in a self-hosted install.
1904
+ * But defence-in-depth dictates we still validate, because:
1905
+ *
1906
+ * - A malicious npm package that planted an env var (e.g.
1907
+ * `AGENTICMAIL_API_URL=http://169.254.169.254/latest/meta-data/`)
1908
+ * could redirect the dispatcher's API calls to a cloud-metadata
1909
+ * endpoint and exfiltrate IAM credentials in the response body.
1910
+ * - A `file://` or `javascript:` scheme would let the operator's
1911
+ * own typo cause undefined behavior.
1912
+ *
1913
+ * # The check
1914
+ *
1915
+ * `validateApiUrl(url)` rejects:
1916
+ *
1917
+ * - Non-`http(s)://` schemes (`file://`, `javascript:`, `data:`,
1918
+ * `ftp://`, etc).
1919
+ * - Cloud metadata IPs (169.254.169.254 — AWS/Azure/GCP) and the
1920
+ * IPv6 equivalent fd00:ec2::254.
1921
+ * - Empty / malformed URLs (via `new URL` parse).
1922
+ *
1923
+ * The check intentionally does NOT restrict to localhost — operators
1924
+ * legitimately run AgenticMail on a NAS or remote VM and point the
1925
+ * host-integration installs at it. The cloud-metadata IPs are the
1926
+ * only blanket block.
1927
+ *
1928
+ * # Why a separate canonicalisation step
1929
+ *
1930
+ * Returning the URL via `url.origin` (rather than the raw input
1931
+ * string) gives CodeQL a sanitiser shape it recognises: the value
1932
+ * is now constrained to whatever `URL` parsed it into, with no
1933
+ * embedded credentials, query strings, or path traversal.
1934
+ */
1935
+ /** Thrown when `validateApiUrl` rejects a candidate URL. */
1936
+ declare class UnsafeApiUrlError extends Error {
1937
+ readonly raw: string;
1938
+ readonly reason: string;
1939
+ constructor(raw: string, reason: string);
1940
+ }
1941
+ /**
1942
+ * Validate the operator-supplied AgenticMail API base URL.
1943
+ *
1944
+ * Returns the canonical origin form of the URL (`http://host:port`)
1945
+ * so callers can build paths against it without worrying about
1946
+ * trailing slashes or embedded auth. Throws `UnsafeApiUrlError` on
1947
+ * any structural problem or blocked host.
1948
+ */
1949
+ declare function validateApiUrl(raw: unknown): string;
1950
+ /**
1951
+ * Build a full request URL from a validated base + path. Used by the
1952
+ * host-integration api clients to ensure the path is appended via
1953
+ * `URL` (escapes correctly) rather than string concat.
1954
+ */
1955
+ declare function buildApiUrl(baseOrigin: string, pathAndQuery: string): string;
1956
+
1586
1957
  interface DependencyStatus {
1587
1958
  name: string;
1588
1959
  installed: boolean;
@@ -2123,4 +2494,4 @@ declare class AgentMemoryStore {
2123
2494
  renderForPrompt(memory: AgentMemoryRead | null): string;
2124
2495
  }
2125
2496
 
2126
- 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 };
2497
+ 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, hostSessionStoragePath, isInternalEmail, isSessionFresh, isValidPhoneNumber, loadHostSession, normalizeAddress, normalizePhoneNumber, normalizeSubject, parseEmail, parseGoogleVoiceSms, recordToolCall, redactObject, redactSecret, resolveConfig, safeJoin, sanitizeEmail, saveConfig, saveHostSession, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge, threadIdFor, tryJoin, validateApiUrl };