@rubytech/create-maxy-code 0.1.279 → 0.1.280

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.
Files changed (122) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  3. package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-directive.test.sh +42 -0
  4. package/payload/platform/plugins/admin/hooks/prompt-optimiser-directive.sh +60 -15
  5. package/payload/platform/plugins/admin/mcp/dist/__tests__/e-sign-discoverable.test.d.ts +2 -0
  6. package/payload/platform/plugins/admin/mcp/dist/__tests__/e-sign-discoverable.test.d.ts.map +1 -0
  7. package/payload/platform/plugins/admin/mcp/dist/__tests__/e-sign-discoverable.test.js +54 -0
  8. package/payload/platform/plugins/admin/mcp/dist/__tests__/e-sign-discoverable.test.js.map +1 -0
  9. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.d.ts +2 -0
  10. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.d.ts.map +1 -0
  11. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.js +19 -0
  12. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.js.map +1 -0
  13. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts.map +1 -1
  14. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js +2 -5
  15. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js.map +1 -1
  16. package/payload/platform/plugins/admin/skills/a4-print-documents/SKILL.md +2 -0
  17. package/payload/platform/plugins/admin/skills/access-manager/SKILL.md +2 -0
  18. package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +2 -0
  19. package/payload/platform/plugins/admin/skills/business-profile/SKILL.md +2 -0
  20. package/payload/platform/plugins/admin/skills/capabilities-here/SKILL.md +2 -0
  21. package/payload/platform/plugins/admin/skills/datetime/SKILL.md +2 -0
  22. package/payload/platform/plugins/admin/skills/deck-pages/SKILL.md +2 -0
  23. package/payload/platform/plugins/admin/skills/file-presentation/SKILL.md +2 -0
  24. package/payload/platform/plugins/admin/skills/investigate/SKILL.md +2 -0
  25. package/payload/platform/plugins/admin/skills/plainly/SKILL.md +2 -0
  26. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +2 -0
  27. package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +2 -0
  28. package/payload/platform/plugins/admin/skills/professional-document/SKILL.md +2 -0
  29. package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +2 -0
  30. package/payload/platform/plugins/admin/skills/qr-code/SKILL.md +2 -0
  31. package/payload/platform/plugins/admin/skills/session-management/SKILL.md +2 -0
  32. package/payload/platform/plugins/admin/skills/skill-builder/SKILL.md +2 -0
  33. package/payload/platform/plugins/admin/skills/specialist-management/SKILL.md +2 -0
  34. package/payload/platform/plugins/admin/skills/stream-log-review/SKILL.md +2 -0
  35. package/payload/platform/plugins/admin/skills/superpowers-sprint/SKILL.md +2 -0
  36. package/payload/platform/plugins/admin/skills/task/SKILL.md +2 -0
  37. package/payload/platform/plugins/admin/skills/update-knowledge/SKILL.md +2 -0
  38. package/payload/platform/plugins/admin/skills/upgrade/SKILL.md +2 -0
  39. package/payload/platform/plugins/aeo/skills/structured-answer/SKILL.md +2 -0
  40. package/payload/platform/plugins/business-assistant/PLUGIN.md +2 -1
  41. package/payload/platform/plugins/business-assistant/{references/e-sign.md → skills/e-sign/SKILL.md} +9 -2
  42. package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +2 -0
  43. package/payload/platform/plugins/deep-research/skills/academic-verify/SKILL.md +2 -0
  44. package/payload/platform/plugins/deep-research/skills/book-mirror/SKILL.md +2 -0
  45. package/payload/platform/plugins/deep-research/skills/data-research/SKILL.md +2 -0
  46. package/payload/platform/plugins/deep-research/skills/deep-research/SKILL.md +2 -0
  47. package/payload/platform/plugins/deep-research/skills/strategic-reading/SKILL.md +2 -0
  48. package/payload/platform/plugins/email/PLUGIN.md +2 -0
  49. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +42 -11
  50. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  51. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +123 -57
  52. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  53. package/payload/platform/plugins/email/mcp/dist/tools/email-otp-extract.d.ts.map +1 -1
  54. package/payload/platform/plugins/email/mcp/dist/tools/email-otp-extract.js +4 -2
  55. package/payload/platform/plugins/email/mcp/dist/tools/email-otp-extract.js.map +1 -1
  56. package/payload/platform/plugins/email/mcp/dist/tools/email-read.d.ts.map +1 -1
  57. package/payload/platform/plugins/email/mcp/dist/tools/email-read.js +18 -8
  58. package/payload/platform/plugins/email/mcp/dist/tools/email-read.js.map +1 -1
  59. package/payload/platform/plugins/email/mcp/dist/tools/email-search.d.ts.map +1 -1
  60. package/payload/platform/plugins/email/mcp/dist/tools/email-search.js +18 -8
  61. package/payload/platform/plugins/email/mcp/dist/tools/email-search.js.map +1 -1
  62. package/payload/platform/plugins/email/skills/email-composition/SKILL.md +2 -0
  63. package/payload/platform/plugins/email/skills/email-ingest/SKILL.md +2 -0
  64. package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +2 -0
  65. package/payload/platform/plugins/linkedin-extension/skills/linkedin-extension/SKILL.md +2 -0
  66. package/payload/platform/plugins/memory/skills/archive-crawler/SKILL.md +2 -0
  67. package/payload/platform/plugins/memory/skills/challenge/SKILL.md +2 -0
  68. package/payload/platform/plugins/memory/skills/concept-synthesis/SKILL.md +2 -0
  69. package/payload/platform/plugins/memory/skills/connect/SKILL.md +2 -0
  70. package/payload/platform/plugins/memory/skills/conversational-memory/SKILL.md +2 -0
  71. package/payload/platform/plugins/memory/skills/emerge/SKILL.md +2 -0
  72. package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +2 -0
  73. package/payload/platform/plugins/prompt-optimiser/skills/prompt-optimiser/SKILL.md +3 -1
  74. package/payload/platform/plugins/prompt-optimiser/skills/prompt-optimiser/__tests__/restatement-ladder.test.sh +21 -0
  75. package/payload/platform/plugins/scheduling/skills/briefing/SKILL.md +2 -0
  76. package/payload/platform/plugins/scheduling/skills/daily-prep/SKILL.md +2 -0
  77. package/payload/platform/plugins/slides/skills/deck-system/SKILL.md +2 -0
  78. package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +2 -0
  79. package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +2 -0
  80. package/payload/platform/plugins/work/skills/execute-task/SKILL.md +2 -0
  81. package/payload/platform/plugins/workflows/skills/workflow-manager/SKILL.md +2 -0
  82. package/payload/platform/scripts/lib/__tests__/admin-skills-bootstrap.test.sh +64 -0
  83. package/payload/platform/scripts/lib/admin-skills-bootstrap.sh +109 -0
  84. package/payload/platform/scripts/setup-account.sh +8 -0
  85. package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -1
  86. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +43 -11
  87. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -1
  88. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  89. package/payload/platform/services/claude-session-manager/dist/http-server.js +74 -1
  90. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  91. package/payload/platform/services/claude-session-manager/dist/index.js +48 -2
  92. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  93. package/payload/platform/services/claude-session-manager/dist/liveness-divergence.d.ts +19 -0
  94. package/payload/platform/services/claude-session-manager/dist/liveness-divergence.d.ts.map +1 -0
  95. package/payload/platform/services/claude-session-manager/dist/liveness-divergence.js +38 -0
  96. package/payload/platform/services/claude-session-manager/dist/liveness-divergence.js.map +1 -0
  97. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +8 -0
  98. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  99. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +12 -0
  100. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  101. package/payload/platform/services/claude-session-manager/dist/scope-record.d.ts +11 -0
  102. package/payload/platform/services/claude-session-manager/dist/scope-record.d.ts.map +1 -0
  103. package/payload/platform/services/claude-session-manager/dist/scope-record.js +81 -0
  104. package/payload/platform/services/claude-session-manager/dist/scope-record.js.map +1 -0
  105. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +10 -7
  106. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  107. package/payload/platform/services/whatsapp-channel/dist/notification.js +14 -14
  108. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  109. package/payload/platform/templates/agents/admin/IDENTITY.md +2 -6
  110. package/payload/server/public/assets/AdminShell-C6_5h1cC.js +1 -0
  111. package/payload/server/public/assets/{admin-xRCXEFNW.js → admin-BpnsTEa3.js} +1 -1
  112. package/payload/server/public/assets/{browser-DT2XfRpL.js → browser-fgBRZ5gK.js} +1 -1
  113. package/payload/server/public/assets/{data-Dr7XUvAx.js → data-Cuynp6Y6.js} +1 -1
  114. package/payload/server/public/assets/{graph-DOEjjQHD.js → graph-Ddg24bjy.js} +1 -1
  115. package/payload/server/public/assets/{whatsapp-cDxgZkbM.js → whatsapp-BE79pNh8.js} +1 -1
  116. package/payload/server/public/browser.html +2 -2
  117. package/payload/server/public/data.html +2 -2
  118. package/payload/server/public/graph.html +2 -2
  119. package/payload/server/public/index.html +2 -2
  120. package/payload/server/public/whatsapp.html +2 -2
  121. package/payload/server/server.js +338 -461
  122. package/payload/server/public/assets/AdminShell-DnH3kou-.js +0 -1
@@ -5,6 +5,8 @@ description: Use when authoring or auditing a page whose primary purpose is to a
5
5
 
6
6
  # Structured-answer page template
7
7
 
8
+ Invoked from `specialists:content-producer`.
9
+
8
10
  Answer engines (Claude, ChatGPT, Perplexity, Google AI Overviews, Bing Copilot) lift a single short span when they cite a page. That span is almost always the first paragraph after the H1, when it is short and direct. Every other element on the page either supports the lift or gets ignored.
9
11
 
10
12
  ## The shape
@@ -32,6 +32,8 @@ Enquiries also arrive through the business's **published-site contact form**, wh
32
32
 
33
33
  Track status through the pipeline: enquiry → quoted → booked → completed → invoiced → paid.
34
34
 
35
+ When a built document — a quote, contract, proposal, or set of terms — needs a recordable signature without DocuSign, load the `e-sign` skill (`skill-load skillName="e-sign"`).
36
+
35
37
  ## Session & Task Continuity
36
38
 
37
39
  Every conversation is a session. At the start of each session:
@@ -57,7 +59,6 @@ Load the relevant reference when the task requires it:
57
59
  - **Triage & escalation** → `references/escalation.md` — urgency classification (RED/AMBER/GREEN), customer handoff protocol, voice IVR handling
58
60
  - **Scheduling & briefings** → `references/scheduling.md` — booking protocol, geographic clustering, travel time estimation, morning briefings, holiday mode, GPS integration
59
61
  - **Invoices & payment** → `references/invoicing.md` — HTML invoice creation, PDF generation via `browser-navigate` + `browser-pdf-save`, sending, payment chase protocol
60
- - **E-signatures** → `references/e-sign.md` — sign any hosted document (quote, contract, proposal): a Cloudflare Pages signing form captures the acceptance to D1, then the agent sweeps the row and emails both parties a PDF of the signed content
61
62
  - **Customer records** → `references/crm.md` — contact record management, `contact_lookup`/`contact_update` usage, pipeline tracking, when to create/update records
62
63
  - **Site form submissions** → `references/site-lead-intake.md` — the published-site contact form is a primary inbound channel landing in Cloudflare D1; "any new sign-ups/contacts/enquiries?" reads D1 first, then dedupes each unswept row into the graph and routes it to the recorded front-line owner
63
64
  - **Document filing** → `references/document-management.md` — dual-scope filing system, naming conventions, folder structure for customer-facing vs internal documents
@@ -1,12 +1,19 @@
1
+ ---
2
+ name: e-sign
3
+ description: "Put a legally-recordable signature on any hosted document — a quote, contract, proposal, or set of terms — without DocuSign. A Cloudflare Pages signing form captures the acceptance to D1; the agent then sweeps the row, renders the signed content to PDF, and emails both parties. Triggers: 'sign this proposal', 'get this contract signed', 'put a signature on this', 'e-signature', 'send for signature', 'accept a proposal online', 'signature on a document'."
4
+ ---
5
+
1
6
  # E-signatures — signing any hosted document (form → D1 → PDF + email)
2
7
 
8
+ **Invoked by the business-assistant (or admin) directly** when a built document — a quote, contract, proposal, or set of terms — needs a recordable signature.
9
+
3
10
  How to put a legally-recordable signature on **any** document the business hosts — a quote, a contract, a proposal, a set of terms — without DocuSign. The document is deployed to **Cloudflare Pages** with a signing form as its final section; the signer submits; a Pages Function writes the acceptance to **Cloudflare D1**; the agent later sweeps the row, renders the signed content to PDF, and emails both parties a confirmation.
4
11
 
5
- This reference owns **only the signature**: the form, the capture, the PDF, and the dispatch. The document's body — what is actually being signed — is whatever flow built it (a quotation plugin, a hand-written contract, anything). This begins at *"a built document needs a signature."*
12
+ This skill owns **only the signature**: the form, the capture, the PDF, and the dispatch. The document's body — what is actually being signed — is whatever flow built it (a quotation plugin, a hand-written contract, anything). This begins at *"a built document needs a signature."*
6
13
 
7
14
  It composes two patterns you must read alongside it:
8
15
 
9
- - **`cloudflare/references/d1-data-capture.md`** — the form → Pages Function → D1 → sweep mechanics, and the token-scope breakage (§ 0). Everything about minting the Pages-+-D1 token, `wrangler.toml`, and `wrangler d1 execute --remote` lives there; this reference does not repeat it.
16
+ - **`cloudflare/references/d1-data-capture.md`** — the form → Pages Function → D1 → sweep mechanics, and the token-scope breakage (§ 0). Everything about minting the Pages-+-D1 token, `wrangler.toml`, and `wrangler d1 execute --remote` lives there; this skill does not repeat it.
10
17
  - **`business-assistant/references/invoicing.md`** — PDF generation via `browser-navigate` + `browser-pdf-save` (its step 4). The signed-content PDF uses that exact pattern.
11
18
 
12
19
  Auth for every `wrangler`/API call is a minted narrow token per `cloudflare/references/api.md`. Deploy mechanics are `cloudflare/references/hosting-sites.md`.
@@ -5,6 +5,8 @@ description: Cloudflare operations for the install — tunnel setup/diagnosis/re
5
5
 
6
6
  # Cloudflare operations
7
7
 
8
+ Invoked from `specialists:personal-assistant`.
9
+
8
10
  This is the entry point for every Cloudflare task on the install. Pick the operation class, read the matching reference, run the command via Bash, relay the literal output, and prove the outcome with a live signal. There are no Cloudflare MCP tools; the plugin registers none. The agent never browser-automates the dashboard — the operator clicks where a click is genuinely required.
9
11
 
10
12
  | You want to… | Read | Outcome contract |
@@ -5,6 +5,8 @@ description: "Traces an academic or scientific claim through the publication cha
5
5
 
6
6
  # Academic verify
7
7
 
8
+ Invoked from `specialists:research-assistant`.
9
+
8
10
  This skill is for claims that arrive as "studies show", "research suggests", or a named paper title. It traces the claim to the actual paper, the actual journal, and the actual conclusion of the source. It never says "verified" without a primary source URL.
9
11
 
10
12
  ## When to run
@@ -5,6 +5,8 @@ description: "Reads a book the owner provides (PDF, ePub, web link, or text), wa
5
5
 
6
6
  # Book mirror
7
7
 
8
+ Invoked from `specialists:research-assistant`.
9
+
8
10
  This skill turns a book into a usable document by walking the chapters one at a time and asking, for each: "what does this say, and how does it apply to this specific owner". The output is a personalised pack the owner can read once and act on. It is grounded in the graph; the right-hand column is always specific to what the graph already knows about the owner.
9
11
 
10
12
  ## When to run
@@ -5,6 +5,8 @@ description: "Recipe-driven structured-data extraction. Takes a named recipe and
5
5
 
6
6
  # Data research
7
7
 
8
+ Invoked from `specialists:research-assistant`.
9
+
8
10
  This skill turns unstructured-looking sources (emails, reports, forms, web pages) into structured graph entities. The transformation is always recipe-driven: a named YAML recipe declares what fields to extract, what validations to apply, and what graph nodes to write. The skill runs a named recipe; it never invents a schema on the fly.
9
11
 
10
12
  **Default parent.** Every recipe declares its hierarchy parent via `graph_target.parent` — either `Project` (the run-time recipe argument names which Project elementId to attach to) or `LocalBusiness` (the account root, for cross-project recipes like investor-updates). The skill refuses to run a recipe whose `parent` field is missing or whose named Project does not exist. Per-row writes attach to that parent via the canonical containment edge; the recipe's `relationships:` block describes cross-hierarchy edges (e.g. `ROUND_FOR → Organization`) which are written separately and do not substitute for the parent.
@@ -20,6 +20,8 @@ config:
20
20
 
21
21
  # Deep Research Skill
22
22
 
23
+ Invoked from `specialists:research-assistant`.
24
+
23
25
  You are a research agent. Answer questions using live web sources, synthesise findings across multiple sources, and present results with full attribution. You do not guess. You search, read, extract, and cite.
24
26
 
25
27
  ## Workflow
@@ -5,6 +5,8 @@ description: "Reads a source (article, book, report, document) through a specifi
5
5
 
6
6
  # Strategic reading
7
7
 
8
+ Invoked from `specialists:research-assistant`.
9
+
8
10
  This skill reads a document with a specific question in mind. The owner names the lens (a project, a problem, a goal). The skill summarises what the source actually says, then translates each section into how it applies to the named lens, and closes with a playbook of three to seven concrete steps. Both halves are grounded: the left half in the source text, the right half in the graph.
9
11
 
10
12
  ## When to run
@@ -58,6 +58,8 @@ Manages the agent's own dedicated email account — IMAP for reading, SMTP for s
58
58
 
59
59
  **Identity boundary (operator-identity fix).** This plugin manages the AGENT's email channel: an `:EmailAccount` node with IMAP/SMTP credentials, polling state, and the agent's `agentAddress`. It is NOT the operator-identity store. The operator's own email lives on the OWNS-bound `:Person` (`Person.email`) and is captured via `profile-update` with `personFields`. Confusing the two surfaces (writing the operator's personal email to `EmailAccount.agentAddress`, or vice versa) breaks routing AND identity coverage. See [.docs/agents.md § Coverage-driven elicitation bias] for the operator-identity contract.
60
60
 
61
+ **Login vs alias — the default recipient set.** A config carries two addresses. `email` is the IMAP login: the address you authenticate as, and an address this mailbox owns. `agentAddress` is an *additional* alias to surface (often a per-agent address on a shared catch-all mailbox). When they differ, `email-read`, `email-search`, and `email-otp-extract` default to surfacing mail addressed to **either** — not the alias alone — because replies commonly arrive at the login address (a proposal sent from `info@` is answered to `info@`, not the `maxy@` alias). When no distinct alias is configured, no recipient filter is applied. Pass an explicit `to=` to narrow to a single address. Whenever the default filter hides messages from the fetched window, `email-read`/`email-search` append a `Recipient filter: kept N, hid M (to ∈ {…}); pass to="…" to change.` line — including when every message was hidden — so "I can't see it" is never indistinguishable from "it isn't there".
62
+
61
63
  ## Capabilities
62
64
 
63
65
  - **Provider lookup:** `email-provider-info` — given just an email address, returns the inferred IMAP/SMTP hosts and the provider's app-password / trusted-application / IMAP-enable requirement, deterministically from the provider map. Call this BEFORE soliciting a password so the requirement is relayed first; most providers reject the normal login password. Returns "unknown" (operator supplies hosts) or "unsupported" (provider blocks IMAP/SMTP, e.g. Tuta).
@@ -14,6 +14,8 @@ export interface PaginatedMessages {
14
14
  messages: EmailMessage[];
15
15
  /** Pass as before_uid to get the next page of older messages. Omitted when results are exhausted. */
16
16
  nextBeforeUid?: number;
17
+ /** Kept-vs-hidden accounting, present only when a client-side recipient filter ran. */
18
+ recipientFilter?: RecipientFilterStats;
17
19
  }
18
20
  export declare const MAX_LIMIT = 50;
19
21
  /**
@@ -130,17 +132,46 @@ export declare function extractBodyFromFetch(msg: {
130
132
  */
131
133
  export declare function testConnection(config: EmailConfig, password: string): Promise<string | null>;
132
134
  /**
133
- * Check whether an IMAP envelope's TO field contains the target address.
134
- * Case-insensitive comparison. Strict: if the server didn't return TO
135
- * addresses, the message is excluded — we cannot confirm it was addressed
136
- * to the target.
135
+ * Check whether an IMAP envelope's TO field contains any of the target
136
+ * addresses. Case-insensitive comparison. Strict: if the server didn't
137
+ * return TO addresses, the message is excluded — we cannot confirm it was
138
+ * addressed to any target. Only the TO field is inspected (not Cc).
139
+ *
140
+ * Accepts a single address or a set of acceptable addresses; a message
141
+ * matches when its TO contains at least one of them.
137
142
  */
138
143
  export declare function matchesRecipient(envelope: {
139
144
  to?: Array<{
140
145
  name?: string;
141
146
  address?: string;
142
147
  }>;
143
- }, targetAddress: string): boolean;
148
+ }, target: string | string[]): boolean;
149
+ /**
150
+ * Resolve the implicit recipient-filter set for read/search when no explicit
151
+ * `to` is supplied. The mailbox always owns its IMAP login (`config.email`),
152
+ * and `config.agentAddress` is an additional alias to surface. When an alias
153
+ * is configured (truthy and different from the login) both addresses are
154
+ * returned, so replies that arrive at the login address are not hidden.
155
+ *
156
+ * When no distinct alias is configured, returns `undefined` — no recipient
157
+ * filter is applied at all, preserving the pre-existing behaviour for plain
158
+ * (non-alias) mailboxes.
159
+ */
160
+ export declare function resolveDefaultRecipients(config: {
161
+ email: string;
162
+ agentAddress?: string;
163
+ }): string[] | undefined;
164
+ /** Kept-vs-hidden accounting for a client-side recipient filter pass. */
165
+ export interface RecipientFilterStats {
166
+ kept: number;
167
+ hidden: number;
168
+ recipients: string[];
169
+ }
170
+ /**
171
+ * Render the operator-facing recipient-filter signal line. Returns null when
172
+ * nothing was hidden, so a fully-matched window emits no misleading line.
173
+ */
174
+ export declare function formatRecipientFilterLine(stats: RecipientFilterStats | undefined): string | null;
144
175
  /**
145
176
  * Build IMAP SEARCH criteria for fetchRecent.
146
177
  * Never includes TO — that filter is applied client-side to avoid
@@ -150,11 +181,11 @@ export declare function buildFetchRecentCriteria(options: {
150
181
  sender?: string;
151
182
  since?: Date;
152
183
  before?: Date;
153
- to?: string;
184
+ to?: string | string[];
154
185
  limit?: number;
155
186
  }): {
156
187
  criteria: Record<string, unknown>;
157
- clientSideTo: string | undefined;
188
+ clientSideRecipients: string[] | undefined;
158
189
  fetchLimit: number;
159
190
  };
160
191
  /**
@@ -169,10 +200,10 @@ export declare function buildSearchCriteria(options: {
169
200
  body?: string;
170
201
  since?: Date;
171
202
  before?: Date;
172
- to?: string;
203
+ to?: string | string[];
173
204
  }): {
174
205
  criteria: Record<string, unknown>;
175
- clientSideTo: string | undefined;
206
+ clientSideRecipients: string[] | undefined;
176
207
  };
177
208
  /**
178
209
  * Fetch recent messages from a mailbox folder.
@@ -187,7 +218,7 @@ export declare function fetchRecent(config: EmailConfig, password: string, optio
187
218
  subjectPattern?: string;
188
219
  since?: Date;
189
220
  before?: Date;
190
- to?: string;
221
+ to?: string | string[];
191
222
  folder?: "inbox" | "sent";
192
223
  beforeUid?: number;
193
224
  }): Promise<PaginatedMessages>;
@@ -201,7 +232,7 @@ export declare function searchMessages(config: EmailConfig, password: string, qu
201
232
  since?: Date;
202
233
  before?: Date;
203
234
  limit?: number;
204
- to?: string;
235
+ to?: string | string[];
205
236
  folder?: "inbox" | "sent";
206
237
  beforeUid?: number;
207
238
  }): Promise<PaginatedMessages>;
@@ -1 +1 @@
1
- {"version":3,"file":"imap.d.ts","sourceRoot":"","sources":["../../src/lib/imap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAA2B,MAAM,UAAU,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAuBpD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,qGAAqG;IACrG,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAGD,eAAO,MAAM,SAAS,KAAK,CAAC;AAE5B;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,OAAO,GAAG,MAAM,GACvB,OAAO,CAAC,MAAM,CAAC,CAYjB;AAqBD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAa9C;AAQD;0EAC0E;AAC1E,eAAO,MAAM,oBAAoB,QAAmB,CAAC;AAErD,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;GAGG;AACH,UAAU,iBAAiB;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAClC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,iBAAiB,GAAG,SAAS,GACvC,cAAc,EAAE,CAgClB;AAQD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,cAAc,EAAE,GAAG,MAAM,CAM7E;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,EAAE,UAAU,GAAG,cAAc,GAAG,aAAa,GAAG,IAAI,CAAC;CACnE;AAED;;;;GAIG;AACH,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,GAC/F,OAAO,CAAC,qBAAqB,EAAE,CAAC,CA6ElC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBrE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IACxC,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,aAAa,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;CAChH,GAAG,MAAM,CAqCT;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyBlG;AAWD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE;IAAE,EAAE,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,EAC7D,aAAa,EAAE,MAAM,GACpB,OAAO,CAIT;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,IAAI,CAAC;IACb,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG;IACF,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;CACpB,CAiBA;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,IAAI,CAAC;IACb,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,GAAG;IACF,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,CA0BA;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IACP,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,IAAI,CAAC;IACb,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACf,GACL,OAAO,CAAC,iBAAiB,CAAC,CAyH5B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE;IACL,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,IAAI,CAAC;IACb,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,iBAAiB,CAAC,CAwG5B;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,WAAW,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAsF1E;AAED;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IACT,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,IAAI,CAAC,CAiER;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS1F"}
1
+ {"version":3,"file":"imap.d.ts","sourceRoot":"","sources":["../../src/lib/imap.ts"],"names":[],"mappings":"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"}
@@ -320,16 +320,48 @@ const TO_OVERFETCH_MULTIPLIER = 3;
320
320
  // ─── Default since floor for unbounded alias searches ───────────────
321
321
  const DEFAULT_SINCE_DAYS = 7;
322
322
  /**
323
- * Check whether an IMAP envelope's TO field contains the target address.
324
- * Case-insensitive comparison. Strict: if the server didn't return TO
325
- * addresses, the message is excluded — we cannot confirm it was addressed
326
- * to the target.
323
+ * Check whether an IMAP envelope's TO field contains any of the target
324
+ * addresses. Case-insensitive comparison. Strict: if the server didn't
325
+ * return TO addresses, the message is excluded — we cannot confirm it was
326
+ * addressed to any target. Only the TO field is inspected (not Cc).
327
+ *
328
+ * Accepts a single address or a set of acceptable addresses; a message
329
+ * matches when its TO contains at least one of them.
327
330
  */
328
- export function matchesRecipient(envelope, targetAddress) {
331
+ export function matchesRecipient(envelope, target) {
329
332
  if (!envelope.to || envelope.to.length === 0)
330
333
  return false;
331
- const target = targetAddress.toLowerCase();
332
- return envelope.to.some((addr) => addr.address?.toLowerCase() === target);
334
+ const targets = (Array.isArray(target) ? target : [target]).map((t) => t.toLowerCase());
335
+ return envelope.to.some((addr) => {
336
+ const a = addr.address?.toLowerCase();
337
+ return a !== undefined && targets.includes(a);
338
+ });
339
+ }
340
+ /**
341
+ * Resolve the implicit recipient-filter set for read/search when no explicit
342
+ * `to` is supplied. The mailbox always owns its IMAP login (`config.email`),
343
+ * and `config.agentAddress` is an additional alias to surface. When an alias
344
+ * is configured (truthy and different from the login) both addresses are
345
+ * returned, so replies that arrive at the login address are not hidden.
346
+ *
347
+ * When no distinct alias is configured, returns `undefined` — no recipient
348
+ * filter is applied at all, preserving the pre-existing behaviour for plain
349
+ * (non-alias) mailboxes.
350
+ */
351
+ export function resolveDefaultRecipients(config) {
352
+ if (config.agentAddress && config.agentAddress !== config.email) {
353
+ return [config.email, config.agentAddress];
354
+ }
355
+ return undefined;
356
+ }
357
+ /**
358
+ * Render the operator-facing recipient-filter signal line. Returns null when
359
+ * nothing was hidden, so a fully-matched window emits no misleading line.
360
+ */
361
+ export function formatRecipientFilterLine(stats) {
362
+ if (!stats || stats.hidden <= 0)
363
+ return null;
364
+ return `Recipient filter: kept ${stats.kept}, hid ${stats.hidden} (to ∈ {${stats.recipients.join(", ")}}); pass to="…" to change.`;
333
365
  }
334
366
  /**
335
367
  * Build IMAP SEARCH criteria for fetchRecent.
@@ -338,6 +370,7 @@ export function matchesRecipient(envelope, targetAddress) {
338
370
  */
339
371
  export function buildFetchRecentCriteria(options) {
340
372
  const { sender, since, before, to, limit = 20 } = options;
373
+ const recipients = to ? (Array.isArray(to) ? to : [to]) : undefined;
341
374
  const criteria = {};
342
375
  if (sender)
343
376
  criteria.from = sender;
@@ -351,8 +384,8 @@ export function buildFetchRecentCriteria(options) {
351
384
  }
352
385
  return {
353
386
  criteria,
354
- clientSideTo: to,
355
- fetchLimit: to ? limit * TO_OVERFETCH_MULTIPLIER : limit,
387
+ clientSideRecipients: recipients,
388
+ fetchLimit: recipients ? limit * TO_OVERFETCH_MULTIPLIER : limit,
356
389
  };
357
390
  }
358
391
  /**
@@ -363,6 +396,7 @@ export function buildFetchRecentCriteria(options) {
363
396
  */
364
397
  export function buildSearchCriteria(options) {
365
398
  const { sender, subject, body, since, before, to } = options;
399
+ const recipients = to ? (Array.isArray(to) ? to : [to]) : undefined;
366
400
  const criteria = {};
367
401
  if (sender)
368
402
  criteria.from = sender;
@@ -375,9 +409,9 @@ export function buildSearchCriteria(options) {
375
409
  if (before)
376
410
  criteria.before = before;
377
411
  // TO is intentionally excluded — filtered client-side after fetch
378
- // When TO was the only effective filter (nothing else bounds the search),
379
- // inject a date floor to prevent unbounded full-mailbox scans
380
- if (to && Object.keys(criteria).length === 0) {
412
+ // When recipient filtering was the only effective filter (nothing else
413
+ // bounds the search), inject a date floor to prevent unbounded full-mailbox scans
414
+ if (recipients && Object.keys(criteria).length === 0) {
381
415
  const sinceFloor = new Date();
382
416
  sinceFloor.setDate(sinceFloor.getDate() - DEFAULT_SINCE_DAYS);
383
417
  criteria.since = sinceFloor;
@@ -387,7 +421,7 @@ export function buildSearchCriteria(options) {
387
421
  }
388
422
  return {
389
423
  criteria,
390
- clientSideTo: to,
424
+ clientSideRecipients: recipients,
391
425
  };
392
426
  }
393
427
  /**
@@ -408,11 +442,11 @@ export async function fetchRecent(config, password, options = {}) {
408
442
  }
409
443
  catch { /* invalid — ignore */ }
410
444
  }
411
- const { criteria, clientSideTo, fetchLimit } = buildFetchRecentCriteria({
445
+ const { criteria, clientSideRecipients, fetchLimit } = buildFetchRecentCriteria({
412
446
  sender, since, before, to, limit,
413
447
  });
414
448
  const t0 = Date.now();
415
- diag("fetchRecent", "start", `folder=${folder} criteria=${JSON.stringify(criteria)} fetchLimit=${fetchLimit} clientSideTo=${clientSideTo ?? "none"} beforeUid=${beforeUid ?? "none"}`);
449
+ diag("fetchRecent", "start", `folder=${folder} criteria=${JSON.stringify(criteria)} fetchLimit=${fetchLimit} clientSideRecipients=${clientSideRecipients ? `[${clientSideRecipients.join(",")}]` : "none"} beforeUid=${beforeUid ?? "none"}`);
416
450
  try {
417
451
  await client.connect();
418
452
  diag("fetchRecent", "connected", `${Date.now() - t0}ms`);
@@ -441,6 +475,8 @@ export async function fetchRecent(config, password, options = {}) {
441
475
  const uidList = recentUids.map(String).join(",");
442
476
  diag("fetchRecent", "fetching envelopes", `UIDs=[${uidList}]`);
443
477
  let envelopeCount = 0;
478
+ let recipientKept = 0;
479
+ let recipientHidden = 0;
444
480
  for await (const msg of client.fetch(uidList, {
445
481
  envelope: true,
446
482
  uid: true,
@@ -449,9 +485,14 @@ export async function fetchRecent(config, password, options = {}) {
449
485
  }, { uid: true })) {
450
486
  envelopeCount++;
451
487
  diag("fetchRecent", `envelope #${envelopeCount}`, `uid=${msg.uid} from=${msg.envelope?.from?.[0]?.address ?? "?"} at ${Date.now() - t2}ms`);
452
- // Client-side TO filter (replaces IMAP SEARCH TO)
453
- if (clientSideTo && !matchesRecipient(msg.envelope ?? {}, clientSideTo))
454
- continue;
488
+ // Client-side recipient filter (replaces IMAP SEARCH TO), with kept-vs-hidden accounting
489
+ if (clientSideRecipients) {
490
+ if (!matchesRecipient(msg.envelope ?? {}, clientSideRecipients)) {
491
+ recipientHidden++;
492
+ continue;
493
+ }
494
+ recipientKept++;
495
+ }
455
496
  const fromAddr = msg.envelope?.from?.[0]?.address ?? "(unknown)";
456
497
  const subject = msg.envelope?.subject ?? "(no subject)";
457
498
  const date = msg.envelope?.date?.toISOString() ?? "(unknown date)";
@@ -459,30 +500,39 @@ export async function fetchRecent(config, password, options = {}) {
459
500
  const inReplyTo = msg.envelope?.inReplyTo ?? null;
460
501
  if (subjectRegex && !subjectRegex.test(subject))
461
502
  continue;
462
- // Stop once we have enough matches
463
- if (messages.length >= limit)
464
- break;
465
- const bodyPreview = extractBodyFromFetch(msg);
466
- diag("fetchRecent", `body #${envelopeCount}`, `uid=${msg.uid} preview=${bodyPreview.length}chars type=${msg.bodyStructure?.type ?? "?"}`);
467
- const attachments = walkAttachmentMetadata(msg.bodyStructure);
468
- messages.push({
469
- uid: msg.uid,
470
- messageId,
471
- inReplyTo,
472
- from: fromAddr,
473
- subject,
474
- date,
475
- bodyPreview,
476
- attachments,
477
- });
503
+ // Collect up to `limit` messages for display, but keep iterating past
504
+ // that point so the recipient kept/hidden accounting covers the whole
505
+ // fetched window — the envelopes are already fetched, so the extra
506
+ // iterations cost nothing on the wire and we skip body extraction.
507
+ if (messages.length < limit) {
508
+ const bodyPreview = extractBodyFromFetch(msg);
509
+ diag("fetchRecent", `body #${envelopeCount}`, `uid=${msg.uid} preview=${bodyPreview.length}chars type=${msg.bodyStructure?.type ?? "?"}`);
510
+ const attachments = walkAttachmentMetadata(msg.bodyStructure);
511
+ messages.push({
512
+ uid: msg.uid,
513
+ messageId,
514
+ inReplyTo,
515
+ from: fromAddr,
516
+ subject,
517
+ date,
518
+ bodyPreview,
519
+ attachments,
520
+ });
521
+ }
478
522
  }
479
523
  diag("fetchRecent", "fetch done", `${envelopeCount} envelopes, ${messages.length} messages in ${Date.now() - t2}ms (total ${Date.now() - t0}ms)`);
524
+ if (clientSideRecipients) {
525
+ diag("fetchRecent", "filter applied", `kept=${recipientKept} hidden=${recipientHidden} recipients=[${clientSideRecipients.join(",")}]`);
526
+ }
480
527
  // Return newest first
481
528
  messages.reverse();
482
529
  // Calculate pagination cursor: use the oldest UID in the *fetched* set
483
530
  // (not the filtered set) to avoid skipping messages between filtered-out
484
- // and kept messages when client-side TO/subject filtering is active
531
+ // and kept messages when client-side recipient/subject filtering is active
485
532
  const result = { messages };
533
+ if (clientSideRecipients) {
534
+ result.recipientFilter = { kept: recipientKept, hidden: recipientHidden, recipients: clientSideRecipients };
535
+ }
486
536
  if (totalAfterCursor > fetchLimit && recentUids.length > 0) {
487
537
  const oldestFetchedUid = recentUids[0]; // UIDs are sorted ascending
488
538
  result.nextBeforeUid = oldestFetchedUid;
@@ -511,13 +561,13 @@ export async function fetchRecent(config, password, options = {}) {
511
561
  export async function searchMessages(config, password, query) {
512
562
  const { sender, subject, body, since, before, limit = 20, to, folder = "inbox", beforeUid } = query;
513
563
  const client = createClient(config, password);
514
- // When client-side TO filtering is active, overfetch to compensate for non-matching messages
515
- const fetchLimit = to ? limit * TO_OVERFETCH_MULTIPLIER : limit;
516
- const { criteria, clientSideTo } = buildSearchCriteria({
564
+ const { criteria, clientSideRecipients } = buildSearchCriteria({
517
565
  sender, subject, body, since, before, to,
518
566
  });
567
+ // When client-side recipient filtering is active, overfetch to compensate for non-matching messages
568
+ const fetchLimit = clientSideRecipients ? limit * TO_OVERFETCH_MULTIPLIER : limit;
519
569
  const t0 = Date.now();
520
- diag("searchMessages", "start", `folder=${folder} criteria=${JSON.stringify(criteria)} clientSideTo=${clientSideTo ?? "none"} beforeUid=${beforeUid ?? "none"}`);
570
+ diag("searchMessages", "start", `folder=${folder} criteria=${JSON.stringify(criteria)} clientSideRecipients=${clientSideRecipients ? `[${clientSideRecipients.join(",")}]` : "none"} beforeUid=${beforeUid ?? "none"}`);
521
571
  try {
522
572
  await client.connect();
523
573
  diag("searchMessages", "connected", `${Date.now() - t0}ms`);
@@ -542,6 +592,8 @@ export async function searchMessages(config, password, query) {
542
592
  const messages = [];
543
593
  const t2 = Date.now();
544
594
  let envelopeCount = 0;
595
+ let recipientKept = 0;
596
+ let recipientHidden = 0;
545
597
  for await (const msg of client.fetch(recentUids.map(String).join(","), {
546
598
  envelope: true,
547
599
  uid: true,
@@ -549,33 +601,47 @@ export async function searchMessages(config, password, query) {
549
601
  bodyParts: [{ key: "1", start: 0, maxLength: BODY_MAX_BYTES }],
550
602
  }, { uid: true })) {
551
603
  envelopeCount++;
552
- // Client-side TO filter (replaces IMAP SEARCH TO)
553
- if (clientSideTo && !matchesRecipient(msg.envelope ?? {}, clientSideTo))
554
- continue;
604
+ // Client-side recipient filter (replaces IMAP SEARCH TO), with kept-vs-hidden accounting
605
+ if (clientSideRecipients) {
606
+ if (!matchesRecipient(msg.envelope ?? {}, clientSideRecipients)) {
607
+ recipientHidden++;
608
+ continue;
609
+ }
610
+ recipientKept++;
611
+ }
555
612
  const fromAddr = msg.envelope?.from?.[0]?.address ?? "(unknown)";
556
613
  const subj = msg.envelope?.subject ?? "(no subject)";
557
614
  const date = msg.envelope?.date?.toISOString() ?? "(unknown date)";
558
615
  const messageId = msg.envelope?.messageId ?? null;
559
616
  const inReplyTo = msg.envelope?.inReplyTo ?? null;
560
- // Stop once we have enough matches
561
- if (messages.length >= limit)
562
- break;
563
- const bodyPreview = extractBodyFromFetch(msg);
564
- const attachments = walkAttachmentMetadata(msg.bodyStructure);
565
- messages.push({
566
- uid: msg.uid,
567
- messageId,
568
- inReplyTo,
569
- from: fromAddr,
570
- subject: subj,
571
- date,
572
- bodyPreview,
573
- attachments,
574
- });
617
+ // Collect up to `limit` messages for display, but keep iterating past
618
+ // that point so the recipient kept/hidden accounting covers the whole
619
+ // fetched window — the envelopes are already fetched, so the extra
620
+ // iterations cost nothing on the wire and we skip body extraction.
621
+ if (messages.length < limit) {
622
+ const bodyPreview = extractBodyFromFetch(msg);
623
+ const attachments = walkAttachmentMetadata(msg.bodyStructure);
624
+ messages.push({
625
+ uid: msg.uid,
626
+ messageId,
627
+ inReplyTo,
628
+ from: fromAddr,
629
+ subject: subj,
630
+ date,
631
+ bodyPreview,
632
+ attachments,
633
+ });
634
+ }
575
635
  }
576
636
  diag("searchMessages", "fetch done", `${envelopeCount} envelopes, ${messages.length} messages in ${Date.now() - t2}ms (total ${Date.now() - t0}ms)`);
637
+ if (clientSideRecipients) {
638
+ diag("searchMessages", "filter applied", `kept=${recipientKept} hidden=${recipientHidden} recipients=[${clientSideRecipients.join(",")}]`);
639
+ }
577
640
  messages.reverse();
578
641
  const result = { messages };
642
+ if (clientSideRecipients) {
643
+ result.recipientFilter = { kept: recipientKept, hidden: recipientHidden, recipients: clientSideRecipients };
644
+ }
579
645
  if (totalAfterCursor > fetchLimit && recentUids.length > 0) {
580
646
  const oldestFetchedUid = recentUids[0]; // UIDs are sorted ascending
581
647
  result.nextBeforeUid = oldestFetchedUid;