apple-mail-mcp 2.19.17 → 2.19.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -9
- package/build/cli.js +163 -55
- package/build/index.js +199 -67
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -280,19 +280,27 @@ human reader; the same information is also returned as structured fields on
|
|
|
280
280
|
`search-messages` and `list-messages`, so a caller can tell _"nothing matched"_
|
|
281
281
|
apart from _"I did not look everywhere"_ without parsing the text:
|
|
282
282
|
|
|
283
|
-
| Field | Type | Meaning
|
|
284
|
-
| ----------------------- | -------- |
|
|
285
|
-
| `partial` | boolean | Coverage was incomplete — **the result is not a confirmed "no such mail"**. True whenever any field below is non-empty.
|
|
286
|
-
| `skippedLargeMailboxes` | string[] | Mailboxes never scanned because their message count exceeded `APPLE_MAIL_MAX_SEARCH_MAILBOX`, formatted `"Account / Mailbox (count)"` — e.g. `"iCloud / Archive (90694)"`.
|
|
287
|
-
| `notSearchedMailboxes` | string[] | Mailboxes that _were_ reached but timed out or errored mid-scan, formatted `"Account / Mailbox"`. Also carries the IMAP path's `failedMailboxes`.
|
|
288
|
-
| `timedOutAccounts` | string[] | Accounts whose whole-account AppleScript was killed by the per-account time budget — nothing from that account was searched.
|
|
289
|
-
| `failedMailboxes` | string[] | IMAP-backend mailboxes that errored. These are merged into `notSearchedMailboxes` as well; read that field unless you need to attribute the failure to the IMAP path specifically.
|
|
283
|
+
| Field | Type | Meaning |
|
|
284
|
+
| ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
285
|
+
| `partial` | boolean | Coverage was incomplete — **the result is not a confirmed "no such mail"**. True whenever any field below is non-empty. |
|
|
286
|
+
| `skippedLargeMailboxes` | string[] | Mailboxes never scanned because their message count exceeded `APPLE_MAIL_MAX_SEARCH_MAILBOX`, formatted `"Account / Mailbox (count)"` — e.g. `"iCloud / Archive (90694)"`. |
|
|
287
|
+
| `notSearchedMailboxes` | string[] | Mailboxes that _were_ reached but timed out or errored mid-scan, formatted `"Account / Mailbox"`. Also carries the IMAP path's `failedMailboxes`. |
|
|
288
|
+
| `timedOutAccounts` | string[] | Accounts whose whole-account AppleScript was killed by the per-account time budget — nothing from that account was searched. |
|
|
289
|
+
| `failedMailboxes` | string[] | IMAP-backend mailboxes that errored. These are merged into `notSearchedMailboxes` as well; read that field unless you need to attribute the failure to the IMAP path specifically. |
|
|
290
|
+
| `failedMailboxReasons` | object | The IMAP server's own error text for each entry in `failedMailboxes`, keyed the same way. |
|
|
291
|
+
| `omittedMessages` | object[] | IMAP messages that belong on the page but whose `FETCH` response could not be read even with a reduced item set — `{ id, mailbox, uid, reason }`. The page is short by exactly these; the `id` still works with `get-message` / `get-message-headers`. ([#256](https://github.com/sweetrb/apple-mail-mcp/issues/256)) |
|
|
290
292
|
|
|
291
293
|
Treat a non-empty `skippedLargeMailboxes` as actionable rather than
|
|
292
294
|
informational: re-run scoped to the named mailbox with a `dateFrom`/`dateTo`
|
|
293
295
|
window, or configure the [IMAP backend](#imap-backend--opt-in), which searches
|
|
294
|
-
those mailboxes server-side.
|
|
295
|
-
coverage was complete.
|
|
296
|
+
those mailboxes server-side. The list fields are optional and are omitted (or
|
|
297
|
+
empty) when coverage was complete.
|
|
298
|
+
|
|
299
|
+
A row the IMAP backend could only read with a reduced item set (its full
|
|
300
|
+
`BODYSTRUCTURE`, or its whole `FETCH` response, was unparseable — e.g. a message
|
|
301
|
+
forwarded as an attachment many levels deep) is still returned, with a
|
|
302
|
+
`metadataIncomplete` note saying what was missing; its `hasAttachments` is then
|
|
303
|
+
inferred from the top-level `Content-Type`.
|
|
296
304
|
|
|
297
305
|
---
|
|
298
306
|
|
package/build/cli.js
CHANGED
|
@@ -58092,6 +58092,7 @@ __export(imapClient_exports, {
|
|
|
58092
58092
|
mailFlagColorIndex: () => mailFlagColorIndex,
|
|
58093
58093
|
matchMailbox: () => matchMailbox,
|
|
58094
58094
|
normalizeMessageId: () => normalizeMessageId,
|
|
58095
|
+
omittedNote: () => omittedNote,
|
|
58095
58096
|
resolveImapConfig: () => resolveImapConfig,
|
|
58096
58097
|
resolveImapConfigs: () => resolveImapConfigs,
|
|
58097
58098
|
resolveMailboxPath: () => resolveMailboxPath,
|
|
@@ -58402,7 +58403,12 @@ function structuredRow(m, account, path) {
|
|
|
58402
58403
|
// a caller from "no attachments", so every IMAP-sourced message claimed to
|
|
58403
58404
|
// have none. Falls back to false only when the fetch carried no
|
|
58404
58405
|
// BODYSTRUCTURE at all.
|
|
58405
|
-
|
|
58406
|
+
// A row read without BODYSTRUCTURE (#256 follow-up) judges by its top-level
|
|
58407
|
+
// Content-Type instead, and says so in `metadataIncomplete`.
|
|
58408
|
+
hasAttachments: m.bodyStructure ? bodyStructureHasAttachments(m.bodyStructure) : m.degraded ? contentTypeSuggestsAttachments(m) : false,
|
|
58409
|
+
...m.degraded ? {
|
|
58410
|
+
metadataIncomplete: m.degraded === "bodystructure" ? "BODYSTRUCTURE unreadable; hasAttachments inferred from Content-Type" : "FETCH response unreadable; row rebuilt from raw headers, hasAttachments inferred from Content-Type"
|
|
58411
|
+
} : {},
|
|
58406
58412
|
// Message-ID (when the envelope carries it) is the strongest cross-/intra-
|
|
58407
58413
|
// backend dedup key for the multi-account merge (imapMultiAccount.ts). The
|
|
58408
58414
|
// AppleScript path does not expose it, so cross-backend dedup falls back to
|
|
@@ -58410,6 +58416,13 @@ function structuredRow(m, account, path) {
|
|
|
58410
58416
|
...env.messageId ? { messageId: env.messageId } : {}
|
|
58411
58417
|
};
|
|
58412
58418
|
}
|
|
58419
|
+
function omittedNote(omitted, merged) {
|
|
58420
|
+
if (omitted.length === 0) return "";
|
|
58421
|
+
const list = omitted.map((o) => `UID ${o.uid} in "${o.mailbox}" (${o.id})`).join(", ");
|
|
58422
|
+
return `
|
|
58423
|
+
|
|
58424
|
+
Partial result. ${omitted.length} message(s) ${merged ? "that may belong" : "that belong"} on this page could not be read and are not listed: ${list}. Reason: ${[...new Set(omitted.map((o) => o.reason))].join("; ")}. get-message or get-message-headers with those ids may still read them.`;
|
|
58425
|
+
}
|
|
58413
58426
|
function describeMailboxFailure(error) {
|
|
58414
58427
|
const raw = errText(error);
|
|
58415
58428
|
const oneLine = raw.split("\n")[0].trim();
|
|
@@ -58451,30 +58464,106 @@ async function searchUids(client, path, criteria, size) {
|
|
|
58451
58464
|
`IMAP SEARCH on "${path}"${sized} failed: ` + (cause ? errText(cause) : "the server rejected it or the connection dropped before it answered (on a mailbox this size, usually a server-side timeout)")
|
|
58452
58465
|
);
|
|
58453
58466
|
}
|
|
58467
|
+
function mergeFetched(into, msg) {
|
|
58468
|
+
const prev = into.get(msg.uid);
|
|
58469
|
+
if (!prev) {
|
|
58470
|
+
into.set(msg.uid, msg);
|
|
58471
|
+
return;
|
|
58472
|
+
}
|
|
58473
|
+
const merged = { ...prev };
|
|
58474
|
+
for (const [key, value] of Object.entries(msg)) {
|
|
58475
|
+
if (value !== void 0 && value !== null)
|
|
58476
|
+
merged[key] = value;
|
|
58477
|
+
}
|
|
58478
|
+
into.set(msg.uid, merged);
|
|
58479
|
+
}
|
|
58480
|
+
async function fetchInto(client, uids, query, into) {
|
|
58481
|
+
const wanted = new Set(uids);
|
|
58482
|
+
for await (const msg of client.fetch(uids.join(","), query, { uid: true })) {
|
|
58483
|
+
if (typeof msg?.uid === "number" && wanted.has(msg.uid)) mergeFetched(into, msg);
|
|
58484
|
+
}
|
|
58485
|
+
}
|
|
58486
|
+
function fetchedHeaders(m) {
|
|
58487
|
+
return m.headers ? parseHeaderBlock(decodeHeaderBytes(asBuffer(m.headers))) : void 0;
|
|
58488
|
+
}
|
|
58489
|
+
function envelopeFromHeaders(m) {
|
|
58490
|
+
const h = fetchedHeaders(m);
|
|
58491
|
+
if (!h || h.headers.length === 0) return void 0;
|
|
58492
|
+
const env = {};
|
|
58493
|
+
if (h.subject) env.subject = h.subject;
|
|
58494
|
+
if (h.from) {
|
|
58495
|
+
const angle = h.from.match(/^\s*"?([^"<]*?)"?\s*<([^>]+)>/);
|
|
58496
|
+
env.from = angle ? [{ ...angle[1].trim() ? { name: angle[1].trim() } : {}, address: angle[2].trim() }] : [{ address: h.from.trim() }];
|
|
58497
|
+
}
|
|
58498
|
+
if (h.messageId) env.messageId = `<${h.messageId}>`;
|
|
58499
|
+
if (h.inReplyTo) env.inReplyTo = `<${h.inReplyTo}>`;
|
|
58500
|
+
if (h.dateHeader) env.date = h.dateHeader;
|
|
58501
|
+
return env;
|
|
58502
|
+
}
|
|
58503
|
+
function contentTypeSuggestsAttachments(m) {
|
|
58504
|
+
const type = fetchedHeaders(m)?.headers.find((h) => h.name.toLowerCase() === "content-type");
|
|
58505
|
+
return /^\s*multipart\/mixed\b/i.test(type?.value ?? "");
|
|
58506
|
+
}
|
|
58454
58507
|
async function fetchRows(client, uids) {
|
|
58455
|
-
if (uids.length === 0) return [];
|
|
58508
|
+
if (uids.length === 0) return { messages: [], omitted: [] };
|
|
58456
58509
|
const byUid = /* @__PURE__ */ new Map();
|
|
58457
|
-
|
|
58458
|
-
|
|
58459
|
-
|
|
58460
|
-
|
|
58461
|
-
|
|
58462
|
-
|
|
58463
|
-
|
|
58464
|
-
|
|
58465
|
-
|
|
58466
|
-
|
|
58467
|
-
|
|
58468
|
-
|
|
58469
|
-
|
|
58470
|
-
|
|
58471
|
-
|
|
58472
|
-
|
|
58473
|
-
|
|
58474
|
-
|
|
58475
|
-
|
|
58476
|
-
|
|
58477
|
-
|
|
58510
|
+
const causes = [];
|
|
58511
|
+
const noteCause = () => {
|
|
58512
|
+
const err = client.takeLastCommandError?.();
|
|
58513
|
+
if (err !== void 0) {
|
|
58514
|
+
const text = describeMailboxFailure(err);
|
|
58515
|
+
if (!causes.includes(text)) causes.push(text);
|
|
58516
|
+
}
|
|
58517
|
+
};
|
|
58518
|
+
const missing = () => uids.filter((uid) => !byUid.get(uid)?.envelope);
|
|
58519
|
+
client.takeLastCommandError?.();
|
|
58520
|
+
await fetchInto(client, uids, ROW_QUERY, byUid);
|
|
58521
|
+
noteCause();
|
|
58522
|
+
let gap = missing();
|
|
58523
|
+
if (gap.length > 0) {
|
|
58524
|
+
await fetchInto(client, gap, ROW_QUERY, byUid);
|
|
58525
|
+
noteCause();
|
|
58526
|
+
gap = missing();
|
|
58527
|
+
}
|
|
58528
|
+
if (gap.length > 0) {
|
|
58529
|
+
const reduced = /* @__PURE__ */ new Map();
|
|
58530
|
+
await fetchInto(client, gap, ROW_QUERY_NO_STRUCTURE, reduced);
|
|
58531
|
+
noteCause();
|
|
58532
|
+
for (const msg of reduced.values()) {
|
|
58533
|
+
if (!msg.envelope) continue;
|
|
58534
|
+
mergeFetched(byUid, { ...msg, degraded: "bodystructure" });
|
|
58535
|
+
}
|
|
58536
|
+
gap = missing();
|
|
58537
|
+
}
|
|
58538
|
+
if (gap.length > 0) {
|
|
58539
|
+
for (const uid of gap) {
|
|
58540
|
+
const bare = /* @__PURE__ */ new Map();
|
|
58541
|
+
await fetchInto(client, [uid], ROW_QUERY_HEADERS_ONLY, bare);
|
|
58542
|
+
noteCause();
|
|
58543
|
+
const msg = bare.get(uid);
|
|
58544
|
+
const envelope = msg ? envelopeFromHeaders(msg) : void 0;
|
|
58545
|
+
if (msg && envelope) {
|
|
58546
|
+
mergeFetched(byUid, { ...msg, envelope, degraded: "envelope" });
|
|
58547
|
+
}
|
|
58548
|
+
}
|
|
58549
|
+
gap = missing();
|
|
58550
|
+
}
|
|
58551
|
+
const omitted = new Set(gap);
|
|
58552
|
+
return {
|
|
58553
|
+
messages: uids.filter((uid) => !omitted.has(uid)).map((uid) => byUid.get(uid)).filter((message) => message !== void 0),
|
|
58554
|
+
omitted: gap,
|
|
58555
|
+
...causes.length > 0 ? { cause: causes.join("; ") } : {}
|
|
58556
|
+
};
|
|
58557
|
+
}
|
|
58558
|
+
function pageRows(fetch) {
|
|
58559
|
+
return {
|
|
58560
|
+
messages: fetch.messages,
|
|
58561
|
+
omitted: fetch.omitted,
|
|
58562
|
+
...fetch.omitted.length > 0 ? { omittedReason: omissionReason(fetch) } : {}
|
|
58563
|
+
};
|
|
58564
|
+
}
|
|
58565
|
+
function omissionReason(fetch) {
|
|
58566
|
+
return "the server's FETCH response for this message could not be read, even without BODYSTRUCTURE or ENVELOPE" + (fetch.cause ? ` (${fetch.cause})` : "");
|
|
58478
58567
|
}
|
|
58479
58568
|
async function walkWindows(exists, page, firstWindow, readWindow) {
|
|
58480
58569
|
const wanted = page.skip + page.take;
|
|
@@ -58514,7 +58603,7 @@ async function listLargeMailbox(client, exists, page) {
|
|
|
58514
58603
|
}
|
|
58515
58604
|
);
|
|
58516
58605
|
return {
|
|
58517
|
-
|
|
58606
|
+
...pageRows(await fetchRows(client, walk.uids)),
|
|
58518
58607
|
total: Math.max(0, exists - deletedSeen),
|
|
58519
58608
|
totalExact: true
|
|
58520
58609
|
};
|
|
@@ -58530,7 +58619,7 @@ async function searchLargeMailbox(client, path, criteria, exists, page) {
|
|
|
58530
58619
|
return found;
|
|
58531
58620
|
});
|
|
58532
58621
|
return {
|
|
58533
|
-
|
|
58622
|
+
...pageRows(await fetchRows(client, walk.uids)),
|
|
58534
58623
|
total: walk.matched,
|
|
58535
58624
|
totalExact: walk.exhausted
|
|
58536
58625
|
};
|
|
@@ -58539,7 +58628,7 @@ async function fetchMailboxMatches(client, path, criteria, page) {
|
|
|
58539
58628
|
const lock = await client.getMailboxLock(path);
|
|
58540
58629
|
try {
|
|
58541
58630
|
const exists = await statusMessageCount(client, path);
|
|
58542
|
-
if (exists === 0) return { messages: [], total: 0, totalExact: true };
|
|
58631
|
+
if (exists === 0) return { messages: [], omitted: [], total: 0, totalExact: true };
|
|
58543
58632
|
if (exists !== void 0 && exists > LARGE_MAILBOX_MESSAGES) {
|
|
58544
58633
|
try {
|
|
58545
58634
|
return isUnfiltered(criteria) ? await listLargeMailbox(client, exists, page) : await searchLargeMailbox(client, path, criteria, exists, page);
|
|
@@ -58552,7 +58641,7 @@ async function fetchMailboxMatches(client, path, criteria, page) {
|
|
|
58552
58641
|
}
|
|
58553
58642
|
const uids = await searchUids(client, path, criteria, exists);
|
|
58554
58643
|
if (uids.length === 0 || page.take === 0) {
|
|
58555
|
-
return { messages: [], total: uids.length, totalExact: true };
|
|
58644
|
+
return { messages: [], omitted: [], total: uids.length, totalExact: true };
|
|
58556
58645
|
}
|
|
58557
58646
|
const messages = exists ?? (await client.status(path, { messages: true })).messages ?? void 0;
|
|
58558
58647
|
if (typeof messages === "number" && uids.length > messages) {
|
|
@@ -58561,7 +58650,7 @@ async function fetchMailboxMatches(client, path, criteria, page) {
|
|
|
58561
58650
|
);
|
|
58562
58651
|
}
|
|
58563
58652
|
const newest = uids.slice().reverse().slice(page.skip, page.skip + page.take);
|
|
58564
|
-
return {
|
|
58653
|
+
return { ...pageRows(await fetchRows(client, newest)), total: uids.length, totalExact: true };
|
|
58565
58654
|
} finally {
|
|
58566
58655
|
lock.release();
|
|
58567
58656
|
}
|
|
@@ -58594,6 +58683,7 @@ async function run(args, listMode, deps) {
|
|
|
58594
58683
|
const fetched = [];
|
|
58595
58684
|
const failedMailboxes = [];
|
|
58596
58685
|
const failedMailboxReasons = {};
|
|
58686
|
+
const omittedMessages = [];
|
|
58597
58687
|
let totalMatched = 0;
|
|
58598
58688
|
let totalExact = true;
|
|
58599
58689
|
for (const path of paths) {
|
|
@@ -58602,6 +58692,14 @@ async function run(args, listMode, deps) {
|
|
|
58602
58692
|
totalMatched += result.total;
|
|
58603
58693
|
totalExact &&= result.totalExact;
|
|
58604
58694
|
fetched.push(...result.messages.map((message) => ({ message, path })));
|
|
58695
|
+
for (const uid of result.omitted) {
|
|
58696
|
+
omittedMessages.push({
|
|
58697
|
+
id: encodeImapId(cfg.accountLabel, path, uid),
|
|
58698
|
+
mailbox: path,
|
|
58699
|
+
uid,
|
|
58700
|
+
reason: result.omittedReason ?? "the server's FETCH response could not be read"
|
|
58701
|
+
});
|
|
58702
|
+
}
|
|
58605
58703
|
} catch (error) {
|
|
58606
58704
|
failedMailboxes.push(path);
|
|
58607
58705
|
failedMailboxReasons[path] = describeMailboxFailure(error);
|
|
@@ -58630,10 +58728,10 @@ async function run(args, listMode, deps) {
|
|
|
58630
58728
|
const messages = ordered.map(
|
|
58631
58729
|
({ message, path }) => structuredRow(message, cfg.accountLabel, path)
|
|
58632
58730
|
);
|
|
58633
|
-
const partial = failedMailboxes.length > 0;
|
|
58634
|
-
const failureNote =
|
|
58731
|
+
const partial = failedMailboxes.length > 0 || omittedMessages.length > 0;
|
|
58732
|
+
const failureNote = (failedMailboxes.length > 0 ? `
|
|
58635
58733
|
|
|
58636
|
-
Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"${path}" (${failedMailboxReasons[path]})`).join(", ")}.` : "";
|
|
58734
|
+
Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"${path}" (${failedMailboxReasons[path]})`).join(", ")}.` : "") + omittedNote(omittedMessages, unscopedSearch);
|
|
58637
58735
|
const verb = listMode ? "listed" : "matched";
|
|
58638
58736
|
const totalText = totalExact ? `${totalMatched} total` : `at least ${totalMatched} total`;
|
|
58639
58737
|
const scope = unscopedSearch ? allMailboxCount === 1 ? `mailbox "${paths[0]}"` : `${allMailboxCount} selectable mailboxes` : `mailbox "${paths[0]}"`;
|
|
@@ -58644,7 +58742,8 @@ Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"
|
|
|
58644
58742
|
count: 0,
|
|
58645
58743
|
partial,
|
|
58646
58744
|
failedMailboxes,
|
|
58647
|
-
failedMailboxReasons
|
|
58745
|
+
failedMailboxReasons,
|
|
58746
|
+
omittedMessages
|
|
58648
58747
|
};
|
|
58649
58748
|
}
|
|
58650
58749
|
const text = `Found ${rows.length} message(s) via IMAP (server-side, account ${cfg.accountLabel}, ${scope}; ${totalText} ${verb}):
|
|
@@ -58657,7 +58756,8 @@ Note: these IMAP IDs (imap:\u2026) work with get-message and the message mutatio
|
|
|
58657
58756
|
count: messages.length,
|
|
58658
58757
|
partial,
|
|
58659
58758
|
failedMailboxes,
|
|
58660
|
-
failedMailboxReasons
|
|
58759
|
+
failedMailboxReasons,
|
|
58760
|
+
omittedMessages
|
|
58661
58761
|
};
|
|
58662
58762
|
},
|
|
58663
58763
|
true
|
|
@@ -59617,31 +59717,21 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
59617
59717
|
}
|
|
59618
59718
|
if (uidSet.size <= 1) return null;
|
|
59619
59719
|
const uids = [...uidSet].slice(0, limit);
|
|
59620
|
-
const
|
|
59621
|
-
|
|
59622
|
-
|
|
59623
|
-
|
|
59624
|
-
|
|
59625
|
-
|
|
59626
|
-
|
|
59627
|
-
|
|
59628
|
-
// sanity-checked exactly as a row's dateSent is (#234).
|
|
59629
|
-
{
|
|
59630
|
-
envelope: true,
|
|
59631
|
-
flags: true,
|
|
59632
|
-
bodyStructure: true,
|
|
59633
|
-
internalDate: true,
|
|
59634
|
-
headers: ["date"]
|
|
59635
|
-
},
|
|
59636
|
-
{ uid: true }
|
|
59637
|
-
)) {
|
|
59638
|
-
msgs.push(msg);
|
|
59639
|
-
}
|
|
59720
|
+
const rows = await fetchRows(client, uids);
|
|
59721
|
+
const msgs = rows.messages.slice();
|
|
59722
|
+
const omittedMessages = rows.omitted.map((uid) => ({
|
|
59723
|
+
id: encodeImapId(ref.account, ref.path, uid),
|
|
59724
|
+
mailbox: ref.path,
|
|
59725
|
+
uid,
|
|
59726
|
+
reason: omissionReason(rows)
|
|
59727
|
+
}));
|
|
59640
59728
|
msgs.sort((a, b) => dateMs(a) - dateMs(b));
|
|
59641
59729
|
const subject = seed.envelope?.subject || "(no subject)";
|
|
59642
59730
|
const structured = {
|
|
59643
59731
|
subject,
|
|
59644
59732
|
count: msgs.length,
|
|
59733
|
+
partial: omittedMessages.length > 0,
|
|
59734
|
+
omittedMessages,
|
|
59645
59735
|
messages: msgs.map((m) => ({
|
|
59646
59736
|
id: encodeImapId(ref.account, ref.path, m.uid),
|
|
59647
59737
|
subject: m.envelope?.subject || "(no subject)",
|
|
@@ -59656,7 +59746,7 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
59656
59746
|
}))
|
|
59657
59747
|
};
|
|
59658
59748
|
const text = `Thread "${subject}" \u2014 ${msgs.length} message(s) via IMAP (References-linked, oldest first):
|
|
59659
|
-
` + msgs.map((m) => formatRow(m, ref.account, ref.path)).join("\n");
|
|
59749
|
+
` + msgs.map((m) => formatRow(m, ref.account, ref.path)).join("\n") + omittedNote(omittedMessages, false);
|
|
59660
59750
|
return { count: msgs.length, text, structured };
|
|
59661
59751
|
} finally {
|
|
59662
59752
|
lock.release();
|
|
@@ -59665,7 +59755,7 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
59665
59755
|
true
|
|
59666
59756
|
);
|
|
59667
59757
|
}
|
|
59668
|
-
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
59758
|
+
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
59669
59759
|
var init_imapClient = __esm({
|
|
59670
59760
|
"src/services/imapClient.ts"() {
|
|
59671
59761
|
"use strict";
|
|
@@ -59739,6 +59829,24 @@ var init_imapClient = __esm({
|
|
|
59739
59829
|
LARGE_MAILBOX_MESSAGES = 1e4;
|
|
59740
59830
|
FIRST_SEARCH_WINDOW = 5e3;
|
|
59741
59831
|
MAX_WINDOW = 5e4;
|
|
59832
|
+
ROW_QUERY = {
|
|
59833
|
+
envelope: true,
|
|
59834
|
+
flags: true,
|
|
59835
|
+
bodyStructure: true,
|
|
59836
|
+
internalDate: true,
|
|
59837
|
+
headers: ["date"]
|
|
59838
|
+
};
|
|
59839
|
+
ROW_QUERY_NO_STRUCTURE = {
|
|
59840
|
+
envelope: true,
|
|
59841
|
+
flags: true,
|
|
59842
|
+
internalDate: true,
|
|
59843
|
+
headers: ["date", "content-type"]
|
|
59844
|
+
};
|
|
59845
|
+
ROW_QUERY_HEADERS_ONLY = {
|
|
59846
|
+
flags: true,
|
|
59847
|
+
internalDate: true,
|
|
59848
|
+
headers: ["date", "from", "subject", "message-id", "in-reply-to", "content-type"]
|
|
59849
|
+
};
|
|
59742
59850
|
poolConnect = defaultConnect;
|
|
59743
59851
|
pools = /* @__PURE__ */ new Map();
|
|
59744
59852
|
connecting = /* @__PURE__ */ new Map();
|
package/build/index.js
CHANGED
|
@@ -65421,6 +65421,7 @@ __export(imapClient_exports, {
|
|
|
65421
65421
|
mailFlagColorIndex: () => mailFlagColorIndex,
|
|
65422
65422
|
matchMailbox: () => matchMailbox,
|
|
65423
65423
|
normalizeMessageId: () => normalizeMessageId,
|
|
65424
|
+
omittedNote: () => omittedNote,
|
|
65424
65425
|
resolveImapConfig: () => resolveImapConfig,
|
|
65425
65426
|
resolveImapConfigs: () => resolveImapConfigs,
|
|
65426
65427
|
resolveMailboxPath: () => resolveMailboxPath,
|
|
@@ -65731,7 +65732,12 @@ function structuredRow(m, account, path) {
|
|
|
65731
65732
|
// a caller from "no attachments", so every IMAP-sourced message claimed to
|
|
65732
65733
|
// have none. Falls back to false only when the fetch carried no
|
|
65733
65734
|
// BODYSTRUCTURE at all.
|
|
65734
|
-
|
|
65735
|
+
// A row read without BODYSTRUCTURE (#256 follow-up) judges by its top-level
|
|
65736
|
+
// Content-Type instead, and says so in `metadataIncomplete`.
|
|
65737
|
+
hasAttachments: m.bodyStructure ? bodyStructureHasAttachments(m.bodyStructure) : m.degraded ? contentTypeSuggestsAttachments(m) : false,
|
|
65738
|
+
...m.degraded ? {
|
|
65739
|
+
metadataIncomplete: m.degraded === "bodystructure" ? "BODYSTRUCTURE unreadable; hasAttachments inferred from Content-Type" : "FETCH response unreadable; row rebuilt from raw headers, hasAttachments inferred from Content-Type"
|
|
65740
|
+
} : {},
|
|
65735
65741
|
// Message-ID (when the envelope carries it) is the strongest cross-/intra-
|
|
65736
65742
|
// backend dedup key for the multi-account merge (imapMultiAccount.ts). The
|
|
65737
65743
|
// AppleScript path does not expose it, so cross-backend dedup falls back to
|
|
@@ -65739,6 +65745,13 @@ function structuredRow(m, account, path) {
|
|
|
65739
65745
|
...env.messageId ? { messageId: env.messageId } : {}
|
|
65740
65746
|
};
|
|
65741
65747
|
}
|
|
65748
|
+
function omittedNote(omitted, merged) {
|
|
65749
|
+
if (omitted.length === 0) return "";
|
|
65750
|
+
const list = omitted.map((o) => `UID ${o.uid} in "${o.mailbox}" (${o.id})`).join(", ");
|
|
65751
|
+
return `
|
|
65752
|
+
|
|
65753
|
+
Partial result. ${omitted.length} message(s) ${merged ? "that may belong" : "that belong"} on this page could not be read and are not listed: ${list}. Reason: ${[...new Set(omitted.map((o) => o.reason))].join("; ")}. get-message or get-message-headers with those ids may still read them.`;
|
|
65754
|
+
}
|
|
65742
65755
|
function describeMailboxFailure(error2) {
|
|
65743
65756
|
const raw = errText(error2);
|
|
65744
65757
|
const oneLine = raw.split("\n")[0].trim();
|
|
@@ -65780,30 +65793,106 @@ async function searchUids(client, path, criteria, size) {
|
|
|
65780
65793
|
`IMAP SEARCH on "${path}"${sized} failed: ` + (cause ? errText(cause) : "the server rejected it or the connection dropped before it answered (on a mailbox this size, usually a server-side timeout)")
|
|
65781
65794
|
);
|
|
65782
65795
|
}
|
|
65796
|
+
function mergeFetched(into, msg) {
|
|
65797
|
+
const prev = into.get(msg.uid);
|
|
65798
|
+
if (!prev) {
|
|
65799
|
+
into.set(msg.uid, msg);
|
|
65800
|
+
return;
|
|
65801
|
+
}
|
|
65802
|
+
const merged = { ...prev };
|
|
65803
|
+
for (const [key, value] of Object.entries(msg)) {
|
|
65804
|
+
if (value !== void 0 && value !== null)
|
|
65805
|
+
merged[key] = value;
|
|
65806
|
+
}
|
|
65807
|
+
into.set(msg.uid, merged);
|
|
65808
|
+
}
|
|
65809
|
+
async function fetchInto(client, uids, query, into) {
|
|
65810
|
+
const wanted = new Set(uids);
|
|
65811
|
+
for await (const msg of client.fetch(uids.join(","), query, { uid: true })) {
|
|
65812
|
+
if (typeof msg?.uid === "number" && wanted.has(msg.uid)) mergeFetched(into, msg);
|
|
65813
|
+
}
|
|
65814
|
+
}
|
|
65815
|
+
function fetchedHeaders(m) {
|
|
65816
|
+
return m.headers ? parseHeaderBlock(decodeHeaderBytes(asBuffer(m.headers))) : void 0;
|
|
65817
|
+
}
|
|
65818
|
+
function envelopeFromHeaders(m) {
|
|
65819
|
+
const h = fetchedHeaders(m);
|
|
65820
|
+
if (!h || h.headers.length === 0) return void 0;
|
|
65821
|
+
const env = {};
|
|
65822
|
+
if (h.subject) env.subject = h.subject;
|
|
65823
|
+
if (h.from) {
|
|
65824
|
+
const angle = h.from.match(/^\s*"?([^"<]*?)"?\s*<([^>]+)>/);
|
|
65825
|
+
env.from = angle ? [{ ...angle[1].trim() ? { name: angle[1].trim() } : {}, address: angle[2].trim() }] : [{ address: h.from.trim() }];
|
|
65826
|
+
}
|
|
65827
|
+
if (h.messageId) env.messageId = `<${h.messageId}>`;
|
|
65828
|
+
if (h.inReplyTo) env.inReplyTo = `<${h.inReplyTo}>`;
|
|
65829
|
+
if (h.dateHeader) env.date = h.dateHeader;
|
|
65830
|
+
return env;
|
|
65831
|
+
}
|
|
65832
|
+
function contentTypeSuggestsAttachments(m) {
|
|
65833
|
+
const type = fetchedHeaders(m)?.headers.find((h) => h.name.toLowerCase() === "content-type");
|
|
65834
|
+
return /^\s*multipart\/mixed\b/i.test(type?.value ?? "");
|
|
65835
|
+
}
|
|
65783
65836
|
async function fetchRows(client, uids) {
|
|
65784
|
-
if (uids.length === 0) return [];
|
|
65837
|
+
if (uids.length === 0) return { messages: [], omitted: [] };
|
|
65785
65838
|
const byUid = /* @__PURE__ */ new Map();
|
|
65786
|
-
|
|
65787
|
-
|
|
65788
|
-
|
|
65789
|
-
|
|
65790
|
-
|
|
65791
|
-
|
|
65792
|
-
|
|
65793
|
-
|
|
65794
|
-
|
|
65795
|
-
|
|
65796
|
-
|
|
65797
|
-
|
|
65798
|
-
|
|
65799
|
-
|
|
65800
|
-
|
|
65801
|
-
|
|
65802
|
-
|
|
65803
|
-
|
|
65804
|
-
|
|
65805
|
-
|
|
65806
|
-
|
|
65839
|
+
const causes = [];
|
|
65840
|
+
const noteCause = () => {
|
|
65841
|
+
const err = client.takeLastCommandError?.();
|
|
65842
|
+
if (err !== void 0) {
|
|
65843
|
+
const text = describeMailboxFailure(err);
|
|
65844
|
+
if (!causes.includes(text)) causes.push(text);
|
|
65845
|
+
}
|
|
65846
|
+
};
|
|
65847
|
+
const missing = () => uids.filter((uid) => !byUid.get(uid)?.envelope);
|
|
65848
|
+
client.takeLastCommandError?.();
|
|
65849
|
+
await fetchInto(client, uids, ROW_QUERY, byUid);
|
|
65850
|
+
noteCause();
|
|
65851
|
+
let gap = missing();
|
|
65852
|
+
if (gap.length > 0) {
|
|
65853
|
+
await fetchInto(client, gap, ROW_QUERY, byUid);
|
|
65854
|
+
noteCause();
|
|
65855
|
+
gap = missing();
|
|
65856
|
+
}
|
|
65857
|
+
if (gap.length > 0) {
|
|
65858
|
+
const reduced = /* @__PURE__ */ new Map();
|
|
65859
|
+
await fetchInto(client, gap, ROW_QUERY_NO_STRUCTURE, reduced);
|
|
65860
|
+
noteCause();
|
|
65861
|
+
for (const msg of reduced.values()) {
|
|
65862
|
+
if (!msg.envelope) continue;
|
|
65863
|
+
mergeFetched(byUid, { ...msg, degraded: "bodystructure" });
|
|
65864
|
+
}
|
|
65865
|
+
gap = missing();
|
|
65866
|
+
}
|
|
65867
|
+
if (gap.length > 0) {
|
|
65868
|
+
for (const uid of gap) {
|
|
65869
|
+
const bare = /* @__PURE__ */ new Map();
|
|
65870
|
+
await fetchInto(client, [uid], ROW_QUERY_HEADERS_ONLY, bare);
|
|
65871
|
+
noteCause();
|
|
65872
|
+
const msg = bare.get(uid);
|
|
65873
|
+
const envelope = msg ? envelopeFromHeaders(msg) : void 0;
|
|
65874
|
+
if (msg && envelope) {
|
|
65875
|
+
mergeFetched(byUid, { ...msg, envelope, degraded: "envelope" });
|
|
65876
|
+
}
|
|
65877
|
+
}
|
|
65878
|
+
gap = missing();
|
|
65879
|
+
}
|
|
65880
|
+
const omitted = new Set(gap);
|
|
65881
|
+
return {
|
|
65882
|
+
messages: uids.filter((uid) => !omitted.has(uid)).map((uid) => byUid.get(uid)).filter((message) => message !== void 0),
|
|
65883
|
+
omitted: gap,
|
|
65884
|
+
...causes.length > 0 ? { cause: causes.join("; ") } : {}
|
|
65885
|
+
};
|
|
65886
|
+
}
|
|
65887
|
+
function pageRows(fetch) {
|
|
65888
|
+
return {
|
|
65889
|
+
messages: fetch.messages,
|
|
65890
|
+
omitted: fetch.omitted,
|
|
65891
|
+
...fetch.omitted.length > 0 ? { omittedReason: omissionReason(fetch) } : {}
|
|
65892
|
+
};
|
|
65893
|
+
}
|
|
65894
|
+
function omissionReason(fetch) {
|
|
65895
|
+
return "the server's FETCH response for this message could not be read, even without BODYSTRUCTURE or ENVELOPE" + (fetch.cause ? ` (${fetch.cause})` : "");
|
|
65807
65896
|
}
|
|
65808
65897
|
async function walkWindows(exists, page, firstWindow, readWindow) {
|
|
65809
65898
|
const wanted = page.skip + page.take;
|
|
@@ -65843,7 +65932,7 @@ async function listLargeMailbox(client, exists, page) {
|
|
|
65843
65932
|
}
|
|
65844
65933
|
);
|
|
65845
65934
|
return {
|
|
65846
|
-
|
|
65935
|
+
...pageRows(await fetchRows(client, walk.uids)),
|
|
65847
65936
|
total: Math.max(0, exists - deletedSeen),
|
|
65848
65937
|
totalExact: true
|
|
65849
65938
|
};
|
|
@@ -65859,7 +65948,7 @@ async function searchLargeMailbox(client, path, criteria, exists, page) {
|
|
|
65859
65948
|
return found;
|
|
65860
65949
|
});
|
|
65861
65950
|
return {
|
|
65862
|
-
|
|
65951
|
+
...pageRows(await fetchRows(client, walk.uids)),
|
|
65863
65952
|
total: walk.matched,
|
|
65864
65953
|
totalExact: walk.exhausted
|
|
65865
65954
|
};
|
|
@@ -65868,7 +65957,7 @@ async function fetchMailboxMatches(client, path, criteria, page) {
|
|
|
65868
65957
|
const lock = await client.getMailboxLock(path);
|
|
65869
65958
|
try {
|
|
65870
65959
|
const exists = await statusMessageCount(client, path);
|
|
65871
|
-
if (exists === 0) return { messages: [], total: 0, totalExact: true };
|
|
65960
|
+
if (exists === 0) return { messages: [], omitted: [], total: 0, totalExact: true };
|
|
65872
65961
|
if (exists !== void 0 && exists > LARGE_MAILBOX_MESSAGES) {
|
|
65873
65962
|
try {
|
|
65874
65963
|
return isUnfiltered(criteria) ? await listLargeMailbox(client, exists, page) : await searchLargeMailbox(client, path, criteria, exists, page);
|
|
@@ -65881,7 +65970,7 @@ async function fetchMailboxMatches(client, path, criteria, page) {
|
|
|
65881
65970
|
}
|
|
65882
65971
|
const uids = await searchUids(client, path, criteria, exists);
|
|
65883
65972
|
if (uids.length === 0 || page.take === 0) {
|
|
65884
|
-
return { messages: [], total: uids.length, totalExact: true };
|
|
65973
|
+
return { messages: [], omitted: [], total: uids.length, totalExact: true };
|
|
65885
65974
|
}
|
|
65886
65975
|
const messages = exists ?? (await client.status(path, { messages: true })).messages ?? void 0;
|
|
65887
65976
|
if (typeof messages === "number" && uids.length > messages) {
|
|
@@ -65890,7 +65979,7 @@ async function fetchMailboxMatches(client, path, criteria, page) {
|
|
|
65890
65979
|
);
|
|
65891
65980
|
}
|
|
65892
65981
|
const newest = uids.slice().reverse().slice(page.skip, page.skip + page.take);
|
|
65893
|
-
return {
|
|
65982
|
+
return { ...pageRows(await fetchRows(client, newest)), total: uids.length, totalExact: true };
|
|
65894
65983
|
} finally {
|
|
65895
65984
|
lock.release();
|
|
65896
65985
|
}
|
|
@@ -65923,6 +66012,7 @@ async function run(args, listMode, deps) {
|
|
|
65923
66012
|
const fetched = [];
|
|
65924
66013
|
const failedMailboxes = [];
|
|
65925
66014
|
const failedMailboxReasons = {};
|
|
66015
|
+
const omittedMessages = [];
|
|
65926
66016
|
let totalMatched = 0;
|
|
65927
66017
|
let totalExact = true;
|
|
65928
66018
|
for (const path of paths) {
|
|
@@ -65931,6 +66021,14 @@ async function run(args, listMode, deps) {
|
|
|
65931
66021
|
totalMatched += result.total;
|
|
65932
66022
|
totalExact &&= result.totalExact;
|
|
65933
66023
|
fetched.push(...result.messages.map((message) => ({ message, path })));
|
|
66024
|
+
for (const uid of result.omitted) {
|
|
66025
|
+
omittedMessages.push({
|
|
66026
|
+
id: encodeImapId(cfg.accountLabel, path, uid),
|
|
66027
|
+
mailbox: path,
|
|
66028
|
+
uid,
|
|
66029
|
+
reason: result.omittedReason ?? "the server's FETCH response could not be read"
|
|
66030
|
+
});
|
|
66031
|
+
}
|
|
65934
66032
|
} catch (error2) {
|
|
65935
66033
|
failedMailboxes.push(path);
|
|
65936
66034
|
failedMailboxReasons[path] = describeMailboxFailure(error2);
|
|
@@ -65959,10 +66057,10 @@ async function run(args, listMode, deps) {
|
|
|
65959
66057
|
const messages = ordered.map(
|
|
65960
66058
|
({ message, path }) => structuredRow(message, cfg.accountLabel, path)
|
|
65961
66059
|
);
|
|
65962
|
-
const partial2 = failedMailboxes.length > 0;
|
|
65963
|
-
const failureNote =
|
|
66060
|
+
const partial2 = failedMailboxes.length > 0 || omittedMessages.length > 0;
|
|
66061
|
+
const failureNote = (failedMailboxes.length > 0 ? `
|
|
65964
66062
|
|
|
65965
|
-
Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"${path}" (${failedMailboxReasons[path]})`).join(", ")}.` : "";
|
|
66063
|
+
Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"${path}" (${failedMailboxReasons[path]})`).join(", ")}.` : "") + omittedNote(omittedMessages, unscopedSearch);
|
|
65966
66064
|
const verb = listMode ? "listed" : "matched";
|
|
65967
66065
|
const totalText = totalExact ? `${totalMatched} total` : `at least ${totalMatched} total`;
|
|
65968
66066
|
const scope = unscopedSearch ? allMailboxCount === 1 ? `mailbox "${paths[0]}"` : `${allMailboxCount} selectable mailboxes` : `mailbox "${paths[0]}"`;
|
|
@@ -65973,7 +66071,8 @@ Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"
|
|
|
65973
66071
|
count: 0,
|
|
65974
66072
|
partial: partial2,
|
|
65975
66073
|
failedMailboxes,
|
|
65976
|
-
failedMailboxReasons
|
|
66074
|
+
failedMailboxReasons,
|
|
66075
|
+
omittedMessages
|
|
65977
66076
|
};
|
|
65978
66077
|
}
|
|
65979
66078
|
const text = `Found ${rows.length} message(s) via IMAP (server-side, account ${cfg.accountLabel}, ${scope}; ${totalText} ${verb}):
|
|
@@ -65986,7 +66085,8 @@ Note: these IMAP IDs (imap:\u2026) work with get-message and the message mutatio
|
|
|
65986
66085
|
count: messages.length,
|
|
65987
66086
|
partial: partial2,
|
|
65988
66087
|
failedMailboxes,
|
|
65989
|
-
failedMailboxReasons
|
|
66088
|
+
failedMailboxReasons,
|
|
66089
|
+
omittedMessages
|
|
65990
66090
|
};
|
|
65991
66091
|
},
|
|
65992
66092
|
true
|
|
@@ -66946,31 +67046,21 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
66946
67046
|
}
|
|
66947
67047
|
if (uidSet.size <= 1) return null;
|
|
66948
67048
|
const uids = [...uidSet].slice(0, limit);
|
|
66949
|
-
const
|
|
66950
|
-
|
|
66951
|
-
|
|
66952
|
-
|
|
66953
|
-
|
|
66954
|
-
|
|
66955
|
-
|
|
66956
|
-
|
|
66957
|
-
// sanity-checked exactly as a row's dateSent is (#234).
|
|
66958
|
-
{
|
|
66959
|
-
envelope: true,
|
|
66960
|
-
flags: true,
|
|
66961
|
-
bodyStructure: true,
|
|
66962
|
-
internalDate: true,
|
|
66963
|
-
headers: ["date"]
|
|
66964
|
-
},
|
|
66965
|
-
{ uid: true }
|
|
66966
|
-
)) {
|
|
66967
|
-
msgs.push(msg);
|
|
66968
|
-
}
|
|
67049
|
+
const rows = await fetchRows(client, uids);
|
|
67050
|
+
const msgs = rows.messages.slice();
|
|
67051
|
+
const omittedMessages = rows.omitted.map((uid) => ({
|
|
67052
|
+
id: encodeImapId(ref.account, ref.path, uid),
|
|
67053
|
+
mailbox: ref.path,
|
|
67054
|
+
uid,
|
|
67055
|
+
reason: omissionReason(rows)
|
|
67056
|
+
}));
|
|
66969
67057
|
msgs.sort((a, b) => dateMs(a) - dateMs(b));
|
|
66970
67058
|
const subject = seed.envelope?.subject || "(no subject)";
|
|
66971
67059
|
const structured = {
|
|
66972
67060
|
subject,
|
|
66973
67061
|
count: msgs.length,
|
|
67062
|
+
partial: omittedMessages.length > 0,
|
|
67063
|
+
omittedMessages,
|
|
66974
67064
|
messages: msgs.map((m) => ({
|
|
66975
67065
|
id: encodeImapId(ref.account, ref.path, m.uid),
|
|
66976
67066
|
subject: m.envelope?.subject || "(no subject)",
|
|
@@ -66985,7 +67075,7 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
66985
67075
|
}))
|
|
66986
67076
|
};
|
|
66987
67077
|
const text = `Thread "${subject}" \u2014 ${msgs.length} message(s) via IMAP (References-linked, oldest first):
|
|
66988
|
-
` + msgs.map((m) => formatRow(m, ref.account, ref.path)).join("\n");
|
|
67078
|
+
` + msgs.map((m) => formatRow(m, ref.account, ref.path)).join("\n") + omittedNote(omittedMessages, false);
|
|
66989
67079
|
return { count: msgs.length, text, structured };
|
|
66990
67080
|
} finally {
|
|
66991
67081
|
lock.release();
|
|
@@ -66994,7 +67084,7 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
66994
67084
|
true
|
|
66995
67085
|
);
|
|
66996
67086
|
}
|
|
66997
|
-
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
67087
|
+
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
66998
67088
|
var init_imapClient = __esm({
|
|
66999
67089
|
"src/services/imapClient.ts"() {
|
|
67000
67090
|
"use strict";
|
|
@@ -67068,6 +67158,24 @@ var init_imapClient = __esm({
|
|
|
67068
67158
|
LARGE_MAILBOX_MESSAGES = 1e4;
|
|
67069
67159
|
FIRST_SEARCH_WINDOW = 5e3;
|
|
67070
67160
|
MAX_WINDOW = 5e4;
|
|
67161
|
+
ROW_QUERY = {
|
|
67162
|
+
envelope: true,
|
|
67163
|
+
flags: true,
|
|
67164
|
+
bodyStructure: true,
|
|
67165
|
+
internalDate: true,
|
|
67166
|
+
headers: ["date"]
|
|
67167
|
+
};
|
|
67168
|
+
ROW_QUERY_NO_STRUCTURE = {
|
|
67169
|
+
envelope: true,
|
|
67170
|
+
flags: true,
|
|
67171
|
+
internalDate: true,
|
|
67172
|
+
headers: ["date", "content-type"]
|
|
67173
|
+
};
|
|
67174
|
+
ROW_QUERY_HEADERS_ONLY = {
|
|
67175
|
+
flags: true,
|
|
67176
|
+
internalDate: true,
|
|
67177
|
+
headers: ["date", "from", "subject", "message-id", "in-reply-to", "content-type"]
|
|
67178
|
+
};
|
|
67071
67179
|
poolConnect = defaultConnect;
|
|
67072
67180
|
pools = /* @__PURE__ */ new Map();
|
|
67073
67181
|
connecting = /* @__PURE__ */ new Map();
|
|
@@ -87569,6 +87677,7 @@ async function fanOutImapMessages(args, kind, deps = {}, configs = resolveImapCo
|
|
|
87569
87677
|
const accountsFailed = [];
|
|
87570
87678
|
const failedMailboxes = [];
|
|
87571
87679
|
const failedMailboxReasons = {};
|
|
87680
|
+
const omittedMessages = [];
|
|
87572
87681
|
for (const config2 of configs) {
|
|
87573
87682
|
const perAccountArgs = { ...args, account: void 0 };
|
|
87574
87683
|
try {
|
|
@@ -87581,12 +87690,25 @@ async function fanOutImapMessages(args, kind, deps = {}, configs = resolveImapCo
|
|
|
87581
87690
|
for (const [mailbox, reason] of Object.entries(res.failedMailboxReasons)) {
|
|
87582
87691
|
failedMailboxReasons[`${config2.accountLabel} / ${mailbox}`] = reason;
|
|
87583
87692
|
}
|
|
87693
|
+
omittedMessages.push(
|
|
87694
|
+
...(res.omittedMessages ?? []).map((o) => ({
|
|
87695
|
+
...o,
|
|
87696
|
+
mailbox: `${config2.accountLabel} / ${o.mailbox}`
|
|
87697
|
+
}))
|
|
87698
|
+
);
|
|
87584
87699
|
} catch (e) {
|
|
87585
87700
|
accountsFailed.push(config2.accountLabel);
|
|
87586
87701
|
console.error(`IMAP fan-out failed for account "${config2.accountLabel}": ${String(e)}`);
|
|
87587
87702
|
}
|
|
87588
87703
|
}
|
|
87589
|
-
return {
|
|
87704
|
+
return {
|
|
87705
|
+
rows,
|
|
87706
|
+
accountsQueried,
|
|
87707
|
+
accountsFailed,
|
|
87708
|
+
failedMailboxes,
|
|
87709
|
+
failedMailboxReasons,
|
|
87710
|
+
omittedMessages
|
|
87711
|
+
};
|
|
87590
87712
|
}
|
|
87591
87713
|
function configMatchesAccount(config2, account) {
|
|
87592
87714
|
const name = account.name.trim().toLowerCase();
|
|
@@ -88189,7 +88311,10 @@ var LIST_OUTPUT_SCHEMA = {
|
|
|
88189
88311
|
failedMailboxes: external_exports.array(external_exports.string()).optional(),
|
|
88190
88312
|
// Underlying error text per entry in `failedMailboxes`, same keys (#246
|
|
88191
88313
|
// follow-up) — declared so a client can rely on it rather than parse text.
|
|
88192
|
-
failedMailboxReasons: external_exports.record(external_exports.string(), external_exports.string()).optional()
|
|
88314
|
+
failedMailboxReasons: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
88315
|
+
// Messages that belong on the page but could not be read, each with its
|
|
88316
|
+
// imap: id and why (#256 follow-up). Non-empty implies `partial: true`.
|
|
88317
|
+
omittedMessages: external_exports.array(external_exports.object({ id: external_exports.string(), mailbox: external_exports.string(), uid: external_exports.number(), reason: external_exports.string() })).optional()
|
|
88193
88318
|
};
|
|
88194
88319
|
var BATCH_COUNT_OUTPUT_SCHEMA = {
|
|
88195
88320
|
ok: external_exports.boolean().optional(),
|
|
@@ -88268,7 +88393,7 @@ function mergedMessageResponse(fan, apple, limit, verb) {
|
|
|
88268
88393
|
const merged = mergeMessages(fan.rows, apple.rows, limit);
|
|
88269
88394
|
const diagnostics = {
|
|
88270
88395
|
...apple.diagnostics,
|
|
88271
|
-
partial: apple.diagnostics.partial || fan.accountsFailed.length > 0 || fan.failedMailboxes.length > 0,
|
|
88396
|
+
partial: apple.diagnostics.partial || fan.accountsFailed.length > 0 || fan.failedMailboxes.length > 0 || fan.omittedMessages.length > 0,
|
|
88272
88397
|
timedOutAccounts: [...apple.diagnostics.timedOutAccounts, ...fan.accountsFailed],
|
|
88273
88398
|
notSearchedMailboxes: [...apple.diagnostics.notSearchedMailboxes, ...fan.failedMailboxes]
|
|
88274
88399
|
};
|
|
@@ -88280,9 +88405,10 @@ function mergedMessageResponse(fan, apple, limit, verb) {
|
|
|
88280
88405
|
notSearchedMailboxes: diagnostics.notSearchedMailboxes,
|
|
88281
88406
|
timedOutAccounts: diagnostics.timedOutAccounts,
|
|
88282
88407
|
failedMailboxes: fan.failedMailboxes,
|
|
88283
|
-
failedMailboxReasons: fan.failedMailboxReasons
|
|
88408
|
+
failedMailboxReasons: fan.failedMailboxReasons,
|
|
88409
|
+
omittedMessages: fan.omittedMessages
|
|
88284
88410
|
};
|
|
88285
|
-
const coverageBlock = partialCoverageBlock(diagnostics);
|
|
88411
|
+
const coverageBlock = partialCoverageBlock(diagnostics) + omittedNote(fan.omittedMessages, true);
|
|
88286
88412
|
if (merged.length === 0) {
|
|
88287
88413
|
const base = diagnostics.partial ? `No messages found in the portions that were ${verb === "matched" ? "searched" : "listed"}.` : "No messages found";
|
|
88288
88414
|
return successResponse(`${base}${coverageBlock}`, structured);
|
|
@@ -88383,7 +88509,8 @@ registerTool(
|
|
|
88383
88509
|
count: r.count,
|
|
88384
88510
|
partial: r.partial,
|
|
88385
88511
|
failedMailboxes: r.failedMailboxes,
|
|
88386
|
-
failedMailboxReasons: r.failedMailboxReasons
|
|
88512
|
+
failedMailboxReasons: r.failedMailboxReasons,
|
|
88513
|
+
omittedMessages: r.omittedMessages
|
|
88387
88514
|
});
|
|
88388
88515
|
}
|
|
88389
88516
|
const fan = await fanOutImapMessages(imapArgs, "search");
|
|
@@ -88737,7 +88864,9 @@ registerTool(
|
|
|
88737
88864
|
messages: external_exports.array(MESSAGE_ROW_SCHEMA).optional(),
|
|
88738
88865
|
count: external_exports.number().optional(),
|
|
88739
88866
|
partial: external_exports.boolean().optional(),
|
|
88740
|
-
failedMailboxes: external_exports.array(external_exports.string()).optional()
|
|
88867
|
+
failedMailboxes: external_exports.array(external_exports.string()).optional(),
|
|
88868
|
+
failedMailboxReasons: LIST_OUTPUT_SCHEMA.failedMailboxReasons,
|
|
88869
|
+
omittedMessages: LIST_OUTPUT_SCHEMA.omittedMessages
|
|
88741
88870
|
}
|
|
88742
88871
|
},
|
|
88743
88872
|
withErrorHandling(async ({ id, account, mailbox, limit = 50 }) => {
|
|
@@ -88767,7 +88896,8 @@ ${r.text}`, {
|
|
|
88767
88896
|
count: r.count,
|
|
88768
88897
|
partial: r.partial,
|
|
88769
88898
|
failedMailboxes: r.failedMailboxes,
|
|
88770
|
-
failedMailboxReasons: r.failedMailboxReasons
|
|
88899
|
+
failedMailboxReasons: r.failedMailboxReasons,
|
|
88900
|
+
omittedMessages: r.omittedMessages
|
|
88771
88901
|
});
|
|
88772
88902
|
}
|
|
88773
88903
|
const fan = await fanOutImapMessages({ subject: base, mailbox, limit }, "search");
|
|
@@ -88792,20 +88922,21 @@ ${r.text}`, {
|
|
|
88792
88922
|
const orderedRows = mergedNewestFirst.slice().reverse().sort(
|
|
88793
88923
|
(a, b) => (a.dateReceived ? new Date(a.dateReceived).getTime() : 0) - (b.dateReceived ? new Date(b.dateReceived).getTime() : 0)
|
|
88794
88924
|
);
|
|
88795
|
-
const partial2 = apple.diagnostics.partial || fan.accountsFailed.length > 0 || fan.failedMailboxes.length > 0;
|
|
88925
|
+
const partial2 = apple.diagnostics.partial || fan.accountsFailed.length > 0 || fan.failedMailboxes.length > 0 || fan.omittedMessages.length > 0;
|
|
88796
88926
|
const coverage = partialCoverageBlock({
|
|
88797
88927
|
...apple.diagnostics,
|
|
88798
88928
|
partial: partial2,
|
|
88799
88929
|
timedOutAccounts: [...apple.diagnostics.timedOutAccounts, ...fan.accountsFailed],
|
|
88800
88930
|
notSearchedMailboxes: [...apple.diagnostics.notSearchedMailboxes, ...fan.failedMailboxes]
|
|
88801
|
-
});
|
|
88931
|
+
}) + omittedNote(fan.omittedMessages, true);
|
|
88802
88932
|
const structured2 = {
|
|
88803
88933
|
subject: base,
|
|
88804
88934
|
messages: orderedRows,
|
|
88805
88935
|
count: orderedRows.length,
|
|
88806
88936
|
partial: partial2,
|
|
88807
88937
|
failedMailboxes: fan.failedMailboxes,
|
|
88808
|
-
failedMailboxReasons: fan.failedMailboxReasons
|
|
88938
|
+
failedMailboxReasons: fan.failedMailboxReasons,
|
|
88939
|
+
omittedMessages: fan.omittedMessages
|
|
88809
88940
|
};
|
|
88810
88941
|
if (orderedRows.length === 0) {
|
|
88811
88942
|
return successResponse(`No messages found in thread "${base}".${coverage}`, structured2);
|
|
@@ -88870,7 +89001,8 @@ registerTool(
|
|
|
88870
89001
|
count: r.count,
|
|
88871
89002
|
partial: r.partial,
|
|
88872
89003
|
failedMailboxes: r.failedMailboxes,
|
|
88873
|
-
failedMailboxReasons: r.failedMailboxReasons
|
|
89004
|
+
failedMailboxReasons: r.failedMailboxReasons,
|
|
89005
|
+
omittedMessages: r.omittedMessages
|
|
88874
89006
|
});
|
|
88875
89007
|
}
|
|
88876
89008
|
const fan = await fanOutImapMessages({ mailbox, limit, offset, from, unreadOnly }, "list");
|
package/package.json
CHANGED